no-missing-security-headers
Detects HTTP responses that set headers but omit a required security header (deprecated).
Detects HTTP responses that set headers but omit a required security header.
⚠️ Deprecated. This rule is deprecated and no longer in any preset. Use
express-security/require-helmet, which sets the whole header family at once. For a document sent without a Content-Security-Policy,require-csp-headersreports it whether or not this rule is enabled. The rule is still exported and still works when enabled explicitly, until the next major removes it.
CWE: CWE-693
OWASP: Secure Headers Project
Detects HTTP responses that set headers but omit a required security header. This rule is part of eslint-plugin-browser-security.
Quick Summary
| Aspect | Details |
|---|---|
| CWE Reference | CWE-693 (Protection Mechanism Failure) |
| Severity | Medium |
| Auto-Fix | ❌ No |
| Category | Security |
| Status | Deprecated — replaced by express-security/require-helmet |
Vulnerability and Risk
Vulnerability: A response that renders a document without Content-Security-Policy, X-Frame-Options and X-Content-Type-Options leaves out the browser-side mitigations for XSS, clickjacking and MIME sniffing.
Risk: An injection that a policy would have contained executes; the page can be framed and its clicks hijacked; a user-uploaded file is sniffed into script.
Rule Details
The rule looks at a scope (a route handler or middleware function) that explicitly sets response headers — through res.setHeader, res.set, res.header, res.writeHead, or a declared headers: { … } block — and reports when one of requiredHeaders is missing from what it sets. Header names are compared case-insensitively, and every setter spelling counts. A header name is read through any chain of const aliases and array tables (H[0] and H['0'] are the same element), and a declared block's keys are read however they are written — 'X-Frame-Options', ['X-Frame-Options'], 'headers', 'status' and a Next.js 'key' / 'value' entry all count. Each function is its own scope and is reported at most once.
A scope that sets no header at all is out of scope: there is no call to judge. A scope that sets only transport or caching headers (Cache-Control, Set-Cookie, Location, ETag, …) is not serving a document and is not reported. A .set(key, value) on something that is not a response (a Map, a feature-flag store) is not a header write.
Examples
❌ Incorrect
app.get('/', (req, res) => {
res.setHeader('X-Frame-Options', 'DENY');
res.send(page); // Content-Security-Policy and X-Content-Type-Options missing
});
app.get('/', (req, res) => {
res.set('Content-Type', 'text/html; charset=utf-8');
res.send(page); // all three required headers missing
});✅ Correct
app.get('/', (req, res) => {
res.setHeader('Content-Security-Policy', "default-src 'self'");
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.send(page);
});
// Caching-only headers: not a document response
app.get('/asset', (req, res) => {
res.setHeader('Cache-Control', 'no-store');
res.send(buffer);
});
// Preferred: let helmet set the family (see express-security/require-helmet)
app.use(helmet());Configuration
// eslint.config.mjs
export default [
{
rules: {
'browser-security/no-missing-security-headers': [
'error',
{ requiredHeaders: ['X-Frame-Options'] },
],
},
},
];requiredHeaders replaces the default list.
Options
| Option | Type | Default | Description |
|---|---|---|---|
requiredHeaders | string[] | ["Content-Security-Policy","X-Frame-Options","X-Content-Type-Options"] | Security headers a response must set |
ignoreInTests | boolean | true | Skip this rule in *.test.* / *.spec.* files |
Related Rules
require-csp-headers- a document emitted without a Content-Security-Policyno-clickjacking- frame protection (also deprecated)
Known False Negatives
Headers set by app-level middleware
Why: The rule judges one scope at a time; a header set in middleware registered elsewhere is invisible to the handler.
// ❌ NOT DETECTED as protected — and not reported either, when the handler sets no header
app.use(setSecurityHeaders);
app.get('/', (req, res) => res.send(page));Mitigation: Use helmet at the app level.
Further Reading
- OWASP Secure Headers Project - Best practices
- CWE-693: Protection Mechanism Failure - Official CWE entry
Use this rule
npm install -D oxlint-plugin-browser-security{ "jsPlugins": ["oxlint-plugin-browser-security"], "rules": { "browser-security/no-missing-security-headers": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-browser-security'], rules: { 'browser-security/no-missing-security-headers': 'error', }, },});oxlint-plugin-browser-security re-exports eslint-plugin-browser-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.
Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.