fix(harness): redact secret-named meta values in the customization manifest (rcust)
All checks were successful
continuous-integration/drone/push Build is passing

Adversary heads-up (inbox 2026-06-10T19:06Z): meta values are repo-public by construction, but
the manifest lands on the dashboard — a field literally named SECRET_KEY_BASE showing a value
(plausible's committed CI dummy) is needless secret-scan noise. Mask values whose key NAME is
secret-shaped (SECRET|PASSWORD|TOKEN|CREDENTIAL|word-segment KEY), top-level and nested dict
keys; the key name stays visible. Unit test pins redacted vs passthrough (KEYCLOAK_URL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
autonomic-bot
2026-06-10 19:09:09 +00:00
parent da558ca946
commit 858e0f582f
2 changed files with 45 additions and 3 deletions

View File

@ -16,14 +16,26 @@ from . import meta as meta_mod
_PRE_OP_RE = re.compile(r"^def (pre_[a-z]+)\(", re.MULTILINE)
# Meta values are repo-public by construction (recipe_meta.py is committed; real secrets are
# class-B generated, never meta), but the manifest lands on the dashboard — mask values whose
# key NAME is secret-shaped so a field literally called SECRET_KEY_BASE never shows a value
# (defense in depth + keeps dashboard secret-scans quiet). `KEY` matches only as a word segment
# (API_KEY yes, KEYCLOAK_URL no).
_SENSITIVE_NAME_RE = re.compile(r"SECRET|PASSWORD|TOKEN|CREDENTIAL|(^|_)KEY(_|$)", re.IGNORECASE)
def _jsonable(v):
def _jsonable(v, name=""):
"""Manifest values must be JSON-serializable + deterministic: hooks render as '<hook>',
tuples become lists."""
tuples become lists, secret-named entries (by key name, incl. nested dict keys) as
'<redacted>'."""
if callable(v):
return "<hook>"
if name and _SENSITIVE_NAME_RE.search(name):
return "<redacted>"
if isinstance(v, tuple):
return list(v)
if isinstance(v, dict):
return {k: _jsonable(x, name=str(k)) for k, x in v.items()}
return v
@ -89,7 +101,7 @@ def build(recipe: str, meta, repo_local: str | None) -> dict:
return {
"meta_non_default": {
k: _jsonable(v) for k, v in sorted(meta_mod.non_default(meta).items())
k: _jsonable(v, name=k) for k, v in sorted(meta_mod.non_default(meta).items())
},
"hooks": hooks,
"overlays": overlays,