WIP: add security event evidence and redaction#8574
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
- Use managed recursive delete with retries in FileUtils - Fallback to registry search for interpreter discovery in tests - Add async WaitAsync for process exit with timeout/cancel - Always set env vars in REPL; log spurious ERRE/DONE as warnings - Limit Python 2.x filtering to PythonCore entries - Clean up orphan registry keys in environment list tests - Make Django UI test assertions more robust to output changes - Update urls.py for Django 2.x+ compatibility with fallback
|
🔒 Automated review in progress — @rchiodo is auto-reviewing this PR. |
|
| @"(?ix)(\b(?:password|passwd|pwd|token|access[_-]?token|refresh[_-]?token|secret|client[_-]?secret|api[_-]?key|subscription[_-]?key|credential|authorization|connection\s*string|connectionstring|sharedaccesssignature|sig|key)\b\s*[:=]\s*)(['""']?)([^'"";\s,&]+)(['""']?)", | ||
| RegexOptions.CultureInvariant | ||
| ); | ||
|
|
There was a problem hiding this comment.
📍 Python/Product/Common/Infrastructure/SensitiveDataRedactor.cs:35
Verified security bypass (Skeptic executed the byte-identical Python twin; Advocate conceded). The SecretKeyValue pattern requires the keyword to be immediately followed by :/=, so quoted-key forms leak verbatim: {"password": "hunter2"}, {'api_key': 'plain-secret'}, and HTTP_AUTHORIZATION (the _ word char defeats \bAuthorization\b) all pass through unchanged. This is the dominant serialized-secret shape for the very sinks this control protects (WSGI environ, ex.ToString(), TaskDialog details), so the redactor silently leaks what it exists to stop. Allow an optional quote between key and separator (e.g. \b(?:...)\b['\"]?\s*[:=]\s*), match a leading quote on the key, and add regression cases for JSON, Python-dict-repr, and HTTP_* keys. The current tests only cover the bare key=/key: forms that already work, giving false confidence.
[verified]
| r"\b(password|passwd|pwd|token|access[_-]?token|refresh[_-]?token|secret|client[_-]?secret|" | ||
| r"api[_-]?key|subscription[_-]?key|credential|authorization|connection\s*string|connectionstring|" | ||
| r"sharedaccesssignature|sig|key)\b(\s*[:=]\s*)(['\"]?)([^'\";\s,&]+)(['\"]?)", | ||
| re.I |
There was a problem hiding this comment.
📍 Python/Product/WFastCgi/wfastcgi.py:341
Same verified quoted-key bypass as the C# redactor (regexes are character-identical). Because environ is literally a dict and FastCGI logs frequently carry JSON/dict-repr, _sanitize_log_text will pass {"password": "..."} / HTTP_AUTHORIZATION straight into WSGI_LOG and AppInsights track_event. Fix the shared spec (optional quote before the separator; match leading key quote) and add the same regression cases here.
[verified]
| } | ||
|
|
||
| /// <summary> | ||
| /// An exception that should not be silently handled and logged. |
There was a problem hiding this comment.
📍 Python/Product/Cookiecutter/Shared/Infrastructure/ExceptionExtensions.cs:62
Review-rule violation (reuse-core-utils): this private SensitiveDataRedactor is a character-for-character duplicate of Python/Product/Common/Infrastructure/SensitiveDataRedactor.cs (Architect verified byte-identical regexes via read_file). The using System.Text.RegularExpressions; added only for this copy signals the Common dependency was avoided rather than wired in. This copy is internal and untested, so it will inevitably drift from the Common one — any pattern fix (e.g. the quoted-key bypass) now has to land in three places across two languages. Remove this class and reference Microsoft.PythonTools.Infrastructure.SensitiveDataRedactor (link it as shared source or add a project reference to Common). Per the shared-review-policy hard gate, this stays an issue until the rule itself is amended.
[verified]
| sanitized = SecretKeyValue.Replace(sanitized, match => | ||
| match.Groups[1].Value + match.Groups[2].Value + RedactedValue + match.Groups[4].Value | ||
| ); | ||
| return sanitized; |
There was a problem hiding this comment.
📍 Python/Product/Common/Infrastructure/SensitiveDataRedactor.cs:44
Verified partial leak: the value class [^'\";\s,&]+ stops at the first whitespace/comma, so any secret containing a space, comma, ;, &, or quote leaks its tail. Confirmed: password=my secret phrase → password=<redacted> secret phrase, token=a,b,c → token=<redacted>,b,c, and a connection string Server=x;Password=p@ss w0rd;... leaks w0rd. Passphrases and comma-joined token lists are realistic. For quoted values, consume to the closing quote instead of stopping at whitespace/comma.
[verified]
| @"(?i)\b([a-z][a-z0-9+.-]*://)([^/\s:@]+(?::[^/\s@]*)?@)", | ||
| RegexOptions.CultureInvariant | ||
| ); | ||
|
|
There was a problem hiding this comment.
📍 Python/Product/Common/Infrastructure/SensitiveDataRedactor.cs:30
Verified partial leak in UriUserInfo: the userinfo class [^/\s:@]*/[^/\s@]* stops at the first @, so a password containing @ (legal and common) partially leaks — https://user:p@ssword@host/path → https://<redacted>@ssword@host/path, leaving ssword@host. Consume up to the last @ before the authority instead.
[verified]
| @"(?ix)(\b(?:password|passwd|pwd|token|access[_-]?token|refresh[_-]?token|secret|client[_-]?secret|api[_-]?key|subscription[_-]?key|credential|authorization|connection\s*string|connectionstring|sharedaccesssignature|sig|key)\b\s*[:=]\s*)(['""']?)([^'"";\s,&]+)(['""']?)", | ||
| RegexOptions.CultureInvariant | ||
| ); | ||
|
|
There was a problem hiding this comment.
📍 Python/Product/Common/Infrastructure/SensitiveDataRedactor.cs:35
Verified over-redaction: the bare keywords key and sig are generic enough to mangle ordinary diagnostic text — Parsing failed at key: value in config → key: <redacted>, and the design sig = v2 → sig = <redacted>. This erodes the diagnostic value the evidence file claims to preserve. Consider dropping bare key/sig or requiring a tighter context (e.g. api_key, signing_sig). Note the current test deliberately encodes sig redaction as desired, so decide the intended behavior explicitly.
[verified]
| match.Groups[1].Value + match.Groups[2].Value + RedactedValue + match.Groups[4].Value | ||
| ); | ||
| return sanitized; | ||
| } |
There was a problem hiding this comment.
📍 Python/Product/Common/Infrastructure/SensitiveDataRedactor.cs:40
Architectural concern (Architect, structurally confirmed): redaction is applied by remembering to wrap each producer (TaskDialog, CustomCommand, ExceptionExtensions, wfastcgi.log) rather than at the logging-sink boundary. This makes coverage aspirational — any new logging call site silently bypasses redaction, which the evidence file already admits ("outside the covered redaction paths"). Consider redacting once where text enters the Trace/EventLog/ActivityLog/AppInsights writers so the guarantee becomes structural. Additionally, share a golden input/expected corpus between the C# and Python copies to enforce spec parity across languages.
[verified]
| // reloaded. | ||
| _canExecute = false; | ||
| ``` | ||
|
|
There was a problem hiding this comment.
📍 .github/compliance/evidence/reports/security/2984085-security-events-sensitive-errors-evidence-2026-07-03.md:91
Given the verified JSON/quoted-key bypass, the Evidence 3/4/5 "Control coverage: … avoids common secret-bearing key/value patterns" claims are overstated — they hold only for unquoted k=v/k: v, not the JSON/dict/HTTP_* forms these sinks most often carry. Temper this wording (or fix the redactor first) before this file is cited as coverage, so the compliance artifact doesn't create false confidence. The file's honest PARTIAL framing is otherwise good.
[verified]
| best.Configuration.GetPrefixPath(), | ||
| best.Configuration.InterpreterPath | ||
| ) : null; | ||
| } |
There was a problem hiding this comment.
📍 Python/Product/Cookiecutter/Model/CookiecutterClientProvider.cs:79
Coupling concern (Architect): the new FindCompatibleInterpreterFromRegistry fallback is a production interpreter-selection branch added "for tests" (comment: "used when no IServiceProvider is available (for example, in unit tests)"). Test needs leaking into product code is a smell, and this path is now coupled to the sibling PythonRegistrySearch 2.x-filter change so they must move together. Consider injecting a test seam instead of branching production logic on the absence of a service provider. The compModel?.GetService null-guard added here is a genuine fix (removes a real NRE).
[verified]
| var env = processInfo.Environment; | ||
| foreach (var kv in _serviceProvider.GetPythonToolsService().GetFullEnvironment(Configuration)) { | ||
| env[kv.Key] = kv.Value; | ||
| } |
There was a problem hiding this comment.
📍 Python/Product/PythonTools/PythonTools/Repl/PythonInteractiveEvaluator.CommandProcessorThread.cs:104
Unexplained behavior change (Architect): removing the #if DEBUG / if (!debugMode) guard means debug builds now always apply the full environment via GetFullEnvironment, silently changing debug-build startup behavior. This is unrelated to redaction and undocumented in the diff. Add a rationale (or a separate PR) explaining why the debug-only carve-out is no longer needed.
[verified]
| // Only filter Python 2.x for PythonCore-compatible entries. | ||
| // Custom configurable interpreters (e.g. company == "VisualStudio") | ||
| // may legitimately have no SysVersion set, and must not be filtered here. | ||
| if (pythonCoreCompatibility && sysVersion > new Version(0, 0) && sysVersion < new Version(3, 0)) { |
There was a problem hiding this comment.
📍 Python/Product/VSInterpreters/Interpreter/PythonRegistrySearch.cs:188
Behavior change riding a redaction PR (Advocate, Skeptic, Architect, all [unverified] runtime): the 2.x filter now only applies when pythonCoreCompatibility && sysVersion > (0,0), so PythonCore entries with a missing/unparseable SysVersion (previously defaulted to (0,0) and filtered out) now pass through. The change is narrowly guarded and the comment explains the intent (custom configurable interpreters), but it widens what surfaces and has no accompanying test in the diff. Add regression coverage for the missing-SysVersion case, or split this into its own PR.
[verified]
| } | ||
|
|
||
| /// <summary> | ||
| /// Enables using 'await' on this object. |
There was a problem hiding this comment.
📍 Python/Product/Cookiecutter/Shared/Infrastructure/ProcessOutput.cs:741
No defect found — all three reviewers independently confirmed WaitAsync correctly closes the subscribe-after-exit race (_haveRaisedExitedEvent/HasExited recheck + idempotent TrySetResult), links cancellation with the delay CTS, cancels the delay timer on the exit-wins path, and unsubscribes in finally on every path. The only gap: this new non-trivial async primitive ships without a visible unit test in the diff ([unverified] whether coverage exists elsewhere). Add a targeted test for the timeout, cancellation, and already-exited paths.
[verified]
|
|
||
| def log(txt): | ||
| """Logs messages to a log file if WSGI_LOG env var is defined.""" | ||
| txt = _sanitize_log_text(txt) |
There was a problem hiding this comment.
📍 Python/Product/WFastCgi/wfastcgi.py:361
Scope/cohesion note (Advocate, Architect): beyond redaction, this PR carries ~8 independent changes (FileUtils managed-delete, WaitAsync, PythonRegistrySearch filter, REPL #if DEBUG removal, REPL Debug.Fail→Trace.TraceWarning, Django re_path compat, UIA fallback, EnvironmentList orphan cleanup). Each is individually defensible, but the bundle can't be reviewed or reverted selectively — if the redaction work needs rollback given the verified leak, it drags unrelated stabilization fixes with it. Recommend splitting into (1) redaction+evidence, (2) test-infra stabilization, (3) interpreter/registry behavior. The WIP: title is appropriate for now.
[verified]
|
Main blocker: the redaction regexes only match bare |
|
Main theme: the new SensitiveDataRedactor is the security-critical core of this PR, but its regex only handles bare |



No description provided.