WARDEN-WP-0026 T03: masking display filter (defense-in-depth)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

- warden/mask.py: fingerprint()/mask_value() — presence, length, 8-char sha256
  prefix; never the value.
- proxy.proxy_fetch_fingerprint + `warden access --fingerprint`: masked status view
  (presence/length/hash) that emits no value, so it bypasses the T02 stdout guard.
  Lets two parties compare sha256 prefixes to confirm a shared value without seeing
  it (e.g. rotation landed).
- documented as defense-in-depth (raw bao bypasses it) in OperatorAccessAssist.md
  and the module docstring.
- tests: tests/test_mask.py + CLI fingerprint test. 299 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-16 14:54:55 +02:00
parent 04c8b2ab1d
commit fc0f18aa5c
7 changed files with 150 additions and 3 deletions

View file

@ -960,6 +960,7 @@ def _access_proxy(
wrap: bool = False,
wrap_ttl: str = "5m",
unsafe_stdout: bool = False,
fingerprint: bool = False,
) -> None:
"""Proxy a non-SSH credential fetch as the caller (WP-0014 T3).
@ -973,6 +974,7 @@ def _access_proxy(
caller_auth_present,
proxy_exec,
proxy_fetch,
proxy_fetch_fingerprint,
proxy_fetch_to_file,
proxy_fetch_wrapped,
resolve_fetch_command,
@ -1061,7 +1063,7 @@ def _access_proxy(
# secret value on stdout. Streaming a value to stdout is the documented anti-pattern:
# allowed only to an interactive terminal, and only with an explicit acknowledgment
# when stdout is captured/piped (the logged-context disclosure risk).
if not is_login and not do_exec and not wrap and not out_path:
if not is_login and not do_exec and not wrap and not out_path and not fingerprint:
import sys as _sys
if not _sys.stdout.isatty() and not unsafe_stdout:
@ -1098,6 +1100,15 @@ def _access_proxy(
elif out_path:
rc = proxy_fetch_to_file(resolved, Path(out_path))
err.print(f"[dim]value written to {out_path} (mode 0600); not shown[/dim]")
elif fingerprint:
fp = proxy_fetch_fingerprint(resolved)
# Masked fingerprint only — presence, length, short hash; never the value.
print(fp.render())
err.print(
"[dim]masked fingerprint (defense-in-depth; not the value). Compare "
"sha256 prefixes to confirm two parties hold the same secret.[/dim]"
)
rc = 0
else:
rc = proxy_fetch(resolved)
except ProxyError as e:
@ -1160,6 +1171,10 @@ def access(
bool,
typer.Option("--unsafe-stdout", help="Acknowledge streaming a value to a captured/piped stdout (anti-pattern)"),
] = False,
fingerprint: Annotated[
bool,
typer.Option("--fingerprint", help="Show a masked fingerprint (presence, length, short hash) — never the value"),
] = False,
no_policy: Annotated[
bool,
typer.Option("--no-policy", help="Acknowledge proxying when the flex-auth gate is not enforced"),
@ -1195,7 +1210,7 @@ def access(
entry = matches[0]
if do_fetch or do_exec or out_path or wrap:
if do_fetch or do_exec or out_path or wrap or fingerprint:
_access_proxy(
entry,
domain=domain,
@ -1208,6 +1223,7 @@ def access(
wrap=wrap,
wrap_ttl=wrap_ttl,
unsafe_stdout=unsafe_stdout,
fingerprint=fingerprint,
)
return