security-txt-cra-lint / index.html
getreadystack's picture
Upload index.html with huggingface_hub
4ac5762 verified
Raw History Blame Contribute Delete
43 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Security.txt RFC 9116 check — check your security contact file online</title>
<meta name="description" content="This browser tool checks security.txt against RFC 9116 and the EU Cyber Resilience Act reporting contact point described on the page.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/security-txt-cra-lint"></head>
<body>
<div class="wrap">
<h1>Security.txt RFC 9116 check — check your security contact file online</h1>
<p class="lede">Lints security.txt against RFC 9116 and the EU Cyber Resilience Act reporting contact point that has applied since 11 September 2026. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false"># security.txt - example.com
Contact: security@example.com
Contacts: https://example.com/report-a-vulnerability
Expires: 2026-06-30T00:00:00Z
Encryption: http://example.com/pgp-key.txt
Canonical: https://example.com/security.txt
CSAF: https://example.com/.well-known/csaf/index.json
Preferred-Languages: en
Preferred-Languages: fr
Acknowledgments https://example.com/hall-of-fame</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here and in the editor:</strong> lint the security.txt you have open - all 13 rules, every line, no key.<br><strong>Next job:</strong> scan every security.txt across a whole workspace in one pass and export a dated JSON report you keep for the audit file.<br><p><a href="https://getreadystack.com/api/buy/cl/polar_cl_ix6VvnndFPQPoppGXnx8ybXWfh7W2eldbZaTM2Zh2br">Get the full version - workspace scan + dated JSON report</a></p><p style="font-size:13px;color:#7a6e65;margin:6px 0 0">$29 once &middot; one licence key per person or team seat &middot; A security consultant reviewing a disclosure contact and policy starts around $150 an hour.</p></div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var STX_RULES = [
{"id": "contact_missing", "sev": "error", "ref": "RFC 9116 s2.5.3", "msg": "No Contact field. RFC 9116 makes Contact mandatory, and the CRA single reporting contact point has nowhere to land."},
{"id": "contact_not_uri", "sev": "error", "ref": "RFC 9116 s2.5.3", "msg": "Contact must be a URI (mailto:, tel: or https://), not a bare address."},
{"id": "expires_missing", "sev": "error", "ref": "RFC 9116 s2.5.5", "msg": "No Expires field. Expires is mandatory and a file without it is not a valid security.txt."},
{"id": "expires_invalid", "sev": "error", "ref": "RFC 9116 s2.5.5", "msg": "Expires must be a full RFC 3339 timestamp, e.g. 2027-03-01T00:00:00Z."},
{"id": "expires_past", "sev": "error", "ref": "RFC 9116 s2.5.5", "msg": "Expires is in the past, so RFC 9116 says the file MUST be ignored: the reporter has no contact point."},
{"id": "expires_over_year", "sev": "warn", "ref": "RFC 9116 s2.5.5", "msg": "Expires is more than 365 days out; RFC 9116 says the value should be less than a year."},
{"id": "field_duplicate", "sev": "error", "ref": "RFC 9116 s2.5.5, s2.5.8", "msg": "This field may appear only once in the file."},
{"id": "insecure_url", "sev": "error", "ref": "RFC 9116 s2.3", "msg": "Web URIs in security.txt must use https://, never http://."},
{"id": "canonical_wellknown", "sev": "warn", "ref": "RFC 9116 s3", "msg": "Canonical should point at https://\u003cdomain\u003e/.well-known/security.txt, the only location scanners read."},
{"id": "policy_missing", "sev": "warn", "ref": "CRA Annex I Part II(5)", "msg": "No Policy field. The CRA expects a published coordinated vulnerability disclosure policy for the reporter to follow."},
{"id": "csaf_not_provider", "sev": "warn", "ref": "RFC 9116 s2.5.4", "msg": "CSAF must point at a provider-metadata.json URI, not at an index or a single advisory."},
{"id": "unknown_field", "sev": "warn", "ref": "RFC 9116 s2.4", "msg": "Field name is not in the RFC 9116 registry, so parsers drop the line."},
{"id": "malformed_line", "sev": "error", "ref": "RFC 9116 s2.2", "msg": "Line is not in \"Field: value\" form, so it is not parsed at all."}
]
;</script>
<script>/* security.txt Lint - RFC 9116 + EU Cyber Resilience Act reporting contact point.
Same engine runs in Node (extension) and in the browser (free web page). */
var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.STX_RULES;
var KNOWN = ['acknowledgments', 'canonical', 'contact', 'csaf', 'encryption',
'expires', 'hiring', 'policy', 'preferred-languages'];
var ONCE = ['expires', 'preferred-languages', 'canonical'];
/* RFC 3339 s5.6 NOTE: "T" and "Z" may be lower case (google.com and github.com publish ...t...z). */
var RFC3339 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/i;
var DAY = 86400000;
function ruleOf(id) {
for (var i = 0; i < RULES.length; i++) if (RULES[i].id === id) return RULES[i];
return {id: id, sev: 'warn', msg: id};
}
function finding(id, line, extra) {
var r = ruleOf(id);
return {check: id, sev: r.sev, line: line, msg: extra ? r.msg + ' ' + extra : r.msg};
}
function check(text, opts) {
opts = opts || {};
var today = opts.today || new Date().toISOString().slice(0, 10);
var now = Date.parse(today + 'T00:00:00Z');
var lines = String(text == null ? '' : text).replace(/\r\n?/g, '\n').split('\n');
var findings = [];
var seen = {};
var inSignature = false;
for (var i = 0; i < lines.length; i++) {
var ln = i + 1;
var line = lines[i].trim();
if (!line || line.charAt(0) === '#') continue;
if (/^-----BEGIN PGP SIGNATURE-----$/.test(line)) { inSignature = true; continue; }
if (/^-----(BEGIN|END) PGP/.test(line) || /^Hash:/i.test(line)) continue;
if (inSignature) continue;
var m = line.match(/^([A-Za-z][A-Za-z0-9-]*)[ \t]*:[ \t]*(\S.*)$/);
if (!m) { findings.push(finding('malformed_line', ln, '(' + line.slice(0, 40) + ')')); continue; }
var field = m[1].toLowerCase();
var value = m[2].trim();
if (!seen[field]) seen[field] = [];
seen[field].push({line: ln, value: value});
if (KNOWN.indexOf(field) === -1) {
findings.push(finding('unknown_field', ln, '(' + m[1] + ')'));
continue;
}
if (/^http:\/\//i.test(value)) findings.push(finding('insecure_url', ln, '(' + m[1] + ')'));
}
for (var k = 0; k < ONCE.length; k++) {
var hits = seen[ONCE[k]] || [];
for (var d = 1; d < hits.length; d++) {
findings.push(finding('field_duplicate', hits[d].line, '(' + ONCE[k] + ', seen ' + (d + 1) + ' times)'));
}
}
var contacts = seen['contact'] || [];
if (!contacts.length) {
findings.push(finding('contact_missing', 1));
} else {
for (var c = 0; c < contacts.length; c++) {
if (!/^(mailto:|tel:|https:\/\/)/i.test(contacts[c].value)) {
findings.push(finding('contact_not_uri', contacts[c].line, '(' + contacts[c].value + ')'));
}
}
}
var exp = (seen['expires'] || [])[0];
if (!exp) {
findings.push(finding('expires_missing', 1));
} else if (!RFC3339.test(exp.value)) {
findings.push(finding('expires_invalid', exp.line, '(' + exp.value + ')'));
} else {
var days = Math.round((Date.parse(exp.value.toUpperCase()) - now) / DAY);
if (days < 0) findings.push(finding('expires_past', exp.line, '(expired ' + (-days) + ' days ago, on ' + exp.value.slice(0, 10) + ')'));
else if (days > 365) findings.push(finding('expires_over_year', exp.line, '(' + days + ' days out)'));
}
var canon = seen['canonical'] || [];
for (var q = 0; q < canon.length; q++) {
if (!/\/\.well-known\/security\.txt$/.test(canon[q].value)) {
findings.push(finding('canonical_wellknown', canon[q].line, '(' + canon[q].value + ')'));
}
}
if (!(seen['policy'] || []).length) findings.push(finding('policy_missing', 1));
var csaf = seen['csaf'] || [];
for (var s = 0; s < csaf.length; s++) {
if (!/provider-metadata\.json$/.test(csaf[s].value)) {
findings.push(finding('csaf_not_provider', csaf[s].line, '(' + csaf[s].value + ')'));
}
}
findings.sort(function (a, b) { return a.line - b.line; });
return {findings: findings};
}
var API = {engine: {check: check}, RULES: RULES, RULE_COUNT: RULES.length};
if (typeof module !== 'undefined') module.exports = API;
if (typeof window !== 'undefined') window.STXENGINE = API;
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
const f = (res && res.findings) || [];
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
return lines.join('\n');
}
function toHtml(res) {
const f = (res && res.findings) || [];
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var STXREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = STXREPORT;
else window.STXREPORT = STXREPORT;
</script>
<script>
(function(){
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
today.value=new Date().toISOString().slice(0,10);
function run(){ try { var res=window.STXENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.STXREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
go.onclick=run; if (src.value.trim()) run();
})();
</script>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.security-txt-cra-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'security-txt-cra-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/security-txt-cra-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'security-txt-cra-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/security-txt-cra-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'security-txt-cra-lint',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://www.npmjs.com/package/@readystack/security-txt-cra-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'security-txt-cra-lint',src:'mcp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/security-txt-cra-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'security-txt-cra-lint',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><a href="https://apify.com/readystack/seo-compliance-audit" rel="noopener" data-yj-install-door="apify" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'security-txt-cra-lint',src:'apify',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Apify (audit live URLs)</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.security-txt-cra-lint
npx @readystack/security-txt-cra-lint &lt;file&gt;
npx @readystack/security-txt-cra-lint --mcp</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 13 rules security.txt Lint - RFC 9116 + CRA contact point checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"security-txt-cra-lint",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">13 rules that decide whether a vulnerability report ever reaches you</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A security consultant reviewing a disclosure contact and policy starts around $150 an hour</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_ix6VvnndFPQPoppGXnx8ybXWfh7W2eldbZaTM2Zh2br" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
<section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-14">Updated 2026-09-14</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-14","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku54858_result_card.jpg" alt="security.txt Lint - RFC 9116 + CRA contact point" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>9 findings in a 10-line file, and the one that matters most is that the vulnerability contact point expired 75 days ago: that is what security.txt Lint returns for a platform maintainer who ships software into the EU and has not opened /.well-known/security.txt since the day it was written.</p>
<p>Since 11 September 2026, Article 14 of the EU Cyber Resilience Act (Regulation (EU) 2024/2847) has been in force for reporting. When a vulnerability in your product is actively exploited, three clocks start: an early warning to your CSIRT and ENISA within 24 hours, a vulnerability notification within 72 hours, and a final report within 14 days. The penalty ceiling for getting the reporting duty wrong is 15 million euro or 2.5% of worldwide annual turnover.</p>
<p>Every one of those clocks starts when the report reaches you. There is one machine-readable place a stranger looks for where to send it: the security.txt file at /.well-known/ on your domain. RFC 9116 is short and blunt about what makes that file real. Contact is mandatory and must be a URI. Expires is mandatory, must be a full RFC 3339 timestamp, and if the timestamp has passed, the file MUST be ignored. A researcher&#x27;s tooling does not argue: it reads the date, drops the file, and the researcher falls back to a public post. Your 24-hour clock then starts from a tweet you did not see.</p>
<p>What the linter reads</p>
<p>security.txt Lint applies 13 rules inside VS Code, each reported on the line where the problem is:</p>
<ul><li>Expires missing, not RFC 3339, already past, or more than 365 days out</li><li>Contact missing, or written as security@example.com with no mailto: scheme, which is not a URI</li><li>http:// where RFC 9116 requires https://</li><li>Expires, Canonical or Preferred-Languages repeated, when each may appear once</li><li>Canonical pointing anywhere other than the /.well-known/security.txt path scanners actually fetch</li><li>CSAF pointing at an advisory index instead of a provider-metadata.json URI</li><li>Contacts:, Policy-URL: and other unregistered field names, which parsers drop in silence</li><li>lines with no colon, which are not parsed at all</li><li>Policy absent, which leaves the reporter with no disclosure process to follow</li></ul>
<p>The sample file shipped with the extension is ten lines long and looks fine to a human eye. The linter returns 9 findings on it: 5 errors and 4 warnings. The first error is the date arithmetic no chatbot can do for you, because it does not know what today is: Expires ran out on 2026-06-30, which was 75 days before the lint.</p>
<p>Free, and the next job</p>
<p>Linting the file you have open is free and complete. All 13 rules, every line number, no key, no watermark, no limit on how many times you run it. The same engine runs in the browser, so you can paste in a file from a server you have not cloned.</p>
<p>The next job is different in scope and in what you keep. A company that ships more than one product has more than one security.txt: one per domain, one per white-label deployment, one buried in a static site repository. The full version sweeps every security.txt in the workspace in one pass and writes a dated JSON report you keep and attach to an audit file or a customer security questionnaire. It is 29 dollars once, one licence key per person or team seat. A security consultant reviewing a disclosure contact and policy starts around 150 dollars an hour, and that review is a snapshot that goes stale on the day the Expires value passes.</p>
<p>Run it on your own file first. If it comes back clean, you have lost thirty seconds. If it comes back with an expired Expires line, you have learned that for two months nobody could tell you your product was being exploited.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku54858_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku54858_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does security.txt Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads a security.txt file and applies 13 rules from RFC 9116 and the EU Cyber Resilience Act disclosure expectation. It flags a missing or expired Expires line, a Contact that is not a URI, http:// URIs, duplicated fields, a Canonical that is not the /.well-known path, a CSAF link that is not provider-metadata.json, unknown field names and unparsable lines, each on its own line number.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is this for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Maintainers, platform and security engineers who ship software or connected products into the EU and own the /.well-known/security.txt file for their domain. If a stranger finding a live exploit has to guess where to send it, this file is your job, and since 11 September 2026 the Cyber Resilience Act treats it as part of your reporting duty.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why is a free checker or a chatbot not enough?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Since 11 September 2026 the EU Cyber Resilience Act, Regulation (EU) 2024/2847 Article 14, gives you 24 hours for an early warning, 72 hours for a notification and 14 days for a final report on an actively exploited vulnerability, with fines up to 15 million euro or 2.5% of turnover. A chatbot cannot know today&#x27;s date, so it will not tell you your Expires line ran out 75 days ago.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what does the full version add?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Linting the file you have open is free and complete: all 13 rules, every line, no key, no watermark, no limit on how often you run it. The full version changes the scope and the ownership. It scans every security.txt in the workspace in one pass and exports a dated JSON report you keep and attach to an audit or a customer questionnaire.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost me otherwise?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A security consultant reviewing a disclosure contact point and policy starts around $150 an hour, and the review is a snapshot that goes stale the day the Expires value passes. The extension is $29 once, runs on every save, and on the sample file in this repository it returns 9 findings from a 10-line file in under a second.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. security.txt Lint - RFC 9116 and CRA contact point checks the file you open against 13 rules, each naming its legal or official basis, from a rule set dated 2026-09-20, and points at the exact line. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does security.txt Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a security.txt file and applies 13 rules from RFC 9116 and the EU Cyber Resilience Act disclosure expectation. It flags a missing or expired Expires line, a Contact that is not a URI, http:// URIs, duplicated fields, a Canonical that is not the /.well-known path, a CSAF link that is not provider-metadata.json, unknown field names and unparsable lines, each on its own line number."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Maintainers, platform and security engineers who ship software or connected products into the EU and own the /.well-known/security.txt file for their domain. If a stranger finding a live exploit has to guess where to send it, this file is your job, and since 11 September 2026 the Cyber Resilience Act treats it as part of your reporting duty."}}, {"@type": "Question", "name": "Why is a free checker or a chatbot not enough?", "acceptedAnswer": {"@type": "Answer", "text": "Since 11 September 2026 the EU Cyber Resilience Act, Regulation (EU) 2024/2847 Article 14, gives you 24 hours for an early warning, 72 hours for a notification and 14 days for a final report on an actively exploited vulnerability, with fines up to 15 million euro or 2.5% of turnover. A chatbot cannot know today's date, so it will not tell you your Expires line ran out 75 days ago."}}, {"@type": "Question", "name": "What is free and what does the full version add?", "acceptedAnswer": {"@type": "Answer", "text": "Linting the file you have open is free and complete: all 13 rules, every line, no key, no watermark, no limit on how often you run it. The full version changes the scope and the ownership. It scans every security.txt in the workspace in one pass and exports a dated JSON report you keep and attach to an audit or a customer questionnaire."}}, {"@type": "Question", "name": "What would this cost me otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "A security consultant reviewing a disclosure contact point and policy starts around $150 an hour, and the review is a snapshot that goes stale the day the Expires value passes. The extension is $29 once, runs on every save, and on the sample file in this repository it returns 9 findings from a 10-line file in under a second."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. security.txt Lint - RFC 9116 and CRA contact point checks the file you open against 13 rules, each naming its legal or official basis, from a rule set dated 2026-09-20, and points at the exact line. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:54858,slug:"security-txt-cra-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/cra-24-72-14-reporting-lint"><span class="yj-rel-t">CRA 24/72/14 Reporting Lint (Article 14)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/robots-txt-ai-crawler-audit"><span class="yj-rel-t">AI Crawler Rules - robots.txt Audit for AI Search</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/csaf-advisory-check-cra-2026"><span class="yj-rel-t">CSAF Advisory Check for CRA 2026</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/firestore-rules-guard"><span class="yj-rel-t">Firestore Rules Guard</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/dotnet-eol-support-gate"><span class="yj-rel-t">.NET EOL &amp; CRA Support Window Gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/bulk-sender-lint"><span class="yj-rel-t">Bulk Sender Lint - the Gmail, Yahoo and Outlook.com rules, checked in your mai</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body>
</html>