diff --git a/lib/core/extensions/sandbox/sandbox_sanitizer.dart b/lib/core/extensions/sandbox/sandbox_sanitizer.dart index 93300b2..99903e7 100644 --- a/lib/core/extensions/sandbox/sandbox_sanitizer.dart +++ b/lib/core/extensions/sandbox/sandbox_sanitizer.dart @@ -22,7 +22,7 @@ class SandboxSanitizer { /// Common password / token assignment forms in dumps. static final _passwordAssignment = RegExp( - r'''\b(password|passwd|pwd|secret|api[_-]?key|access[_-]?token|auth[_-]?token)\b(\s*[:=]\s*)(["']?)([^\s"'&,;]+)(["']?)''', + r'''(['"]?)\b(password|passwd|pwd|secret|api[_-]?key|access[_-]?token|auth[_-]?token)\b\1(\s*[:=]\s*)(?:"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|([^\s"'&,;]+))''', caseSensitive: false, ); @@ -48,9 +48,16 @@ class SandboxSanitizer { return '${m[1]}:$redactionToken@'; }); out = out.replaceAllMapped(_passwordAssignment, (m) { - final quote = m[3] ?? ''; - final endQuote = m[5] ?? ''; - return '${m[1]}${m[2]}$quote$redactionToken$endQuote'; + final qKey = m[1] ?? ''; + final key = m[2] ?? ''; + final sep = m[3] ?? ''; + if (m[4] != null) { + return '$qKey$key$qKey$sep"$redactionToken"'; + } else if (m[5] != null) { + return "$qKey$key$qKey$sep'$redactionToken'"; + } else { + return '$qKey$key$qKey$sep$redactionToken'; + } }); out = out.replaceAllMapped(_bearer, (m) { return '${m[1]}$redactionToken'; diff --git a/test/core/extensions/sandbox/sandbox_sanitization_pipe_test.dart b/test/core/extensions/sandbox/sandbox_sanitization_pipe_test.dart index 1f6b33a..035bad3 100644 --- a/test/core/extensions/sandbox/sandbox_sanitization_pipe_test.dart +++ b/test/core/extensions/sandbox/sandbox_sanitization_pipe_test.dart @@ -102,6 +102,36 @@ QyNTUxOQAAACBA1m7X8J9H6P8Q9J8H6P8Q9J8H6P8Q9J8H6P8Q9J8H6Q== expect(out, contains('mysql://root:${SandboxSanitizer.redactionToken}@')); expect(out, contains('mongodb+srv://admin:${SandboxSanitizer.redactionToken}@')); }); + + test('redacts quoted passwords and tokens containing spaces (#918)', () { + final input = [ + '{"password": "correct horse battery staple"}', + "val = {'api_key': 'super secret key with spaces'};", + 'password: "my secret password123"', + "access_token: 'token with multiple spaces inside'", + 'auth-token = "auth token with spaces"', + 'password=plainSecret&other=1', + '{"secret": "nested \\"quoted\\" secret", "status": "ok"}', + ].join('\n'); + + final out = SandboxSanitizer.sanitize(input); + + expect(out, isNot(contains('correct horse battery staple'))); + expect(out, isNot(contains('super secret key with spaces'))); + expect(out, isNot(contains('my secret password123'))); + expect(out, isNot(contains('token with multiple spaces inside'))); + expect(out, isNot(contains('auth token with spaces'))); + expect(out, isNot(contains('plainSecret'))); + expect(out, isNot(contains('nested \\"quoted\\" secret'))); + + expect(out, contains('{"password": "${SandboxSanitizer.redactionToken}"}')); + expect(out, contains("{'api_key': '${SandboxSanitizer.redactionToken}'}")); + expect(out, contains('password: "${SandboxSanitizer.redactionToken}"')); + expect(out, contains("access_token: '${SandboxSanitizer.redactionToken}'")); + expect(out, contains('auth-token = "${SandboxSanitizer.redactionToken}"')); + expect(out, contains('password=${SandboxSanitizer.redactionToken}&other=1')); + expect(out, contains('{"secret": "${SandboxSanitizer.redactionToken}", "status": "ok"}')); + }); }); group('SandboxRotatingLog', () {