Skip to content

AttackMap · Extending

SDK reference

Analyzer contract#

AnalyzerResult#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
rootstrrequired
languageslist[str]list()
routeslist[Route]list()
external_callslist[ExternalCall]list()
databaseslist[DatabaseHint]list()
auth_hintslist[AuthHint]list()
service_hintslist[ServiceHint]list()
edge_hintslist[EdgeHint]list()
entrypoint_hintslist[EntrypointHint]list()
protocol_hintslist[ProtocolHint]list()
framework_hintslist[FrameworkHint]list()
secret_hintslist[SecretHint]list()
taint_chainslist[TaintChain]list()
dependencieslist[DependencyHint]list()
vulnerabilitieslist[Vulnerability]list()
authz_candidateslist[BolaCandidate]list()
crypto_weaknesseslist[CryptoWeakness]list()
web_hardening_issueslist[WebHardeningIssue]list()
code_weaknesseslist[CodeWeakness]list()
workflow_issueslist[WorkflowIssue]list()
supply_chain_issueslist[SupplyChainIssue]list()
cloud_misconfigslist[CloudMisconfig]list()
app_surface_issueslist[AppSurfaceIssue]list()
secret_historylist[SecretHistoryHit]list()
anomalieslist[Anomaly]list()
limitationslist[str]list()
analyzer_errorslist[AnalyzerError]list()
files_scannedint0

AnalyzerMetadata#

Model (pydantic). Module attackmap.analyzer_contracts.

Static, self-describing metadata every analyzer exposes.

Every analyzer plugin ships a metadata: AnalyzerMetadata attribute so core (and future tooling) can reason about the plugin without loading or running it. The field set is stable: adding fields is safe on a minor release, removing or renaming is a major-version break.

FieldTypeDefault
namestrrequired
display_namestr''
versionstr'0.1.0'
descriptionstr''
scopestr''
targetslist[str]list()
languageslist[str]list()
priorityint100
experimentalboolTrue
enabled_by_defaultboolFalse

AnalyzerRepositoryModule#

Model (pydantic). Module attackmap.analyzer_contracts.

FieldTypeDefault
analyzer_namestrrequired
repo_namestrrequired
web_urlstrrequired

AnalyzerProtocol#

Protocol.

Members:

  • metadata: AnalyzerMetadata
  • name (property)
  • detect(self, root: str | Path) -> bool
  • analyze(self, root: str | Path) -> ScanResult

normalize_analyzer_metadata#

python
def normalize_analyzer_metadata(value: object) -> AnalyzerMetadata

Signal models#

Route#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
pathstrrequired
methodstr'ANY'
filestrrequired
lineint | NoneNone
source_analyzerstr | NoneNone
authLiteral['required', 'anonymous', 'unknown']'unknown'
guardslist[str]list()
guard_evidencestr | NoneNone

ExternalCall#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
targetstrrequired
filestrrequired
lineint | NoneNone
methodstr | NoneNone
evidence_textstr | NoneNone
source_analyzerstr | NoneNone

DatabaseHint#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
kindstrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
source_analyzerstr | NoneNone

AuthHint#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
hintstrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
confidencefloat0.7
source_analyzerstr | NoneNone

ServiceHint#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
hintstrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
confidencefloat0.7
source_analyzerstr | NoneNone

EdgeHint#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
hintstrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
confidencefloat0.7
source_analyzerstr | NoneNone

EntrypointHint#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
hintstrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
confidencefloat0.7
source_analyzerstr | NoneNone

ProtocolHint#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
hintstrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
confidencefloat0.7
source_analyzerstr | NoneNone

FrameworkHint#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
hintstrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
confidencefloat0.7
source_analyzerstr | NoneNone

SecretHint#

Model (pydantic). Module attackmap.models.

FieldTypeDefault
namestrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
confidencefloat0.85
source_analyzerstr | NoneNone
kindstr'env_reference'

ScanResult#

The same class as AnalyzerResult, exported under a second name.

DependencyHint#

Model (pydantic). Module attackmap.models.

A single third-party dependency declared in a manifest or lockfile.

Manifests (#48) carry version ranges verbatim (^4.16.0, >=2,<3, latest) and only direct dependencies. Lockfiles (#143) carry exact resolved versions and the full transitive tree — those hints set resolved=True and populate direct / via.

FieldTypeDefault
namestrrequired
versionstrrequired
ecosystemstrrequired
filestrrequired
lineint | NoneNone
devboolFalse
resolvedboolFalse
directboolTrue
viastr | NoneNone
evidence_textstr | NoneNone
source_analyzerstr | NoneNone

CloudMisconfig#

Model (pydantic). Module attackmap.models.

A cloud, IaC or container-runtime misconfiguration (terraform#4, iac#1).

Emitted by IaC plugins (Terraform, Kubernetes / Helm, compose, Dockerfile). Like the other issue families, each one is a positively-present setting (or a documented insecure default that the file leaves in place) at a file and line. Core turns each kind into findings, one per (kind, severity) tier, so a low /var/log mount never inherits the severity of a docker.sock mount of the same kind.

  • kind: one of CLOUD_MISCONFIG_KINDS. Typed str so a plugin built against a newer core still validates here; core drops records with a kind it doesn’t know (noted under analyzer_errors) and keeps the rest of the plugin’s result.
  • resource: what the setting belongs to, e.g. aws_eks_cluster.main, Deployment/web container app, module.vpc.
  • platform: one of CLOUD_MISCONFIG_PLATFORMS.
  • context: how the resource is instantiated — for_each/count, the calling module of a local module, a Helm template.
  • escalation_path: what an attacker gets from it, in one line, e.g. hostPath / → node root → kubelet credentials.

The free-text fields are redacted, folded to one line and capped.

FieldTypeDefault
kindstrrequired
filestrrequired
lineint | NoneNone
resourcestr | NoneNone
platformLiteral['aws', 'azure', 'gcp', 'kubernetes', 'helm', 'compose', 'docker', 'terraform'] | NoneNone
contextstr | NoneNone
escalation_pathstr | NoneNone
evidence_textstr | NoneNone
severityLiteral['low', 'medium', 'high']'medium'
confidencefloat0.8
source_analyzerstr | NoneNone

AppSurfaceIssue#

Model (pydantic). Module attackmap.models.

A mobile/desktop app attack-surface issue emitted by a platform plugin (#188, #190): the Apple analyzers (Swift, Objective-C) report App Transport Security exceptions, custom URL-scheme handlers, weakening entitlements, WKWebView script built from strings, secrets in UserDefaults or on the pasteboard, unvalidated XPC peers, and classes, selectors or libraries resolved from runtime strings. Native format-string and command-exec constructs are CodeWeakness kinds shared with the C/C++ plugins.

Core owns the findings: one aggregated finding per kind (rule id = the kind, dashed), at the most severe instance’s severity. Like the other issue families, every instance is a positively-present construct, never an absence.

kind is one of APP_SURFACE_ISSUE_KINDS but typed str, so a plugin built against a newer core still validates: core drops records with a kind it doesn’t know (noted under analyzer_errors) and keeps the rest of the plugin’s result.

FieldTypeDefault
kindstrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
severityLiteral['low', 'medium', 'high']'medium'
confidencefloat0.8
source_analyzerstr | NoneNone

CodeWeakness#

Model (pydantic). Module attackmap.models.

A novel vulnerability-class weakness in source (#77).

Emitted by the built-in weaknesses finder — bug classes beyond the taint/crypto/web-hardening families, detected from concrete risky constructs (not absence).

FieldTypeDefault
kindstrrequired
filestrrequired
lineint | NoneNone
evidence_textstr | NoneNone
severityLiteral['low', 'medium', 'high']'medium'
confidenceLiteral['low', 'medium', 'high'] | NoneNone
source_kindstr | NoneNone
source_lineint | NoneNone
source_analyzerstr | NoneNone

SupplyChainIssue#

Model (pydantic). Module attackmap.models.

A dependency supply-chain risk beyond CVEs (#247).

Emitted by the built-in, offline supply_chain pass over manifests, lockfiles and package-manager configs: dependency confusion, typosquat candidates, mutable VCS/URL dependencies, install scripts, unlocked manifests and insecure registries.

FieldTypeDefault
kindLiteral['dependency_confusion', 'typosquat_candidate', 'mutable_vcs_dependency', 'install_script', 'unlocked_manifest', 'insecure_registry']required
filestrrequired
lineint | NoneNone
packagestr | NoneNone
ecosystemstr | NoneNone
evidence_textstr | NoneNone
severityLiteral['low', 'medium', 'high']'medium'
source_analyzerstr | NoneNone

Allowed values#

DependencyEcosystem#

Type: one of 'pypi', 'npm', 'go', 'cargo', 'composer', 'swiftpm', 'maven', 'nuget', 'conan', 'vcpkg', 'cmake', 'gem', 'cocoapods', 'carthage', 'hex', 'pub'.

DEPENDENCY_ECOSYSTEMS#

tuple of 16: pypi, npm, go, cargo, composer, swiftpm, maven, nuget, conan, vcpkg, cmake, gem, cocoapods, carthage, hex, pub

CloudMisconfigKind#

Type: one of 'iac_module_unpinned', 'public_network_access', 'public_cluster_endpoint', 'plaintext_listener', 'api_no_authorizer', 'unencrypted_storage', 'public_data_access', 'public_invoker', 'privileged_container', 'privilege_escalation_allowed', 'container_runs_as_root', 'missing_security_context', 'host_path_mount', 'host_namespace_shared', 'dangerous_capability', 'security_profile_disabled', 'exposed_service', 'unpinned_container_image', 'dockerfile_remote_add', 'dockerfile_curl_pipe_shell', 'rbac_wildcard', 'rbac_escalation', 'rbac_secret_access', 'cluster_admin_binding'.

CLOUD_MISCONFIG_KINDS#

tuple of 24: iac_module_unpinned, public_network_access, public_cluster_endpoint, plaintext_listener, api_no_authorizer, unencrypted_storage, public_data_access, public_invoker, privileged_container, privilege_escalation_allowed, container_runs_as_root, missing_security_context, host_path_mount, host_namespace_shared, dangerous_capability, security_profile_disabled, exposed_service, unpinned_container_image, dockerfile_remote_add, dockerfile_curl_pipe_shell, rbac_wildcard, rbac_escalation, rbac_secret_access, cluster_admin_binding

CloudMisconfigPlatform#

Type: one of 'aws', 'azure', 'gcp', 'kubernetes', 'helm', 'compose', 'docker', 'terraform'.

CLOUD_MISCONFIG_PLATFORMS#

tuple of 8: aws, azure, gcp, kubernetes, helm, compose, docker, terraform

AppSurfaceIssueKind#

Type: one of 'apple_ats_insecure_loads', 'apple_custom_url_scheme', 'apple_webview_js_injection', 'apple_insecure_secret_storage', 'apple_pasteboard_secret', 'apple_hardened_runtime_exception', 'apple_debuggable_entitlement', 'apple_xpc_unvalidated_peer', 'apple_dynamic_dispatch'.

APP_SURFACE_ISSUE_KINDS#

tuple of 9: apple_ats_insecure_loads, apple_custom_url_scheme, apple_webview_js_injection, apple_insecure_secret_storage, apple_pasteboard_secret, apple_hardened_runtime_exception, apple_debuggable_entitlement, apple_xpc_unvalidated_peer, apple_dynamic_dispatch

CODE_WEAKNESS_KINDS#

tuple of 26: prototype_pollution, mass_assignment, jwt_weakness, jwt_alg_confusion, jwt_unverified_decode, jwt_claims_unchecked, jwt_header_trust, jwt_weak_secret, oauth_missing_state, oauth_implicit_flow, oauth_redirect_uri_loose, oauth_missing_pkce, session_fixation, session_weak_secret, xxe, redos, insecure_upload, graphql_exposure, graphql_no_query_limits, graphql_batching, prompt_injection_attempt, unbounded_copy, format_string, native_command_exec, tainted_copy_length, alloc_size_overflow

Filesystem helpers#

walk_repo#

python
def walk_repo(root: str | Path, *, prune: Callable[[str], bool] | None = None, on_symlink: Callable[[Path], None] | None = None) -> Iterator[Path]

Yield regular files under root (depth-first, sorted per directory).

Symlinks are skipped — symlinked files are not yielded and symlinked directories are not descended — and on_symlink(path) is called for each. With ATTACKMAP_FOLLOW_SYMLINKS=1 symlinks whose target resolves inside root are followed (cycle-safe); ones pointing outside are still skipped. prune(dirname) returning True skips descending into a directory.

is_contained#

python
def is_contained(root: str | Path, path: str | Path) -> bool

True when path is inside root and is not reached via a symlink.

Every component between root and path is checked with lstat — a symlinked directory in the middle of the path disqualifies it just like a symlinked file. With ATTACKMAP_FOLLOW_SYMLINKS=1 symlinks are allowed as long as the fully resolved path stays inside root.

contained_file#

python
def contained_file(root: str | Path, rel: str | Path) -> Path | None

Resolve a repo-relative path from untrusted text (evidence, plugin output) to a regular file inside root, or None.

Absolute paths and paths with .. components are rejected outright.

read_repo_text#

python
def read_repo_text(root: str | Path, path: str | Path, *, encoding: str = 'utf-8', errors: str = 'strict') -> str

Path.read_text that refuses paths outside root or behind a symlink.

DEFAULT_SKIP_DIRS#

frozenset of 18: .attackmap, .attackmap-gui, .git, .mypy_cache, .next, .pytest_cache, .tox, .turbo, .venv, __pycache__, bower_components, build, dist, node_modules, out, target, vendor, venv

iter_repo_files#

python
def iter_repo_files(root: str | Path, *, suffixes: Collection[str] | None = None, names: Collection[str] | None = None, skip_dirs: Collection[str] = frozenset({'.git', '.venv', 'out', '__pycache__', '.attackmap', 'bower_components', 'dist', '.tox', 'node_modules', '.attackmap-gui', '.mypy_cache', '.turbo', 'venv', 'vendor', '.pytest_cache', 'build', 'target', '.next'}), max_bytes: int | None = None, include_tests: bool = True, on_skip: Callable[[Path, str], None] | None = None) -> Iterator[Path]

Yield the repo’s regular files, sorted and depth-first.

  • suffixes (e.g. {".go"}, case-insensitive) and names (exact file names, e.g. {"go.mod"}) filter the files; a file matching either is yielded. With neither, every file is.
  • skip_dirs are matched against directory names inside the repo and pruned, so the walk never enters them, and directories above root never count.
  • Files larger than max_bytes (default: the core per-file cap, ATTACKMAP_MAX_FILE_BYTES) are skipped.
  • include_tests=False drops test/spec/fixture files (see ATTACKMAP_INCLUDE_TESTS).
  • on_skip(path, reason) is called for each skipped symlink or oversized file, so a plugin can report what it didn’t analyze.

read_source#

python
def read_source(path: str | Path, *, root: str | Path | None = None) -> str | None

Read a source file as text, or return None if it can’t be read.

Decodes UTF-8 (with or without a BOM), then falls back to cp1252 and finally latin-1, so legacy-encoded sources are analyzed rather than dropped. Returns None for unreadable files (OSError), binary files (a NUL byte in the first 8 KiB) and, when root is given, paths outside it.

rel#

python
def rel(path: str | Path, root: str | Path) -> str

path relative to root, POSIX-style (src/app.py).

line_of#

python
def line_of(content: str, offset: int) -> int

1-indexed line of a character offset (e.g. match.start()).

line_snippet#

python
def line_snippet(content: str, line: int, max_len: int = 200) -> str

The stripped text of 1-indexed line, truncated to max_len.

Lines are split on `

only, matchingline_of; str.splitlines` would also break on form feeds and other separators common in legacy C and put the snippet on the wrong line.

Taint sink plugins#

TAINT_SINK_PLUGIN_GROUP#

'attackmap.taint_sinks'

TAINT_SINK_KINDS#

tuple of 19: sql_execute, subprocess_shell, eval, exec, dynamic_open, path_traversal, zip_slip, unsafe_deserialization, ssti, ssrf, nosql_injection, open_redirect, code_injection, expression_injection, jndi_injection, ldap_injection, xpath_injection, header_injection, regex_injection

validate_taint_sink#

python
def validate_taint_sink(entry: Any, kinds: dict[str, KindMeta] | None = None, *, source: str = 'core') -> SinkSpec

Validate one registry entry and compile it. Raises SinkRegistryError.

Every sink must name a known kind, at least one language, a compiling regex and a cwe — a sink without a CWE can’t be tagged or mapped, so it is rejected outright.