fix(terraform): redact secrets nested inside list values, not just maps (#3644 follow-up) - #3762
abhay-codes07 wants to merge 1 commit into
Conversation
…ps (Graphify-Labs#3644 follow-up) `_redact_value` (the Graphify-Labs#3644 guard that keeps hardcoded credentials in .tf files out of graph.json and the MCP query/get_node surface) recursed into map values but not list values. HCL routinely nests objects inside tuples — `list(object(...))` variables, `dynamic` blocks, tuple defaults — and `_parse_attr_value` turns those into Python lists of dicts, so: config = { password = "x" } -> redacted (map recursion) configs = [{ password = "x" }] -> LEAKED (list not recursed) The list form left the secret in the serialized node verbatim, defeating the guard for a very common Terraform shape (a `connections`/`containers`/`secrets` tuple of objects). Recurse into lists and tuples as well, under the same key — the list branch is only reached when the key is not itself sensitive (a sensitive key redacts the whole value above), so a scalar element carries no key signal and is returned as-is while a dict element is checked against its own inner keys. A non-secret list of scalars is untouched. Tests: a unit check that a secret inside a list-of-objects and a nested list is redacted (while a sensitive key still redacts the whole list and a plain scalar list is left intact), and an end-to-end check that a `password`/`token` inside a tuple-of-objects attribute never reaches the serialized node. Both fail without the fix. The terraform suites otherwise stay green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. 1 change(s) tested, no difference found (not proven).
Graphify review — findings
Extends _redact_value to recurse into lists and tuples, so a secret-named key nested inside a list of objects (or nested lists) is redacted the same way it already was inside a map — closing a leak where configs = [{ password = "x" }] reached graph.json and the MCP query surface verbatim. Scalar list elements carry no key signal and pass through unchanged, while a sensitive key on the list itself still redacts the whole value.
No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1398 functions depend on the 50 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 655 callers, 45 callees - new:
_rebuild_code()— 142 callers, 54 callees - new:
dispatch_command()— 2 callers, 125 callees - new:
extract_terraform()— 21 callers, 8 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
watch()— 5 callers, 7 callees - new:
_build()— 7 callers, 3 callees - new:
main()— 1 callers, 6 callees - …and 7 more — each is listed as a finding
Verification — 1398 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 740 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
2 of 290 test file(s) selected (1%) via static blast radius.
tests/test_terraform.py— impact, changed-testtests/test_terraform_modules.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
No difference found (not proven): No behavior difference found in \_redact\_value (not a proof).
The verifier ran both versions of \_redact\_value on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
· 15 more finding(s) on lines outside this diff (see the check run).
|
Shipped in v0.9.67 (on PyPI). Cherry-picked with authorship preserved. Thanks @abhay-codes07! Clean extension of the map redaction to list values. |
What
_redact_value— the #3644 guard that keeps hardcoded credentials in.tffiles out ofgraph.jsonand the MCPquery/get_nodesurface — recurses into map values but not list values. HCL routinely nests objects inside tuples (list(object(...))variables,dynamicblocks, tuple defaults), and_parse_attr_valueturns those into Python lists of dicts, so:The list form left the secret in the serialized node verbatim, defeating the guard for a very common Terraform shape (a
connections/containers/secretstuple of objects). This reachesgraph.jsonand is surfaced to the model over MCP with nosanitize_metadatapass — the exact leak #3644 set out to close.Fix
Recurse into lists and tuples as well, under the same key. The list branch is only reached when the key is not itself sensitive (a sensitive key redacts the whole value above), so a scalar element carries no key signal and is returned as-is, while a dict element is checked against its own inner keys. A non-secret list of scalars is left untouched.
Verification
Reproduced (pure
_redact_value): before,_redact_value("configs", [{"password": "hunter2"}])returned the secret verbatim; after, it is[{"password": "[redacted]"}]. Nested lists ([[{"api_key": ...}]]) too.Tests (both fail without the fix):
[80, 443]) is intact; a mixed list redacts only the secret-keyed object.password/tokeninside aconnections = [ {…}, {…} ]attribute never appears in the serialized node, while the ordinaryhostvalues remain.The terraform suites otherwise stay green (the one unrelated failure,
test_same_named_directories_..., fails identically on cleanv8).🤖 Generated with Claude Code
https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q