Migrating from argus 0.6.x to 1.x
argus's primary surface in 1.x is the Python SDK / argus CLI driven
by a single argus.yml config file (ADR-013). The composite actions
under .github/actions/scanner-* are now thin wrappers around the
SDK — same scans, same canonical argus-results.json, but with most
tuning living in argus.yml so the same configuration works
identically on GitHub Actions, GitLab CI, Jenkins, Azure DevOps, or a
laptop. This page shows what changed and how to update existing
workflows.
The full design rationale is in
ADR-024.
Full config schema reference lives at
docs/config-reference.md.
TL;DR — the new shape
0.6.x (everything on the composite action):
- uses: huntridge-labs/argus/.github/actions/scanner-zap@1.1.0
with:
target_url: 'http://localhost:8080'
scan_type: 'baseline'
api_spec: 'http://localhost:8080/openapi.json'
rules_file_name: '.zap/rules.tsv'
max_duration_minutes: 30
cmd_options: '-z -config view.locale=en_GB'
healthcheck_url: 'http://localhost:8080/healthz'
registry_username: ${{ secrets.REGISTRY_USER }}
registry_password: ${{ secrets.REGISTRY_TOKEN }}
fail_on_severity: 'high'
1.x — composite action stays minimal, the tuning moves into
argus.yml:
# argus.yml — checked into the repo
scanners:
zap:
target_url: "http://localhost:8080"
scan_type: baseline
api_spec: "http://localhost:8080/openapi.json"
rules_file: ".zap/rules.tsv"
max_duration_minutes: 30
cmd_options:
- "-z"
- "-config view.locale=en_GB"
healthcheck_url: "http://localhost:8080/healthz"
registry_username_env: REGISTRY_USER
registry_password_env: REGISTRY_TOKEN
# .github/workflows/security.yml — workflow stays small
- name: Run argus
env:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
uses: huntridge-labs/argus/.github/actions/scanner-zap@1.1.0
with:
fail_on_severity: 'high'
The same argus.yml works whether you invoke argus via the composite
action, the SDK on any other CI platform, or locally — that's the
point of the refactor.
Quick-reference table
| 0.6.x — composite action input | 1.x — location |
|---|---|
api_spec |
scanners.zap.api_spec |
rules_file_name |
scanners.zap.rules_file |
cmd_options |
scanners.zap.cmd_options (now list[string]) |
max_duration_minutes |
scanners.zap.max_duration_minutes |
healthcheck_url |
scanners.zap.healthcheck_url |
registry_username |
scanners.zap.registry_username_env (env-var name) |
registry_password |
scanners.zap.registry_password_env (env-var name) |
target_url |
unchanged — still a with: input on the action |
app_image_ref, app_ports |
unchanged — still with: inputs |
fail_on_severity, post_pr_comment, enable_code_security |
unchanged — still with: inputs |
What changed for each input
api_spec
Switches the scan from zap-baseline.py to zap-api-scan.py. Used
to discover endpoints from an OpenAPI / Swagger spec rather than
crawling from a base URL.
# 0.6.x
with:
scan_type: 'api'
api_spec: 'http://localhost:8080/openapi.json'
# 1.x
scanners:
zap:
api_spec: "http://localhost:8080/openapi.json"
# scan_type is implicit when api_spec is set
rules_file_name → rules_file
Path to a ZAP .tsv ignore-rules file. Argus mounts it into the
container at /zap/wrk/rules.tsv.
# 1.x
scanners:
zap:
rules_file: ".zap/rules.tsv"
cmd_options
Extra ZAP CLI flags. In 0.6.x this was a single string; in 1.x it's a YAML list so quoting nested arguments isn't a fight.
# 0.6.x
with:
cmd_options: '-z -config view.locale=en_GB'
# 1.x
scanners:
zap:
cmd_options:
- "-z"
- "-config view.locale=en_GB"
max_duration_minutes
Hard cap on scan duration (translates to -T <minutes>).
# 1.x
scanners:
zap:
max_duration_minutes: 30
healthcheck_url
Argus polls this URL for 2xx readiness (default timeout 60s) before invoking ZAP. Useful when the SUT needs a moment to come up.
# 1.x
scanners:
zap:
healthcheck_url: "http://localhost:8080/healthz"
Registry auth — registry_username / registry_password
This is the biggest shape change. In 0.6.x you piped a secret value
directly into the with: block. In 1.x you name the env var that
holds the value; argus reads from os.environ at scan time. The
value never lands in argus's per-scanner config dict, so it can't
leak into argus-audit.json or argus.log.
# 0.6.x — secret VALUE on the with: block
with:
registry_username: ${{ secrets.REGISTRY_USER }}
registry_password: ${{ secrets.REGISTRY_TOKEN }}
# 1.x — argus.yml holds only the env-var NAME
scanners:
zap:
registry_username_env: REGISTRY_USER
registry_password_env: REGISTRY_TOKEN
# 1.x — workflow exports the secret to the env
- name: Run argus
env:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
uses: huntridge-labs/argus/.github/actions/scanner-zap@1.1.0
with:
fail_on_severity: 'high'
The literal form (registry_username: ${{ secrets.X }}) is still
accepted for back-compat, but argus validate will warn if the
value matches a known vendor secret prefix (gh*, AKIA, AIza, glpat-,
etc.). Migrate to the _env form when convenient.
If your invocation is interactive (one-off scan, not CI), the CLI
also accepts --registry-password-stdin mirroring
docker login --password-stdin:
echo "$REGISTRY_TOKEN" | argus scan --registry-password-stdin --config argus.yml
See docs/config-reference.md → Credential fields
for the full credential-precedence table.
scanner-container — multi-image scanning
scanner-container's scan_mode input was removed alongside the
ZAP changes. The auto-discover / matrix semantic now lives in
argus.yml under the top-level containers: block — same posture
as scanners.zap.* — so the same configuration works across every
CI platform and locally.
Quick-reference table
| 0.6.x — composite action input | 1.x — location |
|---|---|
scan_mode: discover |
containers.discover: true + containers.search_paths: [...] |
scan_mode: manifest |
containers.images: [...] (parse-container-config action emits a matrix) |
allow_failure |
fail_on_severity: none (the canonical "informational scan" knob) |
skip_summary |
Use security-summary composite's scan_statuses input |
scan_mode: discover → containers.discover
Auto-find every Dockerfile* in the named paths, build each locally,
scan the built artifact, clean up.
# 0.6.x
- uses: huntridge-labs/argus/.github/actions/scanner-container@1.1.0
with:
scan_mode: discover
# 1.x — argus.yml
containers:
discover: true
search_paths:
- "services"
- "tools"
scanners:
- trivy
- grype
- syft
scan_mode: manifest → containers.images
Explicit list of images to scan. In 0.6.x this came from a separate
config file consumed by parse-container-config; in 1.x it's a
first-class key in argus.yml. The parse-container-config action
still works and emits a GHA matrix from the list when you want one
job per image.
# 1.x — argus.yml
containers:
images:
- image: "nginx:1.27"
- image: "ghcr.io/myorg/api:1.4.0@sha256:..."
- dockerfile: "services/worker/Dockerfile"
name: "worker"
scanners:
- trivy
- grype
You can mix discovery and explicit entries — discovered Dockerfiles
augment the list rather than replacing it. Worked examples in
docs/container-scanning.md.
allow_failure / skip_summary
These mapped 1:1 to existing argus patterns and didn't need their own composite knobs:
allow_failure: true→ setfail_on_severity: noneon the composite. The action runs and emits findings; the job never fails on argus's behalf. The canonical "scan-but-don't-gate" knob, used the same way everywhere else.skip_summary: true→ the per-scanner summary still gets written to artifacts; what 0.6.x'sskip_summaryactually controlled was whether the run participated in the aggregatedsecurity-summaryPR comment. In 1.x thesecurity-summarycomposite'sscan_statusesinput is the explicit gate — pass the scanner'sresultper the silent-failure-gating pattern (ADR-016) and the aggregator includes or excludes it cleanly.
What stayed on the composite action
The action's with: surface keeps the inputs that are cross-scanner
or cross-CI-platform-meaningless:
| Input | Why it stays |
|---|---|
target_url |
Target identification — composite-level concern |
app_image_ref / app_ports / app_env |
Container-target lifecycle, GHA-specific orchestration |
startup_timeout |
GHA runner-side wait |
scan_type |
Top-level scan-mode selector; can also live in argus.yml |
scan_name |
Artifact naming |
fail_on_severity |
Common across every scanner action |
post_pr_comment |
GitHub-Actions-specific output channel |
enable_code_security |
GitHub-Actions-specific (SARIF upload) |
Web-app authentication (new in 1.x)
If your SUT needs login to scan past public pages, 1.x adds a
scanners.zap.auth block that mounts a ZAP context file into the
container. Argus stays out of the DOM-mapping business — you author
the context (login URL, form selectors, logged-in regex) using ZAP's
native format (the ZAP GUI can record it for you), and argus mounts
it at /zap/wrk/context.xml.
scanners:
zap:
target_url: "https://app.example.com"
auth:
context_file: ".zap/context.xml"
username_env: ZAP_APP_USER
password_env: ZAP_APP_PASSWORD
The container reads ZAP_AUTH_USERNAME / ZAP_AUTH_PASSWORD env vars
(argus exports the resolved credentials under those names); ZAP's
{%username%} / {%password%} placeholders in the context file pick
them up.
What to do next
- Run
argus initin your repo root. It detects languages, frameworks, and existing config files, then writes a starterargus.yml. Edit to taste. - Move the seven 0.6.x inputs above from your workflow's
with:block intoargus.ymlunderscanners.zap.*per the table. - Wire registry / web-app secrets by setting env vars at the
workflow-step level (
env:block) and referencing the env-var names fromargus.yml(the*_envkeys). - Validate with
argus validate --strict— catches typos in the config and flags any leftover literal secret values that should move to env-var references. - Run the scan:
argus scan --config argus.yml, or invoke the trimmed composite action.
Questions or migration edge cases not covered above? Open an issue at https://github.com/huntridge-labs/argus/issues.