Fetch Metadata Headers
Fetch Metadata Headers
Using Fetch Metadata for Request Validation
class FetchMetadataProtection {
constructor() {
this.allowedNavigationSources = new Set(['same-origin', 'same-site']);
}
middleware() {
return (req, res, next) => {
// Fetch metadata headers
const secFetchSite = req.headers['sec-fetch-site'];
const secFetchMode = req.headers['sec-fetch-mode'];
const secFetchUser = req.headers['sec-fetch-user'];
const secFetchDest = req.headers['sec-fetch-dest'];
// Log metadata for analysis
req.fetchMetadata = {
site: secFetchSite,
mode: secFetchMode,
user: secFetchUser,
dest: secFetchDest
};
// Protect against CSRF using Fetch Metadata
if (this.shouldBlockRequest(req)) {
res.status(403).json({
error: 'Request blocked by Fetch Metadata policy',
metadata: req.fetchMetadata
});
return;
}
next();
};
}
shouldBlockRequest(req) {
const { site, mode, dest } = req.fetchMetadata;
// Block cross-site requests that are not navigations
if (site === 'cross-site' && mode !== 'navigate') {
return true;
}
// Block requests to sensitive endpoints from cross-origin
if (req.path.startsWith('/api/admin') && site !== 'same-origin') {
return true;
}
// Allow same-site and same-origin requests
return false;
}
// Resource Isolation Policy
resourceIsolationPolicy() {
return (req, res, next) => {
const { site, mode, dest } = req.fetchMetadata;
// Isolate resources based on fetch metadata
if (dest === 'script' || dest === 'style') {
if (site !== 'same-origin') {
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
}
}
// Protect images from hotlinking
if (dest === 'image' && site === 'cross-site') {
res.status(403).send('Hotlinking not allowed');
return;
}
next();
};
}
}