Skip to content

fix(contextual_access): redact post-hook content alongside output [PLT-3790] - #5

Merged
wdawson merged 12 commits into
mainfrom
wils/plt-3746-examples-content
Sep 28, 2026
Merged

wdawson merged 12 commits into
mainfrom
wils/plt-3746-examples-content

Conversation

@wdawson

@wdawson wdawson commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Linear: PLT-3790. Related engine change: PLT-3746 (ArcadeAI/monorepo#4556). Schema: ArcadeAI/schemas#31. Docs: ArcadeAI/docs#1217, which merges after this.

Why

Post-hook requests from remote MCP servers now include content, the blocks the server returned alongside output, and clients receive them. These examples now apply the same redaction and filtering to content as to output.

Changes

  • Docker builds (first commit): the Dockerfiles now COPY and build examples/contextual_access/<name>/, the builder image is golang:1.26-alpine to match go.mod, and the .dockerignore entry for the committed advanced_server binary uses its new path. The earlier move (10b2909) had changed all three, and every build-and-push job had been failing since.
  • pkg/server/schema.gen.go: regenerated from schemas main (ContentBlock, content, override.content).
  • pii_redactor: scans and redacts content text blocks as well, in both redact and block modes.
  • content_filter: keyword and pattern rules cover content text blocks, and replace rules rewrite them. Rules match each field on its own, so anchored patterns work.
  • advanced_server: the PII path scans and redacts every string field of every content block. Fixed-output rules clear content.
  • basic_rules: fixed-output rules clear content.
  • READMEs: one line each noting that content gets the same treatment.
  • Cleanups (follow-up commits):
    • The READMEs, usage comments, example configs, and the basic_rules cert script use go run ./examples/contextual_access/<name>. They had used the paths from before the move, and ./tools/webhook-test-server for basic_rules. Their -config flags point at each example's committed example-config.yaml, so the documented commands find their config when run from the repository root.
    • content_filter's pre-hook checks each input value on its own, as the post-hook does, so anchored input patterns work when a tool has several inputs. The README notes that a rule doesn't match across two separate fields.
    • The phone pattern in pii_redactor and advanced_server redacts a leading ( or +: (555) 123-4567 now becomes [PHONE REDACTED], not ([PHONE REDACTED]. It matches the same strings as before.
    • advanced_server/ab_testing.go is gofmt-clean.
    • The Dockerfiles EXPOSE 8888, the servers' default port.

Judgment calls

  1. pii_redactor and content_filter handle content text blocks, the blocks MCP gateways render, going as deep as they already go into output. Other block types pass through unchanged. advanced_server, the comprehensive example, walks every block but leaves base64 payloads (image/audio data, resource blob) untouched, since a pattern match inside them would alter the encoded bytes.
  2. A rule that replaces output with a fixed value returns override.content: []. Clients then receive the new output as a single text block.
  3. override.content is sent only when the request carried content, so responses for non-MCP tools are unchanged.

Testing

No test suite exists. go build ./... and go vet ./... pass at every commit, and gofmt -l . is clean on the final commit. docker build succeeds for all six examples from the repo root, the same context the workflow uses. I ran each changed example locally and checked with curl that post-hook requests carrying content come back with override.content redacted or filtered. For the cleanups, I compared each change before and after with curl: pre-hook inputs with several fields, and phone numbers against ordinary numbers and other PII. I also ran one image with docker run -p 8888:8888 and checked /health.

🤖 Generated with Claude Code

wdawson and others added 2 commits September 28, 2026 15:16
…LT-3790)

10b2909 moved every example from examples/<name>/ into
examples/contextual_access/<name>/ and updated the workflow's `file:`
paths, but the Dockerfiles still COPY and build examples/<name>/. Every
build-and-push job has failed on main since then with
`"/examples/<name>": not found`.

Point each Dockerfile's COPY and go build at the new path. The same
commit raised go.mod to `go 1.26.0`, which the golang:1.25-alpine
builder rejects at `go mod download`, so move the builder to
golang:1.26-alpine. Also update the .dockerignore entry for the
committed advanced_server binary, which was moved too, so it stays out
of the build context.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Regenerate pkg/server/schema.gen.go from ArcadeAI/schemas main, which
adds ContentBlock, the post-hook request's optional `content`, and
`override.content`. It also picks up the tool metadata types (ToolBehavior,
ToolClassification) that landed in the schema earlier.

Related engine change: PLT-3746.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@wdawson
wdawson force-pushed the wils/plt-3746-examples-content branch from a3ee41e to 63eeb0f Compare September 28, 2026 22:24
wdawson and others added 3 commits September 28, 2026 15:46
Post-hook requests from remote MCP servers now carry `content`, the
content blocks the server returned alongside its structured result, and
clients see those blocks too. A hook that rewrites only `output` leaves
the server's own text in `content` as it was sent.

pii_redactor and content_filter now apply their existing output
handling to `content` text blocks, the blocks MCP gateways render, and
return the result as `override.content`. Other block types pass
through unchanged. advanced_server, the comprehensive example, scans
and redacts every string field of every block (text, uri, annotations,
_meta, an embedded resource's text) and leaves base64 payloads
(image/audio `data`, resource `blob`) as-is, since a regex match inside
them would alter the encoded bytes. Block modes and keyword checks
consider content too.

Related engine change: PLT-3746.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
basic_rules and advanced_server post rules can replace a tool's output
with a fixed value. With a remote MCP server, the original `content`
blocks would still reach the client beside the replacement. When the
request carries content, such a rule now also returns an empty
`override.content`, which removes the server's blocks so clients get the
replacement output rendered as text.

Related engine change: PLT-3746.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The post-hook flattened output and content into one string before
matching rules. An anchored pattern such as ^secret$ could then never
match: with no output, the flattened string starts with "<nil>", so a
text block containing exactly "secret" passed through unfiltered, and
with several output fields the join defeated anchors too.

Check keywords and patterns against each output value and each value in
content text blocks on its own. Replacements were already applied per
string, so they are unchanged. The content matching was introduced in
f6091a4.

Refs PLT-3790. Related engine change: PLT-3746.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@wdawson
wdawson force-pushed the wils/plt-3746-examples-content branch from 63eeb0f to 8aaec30 Compare September 28, 2026 22:48
wdawson and others added 7 commits September 28, 2026 15:55
…(PLT-3790)

The READMEs, usage comments, example configs, and the cert script still
used ./examples/<name> from before the move (and ./tools/webhook-test-server
for basic_rules). They now use ./examples/contextual_access/<name>, run
from the repository root.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The pre-hook joined every input value into one string, so an anchored
input pattern missed when a tool had more than one input. It now checks
each input value on its own with leafValues and anyField, as the post-hook
does since 8aaec30.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…790)

The phone pattern began with \b, which can't match before "(" or "+", so
"(555) 123-4567" became "([PHONE REDACTED]" and "+1 555-123-4567" became
"+[PHONE REDACTED]". The match can now start at "(" or "+". It matches the
same strings as before and only extends the match to the left.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Formatting only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every example server listens on -port 8888 by default, but the
Dockerfiles exposed 8080.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The READMEs, usage comments, and example config headers passed -config
files that aren't in the repo (experiments.yaml, filter-rules.yaml,
blocked-users.yaml, config.yaml), or a bare example-config.yaml that
isn't found from the repository root. They now point at each example's
committed example-config.yaml. The go run paths were fixed in 9849e78.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Say in the README that rules match each value on its own, so a keyword
or pattern doesn't match across two separate fields. The pre-hook began
matching per field in ee0025f, and the post-hook in 8aaec30.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@wdawson
wdawson marked this pull request as ready for review September 28, 2026 23:18
@wdawson
wdawson requested a review from sdreyer September 28, 2026 23:18
@wdawson
wdawson merged commit 25c54f8 into main Sep 28, 2026
6 checks passed
@wdawson
wdawson deleted the wils/plt-3746-examples-content branch September 28, 2026 23:56
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