agent-credential-broker: fork of vaultwarden-agent-broker + OpenBao backend

Mechanical rename (package agent_credential_broker; entry points
agent-broker{,-mcp,-plugin}; env AGENT_BROKER_*; paths agent-broker;
agent-browser credential provider 'broker'; MCP server name agent-broker).

New backend 'openbao': stdlib-only HTTP client for OpenBao/Vault KV v2
(approle login or token file/env; metadata-only ref index; lazy re-login
on 401/403; token renewal). bitwarden-cli backend kept as transition.
This commit is contained in:
me committed 2026-10-14 13:27:33 -06:00
commit be87900794
35 files changed
+5003

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
"""Output sanitizer -- defence in depth.
The *primary* guarantee is architectural: secret values never enter
argv/stdout/stderr of adapter calls. This layer additionally scans any
text crossing the broker boundary (child output, HTTP bodies/headers,
exception text) and replaces occurrences of known secret material with
[REDACTED]. If a secret appears in output, that is already an adapter
bug -- the sanitizer makes sure it can never reach the agent.
"""
from __future__ import annotations
import re
REDACTED = "[REDACTED]"
# Header names that must never be echoed back to the agent.
SENSITIVE_HEADERS = {
"authorization",
"proxy-authorization",
"set-cookie",
"cookie",
"x-api-key",
"x-auth-token",
"x-csrf-token",
"x-session-token",
"www-authenticate",
}
# name=value / name: value patterns whose values are suppressed.
_SECRET_KV = re.compile(
r"(?i)\b(password|passwd|pwd|secret|token|api[_-]?key|access[_-]?token|"
r"refresh[_-]?token|session[_-]?(id|token|key)|client[_-]?secret|"
r"private[_-]?key|authorization)\b(\s*[:=]\s*)(\"[^\"]*\"|'[^']*'|[^\s,;&}]{4,})"
)
_BEARER = re.compile(r"(?i)bearer\s+[A-Za-z0-9._~+/=-]{8,}")
_CRED_IN_URL = re.compile(r"(https?|ssh|git)://[^/@\s]+(:[^/@\s]*)?@")
def sanitize_text(text: str, secrets: list[str] | tuple[str, ...] = ()) -> str:
if not text:
return text
out = text
for secret in secrets:
if secret and len(secret) >= 4:
out = out.replace(secret, REDACTED)
out = _BEARER.sub("Bearer " + REDACTED, out)
out = _CRED_IN_URL.sub(r"\1://" + REDACTED + "@", out)
out = _SECRET_KV.sub(lambda m: m.group(1) + m.group(3) + REDACTED, out)
return out
def sanitize_headers(headers: dict, secrets=()) -> dict:
"""Return a copy of headers with sensitive entries dropped and
remaining values scrubbed for accidental secret content."""
out = {}
for k, v in headers.items():
if k.lower() in SENSITIVE_HEADERS:
out[k] = REDACTED
else:
out[k] = sanitize_text(str(v), secrets)
return out
def sanitize_bytes(data: bytes, secrets: list[bytes] | tuple[bytes, ...] = ()) -> bytes:
for secret in secrets:
if secret:
data = data.replace(secret, REDACTED.encode())
return data