Include the full URL with the https:// prefix
| Header | Status | Description | Value |
|---|
Security headers are HTTP response headers that tell browsers how to behave when handling your content. Each card below covers two perspectives: API builders setting headers on their server, and API consumers writing code that calls someone else's API - including what to do differently if that API scores poorly.
Strict-Transport-Security
Forces browsers to connect exclusively over HTTPS
Attack Prevented
SSL stripping - an attacker on the same network intercepts your initial HTTP request and blocks the browser from upgrading to HTTPS. Login credentials and session cookies travel in plaintext, fully visible to the attacker.
Recommended Value
max-age=31536000; includeSubDomains
For Developers
Only set this once your entire domain - and all subdomains if using includeSubDomains - serves HTTPS. Once a browser caches this header it will refuse HTTP for the full max-age duration. There is no quick rollback.
If missing or low grade
Always hard-code https:// in your base URL. Never rely on the API redirecting HTTP to HTTPS - an attacker on the same network can intercept that unencrypted request before any redirect occurs. Avoid sending credentials or user data to any API that does not enforce HTTPS.
If present
A positive signal that the operator enforces encrypted transport. Still always hard-code https:// yourself. In server-side code, never disable TLS certificate verification - it is the only thing confirming you are talking to the real server.
Content-Security-Policy
Whitelists the sources allowed to load scripts, styles, and media
Attack Prevented
Cross-Site Scripting (XSS) - even when an attacker succeeds in injecting malicious code into your page, a strict CSP prevents unauthorised scripts from executing or sending data to attacker-controlled servers.
Recommended Value
default-src 'self'
Then add specific exceptions for your actual resource origins.
For Developers
The most powerful but most complex header. Start with Content-Security-Policy-Report-Only to log violations without breaking anything. Avoid unsafe-inline and unsafe-eval - these largely defeat the protection.
CSP does not affect your fetch() calls or HTTP client - it governs what resources load inside a browser page, not the behaviour of outbound requests. However, if you render API response data in the browser (displaying HTML or user-generated content returned by the API), always sanitize it before inserting into the DOM. A missing CSP on the API side is also a signal that the operator may have lower overall security hygiene, which should factor into how much you trust the data they return.
X-Frame-Options
Prevents your pages from being embedded in iframes
Attack Prevented
Clickjacking - an attacker embeds your site in an invisible iframe layered over a decoy page. Users think they are clicking harmless buttons but are actually triggering actions on your site, such as approving payments or changing account settings.
Recommended Value
DENY
Or SAMEORIGIN if your own site needs to embed its own pages.
For Developers
Use DENY unless you have a specific need for embedding. For modern browsers, Content-Security-Policy: frame-ancestors is the more flexible successor - but X-Frame-Options gives you wider legacy coverage at zero cost.
Not relevant to programmatic HTTP requests. X-Frame-Options only controls whether a URL can be embedded in an <iframe> - it has no effect on fetch(), XMLHttpRequest, or any server-side HTTP client. Your code does not need to change based on this header's presence or absence.
X-Content-Type-Options
Stops browsers from guessing a response's content type
Attack Prevented
MIME-type confusion - a user uploads a file disguised as an image, the server stores it with the wrong content-type, and the browser "sniffs" the actual content and executes it as JavaScript, running malicious code in the victim's browser.
Recommended Value
nosniff
For Developers
One of the simplest headers to add with no known functional downsides. Set it globally on every response and forget about it. If you see issues, they usually indicate that your server is sending incorrect Content-Type values that need fixing anyway.
If missing
In browser environments, always use explicit response parsers - call response.json() for JSON endpoints rather than reading raw text. Be especially careful with file download endpoints: without nosniff, a browser may execute content as a different type than the server intended, which could be exploited if the API is ever compromised.
If present
The API enforces its declared Content-Type values. Parsers like response.json() behave predictably and consistently across browsers. You are less likely to encounter MIME-sniffing edge cases with file or binary responses from this API.
Referrer-Policy
Controls how much URL information browsers share when users follow links
Attack Prevented
Information leakage - without this header, browsers send the full page URL as the Referer header on every outbound request. URLs frequently contain session tokens, password-reset links, user IDs, and internal path structures that third-party services should never see.
Recommended Value
strict-origin-when-cross-origin
For Developers
The browser default (no-referrer-when-downgrade) sends full URLs to any HTTPS destination. strict-origin-when-cross-origin is the practical safe choice: only the origin is sent cross-site, while same-origin requests keep the full URL for your own analytics.
If missing
Your browser sends your full page URL - including any query parameters - as the Referer header with every request by default. This leaks session tokens, user IDs, and internal paths to the API's logs. Opt out explicitly in browser code: fetch(url, { referrerPolicy: 'no-referrer' }). Server-side HTTP clients typically do not send a Referer header at all, so this mainly concerns browser-based consumers.
If present
The API operator controls what referrer data they log. For full control regardless of their policy, set your own per-request: fetch(url, { referrerPolicy: 'no-referrer' }) prevents your page URL from being sent no matter what the server declares.
Permissions-Policy
Restricts which browser APIs and hardware features your page may use
Attack Prevented
Privilege abuse via XSS - if an attacker gains script execution on your page, a Permissions-Policy prevents them from silently accessing the camera, microphone, geolocation, or payment APIs. It also locks down third-party iframes from using features you haven't granted.
Recommended Value
camera=(), microphone=(), geolocation=()
Disable each feature you don't use. Only permit what your app genuinely needs.
For Developers
Formerly called Feature-Policy. Browser defaults vary, so being explicit is safer than relying on them. Audit which APIs your application actually uses - if your app has no need for geolocation or camera access, explicitly deny them so a compromised dependency cannot enable them.
Not relevant to programmatic HTTP requests. Permissions-Policy governs browser feature access for the document that receives the header - it only takes effect when a URL is loaded as a navigable page, not when data is fetched via fetch() or an HTTP client. Your code does not need to change based on this header's presence or absence.
api/audit.js
A Vercel serverless function. It receives a URL from the browser, validates it, runs the audit, and returns the result as JSON. There is no persistent server - Vercel spins up a fresh instance for each request.
import { getAuditResult } from 'audit-fetch' export default async function handler(req, res) { // Only allow POST requests if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }) } // CORS headers so the browser frontend can call this endpoint res.setHeader('Access-Control-Allow-Origin', '*') const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body const { url } = body // Validate the URL exists and is a valid format if (!url) { return res.status(400).json({ error: 'No URL provided' }) } try { new URL(url) } catch { return res.status(400).json({ error: 'Invalid URL format' }) } // Run the audit - silent:true suppresses the terminal report try { const result = await getAuditResult(url, { audit: { silent: true } }) return res.status(200).json(result) } catch (error) { return res.status(500).json({ error: `Failed to reach URL: ${error.message}` }) } }
No always-on server. Vercel cold-starts the function on each request and tears it down afterwards - there is nothing to maintain or scale manually.
First checks the field exists, then calls new URL() which throws on malformed input - a simple way to reject junk before making any network calls.
Suppresses the chalk-coloured terminal report that audit-fetch prints by default. Vercel's log output doesn't need it; only the JSON response matters here.
audit-fetch
An npm package that wraps the native fetch() API with an automatic security header check. It exposes two functions - one as a drop-in fetch replacement, and one that just returns the audit data.
Makes a standard HTTP request using the native fetch API
Passes response.headers to runAudit()
Each header is marked present, missing, or misconfigured
Counts present headers, converts to a letter grade A–F
getAuditResult returns JSON; auditFetch returns the original Response
A drop-in replacement for fetch(). Swap it into your existing code and it audits headers on every request, printing a terminal report automatically. Accepts a failOn option to throw an error if the grade falls below a threshold - useful for CI pipelines.
Returns structured JSON instead of the original Response object. This is what the API endpoint uses - the web app only needs the audit data, not the raw HTTP response from the target URL.