Skip to content

fix: report the post-redirect URL instead of silently mislabeling it#109

Closed
dmchaledev wants to merge 1 commit into
mainfrom
claude/nice-mendel-lh2vz4
Closed

fix: report the post-redirect URL instead of silently mislabeling it#109
dmchaledev wants to merge 1 commit into
mainfrom
claude/nice-mendel-lh2vz4

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Summary

  • fetchHeaders always fetches with redirect: 'follow' and grades whatever response comes back, but analyze() labeled the report with the pre-redirect input URL, never the URL the headers actually came from.
  • A site that redirects (http://https://, apex → www, marketing domain → app domain, staging → login page, etc.) would silently get a report that says url: "https://example.com" while every finding actually describes a different host — with no field anywhere to tell you that happened.
  • This matters most for the tool's two advertised use cases: a CI gate (security-headers https://staging.example.com || exit 1 grading the wrong host with no indication) and multi-tenant/ASM scanning (misattributing a customer's grade to the wrong hostname).
  • Closes Report silently grades the post-redirect URL while labeling it with the pre-redirect one #93.

Changes

  • src/fetch.ts: add fetchHeadersWithMeta(url, options) which captures res.url (the fetch spec guarantees this is the final URL after redirects) alongside the header map. fetchHeaders is now a thin wrapper around it, so its existing signature/behavior is unchanged.
  • src/types.ts: add optional finalUrl?: string to SecurityHeaderReport — additive, non-breaking.
  • src/index.ts: analyze() sets report.finalUrl whenever it differs from the requested URL.
  • src/cli.ts: prints Redirected to: <url> when a scan followed a redirect; included as-is in --json output since it's just a report field.
  • README.md: documents the new finalUrl field.
  • Tests: test/fetch.test.ts (new) covers fetchHeadersWithMeta/fetchHeaders against a mocked global fetch; test/analyzer.test.ts covers analyze() setting/omitting finalUrl.

Test plan

  • npm run typecheck
  • npm test (90 passing)
  • npm run build

🤖 Generated with Claude Code

https://claude.ai/code/session_01JpbiJ21pAx17d217g7m6Tj


Generated by Claude Code

…loses #93)

fetchHeaders always graded the final response but analyze() labeled the
report with the pre-redirect input URL, so a report could describe host B
while claiming to describe host A. Add fetchHeadersWithMeta to capture
res.url and surface it as report.finalUrl whenever it differs, with a CLI
note when a scan was redirected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpbiJ21pAx17d217g7m6Tj

Copy link
Copy Markdown
Contributor Author

Closing: this PR's fetchHeadersWithMeta reintroduces a plain fetch(url, { redirect: 'follow', ... }) call, which predates #99's SSRF hardening. #99 rewrote fetchHeaders into a manual hop-by-hop loop (redirect: 'manual', re-validating each redirect target against the private/loopback/metadata-IP blocklist before following it). Merging this as-is would silently reintroduce the SSRF-via-redirect vector #99 just closed, on top of hard-conflicting with the current file. The underlying gap (issue #93) is still real and worth fixing — reimplementing it directly on top of the current hop loop instead, so the redirect validation and the final-URL capture live in the same code path.


Generated by Claude Code

@dmchaledev dmchaledev closed this Jul 19, 2026
dmchaledev added a commit that referenced this pull request Jul 19, 2026
…114)

Reimplements #109 on top of #99's SSRF-hardened fetch loop (redirect: 'manual'
with per-hop private-IP validation) instead of the old redirect: 'follow' call
#109 assumed, which would have silently reintroduced the SSRF-via-redirect gap.

- src/fetch.ts: fetchHeadersWithMeta(url, options) returns {headers, finalUrl},
  reusing the existing hop loop; fetchHeaders is now a thin wrapper.
- src/types.ts: optional finalUrl on SecurityHeaderReport (additive).
- src/index.ts: analyze() sets report.finalUrl when it differs from the input.
- src/cli.ts: prints "Redirected to: <url>" and includes finalUrl in --json.
- Updated test/cli.test.ts's fetch.js mock (previously exported fetchHeaders,
  which index.ts no longer calls) to export fetchHeadersWithMeta instead.

Closes #93.

Co-authored-by: claude <noreply@anthropic.com>
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.

Report silently grades the post-redirect URL while labeling it with the pre-redirect one

2 participants