{"id":17648,"library":"fetch-metadata","title":"Fetch Metadata Middleware","description":"The `fetch-metadata` package provides Node.js middleware designed for Express and Connect applications to enforce browser Fetch metadata request headers, such as `Sec-Fetch-Site`, `Sec-Fetch-Mode`, and `Sec-Fetch-Dest`. This middleware plays a crucial role in enhancing application security by helping to prevent common web vulnerabilities like Cross-Site Request Forgery (CSRF), Cross-Site Script Inclusion (XSSI), and information leakage attacks, as part of a defense-in-depth strategy. Currently at stable version 1.0.0, it offers a highly configurable API allowing developers to define granular policies for request origins, navigation types, and specific allowed paths. While a specific release cadence isn't published, its initial stable release suggests a focus on reliability for security-critical applications. Its key differentiator lies in its specific focus on these modern browser security headers, providing a ready-to-use solution for integrating these protections into existing Node.js web servers.","status":"active","version":"1.0.0","language":"javascript","source_language":"en","source_url":"ssh://git@github.com/jperasmus/fetch-metadata","tags":["javascript","node.js","middleware","express","connect","fetch","metadata","request","security","typescript"],"install":[{"cmd":"npm install fetch-metadata","lang":"bash","label":"npm"},{"cmd":"yarn add fetch-metadata","lang":"bash","label":"yarn"},{"cmd":"pnpm add fetch-metadata","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"This library primarily uses a default export, typically consumed as a function to initialize the middleware. While it ships TypeScript types, the import pattern remains the same for both JavaScript and TypeScript.","wrong":"const fetchMetadata = require('fetch-metadata')","symbol":"fetchMetadata","correct":"import fetchMetadata from 'fetch-metadata'"},{"note":"For TypeScript users, the configuration options type can be imported for stronger type checking when defining middleware options.","symbol":"FetchMetadataOptions","correct":"import type { FetchMetadataOptions } from 'fetch-metadata'"}],"quickstart":{"code":"import express from 'express';\nimport fetchMetadata from 'fetch-metadata';\n\nconst app = express();\nconst port = 3000;\n\n// Apply the fetch-metadata middleware with custom configuration.\n// It will allow 'same-origin', 'same-site', and 'none' (user interaction) requests.\n// It will block navigation requests trying to embed 'object' or 'embed' elements.\napp.use(\n  fetchMetadata({\n    allowedFetchSites: ['same-origin', 'same-site', 'none'],\n    disallowedNavigationRequests: ['object', 'embed'],\n    errorStatusCode: 403,\n    allowedPaths: [\n      '/public-data', // Allow access to this path regardless of fetch metadata\n      { path: '/health-check', method: 'GET' } // Allow GET requests to /health-check\n    ],\n    onError: (request, response, next, options) => {\n      console.warn(`[Fetch Metadata] Blocked request to ${request.url} from site ${request.headers['sec-fetch-site'] || 'N/A'}`);\n      response.status(options.errorStatusCode).send('Access Denied: Request blocked by security policy.');\n      // For testing or specific bypasses, you could call next() here to allow the request:\n      // next();\n    }\n  })\n);\n\n// Define a simple root route\napp.get('/', (req, res) => {\n  res.send(`\n    <h1>Fetch Metadata Middleware Demo</h1>\n    <p>This server enforces Fetch Metadata Request Headers.</p>\n    <p>Try making requests from different origins or contexts using browser dev tools.</p>\n    <p>A fetch from 'same-origin' to /protected should succeed.</p>\n    <a href=\"/public-data\">Public Data (always allowed)</a><br/>\n    <a href=\"/health-check\">Health Check (GET allowed)</a>\n    <script>\n      fetch('/protected')\n        .then(response => response.text())\n        .then(text => console.log('GET /protected (same-origin):', text))\n        .catch(error => console.error('GET /protected (same-origin) failed:', error));\n\n      fetch('/public-data')\n        .then(response => response.text())\n        .then(text => console.log('GET /public-data (allowed path):', text))\n        .catch(error => console.error('GET /public-data (allowed path) failed:', error));\n    </script>\n  `);\n});\n\n// A protected route that relies on fetch metadata policies\napp.get('/protected', (req, res) => {\n  res.send('You accessed a protected resource (Sec-Fetch-Site: same-origin/same-site/none allowed).');\n});\n\n// An explicitly allowed public route via 'allowedPaths'\napp.get('/public-data', (req, res) => {\n  res.send('This is public data, accessible via allowedPaths config.');\n});\n\n// A health check route with a specific method allowed via 'allowedPaths'\napp.get('/health-check', (req, res) => {\n  res.status(200).send('Service is healthy!');\n});\n\n// Start the server\napp.listen(port, () => {\n  console.log(`Server listening on http://localhost:${port}`);\n  console.log('To test: Open in browser, then try to fetch /protected from a different origin (e.g., using browser console or a separate HTML page on another domain).');\n});","lang":"typescript","description":"Demonstrates how to install `fetch-metadata` and integrate it into an Express application with a basic configuration. This example shows how to set allowed fetch sites, disallow specific navigation requests, define allowed paths to bypass checks, and implement a custom error handler."},"warnings":[{"fix":"Thoroughly test configurations in various browser contexts and user scenarios. Start with a more permissive configuration and tighten it gradually, monitoring logs for blocked requests (e.g., via the `onError` callback).","message":"Misconfiguring `allowedFetchSites` or `disallowedNavigationRequests` can unintentionally block legitimate requests, leading to application downtime or degraded user experience. Understand the implications of each `Sec-Fetch-*` header value before deployment.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Always ensure your `onError` implementation includes `response.status(statusCode).send(message)` or `next()`.","message":"When providing a custom `onError` callback, it is crucial to ensure that the function either terminates the response (e.g., `response.end()`, `response.send()`) or explicitly calls `next()` to pass control to the next middleware. Failing to do so will cause the request to hang indefinitely.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Refer to the `url-pattern` documentation for advanced path matching syntax. Test all `allowedPaths` entries thoroughly, especially those with dynamic segments or regular expressions, to ensure they match as expected.","message":"The `allowedPaths` configuration uses the `url-pattern` library for path matching, which might have subtle differences compared to Express's native path-to-regexp parsing. While it supports dynamic segments, be mindful of exact syntax for complex patterns.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-23T00:00:00.000Z","next_check":"2026-07-22T00:00:00.000Z","problems":[{"fix":"Use `import fetchMetadata from 'fetch-metadata'` for ESM modules, or `const fetchMetadata = require('fetch-metadata').default` for CommonJS environments (though the former is recommended).","cause":"Attempting to use `require('fetch-metadata')` without accessing the default export, or using `import { fetchMetadata } from 'fetch-metadata'` instead of a default import.","error":"TypeError: fetchMetadata is not a function"},{"fix":"Check the server console for warnings from the `onError` callback. Adjust `allowedFetchSites`, `disallowedNavigationRequests`, or add the problematic path to `allowedPaths` configuration. Ensure your client-side requests are sending appropriate Fetch Metadata headers.","cause":"A request was blocked by the middleware's policy, likely due to an unexpected `Sec-Fetch-Site` or `Sec-Fetch-Dest` header value, or a request to a non-allowed path.","error":"Access Denied: Request blocked by security policy."},{"fix":"Modify your `onError` callback to either call `response.status(statusCode).send(message)` to terminate the request with an error, or `next()` if you wish to bypass the block and allow the request to proceed (e.g., for logging and allowing in specific cases).","cause":"The custom `onError` callback was implemented without sending a response or passing control to the next middleware, leaving the request unresolved.","error":"Request hangs indefinitely (no response from server)."}],"ecosystem":"npm","meta_description":null,"install_score":null,"install_tag":null,"quickstart_score":null,"quickstart_tag":null}