mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 07:29:33 +00:00
fix(terraform): redact the value paired with a secret-named name in name/value lists
The ECS environment idiom { name = "DB_PASSWORD", value = "hunter2" } names the
secret in the name literal, not in a key, so key-name redaction let the value
reach graph.json and the MCP surface verbatim. When a map's name literal
matches the sensitive pattern, redact its value/valueFrom.
Fixes #3787
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e11cf947d0)
This commit is contained in:
1 parent
4377ee93aa
commit
cb1016c405
2 files changed
+43
-1
No files matched your search
@@ -32,6 +32,7 @@ _SENSITIVE_KEY_RE = re.compile(
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_REDACTED = "[redacted]"
|
||||
_PAIRED_VALUE_KEYS = frozenset({"value", "valuefrom", "value_from"})
|
||||
|
||||
|
||||
def _redact_value(key: str, value: object) -> object:
|
||||
@@ -52,7 +53,18 @@ def _redact_value(key: str, value: object) -> object:
|
||||
if _SENSITIVE_KEY_RE.search(key):
|
||||
return _REDACTED
|
||||
if isinstance(value, dict):
|
||||
return {k: _redact_value(str(k), v) for k, v in value.items()}
|
||||
redacted = {k: _redact_value(str(k), v) for k, v in value.items()}
|
||||
# Name/value-pair idiom (ECS `environment`/`secrets`, `[{name, value}]`):
|
||||
# the secret signal is the `name` literal, not a key, so key matching
|
||||
# alone let `{ name = "DB_PASSWORD", value = "hunter2" }` leak (#3787).
|
||||
if any(
|
||||
str(k).lower() == "name" and isinstance(v, str) and _SENSITIVE_KEY_RE.search(v)
|
||||
for k, v in value.items()
|
||||
):
|
||||
for k in redacted:
|
||||
if str(k).lower() in _PAIRED_VALUE_KEYS:
|
||||
redacted[k] = _REDACTED
|
||||
return redacted
|
||||
if isinstance(value, (list, tuple)):
|
||||
return [_redact_value(key, item) for item in value]
|
||||
return value
|
||||
|
||||
@@ -449,3 +449,33 @@ output "bucket_name" {
|
||||
dumped = _json.dumps(r["nodes"])
|
||||
for secret in ("hunter2-default", "tok-in-output", "marked-sensitive"):
|
||||
assert secret not in dumped
|
||||
|
||||
|
||||
def test_terraform_name_value_pair_secret_is_redacted(tmp_path):
|
||||
"""The name/value-pair idiom (ECS `environment` / `secrets`, any
|
||||
`[{ name, value }]` list) names the secret in the `name` LITERAL, while the
|
||||
credential sits under the generic key `value`. Key-name matching never sees
|
||||
that signal, so `{ name = "DB_PASSWORD", value = "hunter2" }` reached
|
||||
graph.json verbatim (#3787). Ordinary name/value config stays intact."""
|
||||
body = """\
|
||||
resource "aws_ecs_task_definition" "app" {
|
||||
family = "app"
|
||||
environment = [
|
||||
{ name = "DB_PASSWORD", value = "hunter2" },
|
||||
{ name = "LOG_LEVEL", value = "debug" },
|
||||
]
|
||||
env_refs = [{ name = "API_KEY", valueFrom = "arn:aws:ssm:us-east-1:1:parameter/api" }]
|
||||
single = { name = "client_secret", value = "in-a-map" }
|
||||
}
|
||||
"""
|
||||
r = extract_terraform(_write(tmp_path, "ecs.tf", body))
|
||||
node = next(n for n in r["nodes"] if n["label"] == "aws_ecs_task_definition.app")
|
||||
attrs = node["attributes"]
|
||||
assert attrs["environment"][0] == {"name": "DB_PASSWORD", "value": "[redacted]"}
|
||||
assert attrs["environment"][1] == {"name": "LOG_LEVEL", "value": "debug"}
|
||||
assert attrs["env_refs"][0] == {"name": "API_KEY", "valueFrom": "[redacted]"}
|
||||
assert attrs["single"] == {"name": "client_secret", "value": "[redacted]"}
|
||||
import json as _json
|
||||
dumped = _json.dumps(node)
|
||||
for secret in ("hunter2", "in-a-map", "parameter/api"):
|
||||
assert secret not in dumped
|
||||
Reference in new issue
Block a user