跳到正文
DEV Community· Roshan·· 3 小時前AI 評分44

@tak-ps/node-safeurl@1.8.0 的重新導向錯誤:POST 如何變成 GET

When POST becomes GET: a redirect bug in a Node.js fetch wrapper

AI 導讀

@tak-ps/node-safeurl@1.8.0 處理 301/302 POST 及 303 PUT 重新導向時,會將方法改為 GET、清除請求本文,卻保留 Content-Type,與原生 fetch 不同。

正文

A POST followed a 302 redirect and became a GET. Its body disappeared, but its Content-Type header stayed behind.

That showed up while extending the client comparisons in ssrf-lab, a testing harness I maintain. The affected package was @tak-ps/node-safeurl@1.8.0, a Node.js fetch wrapper that checks URLs and redirect destinations.

This is a functional redirect-conformance bug. The proposed fix is upstream PR #4, submitted and awaiting review as of October 14, 2026. These tests do not demonstrate an SSRF bypass.

The response looked fine. The request told the story.

The comparison uses a small HTTP server on 127.0.0.1. One route returns a redirect; the destination echoes the method, body, and headers it actually receives. That exact local origin is explicitly allowed in the wrapper's configuration.

Both native fetch and the wrapper successfully completed this request:

{
  method: 'POST',
  body: 'hello',
  headers: { 'content-type': 'text/plain' }
}

After a 302 redirect, the destination observed:

Client Method Body Content-Type
Native fetch GET empty absent
node-safeurl 1.8.0 GET empty text/plain

The same difference appeared for 301 POST and 303 PUT. Checking only the final status or whether the fetch promise resolved would miss it.

The Fetch redirect algorithm changes POST to GET for 301/302, and changes methods other than GET/HEAD to GET for 303. When it discards the body, it also removes the request-body headers: Content-Encoding, Content-Language, Content-Location, and Content-Type.

The wrapper already handled the method and body changes. It was missing the corresponding header cleanup.

The fix belongs in the method-rewrite branch

The patch adds Headers to the existing Undici import, then extends the existing switchToGet branch:

if (switchToGet) {
  fetchInit.method = 'GET';
  fetchInit.body = undefined;

  const headers = new Headers(fetchInit.headers);
  for (const name of [
    'content-encoding',
    'content-language',
    'content-location',
    'content-type',
    'content-length',
  ]) {
    headers.delete(name);
  }
  fetchInit.headers = headers;
}

Content-Length is removed as well, because caller-supplied framing for the discarded body should not carry into the GET. It is separate from the standard's four named request-body headers.

Copying through Headers supports object, tuple-array, and Headers inputs without changing the caller's original headers. Unrelated headers survive. The URL validation logic is unchanged.

The condition matters: a 307 or 308 should preserve the method and body, so stripping these headers on every redirect would introduce another bug.

Prove both the fix and the unchanged cases

The upstream regression test records actual incoming requests and checks seven cases:

Redirect cases Before patch After patch
301 POST, 302 POST, 303 PUT All three fail All three pass
307 POST, 308 POST, 301 PUT, 302 PUT All four pass All four pass

It also checks that an unrelated header is preserved and the caller's headers are not mutated. The full patched upstream suite passes 198 tests on each of Node 22.23.3 and 24.21.0, alongside lint and build.

The recorded evidence includes the original failures, fixed tests, full-suite logs, and published-package comparison.

Run the comparison

With Node 22.19 or newer and npm:

git clone https://github.com/roshan-amble/ssrf-lab.git
cd ssrf-lab
git checkout 41659cda6825b2f7e324294d7242ef1b076b6cce
npm ci --ignore-scripts --prefix integrations/community
node integrations/community/check-redirects.mjs

This prints JSON for native fetch and the unmodified published package across five redirect cases. The HTTP comparison stays on the explicitly allowed loopback origin. The broader SSRF suite has separate network-isolation requirements.

The practical testing lesson is to assert what reached the destination: method, body, and headers. A successful response alone cannot establish that a wrapper preserved fetch semantics.

The harness, adapters, and reproduction are in ssrf-lab. Which redirect or URL-handling edge case would you add to this coverage?

AI disclosure: Codex assisted with the investigation, implementation, and test execution, and drafted this article from the linked code and recorded results.

來源:DEV Community · dev.to