feat(supply-chain): cosign-verify argus images + security policy doc#146
Conversation
Closes hardening items (3) and (4) from "Secret Handling & Credential
Surface Hardening" in docs/developer/SDK-ROADMAP.md.
Item (3) — cosign + digest-pin verification at pull time:
- argus/core/image_verify.py: new module classifies every pulled
image into one of four paths:
* argus-owned (ghcr.io/huntridge-labs/argus/*): cosign keyless
verify against the publish workflow's identity + GitHub
Actions OIDC issuer. Failure is fatal — scanner does not run.
* third-party with @sha256: digest pin: Docker enforces
content-hash match at pull, no cosign call. Logged at DEBUG.
* third-party with tag-only pin: no crypto guarantee. One
WARNING per scan run via report_tag_pinned_summary listing
every tag-pinned image with a migration hint.
* verification disabled: skipped wholesale (DEBUG log).
- argus/core/engine.py: _run_in_container now calls verify_image
right after _pull_image succeeds, accumulating results in
self._verify_results. Fatal verification raises RuntimeError
before subprocess.run, so the scanner subprocess never starts.
Summary tag-pinned WARNING emitted once at end of run().
- argus/core/config.py: ExecutionConfig.verify_image_signatures
defaults to True. Opt-out for air-gapped environments via
execution.verify_image_signatures: false in argus.yml.
- argus/core/schema.py: new bool-typed config key with validator.
- Stdlib + cosign binary on PATH; no Python sigstore dependency.
Fails up front with an install hint if cosign is missing.
Item (4) — written policy doc:
- docs/security.md: TL;DR table, threat model (defends-against +
does-not-defend-against), credential precedence with worked
examples (stdin > <field>_env > literal), container image
provenance section walking through each verification path, the
air-gapped opt-out, and a vulnerability-reporting pointer to
GitHub Security Advisories.
Tests:
- argus/tests/core/test_image_verify.py: 21 tests covering image
classification, all four verification paths, cosign output
truncation, the tag-pinned summary dedup/no-warning behavior.
- argus/tests/test_engine.py::TestSupplyChainVerificationGate: 6
engine-integration tests verifying the verify-then-run gate,
cosign-fail-aborts-scanner contract, third-party digest-pin
no-cosign-call, tag-only summary at run end, opt-out behavior,
and missing-cosign-binary fatal handling.
.ai/ updates:
- architecture.yaml: new core/image_verify.py entry in both SDK
structure blocks describing classification + engine integration.
Roadmap:
- Items (3) and (4) flipped to shipped with implementation summary
and links. Items (5) — defensive audit-trail redact — remains
queued.
Full suite: 3107 passed (+27 new), 2 skipped.
Follow-up roadmap item already noted: migrate third-party image
tags in argus/containers.py to @sha256: digest pins. Renovate can
keep them current once pinned.
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 - 73 vulnerabilities (33 unique)Image: Combined (Deduplicated)
🔷 Trivy Scanner (73 findings, 33 unique)
...and 23 more ⚓ 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 | 44 | 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-6238 | 🟡 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-6238 | 🟡 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 |
...and 64 more
⚓ Grype Scanner (0 findings, 0 unique)
✅ No vulnerabilities detected by Grype
⚠️ scanner-supply-chain - 17 vulnerabilities (17 unique)
Image: ghcr.io/huntridge-labs/argus/scanner-supply-chain:c20c3be4e6710f37d3fee3a456b0946dfd4ccfd6
Combined (Deduplicated)
| 🚨 Critical | 🟡 Medium | 🔵 Low | Total | Unique | |
|---|---|---|---|---|---|
| 0 | 9 | 8 | 0 | 17 | 17 |
🔷 Trivy Scanner (17 findings, 17 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-33811 | stdlib | v1.26.1 | 1.25.10, 1.26.3 | |
| CVE-2026-33814 | stdlib | v1.26.1 | 1.25.10, 1.26.3 | |
| CVE-2026-39820 | stdlib | v1.26.1 | 1.25.10, 1.26.3 | |
| CVE-2026-39836 | stdlib | v1.26.1 | 1.25.10, 1.26.3 | |
| CVE-2026-42499 | stdlib | v1.26.1 | 1.25.10, 1.26.3 | |
| 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 |
| CVE-2026-39823 | 🟡 MEDIUM | stdlib | v1.26.1 | 1.25.10, 1.26.3 |
| CVE-2026-39825 | 🟡 MEDIUM | stdlib | v1.26.1 | 1.25.10, 1.26.3 |
| CVE-2026-39826 | 🟡 MEDIUM | stdlib | v1.26.1 | 1.25.10, 1.26.3 |
⚓ Grype Scanner (0 findings, 0 unique)
✅ No vulnerabilities detected by Grype
Generated by Argus
Description
Closes hardening items (3) and (4) from
docs/developer/SDK-ROADMAP.md→ "Secret Handling & Credential Surface Hardening". Adds container image signature verification at pull time and the written security policy doc that describes argus's threat model.Changes Made
argus/core/image_verify.py+ engine integration; newdocs/security.mdpolicyDetails
Verification policy — three paths, one config knob.
ghcr.io/huntridge-labs/argus/*(the 4 images we cosign-sign at release)cosign verifyagainst the publish workflow's certificate identity + GitHub Actions OIDC issuersubprocess.run@sha256:digest pin (e.g.aquasec/trivy@sha256:abc...)Config knob:
execution.verify_image_signatures: bool(defaulttrue). Opt-out for air-gapped environments where Sigstore / Rekor network access isn't available.Why this shape (from the earlier design discussion): cosign verification only makes sense for images whose signing identity we control. Most upstream scanner publishers don't cosign-sign their images in a way we could validate — so verifying them would just produce constant false WARNINGs. Digest pins from the user's side are cryptographically equivalent to a signature check against a known-good identity, no external trust roots needed. The combined policy scales naturally: as we migrate
argus/containers.pyto digest pins (Renovate keeps them current), more of the surface becomes implicitly verified without changing user-facing config.Engine integration:
docs/security.md lands the written policy with sections for:
<field>_env> literal)Testing
Test Results
27 new tests:
argus/tests/core/test_image_verify.py::TestImageClassificationis_argus_owned,has_digest_pinpredicates with parametrized image listsargus/tests/core/test_image_verify.py::TestVerifyImageArgusOwnedargus/tests/core/test_image_verify.py::TestVerifyImageThirdPartyargus/tests/core/test_image_verify.py::TestVerifyImageDisabledargus/tests/core/test_image_verify.py::TestTagPinnedSummaryargus/tests/test_engine.py::TestSupplyChainVerificationGateFull suite: 3107 passed, 2 skipped, 7 deselected.
Security Considerations
Security Details
Direct implementation of hardening items (3) and (4) from the post-PR-142 audit:
The new code adds no new attack surface: cosign is invoked via
subprocess.runwith an argv array (no shell expansion), the cert-identity regexp is anchored at start (no glob injection), and cosign's stderr is truncated to 500 chars before logging (no traceback dump on auth-server errors).AI Context Updates (.ai/)
.ai/architecture.yamlupdated — newcore/image_verify.pyentry in both SDK structure blocks describing classification + engine integration + the config knob..ai/workflows.yamlupdated.ai/decisions.yamlupdated — implementation of items already decided in the roadmap; no new ADR warranted..ai/errors.yamlupdatedChecklist
Related Issues
Closes hardening items (3) and (4) in
docs/developer/SDK-ROADMAP.md→ "Secret Handling & Credential Surface Hardening". Item (5) — defensive redaction pass at audit-trail write time — remains queued.Follow-up tracked separately: migrate third-party image tags in
argus/containers.pyto@sha256:digest pins. Renovate already supports digest pin updates; once each image is migrated to a digest pin, it gets implicit verification through this PR's logic for free (no further code changes needed).Screenshots/Logs (if applicable)
Diff: 9 files (3 new, 6 modified), +1033 / -3.