CI & pull requests¶
AttackMap is built to run in CI and speak up on pull requests.
GitHub Action¶
The repo ships a composite action (action.yml) that runs a scan, emits SARIF
(so findings show up in the GitHub Security tab), and can post a PR summary.
name: AttackMap
on: pull_request # not pull_request_target — see below
permissions:
contents: read
security-events: write # SARIF upload
pull-requests: write # only if you post the PR comment
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<commit-sha> # pin actions to full commit SHAs
- uses: mlaify/AttackMap@<commit-sha> # e.g. the commit of a release tag
with:
path: .
How the action protects the pipeline (0.4.31+):
- Use
pull_request, neverpull_request_target.pull_request_targetruns fork code with your repository's secrets and a write token. - Pin by commit SHA. The action installs exactly the code your
uses:ref checks out, and the analyzer plugins it pulls in are pinned to immutable commits. A moved tag or branch can't change what runs. - Inputs never reach the shell unquoted. They're passed through environment variables, so a branch name or PR title can't inject commands.
- Reports are written to
$RUNNER_TEMP/attackmap-reports, outside the checkout, so a PR can't plant files or symlinks where reports land. Override with theoutputinput. - Only official analyzers load (
--trusted-analyzers-only). A dependency that registers anattackmap.analyzersentry point can't run inside the scan.
AttackMap also checks your workflows and composite actions for these
mistakes: unpinned uses:, ${{ github.event.* }} or ${{ inputs.* }} in
run: scripts, pull_request_target checkouts, broad permissions and more.
Installing analyzers with -m¶
attackmap analyze -m go uses an installed analyzer. If it isn't installed,
AttackMap prints the exact pinned pip install command and exits. Pass
--install-missing, or confirm at the interactive prompt, to have it install the
official plugin at its pinned commit. Unknown names are rejected without any
network access. To vet third-party analyzers, run with --trusted-analyzers-only.
PR summary bot¶
--pr-comment <file> writes a Markdown summary — the baseline diff plus top
exploitability — for a bot to post as a PR comment:
Diff gating¶
Fail the build when a PR introduces new high-severity findings, comparing against a baseline report from the target branch:
attackmap analyze . \
--baseline baseline/attackmap-report.json \
--diff-output attackmap-diff.md \
--fail-on-new-high
Because findings carry stable IDs, the diff is reliable across runs — you gate on new risk, not on the total count. See Scanning & output.
Suppressing findings¶
Silence a residual false positive without going blind to new signal. Two
mechanisms, both honored across every pass. Suppressed findings are not
dropped: they're excluded from --fail-on-new-high and the console summary,
but kept in attackmap-report.json under suppressed_findings (with reasons)
and marked with a SARIF suppressions array so GitHub Code Scanning shows them
as suppressed, not active. Suppression counts print in the run summary.
Repo-level baseline¶
Drop a .attackmap-suppress.yaml at the repo root:
version: 1
suppress:
# by stable finding id (the 16-hex id in attackmap-report.json)
- id: 1a2b3c4d5e6f7a8b
reason: accepted risk, tracked in JIRA-1234
# by rule (a slug of the finding title — the same string as the SARIF ruleId),
# optionally scoped to paths
- rule: hard-coded-secret-literals-were-found-in-source-or-config
reason: example keys in docs, never shipped
paths: ["docs/**", "examples/**"]
# by path only — any finding whose evidence is *entirely* within these globs
- path: "vendor/**"
reason: third-party code, out of scope
Every entry needs a reason. A path selector matches a finding only when
every file its evidence cites falls under the glob, so a finding that also
touches live code is never silently hidden. * spans path separators
(vendor/* covers the whole subtree); ** is accepted as a synonym.
Inline directives¶
A comment on (or above) the flagged line, in any language:
API_KEY = "AKIA…" # attackmap:ignore[hard-coded-secret-literals-were-found-in-source-or-config] staging only
The bracketed rule is optional — a bare # attackmap:ignore matches any rule.
Because findings aggregate every site of an issue type, an inline directive
suppresses a finding only when all of its cited files carry a matching
directive (or are covered by a baseline path glob); for a multi-file finding,
prefer a baseline entry.
Flags¶
| Flag | Effect |
|---|---|
--no-suppress |
Ignore all suppressions — a full, unfiltered audit. |
--suppress-file PATH |
Use a baseline elsewhere than the repo-root default. |
Finding the rule id
The rule key is the slug you see as the ruleId in SARIF / the GitHub
Security tab, and the id is the 16-hex value in each finding of
attackmap-report.json. Copy either straight from a prior run.