Repository navigation
fix(agent-os): stop the adapter SSN pattern hard-blocking bare nine-digit numbers - #3591
Conversation
…igit numbers PII_PATTERNS[0] made the separator optional, so it matched any bare nine-digit run. These patterns drive blocking call sites (autogen_adapter DropMessage, PolicyViolationError on state updates, bedrock_adapter), so a tracking number, ABA routing number, or ZIP+4 in ordinary content became a hard deny. Ports the separator-required pattern from microsoft#3531, which closed the same gateway-DoS on the MCP gateway copy in credential_redactor.py. Lookarounds rather than \b keep an SSN glued to '_' detectable. This narrows the pattern broadened in microsoft#2635: the space and dot forms that change was about are retained, only the no-separator form is dropped. The detection-only pattern in policy/lib/patterns.rego stays loose on purpose. Fixes microsoft#3532 Signed-off-by: Alper <olperander@outlook.com>
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
PR Review Summary
Verdict: AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims. |
There was a problem hiding this comment.
Pull request overview
TL;DR: 0 blockers, 1 warning. This ships; warning is fine as a follow-up.
This PR aligns the adapter-side SSN blocking regex with the gateway-side fix from #3531 by requiring a separator and using lookaround anchors, preventing false-positive hard blocks on bare nine-digit runs (e.g., tracking numbers / ABA routing numbers) while still detecting underscore-glued SSNs.
Changes:
- Tighten
agent_os.integrations.base.PII_PATTERNS[0]SSN regex to require separators and use lookarounds instead of\b. - Update and extend
test_pii_patterns.pyto cover separator variants, underscore-glued detection, and common false-positive cases that must not trigger blocking.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
agent-governance-python/agent-os/src/agent_os/integrations/base.py |
Replace SSN pattern with separator-required + lookaround-anchored regex to avoid hard-blocking bare nine-digit runs. |
agent-governance-python/agent-os/tests/test_pii_patterns.py |
Update SSN variant expectations and add negative-coverage tests for prior false-positive cases on blocking paths. |
Review feedback: _SSN_FALSE_POSITIVES is not limited to bare nine-digit runs. ZIP+4 is separated, and two cases carry ten and eight digits; those guard the digit grouping rather than the separator requirement. Rename the test and say so in the docstring. Signed-off-by: Alper <olperander@outlook.com>
The comment said policy-engine/policy/lib/patterns.rego keeps the loose form because that path is detection-only. It is not: patterns.rego defines deny_if_pattern, which returns a deny verdict, and agt_default.rego calls it, so the same over-block is reachable on the OPA path. patterns.rego also declares that it tracks base.py PII_PATTERNS, which this change makes untrue, and the wheel-shipped copy under _stock_rego keeps the loose form as well. Record the divergence as a known gap instead, including why the Rego copies cannot take this pattern verbatim: RE2 has no lookarounds, so an equivalent must anchor with (^|[^A-Za-z0-9]) / ([^A-Za-z0-9]|$), which consumes a character and shifts the span offset deny_if_pattern reports. The test module docstring carried the same claim; corrected there too. Signed-off-by: Alper <olperander@outlook.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
agent-governance-python/agent-os/tests/test_pii_patterns.py:35
- The comment above
_SSN_FALSE_POSITIVESsays “Nine-digit content…”, but the list also includes a ZIP+4 (with a dash), a 10-digit run, and an 8-digit build tag. Consider broadening the wording so it matches what the test data actually covers.
# Nine-digit content that a blocking path must let through (#3532).
_SSN_FALSE_POSITIVES = ('Tracking: 123456789', 'order_123456789', 'ZIP 12345-6789', 'ABA 021000021', 'order 1234567890 shipped', 'build 12-34-5678 tagged')
agent-governance-python/agent-os/src/agent_os/integrations/base.py:34
- The comment says both Rego copies “declare that they track this constant”, but only
policy-engine/policy/lib/patterns.regoactually documents that relationship.agt-policies/src/agt/cli/_stock_rego/patterns.regocurrently has no such note, so this is misleading.
# NOT yet in step with the two Rego copies, which still carry the loose
# form: policy-engine/policy/lib/patterns.rego and agt-policies
# .../cli/_stock_rego/patterns.rego. Both declare that they track this
# constant, and the first is reachable as a hard block through
Only policy-engine/policy/lib/patterns.rego declares that it tracks base.py PII_PATTERNS; the wheel-shipped _stock_rego copy carries the pattern with no such note. The previous wording claimed both did. _SSN_FALSE_POSITIVES was described as nine-digit content, but two of the six cases are a ten-digit run and an eight-digit build tag, which guard the digit grouping rather than the separator requirement. Signed-off-by: Alper <olperander@outlook.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
agent-governance-python/agent-os/src/agent_os/integrations/base.py:37
- The comment says the policy-engine Rego pattern "declares that it tracks this constant" and contrasts it with the wheel-shipped copy, but the evidence is the opposite: the stock rego file documents that it tracks
base.py::PII_PATTERNS, whilepolicy-engine/policy/lib/patterns.regodoes not. This makes the new comment misleading.
# .../cli/_stock_rego/patterns.rego. The first declares that it tracks
# this constant and is reachable as a hard block through
# patterns.deny_if_pattern (agt_default.rego), so the same over-block
# survives on the OPA path; the wheel-shipped copy carries the pattern
# with no such note. They cannot take this pattern verbatim: RE2
agent-governance-python/agent-os/tests/test_pii_patterns.py:25
- This module docstring says there are "two adapters" with a live boundary test in this module, but the file only exercises Bedrock via
_scan_pii(Autogen is only checked for importing the shared constant). Update the wording to avoid overstating the coverage.
For the two adapters whose PII enforcement path can be exercised without
optional third-party SDKs, we also drive a live boundary test through the
real call site to verify the regex behaves the same end-to-end.
Only bedrock is driven at its real call site, through bedrock_adapter._scan_pii. The autogen adapter is checked for sharing the same regex objects, not exercised end-to-end, so "the two adapters ... we also drive a live boundary test" overstated what the module covers. Signed-off-by: Alper <olperander@outlook.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (1)
agent-governance-python/agent-os/src/agent_os/integrations/base.py:34
- This comment references the agt-policies Rego copy using an ellipsis path (".../cli/_stock_rego/patterns.rego"), which makes it hard to locate the file in-repo. Since the exact file exists, it should be named explicitly to keep the note actionable.
# NOT yet in step with the two Rego copies, which still carry the loose
# form: policy-engine/policy/lib/patterns.rego and agt-policies
# .../cli/_stock_rego/patterns.rego. The first declares that it tracks
# this constant and is reachable as a hard block through
Imran Siddique (imran-siddique)
left a comment
There was a problem hiding this comment.
Ran the old and new patterns side by side, and checked every claim in the new comment block including
the ones about files this PR does not touch. All of them hold.
Measured:
| input | old \b\d{3}[\s.-]?\d{2}[\s.-]?\d{4}\b |
new |
|---|---|---|
123-45-6789 |
HIT | HIT |
123 45 6789 |
HIT | HIT |
123.45.6789 |
HIT | HIT |
employee_123-45-6789 |
miss | HIT |
123456789 |
HIT | miss |
Tracking: 123456789 |
HIT | miss |
ZIP 12345-6789 |
HIT | miss |
ABA 021000021 |
HIT | miss |
order 1234567890 shipped |
miss | miss |
12-345-6789, 1234-56-7890, build 12-34-5678 tagged |
miss | miss |
Two things worth stating plainly from that table. The ZIP+4 case is the one I would lead with if this
needs justifying to anyone: 12345-6789 matched the old pattern because \d{3} takes 123, the
optional separator takes nothing, \d{2} takes 45, and the rest lines up. A US postal code in
ordinary content was a hard deny. And the change is not purely a narrowing: the lookarounds also fix
employee_123-45-6789, which the old \b missed because _ is a word character. So this closes a
false negative in the same edit that closes the false positives.
I verified the note about the two Rego copies too, because a comment asserting that other files are
out of step is worth being right about. Both still carry the loose form on main:
policy-engine/policy/lib/patterns.rego:17
pii_ssn := `\b\d{3}[\s.\-]?\d{2}[\s.\-]?\d{4}\b`
agent-governance-python/agt-policies/src/agt/cli/_stock_rego/patterns.rego:8
pii_ssn := `\b\d{3}[\s.\-]?\d{2}[\s.\-]?\d{4}\b`
So the over-block does survive on the OPA path, and the RE2 point is correct: RE2 has no lookarounds,
so the equivalent has to consume a character with (^|[^A-Za-z0-9]), which shifts the span offset
deny_if_pattern reports. Documenting that rather than silently leaving them divergent is the right
call, and calling out that only one of the two declares it tracks this constant is the detail that
makes the note actionable.
The issue chain is accurate: #2635 closed (the broadening that introduced this), #3531 merged (the
gateway-side fix this ports), #3532 open (this adapter-side residual).
One interaction the maintainers should see. #3801 is open against the same two files and it is
complementary rather than conflicting, but it will conflict textually. That PR adds a separate
context-cued pattern so a bare nine-digit run is still caught when it follows ssn,
social security or soc sec. Together the two produce the answer you actually want: bare digits no
longer hard-block, but SSN: 745102386 is still detected. Neither is complete without the other, and
whichever lands second needs a rebase.
One concrete thing to watch when they meet: your _ssn_pattern() helper returns "the SSN regex from
the shared PII_PATTERNS tuple", and #3801 adds a second SSN regex to that tuple. That helper will
need to disambiguate, or the false-positive tests could silently start asserting against the wrong
pattern.
Nothing blocking.
|
Closing and reopening to retrigger CI (the previous run was cancelled and is too old to rerun). |
MohammadHaroonAbuomar
left a comment
There was a problem hiding this comment.
- CI 'Spell-check changed files' fails on this PR's own added lines: Spell-check changed files: unknown words Lookarounds, neighbouring in added lines (fresh CI otherwise green after reopen) Please add the terms to .cspell-repo-terms.txt (or reword) so the gate passes.
Spell-check flagged Lookarounds and neighbouring in this PR's own added lines. Reworded rather than extending .cspell-repo-terms.txt: the singular lookaround already passes, and neighboring is the spelling the dictionary carries. Signed-off-by: Alper <olperander@outlook.com>
MohammadHaroonAbuomar
left a comment
There was a problem hiding this comment.
Approved after group integration review; spell-check fix is comment-only rewording.
…PII patterns
CredentialRedactor.PII_PATTERNS and integrations.base.PII_PATTERNS both
require a separator between digit groups so that bare nine-digit numbers
(tracking numbers, ZIP+4, ABA routing numbers) do not hard-block at the
MCP gateway. That requirement also lets a genuine SSN through when it
is written without separators next to an explicit cue:
SSN: 745102386
ssn=745102386
social security number 745102386
Add a "US SSN (context-cued)" pattern to both detection sites. The
pattern fires only when a case-insensitive cue keyword (ssn, social
security, social security number/num/no/#, soc sec) appears immediately
before the nine-digit run, keeping uncued bare digits as non-matches so
the false-positive suppression from microsoft#3531 is preserved. Both detectors
carry the identical regex to stay in lockstep (microsoft#3591 tracks their
alignment).
Call chain (2 entry points, 2 source files, 2 test files):
CredentialRedactor.find_pii_matches()
-> PII_PATTERNS["US SSN (context-cued)"].pattern
adapter PII scan (autogen/bedrock)
-> integrations.base.PII_PATTERNS[1]
18 new tests across test_credential_redactor.py (11 positive, 6
negative, 1 matched_text assertion) and test_pii_patterns.py (5
positive, 4 negative).
Closes microsoft#3592
Signed-off-by: dylanyunlon <dogechat@163.com>
Both SSN detectors, the gateway credential_redactor.py "US SSN" pattern and the adapter integrations/base.py PII_PATTERNS entry, required a separator between digit groups. A bare nine-digit SSN placed next to an explicit cue (SSN: 745102386, ssn=745102386, social security number 745102386) matched neither, so cued tool output was neither redacted at the gateway nor blocked at the adapters. Add an identical context-cued branch to both detectors: a case-insensitive cue (ssn or social security [number]) within a short window before an undelimited nine-digit run. The bare-uncued corpus (tracking, ABA, ZIP+4, ten-digit) stays non-matching, so pii_leak does not hard-block ordinary traffic, and the separated forms still match. The two patterns are kept byte-identical to preserve the parity restored in microsoft#3591; the RE2 Rego copies are intentionally left for a separate change. Lockstep regression tests assert the cued-bare forms match on both detectors and the uncued corpus stays clean; reverting the source makes them fail. Fixes microsoft#3592 Signed-off-by: AlgoVoi <chopmob@gmail.com>
…igit numbers (microsoft#3591) * fix(agent-os): stop the adapter SSN pattern hard-blocking bare nine-digit numbers PII_PATTERNS[0] made the separator optional, so it matched any bare nine-digit run. These patterns drive blocking call sites (autogen_adapter DropMessage, PolicyViolationError on state updates, bedrock_adapter), so a tracking number, ABA routing number, or ZIP+4 in ordinary content became a hard deny. Ports the separator-required pattern from microsoft#3531, which closed the same gateway-DoS on the MCP gateway copy in credential_redactor.py. Lookarounds rather than \b keep an SSN glued to '_' detectable. This narrows the pattern broadened in microsoft#2635: the space and dot forms that change was about are retained, only the no-separator form is dropped. The detection-only pattern in policy/lib/patterns.rego stays loose on purpose. Fixes microsoft#3532 Signed-off-by: Alper <olperander@outlook.com> * test(agent-os): name the SSN negative test for what it actually covers Review feedback: _SSN_FALSE_POSITIVES is not limited to bare nine-digit runs. ZIP+4 is separated, and two cases carry ten and eight digits; those guard the digit grouping rather than the separator requirement. Rename the test and say so in the docstring. Signed-off-by: Alper <olperander@outlook.com> * docs(agent-os): correct the claim about the Rego SSN copies The comment said policy-engine/policy/lib/patterns.rego keeps the loose form because that path is detection-only. It is not: patterns.rego defines deny_if_pattern, which returns a deny verdict, and agt_default.rego calls it, so the same over-block is reachable on the OPA path. patterns.rego also declares that it tracks base.py PII_PATTERNS, which this change makes untrue, and the wheel-shipped copy under _stock_rego keeps the loose form as well. Record the divergence as a known gap instead, including why the Rego copies cannot take this pattern verbatim: RE2 has no lookarounds, so an equivalent must anchor with (^|[^A-Za-z0-9]) / ([^A-Za-z0-9]|$), which consumes a character and shifts the span offset deny_if_pattern reports. The test module docstring carried the same claim; corrected there too. Signed-off-by: Alper <olperander@outlook.com> * docs(agent-os): tighten two inaccurate comments from review Only policy-engine/policy/lib/patterns.rego declares that it tracks base.py PII_PATTERNS; the wheel-shipped _stock_rego copy carries the pattern with no such note. The previous wording claimed both did. _SSN_FALSE_POSITIVES was described as nine-digit content, but two of the six cases are a ten-digit run and an eight-digit build tag, which guard the digit grouping rather than the separator requirement. Signed-off-by: Alper <olperander@outlook.com> * docs(agent-os): stop overstating adapter coverage in the test docstring Only bedrock is driven at its real call site, through bedrock_adapter._scan_pii. The autogen adapter is checked for sharing the same regex objects, not exercised end-to-end, so "the two adapters ... we also drive a live boundary test" overstated what the module covers. Signed-off-by: Alper <olperander@outlook.com> * docs(agent-os): use spellings the repo dictionary already accepts Spell-check flagged Lookarounds and neighbouring in this PR's own added lines. Reworded rather than extending .cspell-repo-terms.txt: the singular lookaround already passes, and neighboring is the spelling the dictionary carries. Signed-off-by: Alper <olperander@outlook.com> --------- Signed-off-by: Alper <olperander@outlook.com> Signed-off-by: yuvrajsingh2428 <offcyuvi2428@gmail.com>
Description
PII_PATTERNS[0]inagent_os/integrations/base.pywas\b\d{3}[\s.-]?\d{2}[\s.-]?\d{4}\b— the separator is optional, so it matchedany bare nine-digit run. Unlike the detection-only Rego pattern, this constant
drives blocking call sites:
autogen_adapterDropMessage,PolicyViolationErroron state updates, andbedrock_adapter(which aliasesthe same tuple through
_PII_RE). A tracking number, ABA routing number, orZIP+4 in ordinary message content produced a hard deny.
This is the adapter-side residual of the gateway-DoS that #3531 fixed on the MCP
path, tracked as #3532.
Fix
Ports #3531's pattern:
(?<![A-Za-z0-9])\d{3}[\s.-]\d{2}[\s.-]\d{4}(?![A-Za-z0-9]).A separator is required, and lookarounds replace
\bso an SSN glued to_(
employee_123-45-6789) is still detected.bedrock_adapter._PII_REalreadyaliases
PII_PATTERNS, so one change covers both copies.Note on #2635
This intentionally narrows the pattern that #2635 broadened. The space and dot
forms that change was about are retained; only the no-separator form is dropped,
and only on this blocking path.
policy-engine/policy/lib/patterns.regokeepsthe looser form, which is correct there — that path reports rather than blocks.
Two assertions in
test_pii_patterns.pythat required the bare form to matchare updated accordingly, and the module docstring now records the tradeoff.
Testing
Added both-direction coverage per the issue: separated forms and the
underscore-glued form must match;
Tracking: 123456789,ZIP 12345-6789,ABA 021000021, and ten-digit runs must not.Mutation-tested: restoring the loose pattern fails 4 of the new tests. Full
agent-ossuite run against pristinemainand this branch — failure sets areidentical (pre-existing, from optional deps and an
agent_control_specificationversion mismatch locally), with 4 additional passing tests.
ruff checkreportsno new findings.
Prior art / related projects
Pattern and test structure ported from #3531 (
agent_os/credential_redactor.py,"US SSN") in this repository.
Fixes #3532