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();
        };
    }
}