Skip to content

feat(audit): defensive redaction pass on log + manifest writes#148

Merged
eFAILution merged 1 commit into
feat/argus-portabilityfrom
feat/audit-trail-defensive-redact
May 13, 2026
Merged

feat(audit): defensive redaction pass on log + manifest writes#148
eFAILution merged 1 commit into
feat/argus-portabilityfrom
feat/audit-trail-defensive-redact

Conversation

@eFAILution
Copy link
Copy Markdown
Collaborator

Description

Closes hardening item #5 from docs/developer/SDK-ROADMAP.md → "Secret Handling & Credential Surface Hardening" — the final item in that section. Adds a recursive walker that masks every string in the audit trail at serialization time, plus fixes a pre-existing bug where masking record.msg corrupted %s format strings.

Changes Made

  • Added new scanner/workflow
  • Modified existing scanner/workflow
  • Updated documentation
  • Fixed bug
  • Other (please specify): new defensive redaction layer in argus/audit/secrets.py wired into both audit write paths

Details

New walkermask_secrets_in_obj(obj) in argus/audit/secrets.py. Recurses through dicts, lists, tuples; applies mask_secrets to every string value; leaves keys and non-string scalars untouched; returns a new structure (does NOT mutate the input).

Wired into both audit write paths:

File Old New
argus/audit/logger.py::JsonLogFormatter Masked record.msg (format string), then record.getMessage() re-interpolated record.args — secrets passed as %s args bypassed the mask entirely Mask the rendered record.getMessage() output. Then walk the assembled JSON entry through mask_secrets_in_obj so any extra fields a contributor adds also get masked.
argus/audit/logger.py::ColoredConsoleFormatter Same record.msg bug Same fix
argus/audit/manifest.py::AuditManifest.save json.dumps(asdict(self), ...) raw Walk asdict(self) through mask_secrets_in_obj before json.dumps

Pre-existing bug also fixed:

The two formatters did record.msg = mask_secrets(str(record.msg)) before calling getMessage(). When a format string contained "token: %s", the token: regex matched and rewrote it to "token: <REDACTED>". Then record.getMessage() tried to substitute record.args into a format string with no %s placeholder and raised TypeError: not all arguments converted during string formatting. The bug had been silently latent because no existing test exercised the printf path with a token-shaped arg. The new test test_secret_in_record_args_masked exposed it; the fix is to mask the rendered output instead of the raw format string.

Design note (vs. roadmap text):

The roadmap entry pointed at argus.core.redact.redact_high_risk_patterns (the vendor-prefix-only set used by Finding.__post_init__). The existing argus.audit.secrets.mask_secrets already covers that surface plus broader patterns appropriate for log lines (token=, password=, Bearer, URL creds, sk- keys). Extending audit/secrets keeps the redactor co-located with its callers — easier to reason about and no cross-module hop at hot-path serialization time.

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • Tested with different scanner combinations

Test Results

19 new tests:

File Tests Covers
argus/tests/audit/test_secrets.py::TestMaskSecretsInObj 10 Root scalar, dict value, nested dict, list, tuple, scalar passthrough, no-mutation guard, deeply nested mix, dict-key preservation, unknown type passthrough
argus/tests/audit/test_logger.py::TestJsonLogSecretLeakProtection 4 Format-string secret, record.args secret (regression fix), extra-field secret, non-secret strings preserved unchanged
argus/tests/audit/test_manifest.py::TestManifestSecretLeakProtection 5 Phase error, artifact path, nested dict at depth 4, input not mutated after save, clean-manifest false-positive guard

Full suite: 3126 passed (+19 new), 2 skipped, 7 deselected.

Security Considerations

  • No security impact
  • Security enhancement
  • Potential security implications (explain below)

Security Details

Direct implementation of hardening item (5) from the post-PR-142 audit. Two improvements:

  1. Defense-in-depth at the audit write boundary. Today neither manifest nor logger schema includes credential fields. This PR doesn't change that — but ensures that if a future regression captures a docker_cmd, env dict, or credential-shaped argv into a manifest field or log entry, the redaction pass catches it before the file lands on disk.

  2. Fixes a real (unrelated) bug where logger.info("token: %s", real_token) crashed the formatter. The crash was the safety mechanism — but the right behavior is to mask, not crash. Pre-fix, callers had to avoid the printf path entirely for any string that might match the token: pattern. Now they can use the natural API and trust the formatter.

AI Context Updates (.ai/)

  • .ai/architecture.yaml updated — new audit/ entry in both SDK structure blocks documenting the redaction posture, the walker, and the rendered-message masking rationale.
  • .ai/workflows.yaml updated
  • .ai/decisions.yaml updated — implementation of an already-decided roadmap item; no new ADR.
  • .ai/errors.yaml updated
  • N/A

Checklist

  • Code follows project style guidelines
  • Documentation updated
  • Changelog updated (if applicable)
  • All tests pass
  • Reviewed by at least one maintainer
  • Reviewed CONTRIBUTING.md guidelines

Related Issues

Closes the final hardening item (#5) in docs/developer/SDK-ROADMAP.md → "Secret Handling & Credential Surface Hardening". All 5 items in that section are now shipped.

Remaining adjacent roadmap items: Container EXPOSE ports (newly actionable from PR #147), OS image research (newly queued from PR #147), and the follow-up "migrate third-party images to @sha256: digest pins" from PR #146.

Screenshots/Logs (if applicable)

============================== 3126 passed, 2 skipped, 7 deselected, 20 warnings in 22.91s ==============================

Diff: 8 files, +331 / -9.

Closes hardening item #5 from "Secret Handling & Credential Surface
Hardening" in docs/developer/SDK-ROADMAP.md. Adds a recursive walker
that masks every string in the audit trail at serialization time,
plus fixes a pre-existing bug where masking record.msg corrupted %s
format strings whose placeholders matched the token: pattern.

argus/audit/secrets.py:
- New mask_secrets_in_obj(obj) walker. Recurses through dicts,
  lists, tuples; applies mask_secrets to every string value; leaves
  keys and non-string scalars untouched; returns a new structure
  (does NOT mutate the input).

argus/audit/logger.py:
- JsonLogFormatter.format: stop mutating record.msg. Mask the
  rendered record.getMessage() instead — catches secrets passed as
  printf-style args (logger.info("token: %s", real_token)) which
  the prior approach missed because record.msg held the format
  string, not the rendered output. Then walk the assembled JSON
  entry through mask_secrets_in_obj so extra fields a contributor
  might add to the formatter also get masked.
- ColoredConsoleFormatter.format: same fix — mask the rendered
  message, not record.msg. Without this fix, "token: %s" matched
  the token=/token: pattern and was rewritten to "token: <REDACTED>",
  then record.getMessage() raised TypeError trying to substitute
  args into a format string with no %s placeholder. Bug had been
  silently masked because no test exercised the printf path.

argus/audit/manifest.py:
- AuditManifest.save: walk asdict(self) through mask_secrets_in_obj
  before json.dumps. Defense-in-depth: today's manifest schema
  doesn't include credential fields, but if a future field captures
  a docker_cmd, env dict, or credential-shaped argv it gets masked
  before hitting argus-audit.json.

Design note (vs. roadmap text):
The roadmap entry suggested reusing core/redact.redact_high_risk_patterns
(the vendor-prefix-only set used by Finding.__post_init__). The
existing audit/secrets.mask_secrets already covers that surface plus
broader patterns appropriate for log lines (token=, password=,
Bearer, URL creds, sk-keys). Extending audit/secrets keeps the
redactor co-located with its callers — easier to reason about and
no cross-module hop at hot-path serialization time.

Test coverage (19 new):
- argus/tests/audit/test_secrets.py::TestMaskSecretsInObj — 10 tests:
  root scalar, dict value, nested dict, list, tuple, scalar passthrough,
  no-mutation guard, deeply nested mix, dict-key preservation, unknown
  type passthrough.
- argus/tests/audit/test_logger.py::TestJsonLogSecretLeakProtection —
  4 tests: format-string secret, record.args secret (the regression
  fix), extra-field secret, non-secret strings preserved unchanged.
- argus/tests/audit/test_manifest.py::TestManifestSecretLeakProtection —
  5 tests: phase error, artifact path, nested dict at depth 4, input
  not mutated after save, clean-manifest false-positive guard.

.ai/architecture.yaml: new audit/ entry in both SDK structure blocks
documenting the redaction posture, the walker, and the rendered-message
masking rationale.

Full suite: 3126 passed (+19 new), 2 skipped.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown
Contributor

🔒 Argus Container Security Scan

Branch: feat/audit-trail-defensive-redact
Commit: 14519bb

📊 Combined Findings Summary

🚨 Critical ⚠️ High 🟡 Medium 🔵 Low 📦 Total 🔢 Unique
1 55 86 64 206 206

Scanned: 4 containers | Build Failures: 0

📦 Container Breakdown

Container Image 🚨 Crit ⚠️ High 🟡 Med 🔵 Low Total Unique Status
cli ghcr.io/huntridge-labs/argus/cli:14519bbf7043689205e53c22faf0896099f1f6f0 1 39 32 1 73 73
scanner-bandit ghcr.io/huntridge-labs/argus/scanner-bandit:14519bbf7043689205e53c22faf0896099f1f6f0 0 0 2 0 2 2
scanner-opengrep ghcr.io/huntridge-labs/argus/scanner-opengrep:14519bbf7043689205e53c22faf0896099f1f6f0 0 7 44 63 114 114
scanner-supply-chain ghcr.io/huntridge-labs/argus/scanner-supply-chain:14519bbf7043689205e53c22faf0896099f1f6f0 0 9 8 0 17 17

🔍 Detailed Findings by Container

🚨 cli - 73 vulnerabilities (33 unique)

Image: ghcr.io/huntridge-labs/argus/cli:14519bbf7043689205e53c22faf0896099f1f6f0

Combined (Deduplicated)

🚨 Critical ⚠️ High 🟡 Medium 🔵 Low Total Unique
1 39 32 1 73 33
🔷 Trivy Scanner (73 findings, 33 unique)
CVE Severity Package Version Fixed
CVE-2025-68121 🚨 CRITICAL stdlib v1.24.11 1.24.13, 1.25.7, 1.26.0-rc.3
CVE-2026-32280 ⚠️ HIGH stdlib v1.26.1 1.25.9, 1.26.2
CVE-2026-32281 ⚠️ HIGH stdlib v1.26.1 1.25.9, 1.26.2
CVE-2026-32283 ⚠️ HIGH stdlib v1.26.1 1.25.9, 1.26.2
CVE-2026-33810 ⚠️ HIGH stdlib v1.26.1 1.26.2
CVE-2026-33811 ⚠️ HIGH stdlib v1.26.1 1.25.10, 1.26.3
CVE-2026-33814 ⚠️ HIGH stdlib v1.26.1 1.25.10, 1.26.3
CVE-2026-39820 ⚠️ HIGH stdlib v1.26.1 1.25.10, 1.26.3
CVE-2026-39836 ⚠️ HIGH stdlib v1.26.1 1.25.10, 1.26.3
CVE-2026-42499 ⚠️ HIGH stdlib v1.26.1 1.25.10, 1.26.3
CVE-2025-61726 ⚠️ HIGH stdlib v1.24.11 1.24.12, 1.25.6
CVE-2025-61728 ⚠️ HIGH stdlib v1.24.11 1.24.12, 1.25.6
CVE-2026-25679 ⚠️ HIGH stdlib v1.24.11 1.25.8, 1.26.1
CVE-2026-32280 ⚠️ HIGH stdlib v1.24.11 1.25.9, 1.26.2
CVE-2026-32281 ⚠️ HIGH stdlib v1.24.11 1.25.9, 1.26.2
CVE-2026-32283 ⚠️ HIGH stdlib v1.24.11 1.25.9, 1.26.2
CVE-2026-33811 ⚠️ HIGH stdlib v1.24.11 1.25.10, 1.26.3
CVE-2026-33814 ⚠️ HIGH stdlib v1.24.11 1.25.10, 1.26.3
CVE-2026-39820 ⚠️ HIGH stdlib v1.24.11 1.25.10, 1.26.3
CVE-2026-39836 ⚠️ HIGH stdlib v1.24.11 1.25.10, 1.26.3
CVE-2026-42499 ⚠️ HIGH stdlib v1.24.11 1.25.10, 1.26.3
CVE-2026-34040 ⚠️ HIGH github.com/docker/docker v28.5.2+incompatible 29.3.1
CVE-2026-45022 ⚠️ HIGH github.com/go-git/go-git/v5 v5.18.0 5.19.0
CVE-2026-33811 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-33814 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-39820 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-39836 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-42499 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-45022 ⚠️ HIGH github.com/go-git/go-git/v5 v5.18.0 5.19.0
CVE-2026-33811 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-33814 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-39820 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-39836 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-42499 ⚠️ HIGH stdlib v1.26.2 1.25.10, 1.26.3
CVE-2026-45022 ⚠️ HIGH github.com/go-git/go-git/v5 v5.17.2 5.19.0
CVE-2026-33811 ⚠️ HIGH stdlib v1.25.9 1.25.10, 1.26.3
CVE-2026-33814 ⚠️ HIGH stdlib v1.25.9 1.25.10, 1.26.3
CVE-2026-39820 ⚠️ HIGH stdlib v1.25.9 1.25.10, 1.26.3
CVE-2026-39836 ⚠️ HIGH stdlib v1.25.9 1.25.10, 1.26.3
CVE-2026-42499 ⚠️ HIGH stdlib v1.25.9 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
CVE-2025-11579 🟡 MEDIUM github.com/nwaples/rardecode/v2 v2.1.0 2.2.0
CVE-2025-58058 🟡 MEDIUM github.com/ulikunitz/xz v0.5.12 0.5.15

...and 23 more

⚓ Grype Scanner (0 findings, 0 unique)

✅ No vulnerabilities detected by Grype

🟡 scanner-bandit - 2 vulnerabilities (2 unique)

Image: ghcr.io/huntridge-labs/argus/scanner-bandit:14519bbf7043689205e53c22faf0896099f1f6f0

Combined (Deduplicated)

🚨 Critical ⚠️ High 🟡 Medium 🔵 Low Total Unique
0 0 2 0 2 2
🔷 Trivy Scanner (2 findings, 2 unique)
CVE Severity Package Version Fixed
CVE-2026-3219 🟡 MEDIUM pip 26.0.1 N/A
CVE-2026-6357 🟡 MEDIUM pip 26.0.1 26.1
⚓ Grype Scanner (0 findings, 0 unique)

✅ No vulnerabilities detected by Grype

⚠️ scanner-opengrep - 114 vulnerabilities (50 unique)

Image: ghcr.io/huntridge-labs/argus/scanner-opengrep:14519bbf7043689205e53c22faf0896099f1f6f0

Combined (Deduplicated)

🚨 Critical ⚠️ High 🟡 Medium 🔵 Low Total Unique
0 7 44 63 114 50
🔷 Trivy Scanner (114 findings, 49 unique)
CVE Severity Package Version Fixed
CVE-2026-4878 ⚠️ HIGH libcap2 1:2.75-10+b8 N/A
CVE-2025-69720 ⚠️ HIGH libncursesw6 6.5+20250216-2 N/A
CVE-2026-29111 ⚠️ HIGH libsystemd0 257.9-1~deb13u1 N/A
CVE-2025-69720 ⚠️ HIGH libtinfo6 6.5+20250216-2 N/A
CVE-2026-29111 ⚠️ HIGH libudev1 257.9-1~deb13u1 N/A
CVE-2025-69720 ⚠️ HIGH ncurses-base 6.5+20250216-2 N/A
CVE-2025-69720 ⚠️ HIGH 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:14519bbf7043689205e53c22faf0896099f1f6f0

Combined (Deduplicated)

🚨 Critical ⚠️ High 🟡 Medium 🔵 Low Total Unique
0 9 8 0 17 17
🔷 Trivy Scanner (17 findings, 17 unique)
CVE Severity Package Version Fixed
CVE-2026-32280 ⚠️ HIGH stdlib v1.26.1 1.25.9, 1.26.2
CVE-2026-32281 ⚠️ HIGH stdlib v1.26.1 1.25.9, 1.26.2
CVE-2026-32283 ⚠️ HIGH stdlib v1.26.1 1.25.9, 1.26.2
CVE-2026-33810 ⚠️ HIGH stdlib v1.26.1 1.26.2
CVE-2026-33811 ⚠️ HIGH stdlib v1.26.1 1.25.10, 1.26.3
CVE-2026-33814 ⚠️ HIGH stdlib v1.26.1 1.25.10, 1.26.3
CVE-2026-39820 ⚠️ HIGH stdlib v1.26.1 1.25.10, 1.26.3
CVE-2026-39836 ⚠️ HIGH stdlib v1.26.1 1.25.10, 1.26.3
CVE-2026-42499 ⚠️ HIGH 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

@eFAILution eFAILution merged commit 6dc1ba3 into feat/argus-portability May 13, 2026
22 checks passed
@eFAILution eFAILution deleted the feat/audit-trail-defensive-redact branch May 13, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant