-
Notifications
You must be signed in to change notification settings - Fork 2
Compat Fetch
Cross-runtime HTTP fetch with TLS client authentication and Unix socket support.
- Features
- Installation
- Quick Start
- TLS Client Authentication
- Unix Socket Connections
- API Reference
- Error Handling
- Security
- Related Documentation
| Feature | Deno | Bun | Node.js |
|---|---|---|---|
| Basic fetch | ✅ | ✅ | ✅ |
| TLS client authentication | ✅ | ✅ | ❌* |
| Unix domain sockets | ✅ | ✅ | ❌* |
| File-based TLS config | ✅ | ✅ | ❌* |
| String-based TLS config | ✅ | ✅ | ❌* |
| Path traversal protection | ✅ | ✅ | N/A |
| PEM validation | ✅ | ✅ | N/A |
*Node.js requires the undici library for TLS client auth and Unix sockets.
Deno:
deno add @tundralibs/compatBun:
bunx jsr add @tundralibs/compatNode.js:
npx jsr add @tundralibs/compatDirect import (Deno):
import { fetch } from 'jsr:@tundralibs/compat/fetch';import { fetch } from '@tundralibs/compat/fetch';
// Basic request (works like standard fetch)
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// With TLS client certificate
const secureResponse = await fetch('https://secure.api.com/data', {
tls: {
certFile: '/path/to/client.crt',
keyFile: '/path/to/client.key',
},
});
// Via Unix socket
const dockerResponse = await fetch('http://localhost/containers/json', {
unix: '/var/run/docker.sock',
});TLS client authentication (mTLS) allows your application to authenticate itself to a server using certificates.
Recommended for production environments where certificates are stored on disk:
import { FetchFileNotFoundError, FetchTLSError } from '@tundralibs/compat';
import { fetch } from '@tundralibs/compat/fetch';
const response = await fetch('https://secure.api.com/data', {
tls: {
certFile: '/etc/ssl/client.crt', // Path to certificate
keyFile: '/etc/ssl/client.key', // Path to private key
caFile: '/etc/ssl/ca.crt', // Optional: Custom CA
},
});For embedded credentials or when loading from environment variables:
import { fetch } from '@tundralibs/compat/fetch';
const response = await fetch('https://secure.api.com/data', {
tls: {
cert: process.env.CLIENT_CERT!, // PEM certificate string
key: process.env.CLIENT_KEY!, // PEM private key string
ca: [process.env.CA_CERT!], // Optional: CA certificates array
},
});All certificates and keys must be in PEM format:
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAJC1HiIAZAiUANBgkqhkiG9w0Bahq...
-----END CERTIFICATE-----
Common key types:
-
PRIVATE KEY(PKCS#8) -
RSA PRIVATE KEY(PKCS#1) -
EC PRIVATE KEY(Elliptic curve)
Note: Unlike
cert/ca(checked against theCERTIFICATElabel),key's PEM label isn't verified — validation only confirms balanced-----BEGIN X-----/-----END X-----markers, not thatXsaysPRIVATE KEY. A mislabeled or wrong-type block still passes this layer; the runtime's TLS stack is what rejects a genuinely invalid key at connect time.
Passphrase-protected (encrypted) private keys are not supported on any
runtime — TLSOptions has no passphrase field. Decrypt the key before use:
# Works for PKCS#8, RSA, and EC keys
openssl pkey -in encrypted.key -out decrypted.key
⚠️ Security Note: Store decrypted keys with restrictive permissions (chmod 600) and never commit them to version control.
Connect to services via Unix domain sockets instead of TCP:
import { fetch } from '@tundralibs/compat/fetch';
// Docker API
const containers = await fetch('http://localhost/containers/json', {
unix: '/var/run/docker.sock',
});
// Local service
const health = await fetch('http://localhost/health', {
unix: '/var/run/myapp.sock',
});import { fetch } from '@tundralibs/compat/fetch';
// Secure Unix socket connection
const response = await fetch('https://localhost/api', {
unix: '/var/run/secure.sock',
tls: {
certFile: '/etc/ssl/client.crt',
keyFile: '/etc/ssl/client.key',
},
});Enhanced fetch with TLS and Unix socket support.
fetch(
input: RequestInfo | URL,
init?: RequestInit & {
unix?: string;
tls?: TLSOptions;
}
): Promise<Response>Parameters:
-
input- URL string, URL object, or Request object -
init- Standard fetch options plus:-
unix- Path to Unix domain socket -
tls- TLS configuration object
-
Returns: Promise resolving to Response
Throws:
-
FetchPathTraversalError- If file paths contain../or null bytes -
FetchFileNotFoundError- If TLS files or socket don't exist -
FetchInvalidPEMError- If certificates are not valid PEM format -
FetchTLSError- Iftlsmixes the inline (cert/key/ca) and file-path (certFile/keyFile/caFile) styles -
UnsupportedRuntimeError- If TLS/Unix used on Node.js -
Error(plain, not aCompatErrorsubclass) - On Deno, whentls.rejectUnauthorizedisfalse— Deno has no in-process way to disable certificate verification. Run Deno with--unsafely-ignore-certificate-errors, or pass the server's CA viatls.ca/tls.caFileinstead.
See Compat-Common → TLSOptions for the full type definition.
The inline PEM form (cert/key/ca) and the file-path form
(certFile/keyFile/caFile) are mutually exclusive — supply one style,
not both. validateTLS throws if they are mixed:
import type { TLSOptions } from '@tundralibs/compat';
type TLSOptions = {
/** PEM-encoded certificate string (mTLS). */
cert?: string;
/** PEM-encoded private key string (mTLS). */
key?: string;
/** Array of PEM-encoded CA certificate strings. */
ca?: string[];
/** Path to PEM-encoded certificate file (mTLS). */
certFile?: string;
/** Path to PEM-encoded private key file (mTLS). */
keyFile?: string;
/** Path to PEM-encoded CA certificates file. */
caFile?: string;
/**
* Whether to reject untrusted server certificates.
* Defaults to `true`. Bun honors `false`; Deno rejects it (see below).
*/
rejectUnauthorized?: boolean;
};Note:
cert/keyandcertFile/keyFilemust always be supplied together. Providing one without the other throwsFetchInvalidPEMError.
Deno +
rejectUnauthorized: false: Deno has no in-process way to bypass certificate verification, sofetch(url, { tls: { rejectUnauthorized: false } })throws a plainErroron Deno — notFetchTLSErroror any otherCompatErrorsubclass, so aninstanceof FetchTLSErrorbranch in a catch block won't see it. Bun honors the flag natively. On Deno, either run with--unsafely-ignore-certificate-errors, or supply the server's CA viatls.ca/tls.caFileinstead of disabling verification.
All error classes are defined in the Compat-Common module and exported from @tundralibs/compat/common and the package root @tundralibs/compat.
Base error for TLS configuration issues.
class FetchTLSError extends CompatError {
source: string; // Which TLS component caused the error
}Thrown when a required file doesn't exist.
class FetchFileNotFoundError extends CompatError {
path: string; // The missing file path
}Thrown when PEM format validation fails.
class FetchInvalidPEMError extends FetchTLSError {
source: string; // e.g., 'cert', 'key', 'ca[0]'
}Thrown when path traversal attack is detected.
class FetchPathTraversalError extends CompatError {
path: string; // The suspicious path
reason: string; // Always 'path_traversal'
}import {
FetchFileNotFoundError,
FetchInvalidPEMError,
FetchPathTraversalError,
FetchTLSError,
} from '@tundralibs/compat';
import { fetch } from '@tundralibs/compat/fetch';
try {
const response = await fetch('https://secure.api.com/data', {
tls: {
certFile: '/etc/ssl/client.crt',
keyFile: '/etc/ssl/client.key',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
if (error instanceof FetchPathTraversalError) {
// Security violation - log and reject
console.error(`Security: Path traversal in ${error.path}`);
} else if (error instanceof FetchFileNotFoundError) {
// Missing certificate file
console.error(`Config: Missing file ${error.path}`);
} else if (error instanceof FetchInvalidPEMError) {
// Invalid certificate format
console.error(`Config: Invalid PEM in ${error.source}`);
} else if (error instanceof FetchTLSError) {
// Other TLS configuration error
console.error(`TLS: ${error.message}`);
} else if (error instanceof TypeError) {
// Network error (standard fetch behavior)
console.error(`Network: ${error.message}`);
} else {
throw error;
}
}All file paths are validated against directory traversal attacks:
import { fetch } from '@tundralibs/compat/fetch';
const url = 'https://secure.api.com/data';
// These will throw FetchPathTraversalError
await fetch(url, {
tls: { certFile: '../../../etc/passwd', keyFile: 'key.pem' },
});
await fetch(url, { tls: { certFile: '/path/with\0null', keyFile: 'key.pem' } });
await fetch(url, { unix: '../../var/run/docker.sock' });Blocked patterns:
-
../sequences (forward or back slash) - Null bytes (
\0)
To prevent Regular Expression Denial of Service (ReDoS) attacks, PEM content is limited to 1MB before regex validation.
- Use file-based TLS in production - Avoid embedding certificates in code
-
Restrict certificate file permissions - Use
chmod 600on key files - Use environment variables - For string-based credentials in CI/CD
- Validate server certificates - Use custom CA when connecting to internal services
-
Monitor for errors - Log
FetchPathTraversalErroras security events
- Compat-Common - TLSOptions type, TLS error classes, and validation utilities
- Compat-Net - TCP/TLS networking utilities
- Compat-Runtime - Runtime detection utilities
- Compat-File - File system operations
- Compat-Path - Path manipulation