WARDEN-WP-0036: tighten attended-login stdout/stderr allowlist
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Reject non-null wrap_info (a wrapping token survives token revoke -self),
non-empty data, empty client_token, and leading whitespace before the OIDC
stderr prompt. Fail-closed and hand-off tests now run through the CLI and
assert sentinel absence from both audit files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 680842@bnt-lap001
Assistant-Session: 54eb9e8e-7d6b-4f0c-b7a6-a6db62075883
This commit is contained in:
tegwick 2026-10-14 12:41:43 +02:00
parent 8a426fba69
commit 6e1430073c
3 changed files with 90 additions and 21 deletions

View file

@ -49,7 +49,9 @@ _TOKEN_HELPER_NAME = ".vault-token"
# values are never logged, returned, stored, or included in an exception.
#
# stdout: empty, or exactly one JSON object shaped like a ``bao login -format=json``
# response (api.Secret with an ``auth`` block).
# response (api.Secret with an ``auth`` block). ``wrap_info`` must be null or
# absent (a wrapping token would survive ``token revoke -self``), ``data`` null,
# absent or empty, and ``auth.client_token`` a non-empty string.
_LOGIN_JSON_TOP_TYPES: dict = {
"request_id": (str,),
"lease_id": (str,),
@ -57,7 +59,7 @@ _LOGIN_JSON_TOP_TYPES: dict = {
"renewable": (bool,),
"data": (dict, type(None)),
"warnings": (list, type(None)),
"wrap_info": (dict, type(None)),
"wrap_info": (type(None),),
"mount_type": (str,),
"auth": (dict,),
}
@ -78,7 +80,7 @@ _LOGIN_JSON_AUTH_TYPES: dict = {
# stderr: empty, or exactly the OpenBao OIDC CLI prompt (browser-launch or
# open-link variant, one https URL, optional wait line). Any other byte fails closed.
_LOGIN_OIDC_STDERR = re.compile(
r"\s*Complete the login via your OIDC provider\. "
r"Complete the login via your OIDC provider\. "
r"(?:Launching browser to:|Open the following link in your browser:)"
r"[ \t]*\n\s*https://[^\s]+[ \t]*\n"
r"\s*(?:Waiting for OIDC authentication to complete\.\.\.[ \t]*\n?\s*)?"
@ -428,8 +430,13 @@ def _login_stdout_allowed(raw: bytes) -> bool:
return False
if not _typed_keys_allowed(parsed, _LOGIN_JSON_TOP_TYPES):
return False
if parsed.get("data"):
return False
auth = parsed["auth"]
return "client_token" in auth and _typed_keys_allowed(auth, _LOGIN_JSON_AUTH_TYPES)
if not _typed_keys_allowed(auth, _LOGIN_JSON_AUTH_TYPES):
return False
token = auth.get("client_token")
return isinstance(token, str) and token != ""
def _login_stderr_allowed(raw: bytes) -> bool: