mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(redaction): match connection-string schemes case-insensitively
RFC 3986 schemes are case-insensitive, so POSTGRESQL+PSYCOPG2:// or HTTPS://user:pass@host in tool output leaked the password past the case-sensitive scheme alternation. Compile with IGNORECASE on both sides of the FE/backend mirror; the structural userinfo requirement is unchanged. Uppercase-scheme cases added to both test suites.
This commit is contained in:
@@ -1350,6 +1350,10 @@ def test_redact_credentials_runtime_smoke() -> None:
|
||||
+ "const apg = redactCredentials('postgresql+asyncpg://user:s3cret@db/app');\n"
|
||||
+ "if (apg !== 'postgresql+asyncpg://user:[REDACTED:password]@db/app') "
|
||||
+ "throw new Error('asyncpg conn redact failed: ' + apg);\n"
|
||||
+ "// RFC 3986 schemes are case-insensitive - uppercase must not bypass\n"
|
||||
+ "const up = redactCredentials('POSTGRESQL+PSYCOPG2://user:s3cret@db/app');\n"
|
||||
+ "if (up !== 'POSTGRESQL+PSYCOPG2://user:[REDACTED:password]@db/app') "
|
||||
+ "throw new Error('uppercase scheme conn redact failed: ' + up);\n"
|
||||
)
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".mjs", delete=False) as f:
|
||||
f.write(harness)
|
||||
|
||||
@@ -250,6 +250,18 @@ class TestCredentialLeakage:
|
||||
assert "s3cret_pass" not in r.sanitized, url
|
||||
assert ":[REDACTED:password]@" in r.sanitized, url
|
||||
|
||||
def test_uppercase_scheme_connection_string(self) -> None:
|
||||
# RFC 3986 schemes are case-insensitive; an uppercase scheme must
|
||||
# not bypass redaction.
|
||||
for url in (
|
||||
"POSTGRESQL+PSYCOPG2://admin:s3cret_pass@db.internal/prod",
|
||||
"HTTPS://admin:s3cret_pass@api.internal/x",
|
||||
):
|
||||
r = evaluate_output(url)
|
||||
assert "connection_string_leak" in r.flags, url
|
||||
assert r.sanitized is not None, url
|
||||
assert "s3cret_pass" not in r.sanitized, url
|
||||
|
||||
def test_bearer_scheme_case_insensitive(self) -> None:
|
||||
# RFC 7235 scheme name is case-insensitive.
|
||||
r = evaluate_output("authorization: bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.sig12345")
|
||||
|
||||
@@ -98,9 +98,12 @@ _RE_PRIVATE_KEY_BLOCK = re.compile(
|
||||
# covers SQLAlchemy dialect+driver URLs (``postgresql+psycopg2``,
|
||||
# ``postgresql+asyncpg``, ``mysql+pymysql``) and ``mongodb+srv`` —
|
||||
# enumerating drivers is a losing game, the suffix shape isn't.
|
||||
# Schemes are case-insensitive per RFC 3986, hence IGNORECASE:
|
||||
# ``POSTGRESQL://`` leaks the same password ``postgresql://`` does.
|
||||
_RE_CONNECTION_STRING = re.compile(
|
||||
r"(?:postgresql|mysql|mongodb|rediss?|amqps?|sqlite|https?)(?:\+[a-z0-9]*)?"
|
||||
r"://[^:@\s]+:[^@\s]+@",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_RE_ENV_SECRET_LINE = re.compile(r"[A-Z][A-Z_0-9]+=\S+")
|
||||
_RE_ENV_SECRET_KEY = re.compile(
|
||||
|
||||
@@ -37,10 +37,11 @@ const _RE_PRIVATE_KEY_BLOCK =
|
||||
// The optional +suffix covers SQLAlchemy dialect+driver URLs
|
||||
// (postgresql+psycopg2, postgresql+asyncpg, mysql+pymysql) and
|
||||
// mongodb+srv — enumerating drivers is a losing game, the suffix
|
||||
// shape isn't.
|
||||
// shape isn't. Schemes are case-insensitive per RFC 3986 (/i):
|
||||
// POSTGRESQL:// leaks the same password postgresql:// does.
|
||||
// ---------------------------------------------------------------------------
|
||||
const _RE_CONNECTION_STRING =
|
||||
/(?:postgresql|mysql|mongodb|rediss?|amqps?|sqlite|https?)(?:\+[a-z0-9]*)?:\/\/[^:@\s]+:[^@\s]+@/g;
|
||||
/(?:postgresql|mysql|mongodb|rediss?|amqps?|sqlite|https?)(?:\+[a-z0-9]*)?:\/\/[^:@\s]+:[^@\s]+@/gi;
|
||||
|
||||
const _RE_CONN_USERINFO = /:\/\/([^:@\s]+):([^@\s]+)@/;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user