Skip to content

chore(security.txt): make compliant by adding an Expires field#9005

Open
kobihikri wants to merge 3 commits into
nodejs:mainfrom
kobihikri:security/add-expires-to-security-txt
Open

chore(security.txt): make compliant by adding an Expires field#9005
kobihikri wants to merge 3 commits into
nodejs:mainfrom
kobihikri:security/add-expires-to-security-txt

Conversation

@kobihikri

Copy link
Copy Markdown

Description

The published security.txt at https://nodejs.org/.well-known/security.txt currently contains only:

Contact: https://hackerone.com/nodejs
Policy: https://github.com/nodejs/node/security/policy#security

RFC 9116 §2.5.5 states the Expires field MUST be present. Without it, a scanner or security researcher following the RFC has no signed expiry to validate the file against (some tooling flags a security.txt with no Expires as non-conformant).

This PR adds the single missing field:

 Contact: https://hackerone.com/nodejs
+Expires: 2027-06-30T23:59:59.000Z
 Policy: https://github.com/nodejs/node/security/policy#security

I set the value roughly a year out. Since a static date will itself lapse eventually, you may want to automate the refresh — glad to wire that up in a follow-up, but I kept this change scoped to the one missing mandatory field.

Check List

  • I have read the Contributing Guidelines and made my commit messages conform to the DCO (Signed-off-by).
  • Change is limited to a single concern.

I used AI assistance to help identify and draft this; I verified the RFC reference and the live file myself and take responsibility for the change. Thank you for maintaining nodejs.org.

RFC 9116 section 2.5.5 states the Expires field MUST be present in a
security.txt file. The published file at nodejs.org/.well-known/security.txt
currently has only Contact and Policy, so a scanner or researcher following
the RFC has no signed expiry to validate the file against.

This adds an Expires value roughly a year out. Renewing it (or automating the
refresh) keeps the file trustworthy per the RFC; happy to wire that up if you
prefer, but kept this change to the single missing field.

Signed-off-by: kobihikri <kobi.hikri@gmail.com>
@kobihikri kobihikri requested a review from a team as a code owner July 14, 2026 08:27
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nodejs-org Ready Ready Preview Jul 14, 2026 4:37pm

Request Review

@cursor

cursor Bot commented Jul 14, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Static disclosure metadata plus a low-privilege scheduled PR workflow; no runtime app or auth changes.

Overview
Brings the published security.txt in line with RFC 9116 by adding the mandatory Expires field (2027-06-30T23:59:59.000Z); Contact and Policy are unchanged.

Adds Refresh security.txt Expires, a yearly (Jan 1) workflow—also runnable via workflow_dispatch—that updates Expires to roughly 400 days ahead and opens a PR on automated/security-txt-expiry for human review of contact/policy before merge. It only runs for the nodejs org owner and follows the same pinned gr2m/create-or-update-pull-request-action pattern as other maintenance workflows.

Reviewed by Cursor Bugbot for commit e2a51ca. Bugbot is set up for automated code reviews on this repo. Configure here.

@bmuenzenmeyer

bmuenzenmeyer commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@nodejs/security-wg should own this file and i's changes

@bjohansebas bjohansebas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmm, I think we should have some automation to update that automatically. I feel like that field is going to be forgotten, and we don't want to show that it's expired when all the other information is actually up to date.

cc: @nodejs/web-infra

@MattIPv4

Copy link
Copy Markdown
Member

👍 Agreed, a once-per-year cron to open a PR to bump it would be nice. That'll prompt folks to actually review the information is still correct before the date is bumped.

@bjohansebas

Copy link
Copy Markdown
Member

@kobihikri

kobihikri commented Jul 14, 2026 via email

Copy link
Copy Markdown
Author

@kobihikri

kobihikri commented Jul 14, 2026 via email

Copy link
Copy Markdown
Author

Addresses review feedback that a static Expires field will be forgotten and
silently expire. Monthly job: if Expires is within 30 days, bump it a year and
open a PR for a human to confirm the contact/policy are still valid.

Modeled on expressjs/expressjs.com's update-external-docs workflow (as
suggested in review) and uses the same gr2m/create-or-update-pull-request-action
pin this repo already uses. Assisted by an AI tool; verified myself.

Signed-off-by: Kobi Hikri <kobi.hikri@gmail.com>
@kobihikri kobihikri requested a review from a team as a code owner July 14, 2026 16:29
@kobihikri

Copy link
Copy Markdown
Author

Good call, and thanks for the pointer @bjohansebas — you're right that a static Expires will drift and eventually show as expired.

I've pushed a workflow modeled on the expressjs.com one you linked: .github/workflows/security-txt-expiry.yml. It runs monthly (and on demand), and when Expires is within 30 days it bumps it a year out and opens a PR — deliberately a PR, not a direct commit, so a human confirms the Contact/Policy are still correct before the extension lands (it cc's @nodejs/security-wg for that). It uses the same gr2m/create-or-update-pull-request-action pin this repo already uses in create-release-post.yml, and pins actions/checkout to a SHA.

Happy to adjust the cadence, the labels, or hand the workflow off to @nodejs/web-infra if you'd prefer it live somewhere else or be shaped differently — whatever fits the repo's conventions.

run: |
set -euo pipefail
new=$(date -u -d '+365 days' +%Y-%m-%dT23:59:59.000Z)
sed -i "s/^Expires:.*/Expires: ${new}/I" "$SECURITY_TXT"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Expires bump drifts each cycle

Low Severity

The bump sets Expires to today plus 365 days whenever the current value is within 30 days, rather than advancing the existing Expires value by a year. With the monthly-on-the-1st cron, that early window makes the anniversary shift about a month earlier each cycle, so the automated PR does not actually extend the prior expiry by one year.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0679b62. Configure here.

Per review: switch from a monthly 'within 30 days -> today+365' check (which
drifts the window earlier each cycle) to a once-a-year cron that always sets
Expires ~13 months out. The >1yr window means the file never expires between
runs, and the yearly PR prompts a human to re-confirm the contact/policy.

Signed-off-by: Kobi Hikri <kobi.hikri@gmail.com>
@kobihikri

Copy link
Copy Markdown
Author

Thanks @MattIPv4 — that's exactly the intent. And good catch on the drift (via the bot review): I've reworked it into a once-a-year cron that always sets Expires ~13 months out (a >1-year window, so the file can't lapse even if a run is delayed), instead of the monthly "within 30 days → today+365" check that crept the window earlier each cycle.

It still opens a PR rather than committing directly, so a human re-confirms the Contact/Policy are still valid before the bump lands, and it cc's @nodejs/security-wg per @bmuenzenmeyer's point about ownership. Happy to adjust the cadence further or hand the workflow off to the security-wg to own.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e2a51ca. Configure here.

SECURITY_TXT: apps/site/public/.well-known/security.txt
run: |
set -euo pipefail
new=$(date -u -d '+400 days' +%Y-%m-%dT23:59:59.000Z)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yearly refresh leaves thin expiry buffer

Medium Severity

The yearly cron plus a human-merge PR only leaves about 35 days of headroom in steady state (+400 days minus one year). A slow review over the Jan 1 holiday window, or a single missed scheduled run, lets Expires lapse — the opposite of the workflow’s “never silently lapses” goal. The header’s claim that a >1yr bump protects against delay only covers cron slippage, not merge delay or a skipped annual run.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e2a51ca. Configure here.

SECURITY_TXT: apps/site/public/.well-known/security.txt
run: |
set -euo pipefail
new=$(date -u -d '+400 days' +%Y-%m-%dT23:59:59.000Z)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Expires bump exceeds RFC one-year limit

Low Severity

The refresh sets Expires with +400 days (~13 months). RFC 9116 §2.5.5 recommends the value be less than a year into the future. This PR’s goal is RFC conformance for scanners that flag a missing Expires; the automated bump will itself produce values those same tools often warn on.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e2a51ca. Configure here.

@flakey5 flakey5 requested a review from a team July 14, 2026 16:40
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Run pinact -u on this file, please

Comment on lines +1 to +5
# Refreshes apps/site/public/.well-known/security.txt so its RFC 9116 Expires
# field never silently lapses. Runs once a year and opens a PR bumping Expires
# ~13 months out — a human must confirm the contact/policy are still valid
# before merging. The generous window (>1yr) means the file never expires even
# if a run is delayed, and anchoring to the fixed yearly run avoids drift.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
# Refreshes apps/site/public/.well-known/security.txt so its RFC 9116 Expires
# field never silently lapses. Runs once a year and opens a PR bumping Expires
# ~13 months out — a human must confirm the contact/policy are still valid
# before merging. The generous window (>1yr) means the file never expires even
# if a run is delayed, and anchoring to the fixed yearly run avoids drift.
# Security Notes
# Only selected Actions are allowed within this repository. Please refer to (https://github.com/nodejs/nodejs.org/settings/actions)
# for the full list of available actions. If you want to add a new one, please reach out a maintainer with Admin permissions.
# REVIEWERS, please always double-check security practices before merging a PR that contains Workflow changes!!
# AUTHORS, please only use actions with explicit SHA references, and avoid using `@master` or `@main` references or `@version` tags.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's call this bump-security-txt-expiry.yml so it's clear this bumps it

Comment on lines +28 to +34
- name: Bump Expires (~13 months out)
env:
SECURITY_TXT: apps/site/public/.well-known/security.txt
run: |
set -euo pipefail
new=$(date -u -d '+400 days' +%Y-%m-%dT23:59:59.000Z)
sed -i "s/^Expires:.*/Expires: ${new}/I" "$SECURITY_TXT"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As Cursor said, we should bump the in-place year, not re-calculate a date, e.g. Expiry: \d{4} gets replaced by (v) => v + 1

new=$(date -u -d '+400 days' +%Y-%m-%dT23:59:59.000Z)
sed -i "s/^Expires:.*/Expires: ${new}/I" "$SECURITY_TXT"

- name: Create Pull Request (only if the date changed)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
- name: Create Pull Request (only if the date changed)
- name: Open pull request

Conformity with our other workflows. No need to add the (...), this will always bump the date

Comment on lines +41 to +55
commit-message: 'chore: refresh security.txt Expires'
title: 'chore: refresh security.txt Expires'
body: |
Annual refresh of the `Expires` field in `apps/site/public/.well-known/security.txt` so it
never silently lapses.

**Before merging, please confirm the rest of the file is still accurate:**
- The `Contact` (HackerOne) is still the correct, actively-monitored channel
- The linked `Policy` is still the intended vulnerability-disclosure path

If anything has changed, update it in this PR before extending the expiration.

cc: @nodejs/security-wg
labels: security
branch: automated/security-txt-expiry

@avivkeller avivkeller Jul 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
commit-message: 'chore: refresh security.txt Expires'
title: 'chore: refresh security.txt Expires'
body: |
Annual refresh of the `Expires` field in `apps/site/public/.well-known/security.txt` so it
never silently lapses.
**Before merging, please confirm the rest of the file is still accurate:**
- The `Contact` (HackerOne) is still the correct, actively-monitored channel
- The linked `Policy` is still the intended vulnerability-disclosure path
If anything has changed, update it in this PR before extending the expiration.
cc: @nodejs/security-wg
labels: security
branch: automated/security-txt-expiry
update-pull-request-title-and-body: true
branch: chore/update-security-txt
body: |
Updates the `Expiry` on our [`security.txt`](https://nodejs.org/.well-known/security.txt] from ${{ steps.update-expiry.outputs.old }} to ${{ steps.update-expiry.outputs.new }}
Before merging, please explicitly confirm that:
- The security contact listed is still correct and actively monitored
- The linked security policy is still accurate
- The project's preferred vulnerability disclosure path has not changed
cc @nodejs/security-wg
Check this workflow's logs at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}.
commit-message: 'meta: update security `Expiry` from ${{ steps.update-expiry.outputs.old }} to ${{ steps.update-expiry.outputs.new }}'
title: 'meta: update security `Expiry` from ${{ steps.update-expiry.outputs.old }} to ${{ steps.update-expiry.outputs.new }}'
draft: true

For conformity

@avivkeller avivkeller changed the title security.txt: add required Expires field chore(security.txt): make compliant by adding an Expires field Jul 14, 2026
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.

7 participants