Skip to content

WIP: add security event evidence and redaction#8574

Open
bschnurr wants to merge 3 commits into
microsoft:mainfrom
bschnurr:wip/fix-tests
Open

WIP: add security event evidence and redaction#8574
bschnurr wants to merge 3 commits into
microsoft:mainfrom
bschnurr:wip/fix-tests

Conversation

@bschnurr

@bschnurr bschnurr commented Jul 7, 2026

Copy link
Copy Markdown
Member

No description provided.

@bschnurr bschnurr requested a review from a team as a code owner July 7, 2026 16:52
@azure-pipelines

Copy link
Copy Markdown
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
@rchiodo

rchiodo commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🔒 Automated review in progress — @rchiodo is auto-reviewing this PR.

@sonarqubecloud

sonarqubecloud Bot commented Jul 7, 2026

Copy link
Copy Markdown

@"(?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
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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 phrasepassword=<redacted> secret phrase, token=a,b,ctoken=<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
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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/pathhttps://<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
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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 configkey: <redacted>, and the design sig = v2sig = <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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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;
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 .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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📍 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.FailTrace.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]

@rchiodo

rchiodo commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Main blocker: the redaction regexes only match bare key=/key: v and miss the dominant serialized-secret shapes for these very sinks — JSON/dict-repr ({"password": "..."}), quoted keys, and HTTP_AUTHORIZATION (the _ defeats \bAuthorization\b). Fix the shared spec (allow an optional quote before the separator, match a leading key quote, handle HTTP_*) and add regression cases for those forms. Also de-duplicate the redactor into one shared implementation, and temper the evidence file's coverage claims accordingly. Secondary: several unrelated stabilization changes (WaitAsync, FileUtils delete, PythonRegistrySearch filter, REPL #if DEBUG removal) ride along without tests/rationale — consider splitting so the redaction work can be reverted independently if needed.

@rchiodo

rchiodo commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Main theme: the new SensitiveDataRedactor is the security-critical core of this PR, but its regex only handles bare key=v/key: v forms — verified bypasses leak {"password": "..."}/dict-repr and HTTP_AUTHORIZATION-style keys straight into logs (C# and Python copies are character-identical, so both leak). Please fix the shared spec, add JSON/dict/HTTP_* regression cases, and reconcile the compliance-evidence wording with the real coverage. Separately, this WIP bundles ~8 unrelated stabilization/behavior changes that would be easier to review and revert if split out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants