Skip to content

fix(terraform): redact secrets nested inside list values, not just maps (#3644 follow-up) - #3762

Closed
abhay-codes07 wants to merge 1 commit into
Graphify-Labs:v8from
abhay-codes07:fix/terraform-redact-lists
Closed

abhay-codes07 wants to merge 1 commit into
Graphify-Labs:v8from
abhay-codes07:fix/terraform-redact-lists

Conversation

@abhay-codes07

Copy link
Copy Markdown
Contributor

What

_redact_value — the #3644 guard that keeps hardcoded credentials in .tf files out of graph.json and the MCP query/get_node surface — recurses 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" }     # -> {"password": "[redacted]"}   (map recursion)
configs = [{ password = "x" }]   # -> [{"password": "x"}]          LEAKED

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). This reaches graph.json and is surfaced to the model over MCP with no sanitize_metadata pass — 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):

  • Unit: a secret inside a list-of-objects and a nested list is redacted; a sensitive key still redacts the whole list value; a plain scalar list ([80, 443]) is intact; a mixed list redacts only the secret-keyed object.
  • End-to-end: a password/token inside a connections = [ {…}, {…} ] attribute never appears in the serialized node, while the ordinary host values remain.

The terraform suites otherwise stay green (the one unrelated failure, test_same_named_directories_..., fails identically on clean v8).

🤖 Generated with Claude Code

https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q

…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
Copilot AI lite review requested due to automatic review settings September 22, 2026 18:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@graphify-labs graphify-labs Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-test
  • tests/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).

@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.67 (on PyPI). Cherry-picked with authorship preserved. Thanks @abhay-codes07! Clean extension of the map redaction to list values.

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.

3 participants