Skip to content

Flag high-risk content before MCP tool calls - #448

Closed
Kohronburton wants to merge 1 commit into
CopilotKit:mainfrom
Kohronburton:feat/content-risk-signals
Closed

Flag high-risk content before MCP tool calls#448
Kohronburton wants to merge 1 commit into
CopilotKit:mainfrom
Kohronburton:feat/content-risk-signals

Conversation

@Kohronburton

Copy link
Copy Markdown
Contributor

Summary

  • extend outbound MCP argument inspection with high-confidence payment-card, US SSN, and prompt-injection signals
  • keep credentials fail-closed while treating PII/injection matches as audit-only review signals, avoiding disruption to legitimate document and research workflows
  • add mcp.content_flagged audit events and an admin audit label without recording matched values
  • recognize temporary AWS access-key IDs (ASIA) and require Luhn-valid payment-card candidates
  • keep blocking independent from the bounded findings list, preventing review-signal flooding from hiding a later credential

This is a focused follow-up to #436 and continues the content-governance slice of #86.

Validation

  • bun test server/tests/content-governance.test.ts app/tests/audit-outcome.test.ts server/tests/audit.test.ts — 32 passed
  • bun run format:check — passed
  • bun run lint — passed
  • bun run typecheck — passed

PostgreSQL-backed integration tests remain delegated to repository CI.

@Kohronburton
Kohronburton force-pushed the feat/content-risk-signals branch from e1b03f7 to 269d7a4 Compare September 8, 2026 20:26

Copy link
Copy Markdown
Contributor Author

Ready for review. The branch is rebased to current main as one commit; focused tests (32), formatting, lint, and typecheck all pass. @codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 269d7a4b6a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

function hasValidPaymentCard(value: string): boolean {
const candidates = value.match(/(?<!\d)(?:\d[ -]?){12,18}\d(?!\d)/g) ?? [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid consuming numeric fields after a payment card

When a valid PAN is followed by a space-separated numeric field, such as 4242 4242 4242 4242 12 34, this greedy expression consumes up to 19 digits as one candidate; the Luhn check then runs against the combined value and returns no finding. Card data commonly appears beside an expiry date or ZIP, so the detector should identify bounded PAN candidates rather than treating every adjacent digit group as part of the card.

Useful? React with 👍 / 👎.

Comment on lines 164 to +165
if (category && findings.length < MAX_FINDINGS) {
findings.push({ category, path });
findings.push({ category, path, action: "block" });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve a blocking finding when review findings fill the cap

When 20 earlier values match review-only signals and a credential appears later, mustBlock becomes true but this length guard prevents the credential finding from being added. The resulting rejected-call audit payload contains only action: "review" findings and no category or path explaining what caused the block; reserve or replace a slot for blocking findings so the bounded result remains consistent with the rejection.

Useful? React with 👍 / 👎.

"mcp.tools_discovered": "Tools offered for one run",
"mcp.call_succeeded": "Called on this Bot's behalf",
"mcp.call_rejected": "Blocked",
"mcp.content_flagged": "Content needs review",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Display what caused a content review flag

For every new mcp.content_flagged row, the admin page shows only this generic label: Row never reads payload.contentInspection.findings. Consequently payment-card, SSN, and prompt-injection events are indistinguishable and their audit-safe paths are inaccessible from the review UI, leaving administrators unable to determine what content needs review.

Useful? React with 👍 / 👎.

@davidmckayv

Copy link
Copy Markdown
Contributor

Thanks for this, and for the care in it. The engineering is genuinely solid: it's advisory-only (a flagged tool call still proceeds, so nothing legitimate is refused), it never puts a matched value in an audit row or error, the regexes are linear (no ReDoS), and the credential path stays fail-closed. No complaints on the code.

We're going to close it on scope rather than quality. The credential firewall (#436, and #450 on top of it) is default-on in the template for a narrow reason: credential material in a model-supplied tool argument is unambiguously anomalous, because the gateway injects real auth separately, so blocking it can't refuse a legitimate call. Broad PII detection (credit card, SSN) and prompt-injection classification are a different kind of thing. They are heuristic, and shipping them default-on would turn on audit flagging for every deployment that forks this template, with false positives that are real: roughly one in ten random 16-digit numbers passes a Luhn check, and 'ignore previous instructions' is ordinary text in any document about prompt injection. The right policy there depends entirely on a given deployment's data and risk posture, which is exactly the kind of decision a template should leave to the fork rather than make for everyone.

So this is something to build into a specific deployment, not into the template's shipped defaults. The content-governance module is the extension point for it: a fork can add precisely these categories, tuned to its own data, and decide block-vs-review for itself. If we later decide the template should offer optional, config-gated content policies, we'll design that deliberately as a product surface.

Appreciate the contribution, and the thoroughness behind it.

@davidmckayv davidmckayv closed this Sep 9, 2026
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