fix(door): never return raw controller reason to the client (D10)

Every door action bubbled the executor's reason/e.message to the
client; redaction was password-literal + a fixed param-name regex, so a
generic-adapter connection with a custom auth param + verbose upstream
error could surface the secret client-side. Client returns are now a
generic 'controller call failed' message; the redacted detail still
goes to door_access_log for operators. redactDoorSecrets + callDoor
gained an optional authParam (scrub the connection's actual auth param
value regardless of name); threading conn.auth_param through the
door-call selects is a deferred defense-in-depth follow-up (client
leak is fully closed; native HeatSync e= is already regex-covered).
This commit is contained in:
Moheeb Zara
2026-05-18 18:44:54 -07:00
parent 785e8e0ef9
commit 66cb84bc57
3 changed files with 23 additions and 8 deletions
+15 -1
View File
@@ -211,11 +211,25 @@ export function applyTemplate(
// the shared door password: any literal occurrence, and any e=/&e= query
// value (the firmware auth param), become "<redacted>". The password is a
// credential and must never land in door_access_log.
export function redactDoorSecrets(text: string, password?: string | null): string {
export function redactDoorSecrets(
text: string,
password?: string | null,
authParam?: string | null,
): string {
let out = text
if (password && password.length > 0) {
out = out.split(password).join('<redacted>')
}
// The connection's actual auth query param (generic adapters may name it
// anything, not just e=/pw=). Scrub its value regardless of the name so a
// verbose upstream error echoing the URL cannot leak the secret even when
// `password` is empty/unknown.
if (authParam && /^\w+$/.test(authParam)) {
out = out.replace(
new RegExp(`([?&]${authParam}=)[^&\\s"']*`, 'gi'),
'$1<redacted>',
)
}
out = out.replace(/([?&](?:e|pw|password|api[_-]?key|token)=)[^&\s"']*/gi, '$1<redacted>')
return out
}