The 8 raw-error console sinks listed in #418 were already routed
through redactError() by the 2026-07-02 audit (PR #451: fd56a30,
ec7e4e0). This adds the remaining deliverable: an enforcement guard so
the "every raw-error console sink goes through redactError()" contract
cannot silently regress.
New type-aware ESLint rule `synapse/no-unredacted-console`
(scripts/eslint-rules/no-unredacted-console.mjs), enabled in
eslint.config.mjs for shipped code (tests/mocks excluded, mirroring the
obsidianmd scope): every value reaching a console.* sink must be
statically string-like — checked at the argument top level, inside
template substitutions, and across `+` concatenation. redactError()/
redactSecrets() return string, so sanctioned sites pass with no
function-name allowlist. String()/JSON.stringify() of non-string values
and direct .message/.stack access are flagged as stringification-is-
not-redaction bypasses. The rule fails closed (throws) if type info is
unavailable. Picked up automatically by `npm run lint` in CI.
Co-Authored-By: Claude <bot@wafflenet.io>