Skip to content

fix(orchestrator): apply prompt masking to pass-through request bodies - #2153

Open
cesaryuan wants to merge 4 commits into
looplj:unstablefrom
cesaryuan:feat/prompt-protection-with-pass-through
Open

fix(orchestrator): apply prompt masking to pass-through request bodies#2153
cesaryuan wants to merge 4 commits into
looplj:unstablefrom
cesaryuan:feat/prompt-protection-with-pass-through

Conversation

@cesaryuan

@cesaryuan cesaryuan commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Preserve prompt-protection mask results when PassThroughBody reuses the original provider-native JSON request body.
  • Keep pass-through's core compatibility guarantee: original JSON structure, unknown fields, provider-specific options, and model mapping are retained; only protected prompt values are patched.
  • Carry the matched masking rules through the orchestrator, without breaking existing PromptProtecter implementations.
  • Cover OpenAI Chat Completions and Ollama Chat, OpenAI Responses, Anthropic Messages, and Gemini GenerateContent request layouts.
  • Apply Gemini tool-scope masks to structured functionResponse.response values while retaining every other value in that response object.

Why this change matters

Pass-through exists to preserve the request body shape and fields that AxonHub does not own. It should not mean that the original prompt text is immutable after a security policy has changed it.

Before this change, a mask rule could successfully modify the unified request, but raw-body replay would then replace that request with the original plaintext JSON. The upstream provider therefore received content the user expected to be masked. reject was unaffected because it aborts before egress, but mask silently lost its protection on the pass-through path.

Without this fix, users must choose between:

  • disabling pass-through and risking loss of provider-specific or unknown request fields; or
  • disabling prompt protection and allowing sensitive text to reach the upstream provider.

This PR makes both features composable: preserve the native request while applying the minimum security-required string changes.

Implementation details

  1. PromptProtectionRuleService.ProtectWithResult returns both the protected request and the mask rules that actually matched. The existing Protect method remains as a compatibility wrapper.
  2. The inbound prompt-protection middleware stores matched mask rules in the request state.
  3. The pass-through middleware first applies its existing model-field patch, then locates known prompt values for the active API format and applies the same ordered rule replacement while preserving each field's role scope.
  4. Gemini functionResponse.response is a structured object but is represented as serialized tool content in the unified request. The raw patch follows that same map unmarshal/marshal representation, replaces matching values under the tool scope, and writes the result back as raw JSON so unrelated response fields survive.
  5. A successful patch in one field is no longer able to let a different matched Gemini tool response through unchanged. If the raw body is invalid, the API layout is unsupported, or a protected raw value cannot safely be patched, raw replay is abandoned. The already-transformed and already-protected outbound body remains in use; plaintext is never replayed as a fallback.

Validation

  • go test ./internal/server/orchestrator -run '^(TestApplyPassThroughBodyMasksPromptProtectedOpenAIContent|TestApplyPassThroughBodyKeepsProtectedBodyForUnsupportedPromptLayout|TestPatchPassThroughPromptProtection|TestProtectPrompts)' -count=1 -v
    • Includes a regression where one mask rule hits both Gemini user text and functionResponse.response.token; both are masked and provider_meta remains intact.
  • go test ./internal/server/biz -run '^TestPromptProtectionRuleService_ProtectMask$' -count=1 -v
  • git diff --check upstream/unstable...HEAD

The full go test ./internal/server/orchestrator -count=1 suite currently has pre-existing failures in TestDefaultSelector_SelectModelCandidates_Cache/cache_invalidated_when_channel_updated and TestChatCompletionOrchestrator_Process_MinuteQuotaExceeded. They are outside this change's execution path; the focused coverage above passes.

Closes #2152

Summary by CodeRabbit

  • New Features
    • Prompt protection now applies to pass-through API requests, masking sensitive content across OpenAI, Anthropic, and Gemini while preserving original JSON structures and provider-specific fields.
    • Masking supports content in user messages and supported tool or function responses, while retaining unrelated request data.
  • Bug Fixes
    • Unsupported or invalid request formats fail safely without replaying protected content.

-【规则结果】保留提示词保护命中的脱敏规则
-【请求处理】透传请求体沿用统一请求的脱敏结果
-【兼容性】保留旧保护接口并兼容现有调用方
-【数据完整性】避免重建请求体时丢失提供商专有字段
-【测试】补充命中规则记录与请求体脱敏验证
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Prompt protection now returns matched mask rules. Orchestration stores them in request state. Pass-through handling applies those rules to provider-native OpenAI, Anthropic, and Gemini request bodies while preserving other fields and rejecting unsafe layouts.

Changes

Prompt protection result capture

Layer / File(s) Summary
Protection result contract and compatibility
internal/server/biz/prompt_protection_request.go, internal/server/biz/prompt_protection_request_test.go, internal/server/orchestrator/prompt_protecter.go
ProtectWithResult returns the protected request and matched rules. Protect delegates to it and keeps its existing signature.
Orchestration state capture
internal/server/orchestrator/prompt_protection.go, internal/server/orchestrator/state.go, internal/server/orchestrator/prompt_protection_test.go
The middleware supports legacy and result-capable protectors and stores matched rules in PromptProtectionMaskRules.

Provider-native body patching

Layer / File(s) Summary
Raw request extraction and masking
internal/server/orchestrator/pass_through_prompt_protection.go
The patcher extracts maskable fields from OpenAI Chat, OpenAI Responses, Anthropic, and Gemini bodies. It applies ordered, role-scoped replacements to string and structured JSON fields.
Patching validation
internal/server/orchestrator/pass_through_prompt_protection_test.go
Tests cover provider-specific paths, Gemini function responses, scoped rules, preserved metadata, and unsupported formats.

Pass-through integration

Layer / File(s) Summary
Pass-through merge and fallback behavior
internal/server/orchestrator/pass_through.go, internal/server/orchestrator/pass_through_test.go
Pass-through merging receives stored mask rules. Tests verify masked OpenAI content, preserved provider fields, and unchanged fallback behavior for unsupported layouts.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PromptProtection
  participant PassThrough
  participant Provider
  Client->>PromptProtection: submit provider-native request
  PromptProtection->>PromptProtection: mask unified request and record matched rules
  PromptProtection->>PassThrough: pass original body and PromptProtectionMaskRules
  PassThrough->>PassThrough: patch supported text fields
  PassThrough->>Provider: send patched provider-native body
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: applying prompt masking to pass-through request bodies.
Linked Issues check ✅ Passed The changes implement rule tracking, provider-specific body patching, scope handling, field preservation, and safe fallback required by issue #2152.
Out of Scope Changes check ✅ Passed The production and test changes directly support prompt masking for pass-through request bodies and the requirements in issue #2152.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cesaryuan
cesaryuan marked this pull request as ready for review August 4, 2026 13:51
Copilot AI lite review requested due to automatic review settings August 4, 2026 13:51
@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR preserves prompt-protection masking when provider-native request bodies are passed through, while retaining unknown fields and provider-specific options.

  • Carries matched masking rules from inbound prompt protection into shared orchestrator state.
  • Patches known prompt fields across OpenAI, Ollama, Anthropic, and Gemini native JSON layouts.
  • Adds OpenAI Responses Compact support, completing the previously reported unsupported-format fix.
  • Falls back to the already-protected outbound body when safe raw-body patching is unavailable.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported OpenAI Responses Compact omission is fixed by routing its shared instructions and input layout through the existing Responses prompt-field extractor.

Important Files Changed

Filename Overview
internal/server/orchestrator/pass_through_prompt_protection.go Adds provider-format-aware prompt masking for native pass-through bodies, including the previously omitted OpenAI Responses Compact format.
internal/server/orchestrator/pass_through.go Integrates masking into raw-body merging and safely retains the generated protected body when patching fails.
internal/server/orchestrator/prompt_protection.go Captures matched mask rules while retaining compatibility with prompt protectors that only implement the original interface.
internal/server/biz/prompt_protection_request.go Exposes the protected request and matched rules through ProtectWithResult, with Protect retained as a compatibility wrapper.
internal/server/orchestrator/state.go Adds request-scoped storage for matched masking rules shared between inbound and outbound middleware.
internal/server/orchestrator/pass_through_prompt_protection_test.go Covers native prompt layouts, role scoping, Gemini structured tool responses, and OpenAI Responses Compact pass-through preservation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Provider-native request] --> B[Unified inbound transformation]
  B --> C[Prompt protection]
  C --> D[Store matched mask rules]
  D --> E[Generate protected outbound body]
  E --> F{Pass-through enabled?}
  F -- No --> G[Send generated protected body]
  F -- Yes --> H[Patch model in original JSON]
  H --> I{Raw prompt patch succeeds?}
  I -- Yes --> J[Send patched native body]
  I -- No --> G
Loading

Reviews (2): Last reviewed commit: "fix(orchestrator): preserve masked pass-..." | Re-trigger Greptile

Comment thread internal/server/orchestrator/pass_through_prompt_protection.go Outdated

Copilot AI 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.

Pull request overview

This PR fixes a security gap where prompt-protection mask transformations could be undone when PassThroughBody replays the original provider-native JSON. It threads matched mask rules through the orchestrator so pass-through can preserve the original JSON structure and provider-specific fields while still patching only the protected prompt text values for supported API formats.

Changes:

  • Extend prompt protection to expose matched mask rules (ProtectWithResult) while keeping the legacy Protect API for compatibility.
  • Persist matched mask rules in orchestrator state and apply them during pass-through body merging for supported provider-native request layouts.
  • Add regression tests covering OpenAI Chat/Responses, Anthropic Messages, and Gemini GenerateContent (including structured functionResponse.response).

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/server/orchestrator/state.go Stores matched prompt-protection mask rules in request state for later pass-through patching.
internal/server/orchestrator/prompt_protection.go Captures matched rules from prompt protection and writes them into persistence state.
internal/server/orchestrator/prompt_protection_test.go Adds coverage ensuring matched rules are recorded for pass-through.
internal/server/orchestrator/prompt_protecter.go Introduces an optional result-bearing prompt protection interface to keep legacy protectors compatible.
internal/server/orchestrator/pass_through.go Uses the new merge+patch path when pass-through is enabled.
internal/server/orchestrator/pass_through_test.go Adds end-to-end tests validating pass-through patches masked prompt values and fails safely on unsupported layouts.
internal/server/orchestrator/pass_through_prompt_protection.go Implements API-format-specific raw JSON prompt field discovery and rule-based patching.
internal/server/orchestrator/pass_through_prompt_protection_test.go Adds focused tests for raw prompt patching, unsupported formats, and Gemini function response masking.
internal/server/biz/prompt_protection_request.go Adds ProtectWithResult to return both protected request and matched rules; keeps Protect as wrapper.
internal/server/biz/prompt_protection_request_test.go Updates tests to validate matched rules are returned.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/server/orchestrator/pass_through_prompt_protection.go Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/server/orchestrator/pass_through_prompt_protection.go`:
- Around line 122-151: Update canonicalPromptProtectionJSON and
replacePassThroughPromptText so JSON numbers retain their exact representation
via json.Decoder.UseNumber, and apply masking recursively only to string leaf
values, never object property names. Preserve all non-matching fields unchanged
when writing the complete function-response object, and add tests covering large
integers and property names that match masking rules.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 46789580-2eea-4570-a83e-052db24b41ee

📥 Commits

Reviewing files that changed from the base of the PR and between 31f8981 and d49635d.

📒 Files selected for processing (10)
  • internal/server/biz/prompt_protection_request.go
  • internal/server/biz/prompt_protection_request_test.go
  • internal/server/orchestrator/pass_through.go
  • internal/server/orchestrator/pass_through_prompt_protection.go
  • internal/server/orchestrator/pass_through_prompt_protection_test.go
  • internal/server/orchestrator/pass_through_test.go
  • internal/server/orchestrator/prompt_protecter.go
  • internal/server/orchestrator/prompt_protection.go
  • internal/server/orchestrator/prompt_protection_test.go
  • internal/server/orchestrator/state.go

Comment thread internal/server/orchestrator/pass_through_prompt_protection.go Outdated

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
internal/server/orchestrator/pass_through_prompt_protection_test.go (1)

116-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a scope-isolation case for Gemini function responses.

This test combines User and Tool scopes on one rule. It confirms masking happens when both scopes are present, but it does not confirm that a User-only rule leaves functionResponse.response values unmasked. Scope isolation is a stated acceptance criterion for this feature. Add a case with Scopes: []objects.PromptProtectionScope{objects.PromptProtectionScopeUser} and assert that contents.1.parts.0.functionResponse.response.token stays "secret-tool" after patching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/server/orchestrator/pass_through_prompt_protection_test.go` around
lines 116 - 144, Add a scope-isolation test alongside
TestPatchPassThroughPromptProtectionMasksGeminiFunctionResponse using a rule
scoped only to objects.PromptProtectionScopeUser, then assert the Gemini
functionResponse response token remains "secret-tool" after patching while the
user content continues to validate user-scope masking.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/server/orchestrator/pass_through_prompt_protection_test.go`:
- Around line 116-144: Add a scope-isolation test alongside
TestPatchPassThroughPromptProtectionMasksGeminiFunctionResponse using a rule
scoped only to objects.PromptProtectionScopeUser, then assert the Gemini
functionResponse response token remains "secret-tool" after patching while the
user content continues to validate user-scope masking.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4667107c-7c11-45e7-a2c4-f4e2cb3d74cd

📥 Commits

Reviewing files that changed from the base of the PR and between d49635d and c07e3af.

📒 Files selected for processing (2)
  • internal/server/orchestrator/pass_through_prompt_protection.go
  • internal/server/orchestrator/pass_through_prompt_protection_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/server/orchestrator/pass_through_prompt_protection.go

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.

[Bug/错误]: 透传请求会覆盖提示词保护的脱敏结果

2 participants