Skip to content

fix: prevent infinite proxy loop with nonce-based detection - #85

Merged
ammario merged 3 commits into
mainfrom
fix-loop
Nov 9, 2025
Merged

fix: prevent infinite proxy loop with nonce-based detection#85
ammario merged 3 commits into
mainfrom
fix-loop

Conversation

@ammar-agent

@ammar-agentammar-agent commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Fixes#84

Problem

When httpjail runs in server mode and receives a request targeting itself (e.g., curl --proxy http://localhost:8080 http://localhost:8080/test), it creates an infinite loop.

Solution

Implemented nonce-based loop detection:

  • Each httpjail instance generates a random nonce on startup
  • Appends nonce to Httpjail-Loop-Prevention header on outgoing requests
  • Blocks incoming requests with 403 if they contain our nonce

Changes

  • Added ProxyContext struct to bundle shared state (DRY refactoring)
  • Loop detection using HTTP multi-value headers
  • Test completes in ~0.3s (vs 3+ second timeout without fix)

Benefits

  • Robust across network topologies (reverse proxies, Docker, NAT)
  • Supports chaining multiple httpjail instances
  • Standards-compliant (native HTTP multi-value headers)

When httpjail in server mode receives a request targeting itself, it now
detects and blocks the loop using a unique nonce per proxy instance.
Each proxy instance:
- Generates a random nonce on startup
- Appends its nonce to Httpjail-Loop-Prevention header (using HTTP's
native multi-value header support)
- Checks incoming requests for its own nonce and blocks with 403 if found
This approach:
- Supports chaining multiple httpjail instances
- Works across network topologies (reverse proxies, Docker, etc.)
- Uses HTTP standard multi-value headers instead of CSV parsing
Also refactored code for DRY:
- Created ProxyContext struct to bundle rule_engine, cert_manager, loop_nonce
- Reduced function signature duplication throughout codebase
- Extracted wait_for_server() helper to tests/common for reuse
Test: New test_server_mode_self_request_loop_prevention verifies the fix,
completing in ~0.3s (vs timing out without the fix).
The perform_tls_interception function uses CertificateManager::is_ca_trusted()
on macOS but the import was missing from the main module (only present in tests).
This caused compilation to fail on macOS CI.
Added conditional import with #[cfg(target_os = "macos")] to match usage.
@ammarioammario changed the title Fix #84: Prevent infinite loop in server mode with nonce-based detectionfix: prevent infinite proxy loop with nonce-based detectionNov 9, 2025
- Convert wait_for_server() to async using tokio::time and tokio::net::TcpStream
- Update all callers (start_server, start_server_with_bind, and tests) to async
- Clean up self_request_loop test: remove verbose comments and println
- Use tracing::debug for test debugging output
- Simplify test assertions to focus on essential behavior
@ammario
ammario merged commit 22f90fd into mainNov 9, 2025
6 checks passed
Sign up for freeto 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.

Endless loop with GET request to httpjail in server mode

2 participants

@ammar-agent@ammario
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix: prevent infinite proxy loop with nonce-based detection by ammar-agent · Pull Request #85 · coder/httpjail · GitHub
Skip to content

fix: prevent infinite proxy loop with nonce-based detection - #85

Merged
ammario merged 3 commits into
mainfrom
fix-loop
Nov 9, 2025
Merged

fix: prevent infinite proxy loop with nonce-based detection#85
ammario merged 3 commits into
mainfrom
fix-loop

Conversation

@ammar-agent

@ammar-agentammar-agent commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Fixes#84

Problem

When httpjail runs in server mode and receives a request targeting itself (e.g., curl --proxy http://localhost:8080 http://localhost:8080/test), it creates an infinite loop.

Solution

Implemented nonce-based loop detection:

  • Each httpjail instance generates a random nonce on startup
  • Appends nonce to Httpjail-Loop-Prevention header on outgoing requests
  • Blocks incoming requests with 403 if they contain our nonce

Changes

  • Added ProxyContext struct to bundle shared state (DRY refactoring)
  • Loop detection using HTTP multi-value headers
  • Test completes in ~0.3s (vs 3+ second timeout without fix)

Benefits

  • Robust across network topologies (reverse proxies, Docker, NAT)
  • Supports chaining multiple httpjail instances
  • Standards-compliant (native HTTP multi-value headers)

When httpjail in server mode receives a request targeting itself, it now
detects and blocks the loop using a unique nonce per proxy instance.
Each proxy instance:
- Generates a random nonce on startup
- Appends its nonce to Httpjail-Loop-Prevention header (using HTTP's
native multi-value header support)
- Checks incoming requests for its own nonce and blocks with 403 if found
This approach:
- Supports chaining multiple httpjail instances
- Works across network topologies (reverse proxies, Docker, etc.)
- Uses HTTP standard multi-value headers instead of CSV parsing
Also refactored code for DRY:
- Created ProxyContext struct to bundle rule_engine, cert_manager, loop_nonce
- Reduced function signature duplication throughout codebase
- Extracted wait_for_server() helper to tests/common for reuse
Test: New test_server_mode_self_request_loop_prevention verifies the fix,
completing in ~0.3s (vs timing out without the fix).
The perform_tls_interception function uses CertificateManager::is_ca_trusted()
on macOS but the import was missing from the main module (only present in tests).
This caused compilation to fail on macOS CI.
Added conditional import with #[cfg(target_os = "macos")] to match usage.
@ammarioammario changed the title Fix #84: Prevent infinite loop in server mode with nonce-based detectionfix: prevent infinite proxy loop with nonce-based detectionNov 9, 2025
- Convert wait_for_server() to async using tokio::time and tokio::net::TcpStream
- Update all callers (start_server, start_server_with_bind, and tests) to async
- Clean up self_request_loop test: remove verbose comments and println
- Use tracing::debug for test debugging output
- Simplify test assertions to focus on essential behavior
@ammario
ammario merged commit 22f90fd into mainNov 9, 2025
6 checks passed
Sign up for freeto 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.

Endless loop with GET request to httpjail in server mode

2 participants

@ammar-agent@ammario
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: prevent infinite proxy loop with nonce-based detection by ammar-agent · Pull Request #85 · coder/httpjail · GitHub
Skip to content

fix: prevent infinite proxy loop with nonce-based detection - #85

Merged
ammario merged 3 commits into
mainfrom
fix-loop
Nov 9, 2025
Merged

fix: prevent infinite proxy loop with nonce-based detection#85
ammario merged 3 commits into
mainfrom
fix-loop

Conversation

@ammar-agent

@ammar-agentammar-agent commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Fixes#84

Problem

When httpjail runs in server mode and receives a request targeting itself (e.g., curl --proxy http://localhost:8080 http://localhost:8080/test), it creates an infinite loop.

Solution

Implemented nonce-based loop detection:

  • Each httpjail instance generates a random nonce on startup
  • Appends nonce to Httpjail-Loop-Prevention header on outgoing requests
  • Blocks incoming requests with 403 if they contain our nonce

Changes

  • Added ProxyContext struct to bundle shared state (DRY refactoring)
  • Loop detection using HTTP multi-value headers
  • Test completes in ~0.3s (vs 3+ second timeout without fix)

Benefits

  • Robust across network topologies (reverse proxies, Docker, NAT)
  • Supports chaining multiple httpjail instances
  • Standards-compliant (native HTTP multi-value headers)

When httpjail in server mode receives a request targeting itself, it now
detects and blocks the loop using a unique nonce per proxy instance.
Each proxy instance:
- Generates a random nonce on startup
- Appends its nonce to Httpjail-Loop-Prevention header (using HTTP's
native multi-value header support)
- Checks incoming requests for its own nonce and blocks with 403 if found
This approach:
- Supports chaining multiple httpjail instances
- Works across network topologies (reverse proxies, Docker, etc.)
- Uses HTTP standard multi-value headers instead of CSV parsing
Also refactored code for DRY:
- Created ProxyContext struct to bundle rule_engine, cert_manager, loop_nonce
- Reduced function signature duplication throughout codebase
- Extracted wait_for_server() helper to tests/common for reuse
Test: New test_server_mode_self_request_loop_prevention verifies the fix,
completing in ~0.3s (vs timing out without the fix).
The perform_tls_interception function uses CertificateManager::is_ca_trusted()
on macOS but the import was missing from the main module (only present in tests).
This caused compilation to fail on macOS CI.
Added conditional import with #[cfg(target_os = "macos")] to match usage.
@ammarioammario changed the title Fix #84: Prevent infinite loop in server mode with nonce-based detectionfix: prevent infinite proxy loop with nonce-based detectionNov 9, 2025
- Convert wait_for_server() to async using tokio::time and tokio::net::TcpStream
- Update all callers (start_server, start_server_with_bind, and tests) to async
- Clean up self_request_loop test: remove verbose comments and println
- Use tracing::debug for test debugging output
- Simplify test assertions to focus on essential behavior
@ammario
ammario merged commit 22f90fd into mainNov 9, 2025
6 checks passed
Sign up for freeto 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.

Endless loop with GET request to httpjail in server mode

2 participants

@ammar-agent@ammario
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: prevent infinite proxy loop with nonce-based detection by ammar-agent · Pull Request #85 · coder/httpjail · GitHub
Skip to content

fix: prevent infinite proxy loop with nonce-based detection - #85

Merged
ammario merged 3 commits into
mainfrom
fix-loop
Nov 9, 2025
Merged

fix: prevent infinite proxy loop with nonce-based detection#85
ammario merged 3 commits into
mainfrom
fix-loop

Conversation

@ammar-agent

@ammar-agentammar-agent commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Fixes#84

Problem

When httpjail runs in server mode and receives a request targeting itself (e.g., curl --proxy http://localhost:8080 http://localhost:8080/test), it creates an infinite loop.

Solution

Implemented nonce-based loop detection:

  • Each httpjail instance generates a random nonce on startup
  • Appends nonce to Httpjail-Loop-Prevention header on outgoing requests
  • Blocks incoming requests with 403 if they contain our nonce

Changes

  • Added ProxyContext struct to bundle shared state (DRY refactoring)
  • Loop detection using HTTP multi-value headers
  • Test completes in ~0.3s (vs 3+ second timeout without fix)

Benefits

  • Robust across network topologies (reverse proxies, Docker, NAT)
  • Supports chaining multiple httpjail instances
  • Standards-compliant (native HTTP multi-value headers)

When httpjail in server mode receives a request targeting itself, it now
detects and blocks the loop using a unique nonce per proxy instance.
Each proxy instance:
- Generates a random nonce on startup
- Appends its nonce to Httpjail-Loop-Prevention header (using HTTP's
native multi-value header support)
- Checks incoming requests for its own nonce and blocks with 403 if found
This approach:
- Supports chaining multiple httpjail instances
- Works across network topologies (reverse proxies, Docker, etc.)
- Uses HTTP standard multi-value headers instead of CSV parsing
Also refactored code for DRY:
- Created ProxyContext struct to bundle rule_engine, cert_manager, loop_nonce
- Reduced function signature duplication throughout codebase
- Extracted wait_for_server() helper to tests/common for reuse
Test: New test_server_mode_self_request_loop_prevention verifies the fix,
completing in ~0.3s (vs timing out without the fix).
The perform_tls_interception function uses CertificateManager::is_ca_trusted()
on macOS but the import was missing from the main module (only present in tests).
This caused compilation to fail on macOS CI.
Added conditional import with #[cfg(target_os = "macos")] to match usage.
@ammarioammario changed the title Fix #84: Prevent infinite loop in server mode with nonce-based detectionfix: prevent infinite proxy loop with nonce-based detectionNov 9, 2025
- Convert wait_for_server() to async using tokio::time and tokio::net::TcpStream
- Update all callers (start_server, start_server_with_bind, and tests) to async
- Clean up self_request_loop test: remove verbose comments and println
- Use tracing::debug for test debugging output
- Simplify test assertions to focus on essential behavior
@ammario
ammario merged commit 22f90fd into mainNov 9, 2025
6 checks passed
Sign up for freeto 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.

Endless loop with GET request to httpjail in server mode

2 participants

@ammar-agent@ammario
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix: prevent infinite proxy loop with nonce-based detection by ammar-agent · Pull Request #85 · coder/httpjail · GitHub
Skip to content

fix: prevent infinite proxy loop with nonce-based detection - #85

Merged
ammario merged 3 commits into
mainfrom
fix-loop
Nov 9, 2025
Merged

fix: prevent infinite proxy loop with nonce-based detection#85
ammario merged 3 commits into
mainfrom
fix-loop

Conversation

@ammar-agent

@ammar-agentammar-agent commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Fixes#84

Problem

When httpjail runs in server mode and receives a request targeting itself (e.g., curl --proxy http://localhost:8080 http://localhost:8080/test), it creates an infinite loop.

Solution

Implemented nonce-based loop detection:

  • Each httpjail instance generates a random nonce on startup
  • Appends nonce to Httpjail-Loop-Prevention header on outgoing requests
  • Blocks incoming requests with 403 if they contain our nonce

Changes

  • Added ProxyContext struct to bundle shared state (DRY refactoring)
  • Loop detection using HTTP multi-value headers
  • Test completes in ~0.3s (vs 3+ second timeout without fix)

Benefits

  • Robust across network topologies (reverse proxies, Docker, NAT)
  • Supports chaining multiple httpjail instances
  • Standards-compliant (native HTTP multi-value headers)

When httpjail in server mode receives a request targeting itself, it now
detects and blocks the loop using a unique nonce per proxy instance.
Each proxy instance:
- Generates a random nonce on startup
- Appends its nonce to Httpjail-Loop-Prevention header (using HTTP's
native multi-value header support)
- Checks incoming requests for its own nonce and blocks with 403 if found
This approach:
- Supports chaining multiple httpjail instances
- Works across network topologies (reverse proxies, Docker, etc.)
- Uses HTTP standard multi-value headers instead of CSV parsing
Also refactored code for DRY:
- Created ProxyContext struct to bundle rule_engine, cert_manager, loop_nonce
- Reduced function signature duplication throughout codebase
- Extracted wait_for_server() helper to tests/common for reuse
Test: New test_server_mode_self_request_loop_prevention verifies the fix,
completing in ~0.3s (vs timing out without the fix).
The perform_tls_interception function uses CertificateManager::is_ca_trusted()
on macOS but the import was missing from the main module (only present in tests).
This caused compilation to fail on macOS CI.
Added conditional import with #[cfg(target_os = "macos")] to match usage.
@ammarioammario changed the title Fix #84: Prevent infinite loop in server mode with nonce-based detectionfix: prevent infinite proxy loop with nonce-based detectionNov 9, 2025
- Convert wait_for_server() to async using tokio::time and tokio::net::TcpStream
- Update all callers (start_server, start_server_with_bind, and tests) to async
- Clean up self_request_loop test: remove verbose comments and println
- Use tracing::debug for test debugging output
- Simplify test assertions to focus on essential behavior
@ammario
ammario merged commit 22f90fd into mainNov 9, 2025
6 checks passed
Sign up for freeto 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.

Endless loop with GET request to httpjail in server mode

2 participants

@ammar-agent@ammario
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: prevent infinite proxy loop with nonce-based detection by ammar-agent · Pull Request #85 · coder/httpjail · GitHub
Skip to content

fix: prevent infinite proxy loop with nonce-based detection - #85

Merged
ammario merged 3 commits into
mainfrom
fix-loop
Nov 9, 2025
Merged

fix: prevent infinite proxy loop with nonce-based detection#85
ammario merged 3 commits into
mainfrom
fix-loop

Conversation

@ammar-agent

@ammar-agentammar-agent commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Fixes#84

Problem

When httpjail runs in server mode and receives a request targeting itself (e.g., curl --proxy http://localhost:8080 http://localhost:8080/test), it creates an infinite loop.

Solution

Implemented nonce-based loop detection:

  • Each httpjail instance generates a random nonce on startup
  • Appends nonce to Httpjail-Loop-Prevention header on outgoing requests
  • Blocks incoming requests with 403 if they contain our nonce

Changes

  • Added ProxyContext struct to bundle shared state (DRY refactoring)
  • Loop detection using HTTP multi-value headers
  • Test completes in ~0.3s (vs 3+ second timeout without fix)

Benefits

  • Robust across network topologies (reverse proxies, Docker, NAT)
  • Supports chaining multiple httpjail instances
  • Standards-compliant (native HTTP multi-value headers)

When httpjail in server mode receives a request targeting itself, it now
detects and blocks the loop using a unique nonce per proxy instance.
Each proxy instance:
- Generates a random nonce on startup
- Appends its nonce to Httpjail-Loop-Prevention header (using HTTP's
native multi-value header support)
- Checks incoming requests for its own nonce and blocks with 403 if found
This approach:
- Supports chaining multiple httpjail instances
- Works across network topologies (reverse proxies, Docker, etc.)
- Uses HTTP standard multi-value headers instead of CSV parsing
Also refactored code for DRY:
- Created ProxyContext struct to bundle rule_engine, cert_manager, loop_nonce
- Reduced function signature duplication throughout codebase
- Extracted wait_for_server() helper to tests/common for reuse
Test: New test_server_mode_self_request_loop_prevention verifies the fix,
completing in ~0.3s (vs timing out without the fix).
The perform_tls_interception function uses CertificateManager::is_ca_trusted()
on macOS but the import was missing from the main module (only present in tests).
This caused compilation to fail on macOS CI.
Added conditional import with #[cfg(target_os = "macos")] to match usage.
@ammarioammario changed the title Fix #84: Prevent infinite loop in server mode with nonce-based detectionfix: prevent infinite proxy loop with nonce-based detectionNov 9, 2025
- Convert wait_for_server() to async using tokio::time and tokio::net::TcpStream
- Update all callers (start_server, start_server_with_bind, and tests) to async
- Clean up self_request_loop test: remove verbose comments and println
- Use tracing::debug for test debugging output
- Simplify test assertions to focus on essential behavior
@ammario
ammario merged commit 22f90fd into mainNov 9, 2025
6 checks passed
Sign up for freeto 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.

Endless loop with GET request to httpjail in server mode

2 participants

@ammar-agent@ammario
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: prevent infinite proxy loop with nonce-based detection by ammar-agent · Pull Request #85 · coder/httpjail · GitHub
Skip to content

fix: prevent infinite proxy loop with nonce-based detection - #85

Merged
ammario merged 3 commits into
mainfrom
fix-loop
Nov 9, 2025
Merged

fix: prevent infinite proxy loop with nonce-based detection#85
ammario merged 3 commits into
mainfrom
fix-loop

Conversation

@ammar-agent

@ammar-agentammar-agent commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Fixes#84

Problem

When httpjail runs in server mode and receives a request targeting itself (e.g., curl --proxy http://localhost:8080 http://localhost:8080/test), it creates an infinite loop.

Solution

Implemented nonce-based loop detection:

  • Each httpjail instance generates a random nonce on startup
  • Appends nonce to Httpjail-Loop-Prevention header on outgoing requests
  • Blocks incoming requests with 403 if they contain our nonce

Changes

  • Added ProxyContext struct to bundle shared state (DRY refactoring)
  • Loop detection using HTTP multi-value headers
  • Test completes in ~0.3s (vs 3+ second timeout without fix)

Benefits

  • Robust across network topologies (reverse proxies, Docker, NAT)
  • Supports chaining multiple httpjail instances
  • Standards-compliant (native HTTP multi-value headers)

When httpjail in server mode receives a request targeting itself, it now
detects and blocks the loop using a unique nonce per proxy instance.
Each proxy instance:
- Generates a random nonce on startup
- Appends its nonce to Httpjail-Loop-Prevention header (using HTTP's
native multi-value header support)
- Checks incoming requests for its own nonce and blocks with 403 if found
This approach:
- Supports chaining multiple httpjail instances
- Works across network topologies (reverse proxies, Docker, etc.)
- Uses HTTP standard multi-value headers instead of CSV parsing
Also refactored code for DRY:
- Created ProxyContext struct to bundle rule_engine, cert_manager, loop_nonce
- Reduced function signature duplication throughout codebase
- Extracted wait_for_server() helper to tests/common for reuse
Test: New test_server_mode_self_request_loop_prevention verifies the fix,
completing in ~0.3s (vs timing out without the fix).
The perform_tls_interception function uses CertificateManager::is_ca_trusted()
on macOS but the import was missing from the main module (only present in tests).
This caused compilation to fail on macOS CI.
Added conditional import with #[cfg(target_os = "macos")] to match usage.
@ammarioammario changed the title Fix #84: Prevent infinite loop in server mode with nonce-based detectionfix: prevent infinite proxy loop with nonce-based detectionNov 9, 2025
- Convert wait_for_server() to async using tokio::time and tokio::net::TcpStream
- Update all callers (start_server, start_server_with_bind, and tests) to async
- Clean up self_request_loop test: remove verbose comments and println
- Use tracing::debug for test debugging output
- Simplify test assertions to focus on essential behavior
@ammario
ammario merged commit 22f90fd into mainNov 9, 2025
6 checks passed
Sign up for freeto 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.

Endless loop with GET request to httpjail in server mode

2 participants

@ammar-agent@ammario
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix: prevent infinite proxy loop with nonce-based detection by ammar-agent · Pull Request #85 · coder/httpjail · GitHub
Skip to content

fix: prevent infinite proxy loop with nonce-based detection - #85

Merged
ammario merged 3 commits into
mainfrom
fix-loop
Nov 9, 2025
Merged

fix: prevent infinite proxy loop with nonce-based detection#85
ammario merged 3 commits into
mainfrom
fix-loop

Conversation

@ammar-agent

@ammar-agentammar-agent commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Fixes#84

Problem

When httpjail runs in server mode and receives a request targeting itself (e.g., curl --proxy http://localhost:8080 http://localhost:8080/test), it creates an infinite loop.

Solution

Implemented nonce-based loop detection:

  • Each httpjail instance generates a random nonce on startup
  • Appends nonce to Httpjail-Loop-Prevention header on outgoing requests
  • Blocks incoming requests with 403 if they contain our nonce

Changes

  • Added ProxyContext struct to bundle shared state (DRY refactoring)
  • Loop detection using HTTP multi-value headers
  • Test completes in ~0.3s (vs 3+ second timeout without fix)

Benefits

  • Robust across network topologies (reverse proxies, Docker, NAT)
  • Supports chaining multiple httpjail instances
  • Standards-compliant (native HTTP multi-value headers)

When httpjail in server mode receives a request targeting itself, it now
detects and blocks the loop using a unique nonce per proxy instance.
Each proxy instance:
- Generates a random nonce on startup
- Appends its nonce to Httpjail-Loop-Prevention header (using HTTP's
native multi-value header support)
- Checks incoming requests for its own nonce and blocks with 403 if found
This approach:
- Supports chaining multiple httpjail instances
- Works across network topologies (reverse proxies, Docker, etc.)
- Uses HTTP standard multi-value headers instead of CSV parsing
Also refactored code for DRY:
- Created ProxyContext struct to bundle rule_engine, cert_manager, loop_nonce
- Reduced function signature duplication throughout codebase
- Extracted wait_for_server() helper to tests/common for reuse
Test: New test_server_mode_self_request_loop_prevention verifies the fix,
completing in ~0.3s (vs timing out without the fix).
The perform_tls_interception function uses CertificateManager::is_ca_trusted()
on macOS but the import was missing from the main module (only present in tests).
This caused compilation to fail on macOS CI.
Added conditional import with #[cfg(target_os = "macos")] to match usage.
@ammarioammario changed the title Fix #84: Prevent infinite loop in server mode with nonce-based detectionfix: prevent infinite proxy loop with nonce-based detectionNov 9, 2025
- Convert wait_for_server() to async using tokio::time and tokio::net::TcpStream
- Update all callers (start_server, start_server_with_bind, and tests) to async
- Clean up self_request_loop test: remove verbose comments and println
- Use tracing::debug for test debugging output
- Simplify test assertions to focus on essential behavior
@ammario
ammario merged commit 22f90fd into mainNov 9, 2025
6 checks passed
Sign up for freeto 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.

Endless loop with GET request to httpjail in server mode

2 participants

@ammar-agent@ammario