Skip to content

fix(request-filter): track header modifications for proper display in session details (#457)#465

Merged
ding113 merged 1 commit into
devfrom
fix/request-filter-header-tracking-457
Dec 26, 2025
Merged

fix(request-filter): track header modifications for proper display in session details (#457)#465
ding113 merged 1 commit into
devfrom
fix/request-filter-header-tracking-457

Conversation

@ding113

@ding113 ding113 commented Dec 26, 2025

Copy link
Copy Markdown
Owner

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

1. ProxySession.fromContext()
   ├─ headers: new Headers(c.req.header())  ← Original
   ├─ userAgent: headers.get("user-agent") ← Read-only snapshot
   └─ headerLog: formatHeadersForLog()     ← For logging

2. Guard Pipeline → RequestFilterGuard
   └─ session.headers.set("user-agent", "custom")  ← Modified

3. ProxyForwarder.buildHeaders()
   ├─ overrides["user-agent"] = session.userAgent || fallback  ← Problem: uses read-only snapshot
   └─ Ignores filtered values in session.headers

Solution

Added header modification tracking to detect when filters change headers:

Changes

src/app/v1/_lib/proxy/session.ts

  • Added originalHeaders field to snapshot headers at session creation
  • Added isHeaderModified(key) method to detect changes
  • Preserves empty strings vs null correctly

src/app/v1/_lib/proxy/forwarder.ts

  • Modified Codex user-agent resolution logic:
    • Check session.isHeaderModified("user-agent") first
    • Use ?? instead of || to preserve empty strings
    • Priority: filtered UA > original UA > fallback
  • Improved debug logging (removed potential sensitive data leak)

tests/unit/proxy/session.test.ts

  • Added 6 test cases for isHeaderModified()
  • Coverage: modify/unmodify/delete/add/empty-string/null cases

tests/unit/proxy/proxy-forwarder.test.ts

  • Added 5 test cases for user-agent resolution
  • Coverage: all priority paths + empty string edge case

Test Results

✓ All type checks passed
✓ All lint checks passed
✓ 22/22 unit tests passed

Test Coverage

  • isHeaderModified(): 6 tests (100% branch coverage)
  • buildHeaders(): 5 tests (100% branch coverage)
  • Edge cases: empty string UA, header deletion, header addition

Edge Cases Fixed

  1. Empty string UA: Now correctly preserved (was falling back incorrectly)
  2. Header deletion: Detected and falls back to original UA as expected
  3. Header addition: Detected as modification
  4. Logging security: Replaced value logging with length to prevent info leak

Backward Compatibility

✅ Fully backward compatible

  • New field is private (originalHeaders)
  • New method is additive (isHeaderModified())
  • No database migrations required
  • No configuration changes needed

Verification Steps

To manually verify:

  1. Create a Request Filter rule: SET user-agent = "Test-UA/1.0"
  2. Send a Codex format request
  3. Check Session detail page → Request Headers
  4. Verify user-agent shows as "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 originalHeaders snapshot in ProxySession and an isHeaderModified() method to detect when filters change headers. The buildHeaders() method in ProxyForwarder now checks for modifications and prioritizes filtered values over the cached session.userAgent property.

Key Changes:

  • Added originalHeaders field to capture initial header state at session creation
  • Implemented isHeaderModified(key) to detect header changes by comparing original vs current values
  • Updated Codex user-agent resolution logic to respect filter modifications
  • Changed from || to ?? operator to properly preserve empty string values
  • Improved debug logging to avoid potential sensitive data leaks (logs length instead of value)

Architecture:
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

  • This PR is safe to merge with minimal risk - the implementation is clean, well-tested, and backward compatible
  • Score reflects excellent implementation quality: proper encapsulation with private originalHeaders field, 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.
  • No files require special attention - all changes are well-implemented and thoroughly tested

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/session.ts Added originalHeaders snapshot and isHeaderModified() method to track filter modifications - clean implementation with proper encapsulation
src/app/v1/_lib/proxy/forwarder.ts Updated Codex user-agent resolution to prioritize filtered headers using modification tracking - logic is correct with proper null coalescence
tests/unit/proxy/session.test.ts Added 6 comprehensive test cases for isHeaderModified() covering all edge cases (modify, delete, add, empty string)
tests/unit/proxy/proxy-forwarder.test.ts Added 5 test cases for user-agent resolution covering filtered/original/fallback priority and empty string preservation

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
Loading

… 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>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @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

  • Header Modification Tracking: Introduced a mechanism to track header modifications made by request filters, ensuring that changes (like to the User-Agent) are correctly reflected in session details and subsequent processing.
  • Codex User-Agent Resolution Logic: Updated the ProxyForwarder to prioritize User-Agent values from request filters, then the original User-Agent, and finally a fallback, correctly handling cases like empty strings and deletions.
  • Enhanced Debug Logging: Improved debug logging for User-Agent resolution by logging value lengths instead of the actual values, preventing potential sensitive data leaks.
  • Comprehensive Unit Tests: Added extensive unit tests for both the isHeaderModified() method in ProxySession (6 cases) and the User-Agent resolution logic in ProxyForwarder (5 cases), covering various edge cases.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added bug Something isn't working area:session labels Dec 26, 2025

@gemini-code-assist gemini-code-assist 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.

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.

Comment on lines +1861 to +1867
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";
}

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.

medium

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";

@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@github-actions github-actions Bot added the size/M Medium PR (< 500 lines) label Dec 26, 2025

@github-actions github-actions 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.

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

@github-actions github-actions 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.

  • 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.

@github-actions github-actions 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.

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:

  1. 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.

  2. Proper null handling: Using ?? instead of || in buildHeaders() (forwarder.ts:1863) correctly preserves empty string user-agent values, fixing a real edge case bug.

  3. Security improvement: Replacing session.userAgent value logging with length logging prevents potential sensitive data leakage in debug logs (forwarder.ts:1873).

  4. Encapsulation: The originalHeaders field is properly marked as private readonly, maintaining good encapsulation (session.ts:56-57).

  5. Comprehensive testing: 11 new test cases cover all branches including edge cases (empty strings, header deletion, header addition). Tests verify behavior rather than implementation.

  6. 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 both headers and originalHeaders to 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

@ding113
ding113 merged commit 70db2a7 into dev Dec 26, 2025
17 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Dec 26, 2025
This was referenced Dec 29, 2025
@ding113
ding113 deleted the fix/request-filter-header-tracking-457 branch December 30, 2025 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:session bug Something isn't working size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant