Skip to content

feat(http-helmet): remove insecure response headers - #148

Open
mcansh wants to merge 3 commits into
devfrom
logan/add-remove-headers-option
Open

feat(http-helmet): remove insecure response headers#148
mcansh wants to merge 3 commits into
devfrom
logan/add-remove-headers-option

Conversation

@mcansh

@mcansh mcansh commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

Adds an opt-in removeInsecureHeaders utility that removes server-identifying, framework, and diagnostic response headers maintained by the OWASP Secure Headers Project without mutating the original
Headers object.

What Changed

  • Added and exported removeInsecureHeaders from @mcansh/http-helmet.
  • Seeded the removal list from the OWASP Secure Headers Project.
  • Added a weekly and manually dispatchable workflow that validates upstream input, regenerates the list deterministically, and opens a changeset-backed pull request.
  • Updated the React Router v7 example to remove insecure response headers before merging generated security headers.
  • Added a patch changeset for @mcansh/http-helmet.

Test Coverage Updates

  • Added coverage proving every configured insecure header is removed.
  • Added coverage for preserving unrelated headers and leaving the original Headers object unchanged.
  • Verified the workflow rejects control characters, invalid names, duplicates, and empty lists while accepting valid HTTP field names.
  • Ran the complete Vitest suite: 17 files and 130 tests passed.
  • Passed HTTP Helmet typechecking, Prettier, Bash syntax validation, changeset validation, diff checks, and the full monorepo build.

Impact

  • Consumers can explicitly remove response headers that disclose server, framework, tracing, or infrastructure details.
  • Existing consumers are unaffected unless they call the new utility.
  • The automated updater keeps the bundled removal list synchronized through reviewable pull requests.

Risk And Rollback

  • Removing diagnostic or tracing headers could reduce observability when consumers opt in; unrelated headers and multiple Set-Cookie values remain preserved.
  • The updater consumes a mutable OWASP source, mitigated by HTTPS-only fetching, response limits, strict schema and HTTP field-name validation, deterministic generation, and pull-request review.
  • Roll back by reverting this PR. No dependency, data, configuration, or deployment migration is required.

Summary by CodeRabbit

  • New Features
    • Added removeInsecureHeaders to sanitize response headers by removing server, framework, proxy, and diagnostic identifiers.
    • Exported the header-sanitization utility for application use.
    • Integrated header sanitization into the React Router server response flow.
  • Documentation
    • Added usage guidance for the new opt-in helper.
  • Tests
    • Added coverage confirming unrelated headers are preserved and original headers remain unchanged.
  • Chores
    • Added automation to keep the removal list aligned with OWASP guidance.

mcansh added 2 commits August 1, 2026 10:04
Add removeInsecureHeaders to strip server-identifying headers from response Headers without mutating caller state.

Seed the removal list from the OWASP Secure Headers Project and add a scheduled workflow that validates upstream input, regenerates the list deterministically, and opens a changeset-backed pull request.

Apply the helper in the React Router v7 example and cover insecure-header removal, safe-header preservation, and input immutability.
Use absolute Oniguruma anchors in the updater jq validator so a trailing newline cannot satisfy the end-of-line anchor.

This keeps generated removal lists restricted to complete, valid HTTP field-name tokens.
Copilot AI review requested due to automatic review settings August 1, 2026 14:15
@changeset-bot

changeset-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f551753

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@mcansh/http-helmet Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 1, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
packages f551753 Aug 02 2026, 02:05 AM

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change adds removeInsecureHeaders, applies it to the React Router server response, documents and tests its behavior, and adds automation to update the OWASP-based removal-header list.

Changes

Response header sanitization

Layer / File(s) Summary
Sanitizer contract and implementation
packages/http-helmet/src/removal-headers.ts, packages/http-helmet/src/utils.ts, packages/http-helmet/src/index.ts, packages/http-helmet/src/utils.spec.ts
Adds the removalHeaders list and the exported removeInsecureHeaders function. Tests cover removal, preservation, and input immutability.
Server response integration
apps/http-helmet/react-router-v7/app/entry.server.tsx, .changeset/tidy-lions-protect.md
Sanitizes response headers before secure headers are merged. Adds a patch changeset.
Automated removal-list updates
.github/workflows/update-removal-headers.yml
Adds scheduled and manual OWASP list retrieval, validation, TypeScript generation, branch updates, changeset creation, and pull request management.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReactRouterServer
  participant removeInsecureHeaders
  participant removalHeaders
  ReactRouterServer->>removeInsecureHeaders: pass responseHeaders
  removeInsecureHeaders->>removalHeaders: read configured header names
  removeInsecureHeaders-->>ReactRouterServer: return sanitized headers
  ReactRouterServer->>ReactRouterServer: merge secure headers
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the addition of insecure response header removal for http-helmet.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch logan/add-remove-headers-option

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.56%. Comparing base (edfff9c) to head (f551753).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #148      +/-   ##
==========================================
+ Coverage   93.97%   94.56%   +0.58%     
==========================================
  Files          19       20       +1     
  Lines         896      993      +97     
  Branches      221      222       +1     
==========================================
+ Hits          842      939      +97     
  Misses         54       54              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new

pkg-pr-new Bot commented Aug 1, 2026

Copy link
Copy Markdown
More templates

npm i https://pkg.pr.new/mcansh/packages/@mcansh/create-temporary-files@148
npm i https://pkg.pr.new/mcansh/packages/@mcansh/http-helmet@148
npm i https://pkg.pr.new/mcansh/packages/@mcansh/url@148
npm i https://pkg.pr.new/mcansh/packages/@mcansh/vitest-response-matchers@148

commit: f551753

Copilot AI 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.

🟡 Not ready to approve

The updater workflow’s validation currently allows case-insensitive duplicate headers, and the new public utility’s exported signature should be made more robust/explicit for API stability.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR adds an opt-in removeInsecureHeaders utility to @mcansh/http-helmet that strips a curated list of server/framework/diagnostic response headers (seeded from OWASP Secure Headers), updates an example app to apply the removal before merging security headers, and introduces a scheduled workflow to keep the bundled removal list refreshed via automated PRs.

Changes:

  • Added removeInsecureHeaders and re-exported it from the package entrypoint.
  • Introduced a generated removalHeaders list (OWASP-seeded) plus Vitest coverage verifying removal + non-mutation.
  • Added a GitHub Actions workflow to validate upstream JSON and regenerate removal-headers.ts with a changeset-backed PR.
File summaries
File Description
packages/http-helmet/src/utils.ts Adds removeInsecureHeaders implementation and pulls in the removal list.
packages/http-helmet/src/utils.spec.ts Adds tests for removal coverage, preservation of unrelated headers, and non-mutation.
packages/http-helmet/src/removal-headers.ts Adds the OWASP-seeded removal header name list.
packages/http-helmet/src/index.ts Re-exports removeInsecureHeaders from the public package entrypoint.
apps/http-helmet/react-router-v7/app/entry.server.tsx Applies removeInsecureHeaders before merging generated secure headers in the example.
.github/workflows/update-removal-headers.yml Adds scheduled/manual updater workflow that validates and regenerates the removal list and opens/updates a PR.
.changeset/tidy-lions-protect.md Adds a patch changeset documenting the new public utility.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 2
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +71 to +79
export function removeInsecureHeaders(responseHeaders: Headers) {
const headers = new Headers(responseHeaders);

for (const key of removalHeaders) {
headers.delete(key);
}

return headers;
}
Comment on lines +63 to +78
jq --exit-status '
.headers
| type == "array"
and length > 0
and length <= 1000
and (unique | length) == length
and all(.[];
if type == "string" then
length > 0
and length <= 256
and test("\\A[!#$%&\u0027*+.^_`|~0-9A-Za-z-]+\\z")
else
false
end
)
' "$response_file" > /dev/null

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/update-removal-headers.yml:
- Around line 22-26: Update the checkout step using actions/checkout to set
persist-credentials to false, then add explicit Git authentication with gh auth
setup-git before the later bare git push commands in the script, using the
existing GH_TOKEN environment configuration.
- Around line 8-10: Move the workflow-level permissions block into the
jobs.update job so contents: write and pull-requests: write apply only to that
job. Add brief comments documenting why each permission is required, preserving
the existing permission scopes and workflow behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f24e3800-61e5-4546-bcc7-f51d2bd54297

📥 Commits

Reviewing files that changed from the base of the PR and between edfff9c and 7fbb937.

📒 Files selected for processing (7)
  • .changeset/tidy-lions-protect.md
  • .github/workflows/update-removal-headers.yml
  • apps/http-helmet/react-router-v7/app/entry.server.tsx
  • packages/http-helmet/src/index.ts
  • packages/http-helmet/src/removal-headers.ts
  • packages/http-helmet/src/utils.spec.ts
  • packages/http-helmet/src/utils.ts

Comment on lines +8 to +10
permissions:
contents: write
pull-requests: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Scope permissions to the job, not the workflow.

contents: write and pull-requests: write are declared at the workflow level. This grants both permissions to every job in the workflow, including any jobs added later, rather than only the update job that needs them. Move permissions under jobs.update and add a short comment on why each scope is needed.

🔒 Proposed fix
-permissions:
-  contents: write
-  pull-requests: write
+permissions:
+  contents: read
 
 concurrency:
   group: update-http-helmet-removal-headers
   cancel-in-progress: false
 
 jobs:
   update:
     name: 🛡️ Update removal headers
     runs-on: ubuntu-latest
     timeout-minutes: 10
+    permissions:
+      contents: write # push the automation branch with the regenerated removal-header list
+      pull-requests: write # open or update the changeset-backed pull request
     steps:

As per the static analysis hints, this triggers excessive-permissions at lines 9-10 and undocumented-permissions at line 9.

🧰 Tools
🪛 zizmor (1.28.0)

[error] 9-9: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level

(excessive-permissions)


[error] 10-10: overly broad permissions (excessive-permissions): pull-requests: write is overly broad at the workflow level

(excessive-permissions)


[warning] 9-9: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/update-removal-headers.yml around lines 8 - 10, Move the
workflow-level permissions block into the jobs.update job so contents: write and
pull-requests: write apply only to that job. Add brief comments documenting why
each permission is required, preserving the existing permission scopes and
workflow behavior.

Source: Linters/SAST tools

Comment on lines +22 to +26
- name: ⬇️ Checkout repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.repository.default_branch }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Disable credential persistence and configure git auth explicitly for the push.

actions/checkout is not configured with persist-credentials: false, so the checkout token stays available in the job's git configuration for the rest of the run. The script later relies on this persisted credential for the bare git push commands (lines 118-126), since no explicit git authentication is set up otherwise. Set persist-credentials: false on checkout and configure git authentication explicitly via gh auth setup-git, which uses the GH_TOKEN already exported for the script step.

🔒 Proposed fix
       - name: ⬇️ Checkout repo
         uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
         with:
           fetch-depth: 0
+          persist-credentials: false
           ref: ${{ github.event.repository.default_branch }}
         run: |
           set -euo pipefail
 
+          gh auth setup-git
+
           response_file=$(mktemp)

As per the static analysis hints, this triggers artipacked (credential persistence through GitHub Actions checkout) at lines 22-26.

🧰 Tools
🪛 zizmor (1.28.0)

[warning] 22-26: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/update-removal-headers.yml around lines 22 - 26, Update
the checkout step using actions/checkout to set persist-credentials to false,
then add explicit Git authentication with gh auth setup-git before the later
bare git push commands in the script, using the existing GH_TOKEN environment
configuration.

Source: Linters/SAST tools

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ℹ️ No critical issues — minor suggestions only. The utility, tests, and workflow validation are all sound (I verified the jq token regex rejects control characters and duplicates, the OWASP list matches upstream exactly, and the package tests, typecheck, prettier, and build all pass). Two design observations and a few nitpicks below.

Reviewed changes

  • removeInsecureHeaders utility — new opt-in export that copies the incoming Headers and deletes every header in the OWASP-derived removalHeaders list, leaving the original object untouched.
  • removalHeaders list — 87 entries seeded from the OWASP Secure Headers Project ci/headers_remove.json; the committed list matches the current upstream source exactly.
  • Updater workflow — new .github/workflows/update-removal-headers.yml that validates upstream JSON shape (count limits, uniqueness, RFC 7230 token regex with \A…\z anchors), deterministically regenerates removal-headers.ts via jq tojson, and opens a changeset-backed PR.
  • Testsutils.spec.ts covers every-listed-header removal, preservation of unrelated headers, and non-mutation of the original; the full package suite, tsc, prettier, and the tsdown build pass.
  • Example app — React Router v7 entry.server.tsx now applies removeInsecureHeaders(responseHeaders) before merging secure headers.
  • Changeset — patch changeset for @mcansh/http-helmet.

ℹ️ Update PRs created by this workflow won't run CI checks

The workflow opens/updates its PR using ${{ github.token }} via gh pr create. PRs created by a workflow running on GITHUB_TOKEN do not run pull_request-triggered checks automatically — GitHub places the runs in an approval-required state, so test-pr.yml and autofix.yml won't run unless a maintainer manually approves them. The generated update PR therefore merges without tests, lint, or typecheck verification.

Technical details
# Update PRs skip CI

## Affected sites
- .github/workflows/update-removal-headers.yml:148 — `gh pr create` under the workflow's `github.token`
- .github/workflows/update-removal-headers.yml:128 — `gh pr list` update path, same token

## Required outcome
- Decide and document how generated update PRs get verified. Options: (a) accept the tradeoff — input is strictly schema/token validated, output is generated deterministically, and a human reviews before merge; (b) approve the approval-required `pull_request` runs when reviewing; or (c) use a GitHub App / PAT token for the push + PR steps so `pull_request` workflows fire normally.

## Open questions for the human
- Is the lack of automated checks on generated PRs acceptable given the strict upstream validation?

ℹ️ The removal list has no per-header escape hatch

removeInsecureHeaders always removes every entry in the OWASP list. Several of those entries are headers real applications depend on — SourceMap/X-SourceMap (production source maps for error reporting), X-B3-* (Zipkin), X-Datadog-*, X-Envoy-* (mesh observability). A consumer who opts in but relies on any of these has no way to keep them. Consider documenting this explicitly, or in a future iteration accept an override set (e.g. removeInsecureHeaders(headers, { exclude: ["SourceMap"] })).

ℹ️ Nitpicks

  • removeInsecureHeaders is a new public API but the package README doesn't mention it — the README documents mergeHeaders/createNonce usage but has no example for the new utility.
  • The changeset bumps @mcansh/http-helmet as patch, but this adds a new public export (the PR title is feat(...)); conventional changesets practice would be minor for an additive feature.

Pullfrog  | Fix it ➔View workflow run | Using Big Pickle (free) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Pullfrog addressed all of its review feedback on this PR.

Addressed review #4834820278 (0 inline threads; feedback was in the review body).

Applied

  • Added a "Remove Insecure Headers" section to packages/http-helmet/README.md documenting removeInsecureHeaders usage. The docs also state there's no per-header allowlist and that every OWASP-listed header is removed — addressing the "no escape hatch" observation's "document this explicitly" suggestion without adding an override API (avoiding scope creep for a first version). Committed and pushed as f551753.

Not applied, with reasons

  • Changeset patchminor: kept patch. This repo's own changelog precedent labels additive new exports as patch changes (e.g. 0.13.0 "add prequoted keyword exports"), reserving minor for potentially-breaking changes (peerDep range updates, behavior changes). Installed @changesets/cli@2.29.7 uses standard semver increments, so minor would overstate an opt-in, purely additive export.
  • CI on generated update PRs (approval-required runs from github.token-created PRs): left as an author decision — options (a) accept the tradeoff given strict schema/token validation, (b) approve the runs manually, or (c) switch to a GitHub App/PAT. I can't configure (c) from here, and (a)/(b) are policy choices.

Verification: http-helmet suite (28 tests) passes; prettier clean.

Task list (6/6 completed)
  • Checkout PR 148 and read review threads (0 threads, review body has 2 nitpicks + 2 design observations)
  • Add removeInsecureHeaders documentation to package README
  • Evaluate changeset patch vs minor: keep patch (repo precedent = additive exports are patch), document reasoning
  • Evaluate 2 design observations (workflow CI tradeoff, escape hatch) - escape hatch documented in README, CI tradeoff left for author
  • Verify changes (tests + prettier), commit, push
  • Finalize: report_progress summary

Pullfrog  | View workflow run | Using Big Pickle (free) | 𝕏

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/http-helmet/README.md`:
- Around line 77-80: Update the README example around removeInsecureHeaders to
define responseHeaders before use, preferably by initializing it as a Headers
instance, so the snippet is self-contained and runnable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1d92b126-6727-448d-b12b-62cc7ec79b5b

📥 Commits

Reviewing files that changed from the base of the PR and between 7fbb937 and f551753.

📒 Files selected for processing (1)
  • packages/http-helmet/README.md

Comment on lines +77 to +80
```js
import { removeInsecureHeaders } from "@mcansh/http-helmet";

let headers = removeInsecureHeaders(responseHeaders);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Define responseHeaders in the example.

The snippet uses responseHeaders without declaring it. Add a Headers instance or show the call within a complete response-handling example so users can run the documented code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/http-helmet/README.md` around lines 77 - 80, Update the README
example around removeInsecureHeaders to define responseHeaders before use,
preferably by initializing it as a Headers instance, so the snippet is
self-contained and runnable.

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.

2 participants