CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-make-fetch-happen

Opinionated, caching, retrying fetch client for Node.js with enterprise-grade HTTP features

74

1.68x
Overview
Eval results
Files

security.mddocs/

Security and Integrity

Security features including SSL configuration, certificate validation, and subresource integrity verification for secure HTTP communications.

Capabilities

SSL/TLS Configuration

Configure SSL certificate validation and client certificate authentication:

/**
 * SSL/TLS configuration options
 */
interface SSLOptions {
  strictSSL?: boolean;    // Enable/disable SSL certificate validation (default: true)
  ca?: string | Buffer | string[] | Buffer[]; // Certificate authority certificates
  cert?: string | Buffer; // Client certificate
  key?: string | Buffer;  // Client private key
  rejectUnauthorized?: boolean; // Alias for strictSSL (internal use)
}

Usage Examples:

const fs = require('fs');
const fetch = require('make-fetch-happen');

// Disable SSL verification (not recommended for production)
const response1 = await fetch('https://self-signed.example.com/api', {
  strictSSL: false
});

// Custom CA certificate
const response2 = await fetch('https://internal-api.company.com/data', {
  ca: fs.readFileSync('./company-ca.pem')
});

// Client certificate authentication
const response3 = await fetch('https://secure-api.example.com/data', {
  cert: fs.readFileSync('./client-cert.pem'),
  key: fs.readFileSync('./client-key.pem'),
  ca: fs.readFileSync('./ca-cert.pem')
});

// Multiple CA certificates
const response4 = await fetch('https://api.example.com/data', {
  ca: [
    fs.readFileSync('./ca1.pem'),
    fs.readFileSync('./ca2.pem'),
    fs.readFileSync('./ca3.pem')
  ]
});

Environment-Based SSL Configuration

SSL behavior respects Node.js environment variables:

/**
 * Environment variable support:
 * - NODE_TLS_REJECT_UNAUTHORIZED: When set to '0', disables SSL verification
 * - This affects the default value of strictSSL option
 * 
 * Priority order:
 * 1. Explicit strictSSL option in request
 * 2. NODE_TLS_REJECT_UNAUTHORIZED environment variable
 * 3. Default (true - SSL verification enabled)
 */

Usage Examples:

# Disable SSL verification globally (not recommended for production)
export NODE_TLS_REJECT_UNAUTHORIZED=0
node app.js

# Or per command
NODE_TLS_REJECT_UNAUTHORIZED=0 node app.js
// Environment variable affects default behavior
const response = await fetch('https://self-signed.example.com/api');
// If NODE_TLS_REJECT_UNAUTHORIZED=0, SSL verification is disabled

// Explicit override of environment
const response2 = await fetch('https://self-signed.example.com/api', {
  strictSSL: true  // Forces SSL verification regardless of environment
});

Subresource Integrity (SRI)

Verify response content integrity using cryptographic hashes:

/**
 * Subresource Integrity options
 */
interface IntegrityOptions {
  integrity?: string;      // SRI hash string (sha256-, sha384-, sha512-)
  algorithms?: string[];   // Allowed hash algorithms for verification
  size?: number;          // Expected content size in bytes
}

/**
 * Supported SRI hash formats:
 * - sha256-<base64-hash>
 * - sha384-<base64-hash>
 * - sha512-<base64-hash>
 * - Multiple hashes separated by spaces
 */

Usage Examples:

// Verify file integrity with SHA256
const response1 = await fetch('https://cdn.example.com/library.js', {
  integrity: 'sha256-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC'
});

// Multiple hash algorithms (any can match)
const response2 = await fetch('https://cdn.example.com/library.js', {
  integrity: 'sha256-abc123... sha512-def456...'
});

// With size verification
const response3 = await fetch('https://cdn.example.com/large-file.zip', {
  integrity: 'sha256-xyz789...',
  size: 1024 * 1024 * 50  // Expect 50MB file
});

// Custom algorithms (limits which hashes are accepted)
const response4 = await fetch('https://cdn.example.com/file.dat', {
  integrity: 'sha512-abc123...',
  algorithms: ['sha512']  // Only accept SHA512 hashes
});

Integrity Verification Process

Understanding how integrity verification works:

/**
 * Integrity verification process:
 * 1. Response body is streamed through integrity verification
 * 2. Hash is calculated as data flows through
 * 3. Final hash is compared against expected integrity value
 * 4. If mismatch, request fails with EINTEGRITY error
 * 5. Verification only occurs on 200 responses
 */

Usage Examples:

try {
  const response = await fetch('https://cdn.example.com/tampered-file.js', {
    integrity: 'sha256-expected-hash-that-wont-match'
  });
  const content = await response.text();
} catch (error) {
  if (error.code === 'EINTEGRITY') {
    console.log('File integrity verification failed!');
    console.log('Expected:', error.expected);
    console.log('Actual:', error.actual);
  }
}

// Integrity verification with caching
const response = await fetch('https://cdn.example.com/library.js', {
  cachePath: './cache',
  integrity: 'sha256-abc123...'
});
// Cached responses are also verified against integrity hash

Certificate Management Patterns

Common patterns for certificate management:

const fs = require('fs');
const path = require('path');

// Certificate loading utility
const loadCertificates = (certDir) => {
  return {
    ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
    cert: fs.readFileSync(path.join(certDir, 'client.pem')),
    key: fs.readFileSync(path.join(certDir, 'client-key.pem'))
  };
};

// Environment-specific certificate configuration
const createSecureFetch = (env) => {
  const config = {};
  
  if (env === 'development') {
    // More permissive for development
    config.strictSSL = false;
  } else if (env === 'staging') {
    // Staging certificates
    const certs = loadCertificates('./certs/staging');
    Object.assign(config, certs);
  } else if (env === 'production') {
    // Production certificates with strict validation
    const certs = loadCertificates('./certs/production');
    Object.assign(config, certs, { strictSSL: true });
  }
  
  return fetch.defaults(config);
};

// Certificate rotation handling
class CertificateManager {
  constructor(certDir) {
    this.certDir = certDir;
    this.certificates = null;
    this.lastLoaded = 0;
    this.refreshInterval = 60 * 60 * 1000; // 1 hour
  }
  
  loadCertificates() {
    const now = Date.now();
    if (!this.certificates || (now - this.lastLoaded) > this.refreshInterval) {
      this.certificates = {
        ca: fs.readFileSync(path.join(this.certDir, 'ca.pem')),
        cert: fs.readFileSync(path.join(this.certDir, 'client.pem')),
        key: fs.readFileSync(path.join(this.certDir, 'client-key.pem'))
      };
      this.lastLoaded = now;
    }
    return this.certificates;
  }
  
  createFetch() {
    const certs = this.loadCertificates();
    return fetch.defaults(certs);
  }
}

// Usage
const certManager = new CertificateManager('./certs');
const secureFetch = certManager.createFetch();

Security Error Handling

Handle security-related errors appropriately:

/**
 * Security-related error codes:
 * - EINTEGRITY: Subresource integrity verification failed
 * - DEPTH_ZERO_SELF_SIGNED_CERT: Self-signed certificate
 * - UNABLE_TO_VERIFY_LEAF_SIGNATURE: Certificate verification failed
 * - CERT_HAS_EXPIRED: Certificate has expired
 * - CERT_NOT_YET_VALID: Certificate not yet valid
 * - UNABLE_TO_GET_ISSUER_CERT: Cannot get certificate issuer
 */

Usage Examples:

try {
  const response = await fetch('https://secure-api.example.com/data', {
    cert: clientCert,
    key: clientKey,
    ca: caCert,
    integrity: 'sha256-expected-hash'
  });
} catch (error) {
  switch (error.code) {
    case 'EINTEGRITY':
      console.log('Response integrity verification failed');
      // Log security incident
      break;
    case 'DEPTH_ZERO_SELF_SIGNED_CERT':
      console.log('Server uses self-signed certificate');
      // Decide whether to allow or reject
      break;
    case 'CERT_HAS_EXPIRED':
      console.log('Server certificate has expired');
      // Alert operations team
      break;
    case 'UNABLE_TO_VERIFY_LEAF_SIGNATURE':
      console.log('Certificate verification failed');
      // Security error - do not retry
      break;
    default:
      console.log('Security error:', error.message);
  }
}

// Graceful degradation for certificate issues
const fetchWithFallback = async (url, options = {}) => {
  try {
    return await fetch(url, options);
  } catch (error) {
    if (error.code === 'DEPTH_ZERO_SELF_SIGNED_CERT' && 
        process.env.NODE_ENV === 'development') {
      console.warn('Allowing self-signed cert in development');
      return await fetch(url, { ...options, strictSSL: false });
    }
    throw error;
  }
};

Content Security Policies

Integrate with content security policies and security headers:

// Generate SRI hashes for CSP
const crypto = require('crypto');

const generateIntegrityHash = (content, algorithm = 'sha256') => {
  const hash = crypto.createHash(algorithm);
  hash.update(content);
  return `${algorithm}-${hash.digest('base64')}`;
};

// Fetch and generate CSP-compatible integrity hash
const response = await fetch('https://cdn.example.com/library.js');
const content = await response.text();
const integrityHash = generateIntegrityHash(content);

console.log(`<script src="https://cdn.example.com/library.js" 
             integrity="${integrityHash}" 
             crossorigin="anonymous"></script>`);

// Verify against known good hashes
const knownGoodHashes = {
  'library.js': 'sha256-abc123...',
  'framework.js': 'sha256-def456...'
};

const secureResponse = await fetch('https://cdn.example.com/library.js', {
  integrity: knownGoodHashes['library.js']
});

Install with Tessl CLI

npx tessl i tessl/npm-make-fetch-happen

docs

caching.md

configuration.md

core-fetch.md

index.md

network.md

retry.md

security.md

tile.json