fix(security): make CDS-SEC-070 evaluate rendered Compose command/logging fields - #353
Conversation
SemTiOne
left a comment
There was a problem hiding this comment.
Solid work. I have some suggestions for you to consider (see inline comments).
- Add a third CDS-SEC-070 match branch to catch bare positional
${CDS_*}/${config.*}/${secrets.*} placeholders, not just "--flag="
style command arguments.
- Extend pathPatterns/coverage to include healthcheck.* alongside
command/entrypoint/logging, since healthcheck probes leak secrets
the same way.
- Map rendered-compose findings back to the owning CDS module id
(via a new _map_service_to_module helper reusing the renderer's
service-naming rule) instead of the raw Compose service name, which
only coincidentally matched module ids in the fixture.
- Replace the silent `except Exception: return None` in
_try_render_compose_for_scan with a warning diagnostic (W096) so
unexpected internal errors are surfaced instead of vanishing as zero
findings.
- Let callers pass an already-computed plan/rendered Compose YAML into
run_security_validation(), and wire cli/main.py's `cds test` to do so,
so the profile isn't planned and rendered twice per run.
- Expand the regression fixture to exercise all three leak surfaces
(flag-style, bare positional, healthcheck) and assert exact
path/value/module attribution instead of loose substring checks.
- Add coverage for the plan/render reuse behavior and for the new
warning-diagnostic path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks for the thorough review, @SemTiOne! Addressed all 7 comments in 6a76339:
Full suite (417 tests) and |
SemTiOne
left a comment
There was a problem hiding this comment.
Nice work! I re-checked and found 4 tweaks that need to be addressed (see inline comments).
- CDS-SEC-070: remove the two match.any branches that could never fire
against rendered output (keyRegex only tests the final path segment,
which is a list index or ordinary leaf key on rendered command/
entrypoint/healthcheck/logging paths; and ${secrets.*}/${config.*}
resolve or fail before this rule ever runs), leaving only the single
working "${CDS_" branch.
- cds test: pass skip_self_plan_render=True to run_security_validation
when its own plan/render stages already failed, so
_try_render_compose_for_scan doesn't retry (and re-fail) the same
build_plan()/render_compose() calls a second time.
- _try_render_compose_for_scan now emits a W096 warning diagnostic when
planning or rendering fails (not just on unexpected exceptions), so
`cds security` (which has no separate plan/render stage) can no
longer silently report "No security findings" for a profile that
never actually got scanned by rendered-compose-scoped rules.
- run_security_validation now only plans+renders the profile when some
enabled rule in the active rule set actually declares the
"rendered-compose" scope, avoiding unconditional plan+render overhead
on every security scan when no such rule applies.
- Add regression tests for each of the above.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks for the second pass, @SemTiOne — all 4 addressed in 071d4e6:
Added regression tests for all four. Full suite (420 tests) and `make lint` pass. |
SemTiOne
left a comment
There was a problem hiding this comment.
Nice, progress here. Some issues needs to be resolved and this good to go.
|
Also @RonaldHensbergen, |
- Add a third CDS-SEC-070 match branch to catch bare positional
${CDS_*}/${config.*}/${secrets.*} placeholders, not just "--flag="
style command arguments.
- Extend pathPatterns/coverage to include healthcheck.* alongside
command/entrypoint/logging, since healthcheck probes leak secrets
the same way.
- Map rendered-compose findings back to the owning CDS module id
(via a new _map_service_to_module helper reusing the renderer's
service-naming rule) instead of the raw Compose service name, which
only coincidentally matched module ids in the fixture.
- Replace the silent `except Exception: return None` in
_try_render_compose_for_scan with a warning diagnostic (W096) so
unexpected internal errors are surfaced instead of vanishing as zero
findings.
- Let callers pass an already-computed plan/rendered Compose YAML into
run_security_validation(), and wire cli/main.py's `cds test` to do so,
so the profile isn't planned and rendered twice per run.
- Expand the regression fixture to exercise all three leak surfaces
(flag-style, bare positional, healthcheck) and assert exact
path/value/module attribution instead of loose substring checks.
- Add coverage for the plan/render reuse behavior and for the new
warning-diagnostic path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CDS-SEC-070: remove the two match.any branches that could never fire
against rendered output (keyRegex only tests the final path segment,
which is a list index or ordinary leaf key on rendered command/
entrypoint/healthcheck/logging paths; and ${secrets.*}/${config.*}
resolve or fail before this rule ever runs), leaving only the single
working "${CDS_" branch.
- cds test: pass skip_self_plan_render=True to run_security_validation
when its own plan/render stages already failed, so
_try_render_compose_for_scan doesn't retry (and re-fail) the same
build_plan()/render_compose() calls a second time.
- _try_render_compose_for_scan now emits a W096 warning diagnostic when
planning or rendering fails (not just on unexpected exceptions), so
`cds security` (which has no separate plan/render stage) can no
longer silently report "No security findings" for a profile that
never actually got scanned by rendered-compose-scoped rules.
- run_security_validation now only plans+renders the profile when some
enabled rule in the active rule set actually declares the
"rendered-compose" scope, avoiding unconditional plan+render overhead
on every security scan when no such rule applies.
- Add regression tests for each of the above.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed all 6 comments from this review round in 1fc156c:
Full suite (421 tests) and |
…ging fields
CDS-SEC-070 ("Secret appears in command args or log configuration") had
scope: ["none"], so it was never dispatched despite enabled: true. Its
valueRegex was also malformed ("(?i)(--******"), an invalid/unbalanced
regex that nothing ever compiled or exercised -- exactly the kind of
silent dead-rule regression #297 warned about, and the rule that should
have caught the Vault dev-root-token-as-command-arg issue fixed in #290.
- Add a "rendered-compose" scope to cli/security.py: run_security_validation
now does a best-effort plan+render of the profile and flattens each
service's command/entrypoint/logging fields, so rules can inspect where a
module's implementation template actually places a secret-bearing value
(only visible after ${config.*} template expressions resolve into their
final Compose "${CDS_*}" placeholders). Plan/render failures are handled
silently here since the separate plan/render stages already report them.
- Re-scope CDS-SEC-070 to "rendered-compose", fix its pathPatterns to match
list-shaped command/entrypoint/logging fields, and fix the broken
valueRegex to actually catch secret-like command-line flags.
- Add "rendered-compose" to the rule-schema.json scope enum.
- Add regression fixtures mirroring the real PR #290 bug/fix (a fixture
module passing a secret via "command:", and a safe variant passing it via
"environment:"), and tests proving CDS-SEC-070 now fires for the
vulnerable shape and stays silent for the safe one.
- Update the #297 deferred-rule documentation test: CDS-SEC-070 is no
longer scope: ["none"].
CDS-SEC-006, 030, 032, 050-054, and 071 remain scope: ["none"] as
documented by #303 -- they need rendered-artifact/CLI-output scanning or
file-permission checks that are out of scope here, and 050-052/054 are
already enforced separately by cli/image_verification.py.
Addresses #297
- Add a third CDS-SEC-070 match branch to catch bare positional
${CDS_*}/${config.*}/${secrets.*} placeholders, not just "--flag="
style command arguments.
- Extend pathPatterns/coverage to include healthcheck.* alongside
command/entrypoint/logging, since healthcheck probes leak secrets
the same way.
- Map rendered-compose findings back to the owning CDS module id
(via a new _map_service_to_module helper reusing the renderer's
service-naming rule) instead of the raw Compose service name, which
only coincidentally matched module ids in the fixture.
- Replace the silent `except Exception: return None` in
_try_render_compose_for_scan with a warning diagnostic (W096) so
unexpected internal errors are surfaced instead of vanishing as zero
findings.
- Let callers pass an already-computed plan/rendered Compose YAML into
run_security_validation(), and wire cli/main.py's `cds test` to do so,
so the profile isn't planned and rendered twice per run.
- Expand the regression fixture to exercise all three leak surfaces
(flag-style, bare positional, healthcheck) and assert exact
path/value/module attribution instead of loose substring checks.
- Add coverage for the plan/render reuse behavior and for the new
warning-diagnostic path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CDS-SEC-070: remove the two match.any branches that could never fire
against rendered output (keyRegex only tests the final path segment,
which is a list index or ordinary leaf key on rendered command/
entrypoint/healthcheck/logging paths; and ${secrets.*}/${config.*}
resolve or fail before this rule ever runs), leaving only the single
working "${CDS_" branch.
- cds test: pass skip_self_plan_render=True to run_security_validation
when its own plan/render stages already failed, so
_try_render_compose_for_scan doesn't retry (and re-fail) the same
build_plan()/render_compose() calls a second time.
- _try_render_compose_for_scan now emits a W096 warning diagnostic when
planning or rendering fails (not just on unexpected exceptions), so
`cds security` (which has no separate plan/render stage) can no
longer silently report "No security findings" for a profile that
never actually got scanned by rendered-compose-scoped rules.
- run_security_validation now only plans+renders the profile when some
enabled rule in the active rule set actually declares the
"rendered-compose" scope, avoiding unconditional plan+render overhead
on every security scan when no such rule applies.
- Add regression tests for each of the above.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- rule-set.json: document CDS-SEC-070's scope limitation directly in
its $comment (literal hardcoded secrets aren't caught, only
unresolved ${CDS_*} placeholders)
- main.py: drop the duplicate resolve_env_file_path() call in `cds
test` by reusing the already-computed env_file
- security.py: replace the plan/rendered_compose_yaml/
skip_self_plan_render parameter matrix with a single
PrecomputedRender(plan=, rendered_compose_yaml=, failed=) object
- security.py: W096 warnings now include the first underlying
plan/render error code so the message is self-contained for `cds
security` callers with no separate plan/render stage to consult
- main.py: `cds security` now exits non-zero when rendered-compose
scoped rules were skipped due to a plan/render failure, instead of
reporting "No security findings." as if the scan completed
- test_environment_classification.py: add an end-to-end regression
test that actually plans/renders the real vault profile (building a
full temp .env from its own secrets declarations) and asserts zero
CDS-SEC-070 findings, making the "no false positive" claim
CI-reproducible rather than only manually verified
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CDS-SEC-070 is no longer a dead/deferred rule with scope: ["none"];
it's now evaluated against rendered Compose command/entrypoint/
healthcheck/logging fields ("rendered-compose" scope, PR #353,
closing #297). Update T3's control/residual-risk description and the
risk-prioritization summary to reflect the fix instead of the
now-resolved dead-scope bug, and split the former "T3 / T4" combined
entry since only T4 (CDS-SEC-013 disabled) remains an open gap.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1fc156c to
af12f26
Compare

Summary
CDS-SEC-070 ("Secret appears in command args or log configuration") had
scope: ["none"], so it was never dispatched byrun_security_validation()despite"enabled": true. ItsvalueRegexwas also malformed ("(?i)(--******", an invalid/unbalanced regex) — since the rule never ran, nothing ever compiled or exercised it. This is exactly the rule that should have caught the Vault dev-root-token-as-command-arg issue fixed in #290.Fixed in this PR
"rendered-compose"scope tocli/security.py.run_security_validation()now does a best-effort plan + render of the profile and flattens each service'scommand/entrypoint/loggingfields, so rules can inspect where a module's implementation template actually places a secret-bearing value — this is only visible once${config.*}template expressions resolve into their final Compose${CDS_*}placeholders, which the profile/env scanners alone can't see. Plan/render failures are handled silently (return no findings for this scope) since the separateplan/renderstages incds testalready report them with full diagnostics.["none"]to["rendered-compose"], fixed itspathPatternsto match list-shapedcommand/entrypoint/loggingfields (services.*.command*etc., since flattened list items produce paths likeservices.vault.command[2]), and fixed the brokenvalueRegexto actually catch secret-like command-line flags (e.g.-dev-root-token-id=${CDS_VAULT_TOKEN})."rendered-compose"to therule-schema.jsonscope enum.tests/fixtures/security/rendered-command-secret/mirroring the real PR Pass Vault dev root token via env var instead of command-line arg #290 bug/fix in miniature: one fixture module passes a secret viacommand:(vulnerable), the other passes the same secret viaenvironment:instead (safe). Tests prove CDS-SEC-070 now fires for the vulnerable shape and stays silent for the safe one.#297deferred-rule documentation test (DeferredNoneScopeRuleDocumentationTest) since CDS-SEC-070 is no longerscope: ["none"].Deferred (unchanged)
CDS-SEC-006, 030, 032, 050-054, and 071 remain
scope: ["none"]as already documented by #303 — they need rendered-artifact/CLI-output scanning or file-permission checks that are a larger, separate effort. CDS-SEC-050/051/052/054 are already enforced separately bycli/image_verification.py, as their$comments note.Testing
python -m unittest tests.test_security -v: 17/17 pass, including 3 new tests for CDS-SEC-070.python -m unittest discover -s tests -p "test_*.py": 414/414 pass (1 skipped, Docker-only).make lint: clean.cds validate profiles/local-dagster-postgres-superset-vaultstill passes, and the real (already-fixed) Vault profile does not produce a false-positive CDS-SEC-070 finding.Addresses #297
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com