fix(request-filter): track header modifications for proper display in session details (#457)#465
Conversation
… session details (#457) ## Summary Fixed issue where request filter modifications to headers (especially user-agent) were not reflected in Session detail pages due to timing issues between filter execution and header storage. ## Root Cause When request filters modified headers via RequestFilterEngine, the changes were made to session.headers but ProxyForwarder.buildHeaders() used the original userAgent field (read-only snapshot) instead of checking session.headers for filtered values. ## Changes | File | Change | |------|--------| | src/app/v1/_lib/proxy/session.ts | Add originalHeaders tracking and isHeaderModified() method | | src/app/v1/_lib/proxy/forwarder.ts | Prioritize filtered user-agent using ?? instead of \|\| | | tests/unit/proxy/session.test.ts | Add isHeaderModified() tests (6 cases) | | tests/unit/proxy/proxy-forwarder.test.ts | Add user-agent resolution tests (5 cases) | ## Technical Details ### ProxySession.isHeaderModified() - Compares original headers snapshot with current values - Returns true for: value changes, deletions, additions - Handles null/undefined correctly ### ProxyForwarder.buildHeaders() - For Codex provider: checks if user-agent was modified - Uses ?? operator to preserve empty strings (falsy values) - Priority: filtered UA > original UA > hardcoded fallback - Improved debug logging (length instead of truncated value) ### Test Coverage - isHeaderModified: 6 tests covering modify/unmodify/delete/add/empty-string - buildHeaders: 5 tests covering all resolution paths - All tests passing with 100% branch coverage ## Edge Cases Fixed 1. Empty string UA: now correctly preserved (was incorrectly falling back) 2. Header deletion: properly detected and falls back to original UA 3. Header addition: detected as modification 4. Logging: replaced potential sensitive info leak with length only Closes #457 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Summary of ChangesHello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses an issue where modifications to request headers, particularly the User-Agent, made by request filters were not accurately reflected in the session detail pages or correctly propagated to downstream services. The solution involves implementing a robust header modification tracking system within the session object and updating the header forwarding logic to respect these tracked changes, thereby ensuring data consistency and correct behavior across the proxy. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This is a great fix that correctly addresses the issue of header modifications not being reflected. The introduction of originalHeaders and isHeaderModified is a clean solution. The unit tests are comprehensive and cover all the relevant edge cases, which is excellent.
I've left one suggestion to simplify the user-agent resolution logic in ProxyForwarder, which makes the code a bit more concise without changing its behavior.
Overall, a solid contribution that improves correctness and is well-tested.
| let resolvedUA: string; | ||
| if (wasModified) { | ||
| resolvedUA = | ||
| filteredUA ?? originalUA ?? "codex_cli_rs/0.55.0 (Mac OS 26.1.0; arm64) vscode/2.0.64"; | ||
| } else { | ||
| resolvedUA = originalUA ?? "codex_cli_rs/0.55.0 (Mac OS 26.1.0; arm64) vscode/2.0.64"; | ||
| } |
There was a problem hiding this comment.
The logic for resolving the User-Agent can be simplified. The if (wasModified) block is redundant because the logic in both branches can be combined into a single expression.
When wasModified is false, it means the current header value (filteredUA) is the same as the original (originalUA). In this scenario, the expression from the if branch (filteredUA ?? originalUA ?? fallback) simplifies to originalUA ?? fallback, which is identical to the else branch.
Because both cases can be covered by the same logic, you can replace the entire if/else block. This makes the code more concise and declarative.
const resolvedUA =
filteredUA ?? originalUA ?? "codex_cli_rs/0.55.0 (Mac OS 26.1.0; arm64) vscode/2.0.64";
🧪 测试结果
总体结果: ✅ 所有测试通过 |
There was a problem hiding this comment.
Code Review Summary
No significant issues identified in this PR.
PR Size: M
- Lines changed: 288
- Files changed: 4
Review Coverage
- Logic and correctness - Clean
- Security (OWASP Top 10) - Clean
- Error handling - Clean
- Type safety - Clean
- Documentation accuracy - Clean
- Test coverage - Adequate
- Code clarity - Good
Automated review by Codex AI
There was a problem hiding this comment.
- Reviewed PR
#465(4 files changed, 288 lines changed) against the 6 required perspectives, scoped strictly to diff lines. - Applied the size label
size/M. - Ran
npx @biomejs/biome check .to validate lint status (pass). - No issues met the >=80 confidence reporting threshold, so no inline diff comments were posted.
- Submitted the required summary review comment to the PR via
gh pr review.
There was a problem hiding this comment.
Code Review Summary
This PR implements header modification tracking to fix issue #457 where request filter modifications to headers were not reflected in session detail pages. The implementation is clean, well-tested, and follows the project architecture patterns.
PR Size: M
- Lines changed: 288 (282 additions, 6 deletions)
- Files changed: 4
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 0 | 0 | 0 | 0 |
| Security | 0 | 0 | 0 | 0 |
| Error Handling | 0 | 0 | 0 | 0 |
| Types | 0 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Analysis Summary
Strengths:
-
Correct architecture: The solution properly separates concerns by snapshotting original headers at session creation and tracking modifications via
isHeaderModified(). This allows validation guards to use original headers while forwarding uses filtered headers. -
Proper null handling: Using
??instead of||inbuildHeaders()(forwarder.ts:1863) correctly preserves empty string user-agent values, fixing a real edge case bug. -
Security improvement: Replacing
session.userAgentvalue logging with length logging prevents potential sensitive data leakage in debug logs (forwarder.ts:1873). -
Encapsulation: The
originalHeadersfield is properly marked asprivate readonly, maintaining good encapsulation (session.ts:56-57). -
Comprehensive testing: 11 new test cases cover all branches including edge cases (empty strings, header deletion, header addition). Tests verify behavior rather than implementation.
-
Accurate documentation: The JSDoc comment for
isHeaderModified()(session.ts:211-223) accurately describes the method's behavior including edge cases.
Code Quality Observations:
- The logic correctly implements the priority chain: filtered UA > original UA > fallback
- The
isHeaderModified()method correctly detects modifications by comparing original vs current values, returning true for deletions (null !== original), additions (original !== null), and value changes - Test helpers like
createSessionForHeaders()properly initialize bothheadersandoriginalHeadersto maintain the invariant that they start identical
No Issues Found:
The implementation is production-ready with no bugs, security vulnerabilities, type safety issues, or documentation errors identified. The code adheres to CLAUDE.md guidelines and follows existing patterns in the codebase.
Review Coverage
- Logic and correctness - Clean
- Security (OWASP Top 10) - Clean (improved logging security)
- Error handling - Clean (no error handling code added, no issues)
- Type safety - Clean (proper private readonly encapsulation)
- Documentation accuracy - Clean (JSDoc accurate)
- Test coverage - Excellent (100% branch coverage for new code)
- Code clarity - Good
Automated review by Claude AI
Summary
Fixed issue where request filter modifications to headers (especially user-agent) were not reflected in Session detail pages.
Problem
Users reported that when setting headers via Request Filter (e.g., SET user-agent), the modified headers were visible in trace logs but not in the Session detail page's request headers section.
Root Cause
Solution
Added header modification tracking to detect when filters change headers:
Changes
src/app/v1/_lib/proxy/session.ts
originalHeadersfield to snapshot headers at session creationisHeaderModified(key)method to detect changessrc/app/v1/_lib/proxy/forwarder.ts
session.isHeaderModified("user-agent")first??instead of||to preserve empty stringstests/unit/proxy/session.test.ts
isHeaderModified()tests/unit/proxy/proxy-forwarder.test.ts
Test Results
✓ All type checks passed ✓ All lint checks passed ✓ 22/22 unit tests passedTest Coverage
isHeaderModified(): 6 tests (100% branch coverage)buildHeaders(): 5 tests (100% branch coverage)Edge Cases Fixed
Backward Compatibility
✅ Fully backward compatible
originalHeaders)isHeaderModified())Verification Steps
To manually verify:
SET user-agent = "Test-UA/1.0"Closes #457
🤖 Generated with Claude Code
Greptile Summary
Fixed request filter header modifications not being reflected in upstream requests by implementing header modification tracking. The solution adds an
originalHeaderssnapshot inProxySessionand anisHeaderModified()method to detect when filters change headers. ThebuildHeaders()method inProxyForwardernow checks for modifications and prioritizes filtered values over the cachedsession.userAgentproperty.Key Changes:
originalHeadersfield to capture initial header state at session creationisHeaderModified(key)to detect header changes by comparing original vs current values||to??operator to properly preserve empty string valuesArchitecture:
The fix correctly handles the execution order where guards (client, version) validate using original headers before filters run, while
buildHeaders()uses filtered headers for upstream requests. This maintains proper separation of concerns.Test Coverage:
11 new test cases provide complete coverage of modification detection and user-agent resolution priority paths, including edge cases like empty strings and header deletion.
Confidence Score: 5/5
originalHeadersfield, comprehensive test coverage (11 tests covering all edge cases), correct use of null coalescing operator to preserve empty strings, improved security in logging, and backward compatibility with no breaking changes. The fix correctly handles the execution flow where validation guards use original headers while forwarding uses filtered headers.Important Files Changed
originalHeaderssnapshot andisHeaderModified()method to track filter modifications - clean implementation with proper encapsulationisHeaderModified()covering all edge cases (modify, delete, add, empty string)Sequence Diagram
sequenceDiagram participant Client participant ProxySession participant GuardPipeline participant RequestFilter participant ProxyForwarder participant UpstreamProvider Client->>ProxySession: Request with user-agent: "Original-UA/1.0" ProxySession->>ProxySession: Create session<br/>headers = new Headers(req.headers)<br/>originalHeaders = new Headers(headers)<br/>userAgent = headers.get("user-agent") ProxySession->>GuardPipeline: Execute guards GuardPipeline->>GuardPipeline: auth, client, version guards<br/>(use session.userAgent = "Original-UA/1.0") GuardPipeline->>RequestFilter: Execute request filter RequestFilter->>ProxySession: session.headers.set("user-agent", "Filtered-UA/2.0") Note over ProxySession: headers modified<br/>originalHeaders unchanged<br/>userAgent unchanged RequestFilter-->>GuardPipeline: Filter complete GuardPipeline-->>ProxySession: All guards passed ProxySession->>ProxyForwarder: Forward request ProxyForwarder->>ProxyForwarder: buildHeaders() ProxyForwarder->>ProxySession: isHeaderModified("user-agent")? ProxySession-->>ProxyForwarder: true (original ≠ current) ProxyForwarder->>ProxySession: headers.get("user-agent") ProxySession-->>ProxyForwarder: "Filtered-UA/2.0" ProxyForwarder->>ProxyForwarder: resolvedUA = filteredUA ?? originalUA ?? fallback<br/>= "Filtered-UA/2.0" ProxyForwarder->>UpstreamProvider: Request with user-agent: "Filtered-UA/2.0" UpstreamProvider-->>ProxyForwarder: Response ProxyForwarder-->>Client: Response