Generate RFC 9116-compliant security.txt for responsible vulnerability disclosure. Automated deployment with GitHub Actions.
✅ RFC 9116 Compliant - Follows official security.txt standard
✅ Auto-Generated - Creates properly formatted files from simple inputs
✅ Flexible Configuration - Support for all RFC 9116 fields
✅ Layered Configuration - Bundled marketplace baseline → org global config → repo config → action inputs
✅ Compliance Audit - 20 evidence-based controls with per-rule fail/warn/skip severities
✅ Enterprise Reporting - Markdown step summary, SARIF 2.1.0 for code scanning, JSON report, recommendations sidecar
✅ AI Findings Summary - Optional GitHub Models summary with a deterministic local fallback
✅ Local CLI - bos-securitytxt validate|generate|audit|sarif reproduces CI output on your machine
✅ Artifact Upload - Optional automatic artifact storage
Add this to your GitHub Actions workflow:
name: Generate Security.txt
on:
push:
branches: [main]
workflow_dispatch:
jobs:
generate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Generate security.txt
uses: blackoutsecure/bos-securitytxt-generator@v1
with:
security_contact: 'security@example.com'See action.yml for all available inputs.
security_contact- Contact method(s) for security vulnerability reports. Required per RFC 9116 § 2.5.3. Must be a URI usingmailto:,https://, ortel:scheme. Emails are auto-converted tomailto:URIs. Comma-separated for multiple contacts.- Examples:
security@example.com(auto-converted tomailto:security@example.com)https://example.com/securitysecurity@example.com,https://example.com/report
- Examples:
-
security_expires- Expiration date for security.txt validity. RFC 9116 § 2.5.5 recommends < 1 year (365 days).- Default:
180d(6 months) - Formats supported:
- ISO 8601 timestamp:
2026-12-31T23:59:59Z - Days:
30d,180d,365d - Months:
6m,12m - Years:
1y
- ISO 8601 timestamp:
⚠️ Values > 365 days trigger RFC compliance warning
- Default:
-
site_url- Your website URL (e.g.,https://example.com). Optional but recommended for canonical URI generation. -
public_dir- Directory to write security.txt. Default:dist -
security_policy- Link to vulnerability disclosure policy -
security_acknowledgments- Link to security researchers hall of fame -
security_encryption- OpenPGP encryption key URL -
security_hiring- Link to security job postings -
security_preferred_languages- Preferred report languages (e.g.,en, es, fr) -
security_comments- Include RFC 9116 comments in output (default:true) -
write_root_fallback- Also write a legacy copy at<public_dir>/security.txt(default:false) -
upload_artifacts- Upload to GitHub artifacts (default:true) -
debug- Display generated security.txt content (default:false)
| Input | Description | Default |
|---|---|---|
config_path |
Explicit repository config file | auto-discover |
global_config_path |
Organization-level global config | .github/blackout-secure-securitytxt-generator-global-config.yml |
use_global_config |
Global tier: auto, true (require), false (disable) |
auto |
use_marketplace_config |
Apply the bundled marketplace baseline | true |
enable_audit |
Run the RFC 9116 compliance audit | true |
audit_fail_on |
fail or never; empty uses security_txt.audit.fail_on |
from config |
sarif_output |
Write SARIF 2.1.0 for GitHub code scanning | disabled |
report_json |
Write the machine-readable JSON audit report | disabled |
redact_sensitive |
Redact credential-shaped values from report surfaces | true |
redaction_placeholder |
Replacement text for redacted values | *** |
recommendations_json |
Write structured remediation recommendations | disabled |
skips_json |
Write the skipped-controls sidecar | disabled |
step_summary |
Append the Markdown report to $GITHUB_STEP_SUMMARY |
true |
enable_ai_summary |
Generate a natural-language findings summary | true |
ai_provider |
auto, none, or a named provider |
auto |
Configuration is deep-merged, then validated. Precedence, lowest to highest:
- Bundled marketplace baseline —
src/marketplace-config.json, shipped with the action - Organization global config —
.github/blackout-secure-securitytxt-generator-global-config.yml - Repository config — first match of
.github/bos-universal-config.json|yml|yaml,bos-universal-config.*, or.bos-securitytxt.yml|yaml - Action inputs — any input you explicitly set wins over every config tier
Unknown top-level keys are ignored so the same bos-universal-config.json can be shared
with other Blackout Secure kits. Unknown keys inside security_txt.audit.rules are
rejected, so a typo in a rule name fails fast instead of silently disabling a control.
# .github/bos-universal-config.json (YAML shown for readability)
security_txt:
owner: blackoutsecure
project_name: example-site
generate:
include_comments: true
filename: security.txt
write_root_fallback: false
fields:
expires: 180d
contact:
- mailto:security@example.com
- https://example.com/report
policy:
- https://example.com/security-policy
encryption:
- https://example.com/pgp-key.txt
preferred_languages: 'en, es'
audit:
enable: true
fail_on: fail # or `never` to keep the audit advisory
max_size_kb: 32
expires_max_days: 365
rules:
require_contact: fail
require_canonical: fail
recommend_encryption: warn
reporting:
step_summary: true
sarif: true
json_report: true
recommendations: true
remediation:
enable_ai_findings_summary: true
ai_findings_summary_provider: auto
local_heuristic_fallback: trueBecause fields.contact can come from config, security_contact is no longer a hard
action-level requirement — supply it through either surface.
Every control is evidence-based and configurable through security_txt.audit.rules.<name>.
A rule set to skip still emits a finding, so the report records that the control was
deliberately not assessed.
| Rule | Config key | Checks | Default |
|---|---|---|---|
ST001 |
require_contact |
At least one Contact directive (§ 2.5.3) | warn |
ST002 |
require_expires |
An Expires directive is present (§ 2.5.5) | warn |
ST003 |
expires_not_expired |
Expires is still in the future | warn |
ST004 |
expires_within_max_days |
Expires is within audit.expires_max_days |
warn |
ST005 |
require_canonical |
A Canonical directive is present (§ 2.5.2) | warn |
ST006 |
canonical_matches_site_url |
Canonical points at <site_url>/.well-known/security.txt |
warn |
ST010 |
require_https_uris |
No directive value uses insecure http:// |
warn |
ST011 |
valid_contact_uri |
Contacts use mailto:, https://, or tel: |
warn |
ST012 |
require_multiple_contacts |
More than one contact channel is published | skip |
ST013 |
valid_preferred_languages |
Preferred-Languages uses RFC 5646 tags (§ 2.5.8) | warn |
ST014 |
single_valued_fields |
Expires/Preferred-Languages appear at most once (§ 2.4) | warn |
ST015 |
forbid_unknown_fields |
No unregistered directives or malformed lines | warn |
ST020 |
recommend_encryption |
An Encryption key is published (§ 2.5.4) | skip |
ST021 |
recommend_policy |
A disclosure Policy is published (§ 2.5.7) | warn |
ST022 |
recommend_acknowledgments |
An Acknowledgments page is published (§ 2.5.1) | skip |
ST023 |
recommend_hiring |
A security Hiring page is published (§ 2.5.6) | skip |
ST030 |
well_known_location |
File is written to /.well-known/security.txt (§ 3) |
warn |
ST031 |
file_size_limit |
File stays within audit.max_size_kb |
warn |
ST032 |
require_signature |
Inline or detached OpenPGP signature (§ 2.3) | skip |
ST040 |
require_utf8_no_bom |
UTF-8 encoded without a byte-order mark (§ 2.1) | warn |
No rule defaults to fail, so adopting the audit never breaks an existing pipeline on
day one. Opt individual rules up to fail once your file is clean.
- name: Generate and audit security.txt
id: securitytxt
uses: blackoutsecure/bos-securitytxt-generator@v1
with:
site_url: 'https://example.com'
public_dir: 'dist'
security_contact: 'security@example.com'
sarif_output: 'securitytxt-audit.sarif'
report_json: 'securitytxt-audit.json'
recommendations_json: 'securitytxt-recommendations.json'
audit_fail_on: 'fail'
- name: Upload audit findings to code scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: securitytxt-audit.sarif
- run: echo "Verdict: ${{ steps.securitytxt.outputs.audit_verdict }}"skip findings are intentionally omitted from SARIF — they would clutter the Security
tab with controls that were never assessed. Use skips_json when you need that record.
When enable_ai_summary is on, the action asks a model for a three-bullet triage summary
of the non-passing findings and appends it to the step summary and JSON report.
ai_provider: auto(default) uses GitHub Models wheneverGITHUB_MODELS_TOKENorGITHUB_TOKENis exposed to the job. Grantmodels: readin the job permissions.ai_provider: nonedisables the model call.- Any other name uses
<NAME>_API_KEYplus<NAME>_API_ENDPOINTfrom the environment.
AI is never on the critical path: any missing credential, authorization failure, timeout, or transport error falls back to a deterministic local summary, and the run continues.
The CLI shares every module with the Action, so a local dry-run produces the same report
as CI — including auditing a security.txt this action did not generate.
npm install
# Resolve and print the merged configuration cascade
npx bos-securitytxt validate
# Write security.txt from the resolved configuration
npx bos-securitytxt generate \
--public-dir dist \
--site-url https://example.com \
--contact mailto:security@example.com
# Audit any existing security.txt and write every report artefact
npx bos-securitytxt audit \
--public-dir dist \
--site-url https://example.com \
--sarif securitytxt-audit.sarif \
--json securitytxt-audit.json \
--recommendations securitytxt-recommendations.json \
--fail-on never
# Merge SARIF logs before a single code-scanning upload
npx bos-securitytxt sarif --input a.sarif --input b.sarif --output merged.sarifExit codes: 0 success, 1 audit failed under the fail policy, 2 usage or
configuration error.
Security.txt is automatically generated at:
<public_dir>/.well-known/security.txt
Per RFC 9116, the file must be served at /.well-known/security.txt over HTTPS.
| Output | Description |
|---|---|
security_path |
Path to the generated security.txt |
security_root_path |
Path to the legacy root copy, when write_root_fallback is enabled |
security_expires |
Resolved Expires value written to the file |
config_sources |
Applied config tiers, in precedence order |
audit_verdict |
Pass, Review recommended, Action required, Inconclusive, or Not assessed |
audit_pass_count |
Controls that passed |
audit_warn_count |
Controls that warned |
audit_fail_count |
Controls that failed |
audit_error_count |
Controls that could not be evaluated |
audit_skip_count |
Controls that were not assessed |
sarif_path |
Written SARIF file, when sarif_output is set |
report_json_path |
Written JSON report, when report_json is set |
recommendations_json_path |
Written recommendations sidecar, when recommendations_json is set |
ai_summary |
Short natural-language summary of the audit findings |
- name: Generate security.txt
uses: blackoutsecure/bos-securitytxt-generator@v1
with:
public_dir: 'public'
site_url: 'https://example.com'
security_contact: 'security@example.com,https://example.com/report'
security_expires: '6m' # 6 months (or use 180d, 2026-12-31T23:59:59Z, etc.)
security_policy: 'https://example.com/security'
security_acknowledgments: 'https://example.com/hall-of-fame'
security_encryption: 'https://example.com/security-key.asc'
security_hiring: 'https://example.com/careers/security'
security_preferred_languages: 'en, es, fr'
security_comments: true
upload_artifacts: true
artifact_name: 'securitytxt'Fully compliant with RFC 9116 (securitytxt.org):
- ✅ Required fields:
Contact(§ 2.5.3),Expires(§ 2.5.5) - ✅ Optional fields:
Acknowledgments,Canonical,Encryption,Hiring,Policy,Preferred-Languages - ✅ Contact validation: Must be URI (
mailto:,https://,tel:) per § 2.5.3 - ✅ Expires recommendation: < 1 year (365 days) per § 2.5.5
- ✅ Proper UTF-8 encoding and machine-parsable format
- ✅ Serves at
/.well-known/security.txtover HTTPS
Per RFC 9116 § 2.5.3, the Contact field:
- MUST appear at least once (required)
- MUST be a valid URI (e.g.,
mailto:,https://,tel:) - MAY appear multiple times for different contact methods
- Web URIs (
https://) are preferred over email for spam prevention
This action automatically converts email addresses to mailto: URIs for RFC compliance.
Contributions welcome! See CONTRIBUTING.md for guidelines.
Apache License 2.0 - See LICENSE for details.
Copyright © 2025-2026 Blackout Secure
Website: blackoutsecure.app
This repository is built with Blackout Secure's reusable GitHub Actions
workflows. If you fork or self-host these workflows and need to provision
your own credentials (GitHub App vs. PAT guidance, secret tiers, Docker
Hub/Cloudflare/Balena setup walkthroughs), see the
"Secrets pipelining strategy"
section of bos-automation-hub. To report a vulnerability, see
SECURITY.md.