Skip to content

fix(engine): pass credentials by name, not value, on docker run#144

Merged
eFAILution merged 1 commit into
feat/argus-portabilityfrom
fix/engine-env-passthrough-no-value
May 12, 2026
Merged

fix(engine): pass credentials by name, not value, on docker run#144
eFAILution merged 1 commit into
feat/argus-portabilityfrom
fix/engine-env-passthrough-no-value

Conversation

@eFAILution
Copy link
Copy Markdown
Collaborator

Description

Closes hardening item (1) from "Secret Handling & Credential Surface Hardening" in docs/developer/SDK-ROADMAP.md. Switches the engine's container-path env passthrough from -e NAME=VALUE (value on argv) to -e NAME (name only, value in the subprocess's private env). Closes the ps / docker inspect / docker-daemon-audit leak vector identified in the post-#142 security audit.

Changes Made

  • Added new scanner/workflow
  • Modified existing scanner/workflow
  • Updated documentation
  • Fixed bug
  • Other (please specify)

Details

Before:

docker_cmd.extend(["-e", f"{env_name}={env_value}"])
# docker run -e ZAP_AUTH_PASSWORD=hunter2 ...

After:

subprocess_env[env_name] = env_value           # private to argus's subprocess
docker_cmd.extend(["-e", env_name])            # docker inherits by name
# docker run -e ZAP_AUTH_PASSWORD ...
# subprocess.run(..., env=subprocess_env)

Docker inherits the named env var from the parent process (the python subprocess argus spawns). The value lives only in that subprocess's private environment — never on argv. Three concrete leak vectors close:

Vector Before After
ps -ef | grep "docker run" Shows credential to any local user Shows only the NAME
docker inspect <container> Shows credential to anyone with docker socket access Shows only the NAME
Docker daemon audit logs Capture full NAME=VALUE Capture only NAME

The subprocess env is private to argus's docker-run child and its own children. Visible only to processes that can already read /proc/<pid>/environ for that specific pid — strictly tighter access than the ps-visible argv list.

Behavior preservation:

  • Scanners without container_env: unchanged (subprocess.run receives env=None, inherits parent unmodified).
  • container_env entries with None values: still filtered before the dict is built. Unset env-var-name references don't materialize at all (closes the "and don't accidentally inherit the host's value for that name" gap explicitly).
  • container_env that resolves to an all-None dict: drops the env=... override entirely (matches the no-container_env path; subprocess.run sees env=None).

Testing

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

Test Results

Updated 3 existing engine-hook tests + added 1 new one in argus/tests/test_engine.py::TestContainerEnvHook:

  • test_env_vars_passed_by_name_not_value (renamed from test_env_vars_passed_via_dash_e) — explicit assertion that VALUE never appears on argv, and that it lands in subprocess.run's env= dict; PATH preserved from parent env.
  • test_none_value_skipped — verifies None values don't reach argv OR subprocess env.
  • test_no_container_env_method_no_extra_flags — verifies env=None is passed for back-compat scanners.
  • test_all_none_values_no_subprocess_env_override (new) — guards the "drop override on empty dict" path.

Full suite: 3066 passed, 2 skipped, 7 deselected.

Security Considerations

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

Security Details

Closes the highest-payoff item from the secret-handling audit. Severity classification per the audit: medium — exploitable only by an attacker who already has local user access to the scan host, but trivial once they do. After this PR, even an attacker with local user access can no longer scrape credentials from ps -ef; they'd need to read /proc/<pid>/environ for the specific docker-spawning subprocess, which requires the same uid (or root).

AI Context Updates (.ai/)

  • .ai/architecture.yaml updated — both core/engine.py description copies updated to describe the new passthrough strategy (NAME-only argv + private subprocess env).
  • .ai/workflows.yaml updated
  • .ai/decisions.yaml updated
  • .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

Screenshots/Logs (if applicable)

Diff: 4 files, +96 / -21.

Closes hardening item #1 from "Secret Handling & Credential Surface
Hardening" in docs/developer/SDK-ROADMAP.md.

Before:
  docker run -e NAME=VALUE ... image ...

After:
  docker run -e NAME       ... image ...
  subprocess.run(..., env={**os.environ, NAME: VALUE})

Docker inherits the named env var from the parent process (the
python subprocess argus spawns). The value lives only in that
subprocess's private environment — not in argv. Closes three
specific leak vectors:

  - `ps -ef | grep "docker run"` no longer shows credentials to
    other local users on the scan host.
  - `docker inspect <container>` no longer shows credentials to
    anyone with docker socket access.
  - Docker daemon audit logs (if configured) capture only NAME,
    not VALUE.

The subprocess env is private to argus's docker-run child and its
own children (the docker daemon's container). Visible only to
processes that can already read /proc/<pid>/environ for that
specific pid — a strictly tighter access set than the ps-visible
argv list.

Behavior preservation:
  - Scanners without container_env: unchanged (subprocess env=None,
    inherits parent unmodified).
  - container_env entries with None values: still filtered before
    the dict is built, so unset env-var-name references don't
    materialize at all.
  - container_env that resolves to an all-None dict: drops the
    env=... override entirely (matches no-container_env behavior).

Updated 4 engine hook tests:
  - test_env_vars_passed_by_name_not_value (renamed from
    test_env_vars_passed_via_dash_e) — explicit assertion that
    VALUE never appears on argv, and that it lands in the
    subprocess env.
  - test_none_value_skipped — verifies None values don't reach
    either argv OR subprocess env.
  - test_no_container_env_method_no_extra_flags — verifies env=None
    is passed for back-compat scanners.
  - test_all_none_values_no_subprocess_env_override (new) — guards
    the "drop override on empty dict" path.

.ai/architecture.yaml engine-hook description updated in both
copies to reflect the new passthrough strategy.

Full suite: 3066 passed, 2 skipped.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 12, 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: fix/engine-env-passthrough-no-value
Commit: 4efd24f

📊 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:4efd24ff3b6671770b1a3085c48f1a7722c44e39 1 39 32 1 73 73
scanner-bandit ghcr.io/huntridge-labs/argus/scanner-bandit:4efd24ff3b6671770b1a3085c48f1a7722c44e39 0 0 2 0 2 2
scanner-opengrep ghcr.io/huntridge-labs/argus/scanner-opengrep:4efd24ff3b6671770b1a3085c48f1a7722c44e39 0 7 44 63 114 114
scanner-supply-chain ghcr.io/huntridge-labs/argus/scanner-supply-chain:4efd24ff3b6671770b1a3085c48f1a7722c44e39 0 9 8 0 17 17

🔍 Detailed Findings by Container

🚨 cli - 73 vulnerabilities (33 unique)

Image: ghcr.io/huntridge-labs/argus/cli:4efd24ff3b6671770b1a3085c48f1a7722c44e39

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:4efd24ff3b6671770b1a3085c48f1a7722c44e39

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:4efd24ff3b6671770b1a3085c48f1a7722c44e39

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:4efd24ff3b6671770b1a3085c48f1a7722c44e39

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 37e433d into feat/argus-portability May 12, 2026
22 checks passed
@eFAILution eFAILution deleted the fix/engine-env-passthrough-no-value branch May 12, 2026 02:50
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