APS-29Recommended
Refuse odd requests from other sites
Browsers send Sec-Fetch-* headers that say where a request came from. Use them to refuse cross-site requests that are not normal page visits.
How to do it
In middleware, allow requests with no Sec-Fetch-Site, or with same-origin, same-site or none. Also allow cross-site GET navigations, except into object or embed. Answer 403 to the rest. Exempt public APIs and files meant for other sites. It backs up the token or origin check in Block cross-site form posts.
Examples
JavaScript example
function fetchMetadata(req, res, next) {
const site = req.get('Sec-Fetch-Site');
if (!site || ['same-origin', 'same-site', 'none'].includes(site)) return next();
const dest = req.get('Sec-Fetch-Dest');
if (req.method === 'GET' && req.get('Sec-Fetch-Mode') === 'navigate' && dest !== 'object' && dest !== 'embed') return next();
res.status(403).end();
}
Python example
# Flask
@app.before_request
def fetch_metadata():
site = request.headers.get("Sec-Fetch-Site")
if not site or site in ("same-origin", "same-site", "none"):
return None
navigating = request.method == "GET" and request.headers.get("Sec-Fetch-Mode") == "navigate"
if navigating and request.headers.get("Sec-Fetch-Dest") not in ("object", "embed"):
return None
abort(403)
How to check
curl -s -o /dev/null -w '%{http_code}' -H 'Sec-Fetch-Site: cross-site' -H 'Sec-Fetch-Mode: cors' URL prints 403. The same request with Sec-Fetch-Mode: navigate and Sec-Fetch-Dest: document prints 200.
Sources
Related rules
- APS-18 Block cross-site form posts in Application security
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/application-security/browser-security-headers/fetch-metadata">Agent Wranglers application security playbook</a>. In code you write, add a comment next to it with the same link.