Skip to content

Block credential material in MCP tool arguments - #436

Merged
davidmckayv merged 3 commits into
CopilotKit:mainfrom
Kohronburton:feat/content-governance-hardening
Sep 8, 2026
Merged

davidmckayv merged 3 commits into
CopilotKit:mainfrom
Kohronburton:feat/content-governance-hardening

Conversation

@Kohronburton

@Kohronburton Kohronburton commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • inspect MCP tool arguments after structural policy approval and before credentials are read or a vendor is contacted
  • inspect both values and property names for explicit credential fields, provider tokens, authorization headers, and private keys
  • keep refused values and arbitrary property names out of errors and audit records; record only finding categories and audit-safe structural paths
  • fail closed on cyclic, over-depth, over-node, oversized-string, or otherwise uninspectable input
  • add focused unit coverage and an integration assertion that the secret never reaches the audit trail

This is the narrow secret-leak-prevention slice of #86. It deliberately does not attempt broad PII detection, prompt-injection classification, or cost accounting in the same change.

Validation

  • bun test server/tests/content-governance.test.ts — 8 passed
  • bun run format:check — passed
  • bun run lint — passed
  • bun run typecheck — passed
  • current diff verified as additive with zero deletions

The PostgreSQL-backed integration assertion is included for repository CI; the local execution environment did not provide Docker/PostgreSQL.

@Kohronburton
Kohronburton force-pushed the feat/content-governance-hardening branch from 27edda5 to 60bbab4 Compare September 8, 2026 19:58

@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: 27edda5431

ℹ️ 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".

Comment thread server/src/plugins/store.ts Outdated
Comment on lines +115 to +116
for (const [key, child] of Object.entries(value)) {
const childPath = path ? `${path}.${key}` : key;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Inspect argument keys before forwarding or auditing them

When an authenticated or compromised Bot puts credential material in a property name, such as { "sk-<token>": "ordinary" }, only the ordinary value is visited, so the inspection reports safe: true and forwards the token to the vendor. If a nested value is also rejected, the same uninspected key is copied into findings[].path and persisted in the audit trail. Inspect keys as content and use a redacted structural path rather than embedding unsafe key text.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in edc4ea7. Keys are now inspected as content, credential-bearing keys are detected, and arbitrary/unsafe property names are replaced with an audit-safe structural marker. Added tests proving both detection and non-disclosure.

Copy link
Copy Markdown
Contributor Author

@codex review

The branch has been rebuilt from the complete latest upstream tree, and the argument-key bypass plus bounded-scan cases are addressed in edc4ea7.

@davidmckayv davidmckayv left a comment

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.

Deep-reviewed against live code (correctness, governance, no vendor/secret/scale issues). Composed build+tests green. CHANGELOG/format rebase on CI-validated substance.

@davidmckayv
davidmckayv merged commit 38bb57e into CopilotKit:main Sep 8, 2026
14 checks passed

@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: edc4ea79a5

ℹ️ 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".

findings.push({ category: keyCategory, path: childPath });
}
if (
sensitiveFieldNames.has(normalizedFieldName(key)) &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Match compound credential field names

When a tool accepts a standard compound credential field such as x-api-key or aws_secret_access_key, normalization produces x_api_key or leaves aws_secret_access_key, neither of which exactly matches this set. Because these credentials are commonly opaque strings with no recognizable provider prefix, the value inspection also returns no finding and the credential is forwarded to the vendor. Match credential-bearing components/suffixes rather than only the current exact names.

Useful? React with 👍 / 👎.

if (/-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/.test(value)) {
return "private_key";
}
if (/^\s*(?:Basic|Bearer)\s+\S+/i.test(value)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Detect authorization credentials embedded in text

When a credential appears inside an ordinary argument, such as curl -H 'Authorization: Bearer <opaque-token>', the start anchor prevents this expression from recognizing it. An opaque OAuth token need not match any provider-specific pattern, so prepending explanatory text turns a blocked bearer token into a safe: true result and sends it to the vendor.

Useful? React with 👍 / 👎.

);
}

for (const [key, child] of Object.entries(value)) {

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 Enumerate object properties lazily

On an authenticated call containing a very wide object, Object.entries(value) materializes every property before the loop can stop at MAX_NODES, so the advertised node bound does not cap either enumeration work or the temporary allocation. The rebuilt tree still performs this eager enumeration (and the caller also shallow-copies top-level arguments before inspection), allowing a large argument object to consume resources well beyond the 2,000-node limit; iterate lazily and apply the limit before collecting entries.

Useful? React with 👍 / 👎.

Comment on lines +2959 to +2961
payload: {
...decided,
refusal: "sensitive_tool_arguments",

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 Mark content-refused dry-run calls as not carried out

When a policy deny rule is in dry-run mode and the arguments also contain a credential, decided.decision.carriedOut is true because the policy would forward, but content inspection then prevents any vendor call. Spreading decided unchanged into this second rejection row makes the audit UI display “dry-run: recorded, not enforced” for a call that was actually enforced by content governance; override carriedOut to false for this refusal.

Useful? React with 👍 / 👎.

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