Agent Wranglers

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

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.