fix(validate): catch typos in containers config and close self-scan UX gaps#118
Conversation
Three fixes plus a workflow refactor, all surfaced while running argus against argus's own source and container images. 1. argus validate now validates the top-level containers block. Until now the schema validator recognized 'containers' as a known top-level key but never inspected its contents — typos (e.g. image_path instead of dockerfile), an empty images list, missing both image and dockerfile on an entry, and sub-scanner names outside trivy/grype/ syft all sailed past argus validate and only surfaced (or failed silently) at scan time. New _validate_containers helper in argus/core/schema.py walks the block and produces the same ConfigError objects the rest of the validator already emits, so argus validate and the pre-scan validation in ArgusConfig._load_file both pick it up. 15 unit tests in argus/tests/core/test_schema_containers.py. 2. argus scan container auto-loads argus.yml when no --config is given. The source-scan dispatcher has always done this; the container subcommand used to require an explicit --config FILE even when an argus.yml sat right at the project root with a containers block. Now both flows search the canonical _DEFAULT_CONFIG_NAMES list. CLI flags still take precedence over config-file values. Test in argus/tests/test_cli.py. 3. .ai/ doc cleanup: the recent context refresh claimed argus list and argus version are subcommands. They are not — argus --version is a top-level flag, and the registered subcommand list is now correctly spelled out in .ai/context.yaml and .ai/architecture.yaml. 4. .github/workflows/build-containers.yml: extract the four hard-coded image entries into a preflight matrix job that reads argus.yml containers.images and emits a JSON matrix the build, scan, and test-cli jobs consume. argus.yml becomes the single source of truth for the dogfood image list — adding a fifth scanner image is now one entry, not three matrix edits across the workflow plus the dogfood config. The actual scanning still runs aquasecurity/trivy-action and anchore/scan-action (authored actions, not argus-scanning-argus) — argus.yml drives the matrix, the trust boundary stays outside our own codebase. Tests: 1513 passed (was 1497 before this PR; 16 new from schema tests and the auto-config-load regression test).
E2E Test Coverage Report
Summary
✅ All Actions Have E2E Coverage |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
🔒 Argus Container Security ScanBranch: 📊 Combined Findings Summary
Scanned: 4 containers | Build Failures: 0 📦 Container Breakdown
🔍 Detailed Findings by Container🚨 cli - 29 vulnerabilities (23 unique)Image: Combined (Deduplicated)
🔷 Trivy Scanner (29 findings, 23 unique)
⚓ Grype Scanner (0 findings, 0 unique)✅ No vulnerabilities detected by Grype 🟡 scanner-bandit - 2 vulnerabilities (2 unique)Image: Combined (Deduplicated)
🔷 Trivy Scanner (2 findings, 2 unique)
⚓ Grype Scanner (0 findings, 0 unique)✅ No vulnerabilities detected by Grype
|
| 🚨 Critical | 🟡 Medium | 🔵 Low | Total | Unique | |
|---|---|---|---|---|---|
| 0 | 7 | 42 | 63 | 114 | 50 |
🔷 Trivy Scanner (114 findings, 49 unique)
| CVE | Severity | Package | Version | Fixed |
|---|---|---|---|---|
| CVE-2026-4878 | libcap2 | 1:2.75-10+b8 | N/A | |
| CVE-2025-69720 | libncursesw6 | 6.5+20250216-2 | N/A | |
| CVE-2026-29111 | libsystemd0 | 257.9-1~deb13u1 | N/A | |
| CVE-2025-69720 | libtinfo6 | 6.5+20250216-2 | N/A | |
| CVE-2026-29111 | libudev1 | 257.9-1~deb13u1 | N/A | |
| CVE-2025-69720 | ncurses-base | 6.5+20250216-2 | N/A | |
| CVE-2025-69720 | ncurses-bin | 6.5+20250216-2 | N/A | |
| CVE-2026-27456 | 🟡 MEDIUM | bsdutils | 1:2.41-5 | N/A |
| CVE-2026-3184 | 🟡 MEDIUM | bsdutils | 1:2.41-5 | N/A |
| CVE-2026-27456 | 🟡 MEDIUM | libblkid1 | 2.41-5 | N/A |
| CVE-2026-3184 | 🟡 MEDIUM | libblkid1 | 2.41-5 | N/A |
| CVE-2026-4046 | 🟡 MEDIUM | libc-bin | 2.41-12+deb13u2 | N/A |
| CVE-2026-4437 | 🟡 MEDIUM | libc-bin | 2.41-12+deb13u2 | N/A |
| CVE-2026-4438 | 🟡 MEDIUM | libc-bin | 2.41-12+deb13u2 | N/A |
| CVE-2026-5435 | 🟡 MEDIUM | libc-bin | 2.41-12+deb13u2 | N/A |
| CVE-2026-5450 | 🟡 MEDIUM | libc-bin | 2.41-12+deb13u2 | N/A |
| CVE-2026-5928 | 🟡 MEDIUM | libc-bin | 2.41-12+deb13u2 | N/A |
| CVE-2026-4046 | 🟡 MEDIUM | libc6 | 2.41-12+deb13u2 | N/A |
| CVE-2026-4437 | 🟡 MEDIUM | libc6 | 2.41-12+deb13u2 | N/A |
| CVE-2026-4438 | 🟡 MEDIUM | libc6 | 2.41-12+deb13u2 | N/A |
| CVE-2026-5435 | 🟡 MEDIUM | libc6 | 2.41-12+deb13u2 | N/A |
| CVE-2026-5450 | 🟡 MEDIUM | libc6 | 2.41-12+deb13u2 | N/A |
| CVE-2026-5928 | 🟡 MEDIUM | libc6 | 2.41-12+deb13u2 | N/A |
| CVE-2026-27456 | 🟡 MEDIUM | liblastlog2-2 | 2.41-5 | N/A |
| CVE-2026-3184 | 🟡 MEDIUM | liblastlog2-2 | 2.41-5 | N/A |
| CVE-2026-34743 | 🟡 MEDIUM | liblzma5 | 5.8.1-1 | N/A |
| CVE-2026-27456 | 🟡 MEDIUM | libmount1 | 2.41-5 | N/A |
| CVE-2026-3184 | 🟡 MEDIUM | libmount1 | 2.41-5 | N/A |
| CVE-2026-27456 | 🟡 MEDIUM | libsmartcols1 | 2.41-5 | N/A |
| CVE-2026-3184 | 🟡 MEDIUM | libsmartcols1 | 2.41-5 | N/A |
| CVE-2026-40225 | 🟡 MEDIUM | libsystemd0 | 257.9-1~deb13u1 | N/A |
| CVE-2026-40226 | 🟡 MEDIUM | libsystemd0 | 257.9-1~deb13u1 | N/A |
| CVE-2026-4105 | 🟡 MEDIUM | libsystemd0 | 257.9-1~deb13u1 | N/A |
| CVE-2026-40225 | 🟡 MEDIUM | libudev1 | 257.9-1~deb13u1 | N/A |
| CVE-2026-40226 | 🟡 MEDIUM | libudev1 | 257.9-1~deb13u1 | N/A |
| CVE-2026-4105 | 🟡 MEDIUM | libudev1 | 257.9-1~deb13u1 | N/A |
| CVE-2026-27456 | 🟡 MEDIUM | libuuid1 | 2.41-5 | N/A |
| CVE-2026-3184 | 🟡 MEDIUM | libuuid1 | 2.41-5 | N/A |
| CVE-2026-27456 | 🟡 MEDIUM | login | 1:4.16.0-2+really2.41-5 | N/A |
| CVE-2026-3184 | 🟡 MEDIUM | login | 1:4.16.0-2+really2.41-5 | N/A |
| CVE-2026-27456 | 🟡 MEDIUM | mount | 2.41-5 | N/A |
| CVE-2026-3184 | 🟡 MEDIUM | mount | 2.41-5 | N/A |
| CVE-2026-5958 | 🟡 MEDIUM | sed | 4.9-2 | N/A |
| CVE-2026-5704 | 🟡 MEDIUM | tar | 1.35+dfsg-3.1 | N/A |
| CVE-2026-27456 | 🟡 MEDIUM | util-linux | 2.41-5 | N/A |
| CVE-2026-3184 | 🟡 MEDIUM | util-linux | 2.41-5 | N/A |
| CVE-2026-27171 | 🟡 MEDIUM | zlib1g | 1:1.3.dfsg+really1.3.1-1+b1 | N/A |
| CVE-2026-3219 | 🟡 MEDIUM | pip | 26.0.1 | N/A |
| CVE-2026-6357 | 🟡 MEDIUM | pip | 26.0.1 | 26.1 |
| CVE-2011-3374 | 🔵 LOW | apt | 3.0.3 | N/A |
...and 64 more
⚓ Grype Scanner (0 findings, 0 unique)
✅ No vulnerabilities detected by Grype
⚠️ scanner-supply-chain - 9 vulnerabilities (9 unique)
Image: ghcr.io/huntridge-labs/argus/scanner-supply-chain:19236951317d0f7b30fd807408558548f0b20cbb
Combined (Deduplicated)
| 🚨 Critical | 🟡 Medium | 🔵 Low | Total | Unique | |
|---|---|---|---|---|---|
| 0 | 4 | 5 | 0 | 9 | 9 |
🔷 Trivy Scanner (9 findings, 9 unique)
| CVE | Severity | Package | Version | Fixed |
|---|---|---|---|---|
| CVE-2026-32280 | stdlib | v1.26.1 | 1.25.9, 1.26.2 | |
| CVE-2026-32281 | stdlib | v1.26.1 | 1.25.9, 1.26.2 | |
| CVE-2026-32283 | stdlib | v1.26.1 | 1.25.9, 1.26.2 | |
| CVE-2026-33810 | stdlib | v1.26.1 | 1.26.2 | |
| CVE-2026-3219 | 🟡 MEDIUM | pip | 26.0.1 | N/A |
| CVE-2026-6357 | 🟡 MEDIUM | pip | 26.0.1 | 26.1 |
| CVE-2026-32282 | 🟡 MEDIUM | stdlib | v1.26.1 | 1.25.9, 1.26.2 |
| CVE-2026-32288 | 🟡 MEDIUM | stdlib | v1.26.1 | 1.25.9, 1.26.2 |
| CVE-2026-32289 | 🟡 MEDIUM | stdlib | v1.26.1 | 1.25.9, 1.26.2 |
⚓ Grype Scanner (0 findings, 0 unique)
✅ No vulnerabilities detected by Grype
Generated by Argus
The validate-success summary listed scanners, formats, and backend but
ignored the top-level containers block entirely. With containers
silently absent from the summary the user had no signal that argus
validate had inspected it — a typo at the block name (e.g.
``containerz:``) would still show up as a top-level-key warning, but a
correctly-named block with valid contents looked identical to no block
at all.
Add a Containers line that shows:
Containers: 4 image(s)
- ghcr.io/myorg/scanner-bandit:dev
- ghcr.io/myorg/scanner-opengrep:dev
...
Containers: discover from docker/, .
Containers: 2 image(s) + discover from docker/
The line is only printed when the block is structurally a mapping; if
no containers block exists, the validator stays silent (matching the
optional nature of the block). Three new regression tests in
TestCmdValidate cover the present, absent, and discover variants.
Description
Three fixes plus a workflow refactor, all surfaced while running
argus scanandargus scan containeragainst argus's own source and image set during the validation pass after PR #117.Changes Made
Details
1.
argus validatenow validates the top-levelcontainers:blockUntil now
argus/core/schema.pyrecognizedcontainersas a known top-level key but never inspected its contents. Typos and structural mistakes only surfaced (or failed silently) at scan time:image_path:instead ofdockerfile:argus validateimages: []image:nordockerfile:{trivy, grype, syft}containers: not-a-mappingNew
_validate_containershelper walks the block and produces the sameConfigErrorobjects the rest of the validator already emits, so bothargus validateand the pre-scan validation inArgusConfig._load_filepick it up. 15 unit tests inargus/tests/core/test_schema_containers.py.2.
argus scan containerauto-loadsargus.ymlwhen no--configis givenThe source-scan dispatcher has always done this; the container subcommand used to require an explicit
--config FILEeven whenargus.ymlsat right at the project root with a populatedcontainers:block. Now both flows search the canonical_DEFAULT_CONFIG_NAMESlist (argus.yml,argus.yaml,.argus.yml,.argus.yaml). CLI flags still take precedence over config-file values.3.
.ai/doc cleanupThe recent context refresh (commit
fa22779) claimedargus listandargus versionare subcommands. They're not —argus --versionis a top-level flag, and the registered subcommand list isinit / scan / classify / collect / report / validate / mcp / completion / cache / view..ai/context.yamland.ai/architecture.yamlcorrected.4.
.github/workflows/build-containers.yml— argus.yml as matrix source of truthHardcoded matrices in three places (build, scan, test-cli) replaced with a single preflight
matrixjob that readsargus.yml'scontainers.imageslist and emits a JSON matrix the downstream jobs consume viafromJson(needs.matrix.outputs.matrix). Adding a fifth scanner image is now one entry inargus.yml, not three matrix edits across the workflow plus the dogfood config.The actual scanning still runs
aquasecurity/trivy-action+anchore/scan-action— authored actions, not argus-scanning-argus. Argus-scanning-argus to validate argus is circular trust, and the user explicitly called this out:So argus.yml drives the matrix, the trust boundary stays outside our own codebase.
Testing
Test Results
test_schema_containers.py(happy paths, structural errors, image-entry validation, sub-scanner whitelist, unknown-key warnings)Manual validation against the local checkout (the trigger for this PR):
argus scan --config argus.yml: 6 scanners, 163 findingsargus scan container --config argus.yml: 4 images built locally, 230s wall, 141 total findings (98 unique)argus scan container(no--config): now auto-detectsargus.yml, runs identically to the explicit form ✅Security Considerations
Security Details
The workflow refactor (#4) makes argus.yml the input to a CI-time matrix builder, but the runner step reads only the checked-out repo file via
yq(no untrusted input from PR titles/body/etc.). The pre-commit yamllint and the husky hook chain reviewed the change.The trust-boundary point is the substantive one: by keeping
aquasecurity/trivy-actionandanchore/scan-actionfor the actual container scanning, argus's own image set is validated by external tools rather than by argus itself.AI Context Updates (.ai/)
.ai/architecture.yamlupdated (removed falseargus listclaim, added missing--versionclarification).ai/workflows.yamlupdated (if commands/tasks changed).ai/decisions.yamlupdated (if design decision made).ai/errors.yamlupdated (if common error addressed).ai/context.yamlcli_subcommands list reorganized;argus listandargus versionremoved (they don't exist);--versionflag explicitly noted under a newcli_flags:section.Checklist
For New Scanners/Actions (if applicable)
Follow-ups (not in this PR)
Surfaced during the same dogfooding pass; deferred to keep this PR focused:
lint-dockerfiledoesn't appear in source-scan results even when its container image (hadolint) is unavailable locally. Other scanners surface as failed-execution rows withmetadata.execution_failed; lint-dockerfile silently drops out. Worth investigating — likely a registry routing issue betweenLINTER_REGISTRYandSCANNER_REGISTRYfor the engine's failure-row construction.