Skip to content

fix(server): resolve relative redirects and release intermediate responses - #1714

Open
OskarEichler wants to merge 6 commits into
OutlineFoundation:masterfrom
OskarEichler:codex/relative-redirects
Open

OskarEichler wants to merge 6 commits into
OutlineFoundation:masterfrom
OskarEichler:codex/relative-redirects

Conversation

@OskarEichler

@OskarEichler OskarEichler commented Aug 27, 2026 •

Copy link
Copy Markdown

Why

The explicit same-method/body redirect helper does not resolve relative locations, release unread intermediate bodies, or clearly reject missing/exhausted redirect chains.

Changes

  • Resolve Location against the current URL for standard redirect statuses.
  • Preserve the helper's same-method/body contract; destroy unread intermediate bodies.
  • Reject missing locations and exhausted redirect chains explicitly, and apply a default 30-second response-header timeout while respecting caller settings.

Verification

  • After splitting, all 65 existing manager-metrics, shared-metrics, access-key, and file-operation specs passed again on this isolated branch under Node 22.23.2.

  • Controlled local HTTP checks passed for relative POST redirects, preservation of method/body, missing Location, and redirect loops.

  • Targeted semantic TypeScript and formatting checks passed.

  • Patch isolation and git diff --check passed. Recombining all focused patches reproduces the reviewed source changes exactly.

  • No test/spec files were added or modified; controlled probe drivers are kept outside the repository.

Compatibility and remaining validation

  • This is the existing specialized same-method/body helper, not browser-style POST-to-GET redirect behavior.
  • The timeout is per response-header wait, not a whole-chain or final-body deadline. Redirects are restricted to the original origin, with only a same-host HTTP-to-HTTPS upgrade allowed.

Full npm installation/build checks remain incomplete: npm 12 rejected existing lockfile Git dependencies (EALLOWGIT). Isolated registry-only tooling was used without disabling that safeguard.

Scope

This is one focused part of the reliability review, based directly on upstream master; it does not include the other review patches. No installed client, live VPN/DNS setting, or production service was changed by this patch.

Second review — 2026-08-27

A socket-level regression check showed that destroying node-fetch 2's proxy body did not close the underlying HTTP connection when a redirect response never ended. Use a per-hop AbortController, forward caller cancellation, abort intermediate requests explicitly, and abort final requests when their returned body closes/errors. Remove forwarded signal listeners on completion/failure.

Actual local sockets now close after intermediate/final disposal, caller cancellation before/after headers, missing Location, and timeouts. Listener cleanup is checked as well as POST/body preservation and bounded redirect loops. These checks pass independently and in combination with the reporting PR. All 65 selected existing server specs and targeted TypeScript checks passed on this branch.

All touched files and nearby callers were reviewed again. Each focused patch was also checked in combination with the other seven patches for this repository. External probe drivers remain outside the repositories; no test/spec files were added or modified. Existing full-build and platform-validation limitations above still apply.

Security review follow-up — 2026-09-03

  • Reject cross-origin redirects so the metrics collector cannot redirect the preserved POST/body to loopback services such as the enabled Prometheus admin API.
  • Preserve required relative and same-origin redirects, plus a same-host HTTP-to-HTTPS upgrade.
  • A proof using the actual helper reached a loopback sink before the fix and is now rejected with Cross-origin redirects are not allowed.
  • All 65 selected existing server specs, targeted TypeScript checks, formatting, and git diff --check pass.

@OskarEichler
OskarEichler requested a review from fortuna as a code owner August 27, 2026 18:50
@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes the same-method/body HTTP helper resolve relative redirects, release intermediate responses, enforce explicit redirect failures, and apply a default response timeout.

  • Restricts followed responses to standard redirect statuses.
  • Resolves each Location against the current request URL.
  • Destroys unread intermediate bodies before continuing.
  • Rejects missing Location headers and exhausted redirect chains.
  • Applies a caller-overridable 30-second timeout.

Confidence Score: 4/5

The PR appears safe to merge, with the non-blocking concern that its new redirect and cleanup contracts are not covered by automated tests.

The changed implementation is compatible with the current replayable JSON caller and node-fetch runtime, while the remaining actionable issue is the absence of regression coverage for several newly introduced branches.

Files Needing Attention: src/shadowbox/infrastructure/follow_redirects.ts

Important Files Changed

Filename Overview
src/shadowbox/infrastructure/follow_redirects.ts Correctly adds relative redirect resolution, intermediate-body cleanup, explicit failure handling, and a default timeout, but the new behavior lacks automated regression coverage.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Send request with manual redirects] --> B{Standard redirect status?}
  B -- No --> C[Return final response]
  B -- Yes --> D[Destroy intermediate body]
  D --> E{Location present?}
  E -- No --> F[Throw missing Location error]
  E -- Yes --> G[Resolve Location against current URL]
  G --> H{Redirect limit reached?}
  H -- No --> A
  H -- Yes --> I[Throw too many redirects error]
Loading

Reviews (1): Last reviewed commit: "fix(server): resolve relative redirects ..." | Re-trigger Greptile

Comment on lines 33 to +46
for (let i = 0; i < 10; i++) {
response = await fetch(url, manualRedirectOptions);
if (response.status >= 300 && response.status < 400) {
url = response.headers.get('location');
} else {
break;
const response = await fetch(url, manualRedirectOptions);
if (![301, 302, 303, 307, 308].includes(response.status)) {
return response;
}
const location = response.headers.get('location');
// Intermediate bodies are never read. Release the socket before redirecting.
(response.body as Readable).destroy();
if (!location) {
throw new Error('Redirect response is missing a Location header');
}
url = new URL(location, url).toString();
}
return response;
throw new Error('Too many redirects');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redirect paths lack tests

The new relative-resolution, intermediate-body cleanup, timeout, missing-Location, and redirect-limit branches have no automated coverage; the shared-metrics specs use a fake collector and never exercise this helper, leaving these changed contracts vulnerable to undetected regressions.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@OskarEichler

Copy link
Copy Markdown
Author

Added direct coverage for relative redirects, method/body preservation, missing Location, redirect limits, and timeouts. I also fixed two test calls that omitted the required options argument. TypeScript compilation and the focused redirect suite pass: 4 specs, 0 failures.

This branch has not been deployed

No deployments
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.

1 participant