@peac/net-node 0.12.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +736 -0
  3. package/dist/codes.d.ts +47 -0
  4. package/dist/codes.d.ts.map +1 -0
  5. package/dist/codes.js +53 -0
  6. package/dist/codes.js.map +1 -0
  7. package/dist/constants.d.ts +39 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +39 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/evidence-utils.d.ts +49 -0
  12. package/dist/evidence-utils.d.ts.map +1 -0
  13. package/dist/evidence-utils.js +109 -0
  14. package/dist/evidence-utils.js.map +1 -0
  15. package/dist/impl.d.ts +331 -0
  16. package/dist/impl.d.ts.map +1 -0
  17. package/dist/impl.js +1188 -0
  18. package/dist/impl.js.map +1 -0
  19. package/dist/index.d.ts +772 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +1121 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/internal.d.ts +28 -0
  24. package/dist/internal.d.ts.map +1 -0
  25. package/dist/internal.js +59 -0
  26. package/dist/internal.js.map +1 -0
  27. package/dist/receipt-resolver.d.ts +65 -0
  28. package/dist/receipt-resolver.d.ts.map +1 -0
  29. package/dist/receipt-resolver.js +140 -0
  30. package/dist/receipt-resolver.js.map +1 -0
  31. package/dist/receipt-url-middleware.d.ts +87 -0
  32. package/dist/receipt-url-middleware.d.ts.map +1 -0
  33. package/dist/receipt-url-middleware.js +141 -0
  34. package/dist/receipt-url-middleware.js.map +1 -0
  35. package/dist/ssrf.d.ts +114 -0
  36. package/dist/ssrf.d.ts.map +1 -0
  37. package/dist/ssrf.js +214 -0
  38. package/dist/ssrf.js.map +1 -0
  39. package/dist/testing.d.ts +65 -0
  40. package/dist/testing.d.ts.map +1 -0
  41. package/dist/testing.js +103 -0
  42. package/dist/testing.js.map +1 -0
  43. package/package.json +79 -0
@@ -0,0 +1,772 @@
1
+ /**
2
+ * PEAC Net (Node.js) - SSRF-Safe Network Utilities with DNS Pinning
3
+ *
4
+ * This module provides a reference implementation of network-layer SSRF protection
5
+ * with proper DNS resolution pinning using undici's custom dispatcher.
6
+ *
7
+ * SECURITY NOTES:
8
+ * - String-level URL checks alone are NOT sufficient for SSRF protection
9
+ * - DNS rebinding attacks require connection-time IP pinning
10
+ * - This implementation resolves DNS, validates ALL candidate IPs, and PINS
11
+ * the chosen IP at the connection layer (not just pre-check)
12
+ *
13
+ * RUNTIME: Node.js only (requires dns module and undici)
14
+ *
15
+ * @since v0.10.x
16
+ * @module @peac/net-node
17
+ */
18
+ import { type SSRFPolicy } from './ssrf.js';
19
+ import { getAuditQueueStats, type RequestAuditContext } from './impl.js';
20
+ import { SAFE_FETCH_ERROR_CODES } from './codes.js';
21
+ import { SAFE_FETCH_EVENT_SCHEMA_VERSION, SAFE_FETCH_EVIDENCE_SCHEMA_VERSION, MAX_PENDING_AUDIT_EVENTS } from './constants.js';
22
+ export { getAuditQueueStats };
23
+ export type { RequestAuditContext };
24
+ export { SAFE_FETCH_ERROR_CODES };
25
+ export { SAFE_FETCH_EVENT_SCHEMA_VERSION, SAFE_FETCH_EVIDENCE_SCHEMA_VERSION, MAX_PENDING_AUDIT_EVENTS, };
26
+ /**
27
+ * Structured error with code, cause, and warnings
28
+ *
29
+ * NOTE: Error messages are intentionally generic to avoid leaking sensitive
30
+ * information (internal IPs, hostnames). Use `evidence` for audit details.
31
+ */
32
+ export interface SafeFetchError {
33
+ error: string;
34
+ code: string;
35
+ cause_code?: string;
36
+ warnings?: string[];
37
+ /** Evidence object for audit trail (always present on error) */
38
+ evidence?: SafeFetchEvidence;
39
+ }
40
+ /**
41
+ * Result type for safeFetch operations
42
+ */
43
+ export type SafeFetchResult<T> = {
44
+ ok: true;
45
+ response: Response;
46
+ data: T;
47
+ warnings?: string[];
48
+ evidence: SafeFetchEvidence;
49
+ } | ({
50
+ ok: false;
51
+ } & SafeFetchError);
52
+ /**
53
+ * Result type for raw fetch (returns stream with cleanup)
54
+ */
55
+ export type SafeFetchRawResult = {
56
+ ok: true;
57
+ response: Response;
58
+ close: () => Promise<void>;
59
+ warnings?: string[];
60
+ evidence: SafeFetchEvidence;
61
+ } | ({
62
+ ok: false;
63
+ } & SafeFetchError);
64
+ /**
65
+ * DNS resolution result
66
+ */
67
+ export interface DnsResolutionResult {
68
+ ok: true;
69
+ addresses: string[];
70
+ selectedAddress: string;
71
+ ipv4Addresses: string[];
72
+ ipv6Addresses: string[];
73
+ }
74
+ export interface DnsResolutionError {
75
+ ok: false;
76
+ error: string;
77
+ code: string;
78
+ /** Structured DNS answers for audit trail */
79
+ dns_answers?: DnsAnswer[];
80
+ }
81
+ /**
82
+ * DNS resolver interface for dependency injection
83
+ * Allows mocking in tests without network calls
84
+ */
85
+ export interface DnsResolver {
86
+ /**
87
+ * Resolve a hostname to all available IP addresses
88
+ * @param hostname - The hostname to resolve
89
+ * @returns Array of IP addresses (both IPv4 and IPv6)
90
+ */
91
+ resolveAll(hostname: string): Promise<{
92
+ ipv4: string[];
93
+ ipv6: string[];
94
+ }>;
95
+ }
96
+ /**
97
+ * Phase-specific timeout options for HTTP client (P1.3)
98
+ *
99
+ * These map directly to undici's timeout options for precise control.
100
+ */
101
+ export interface HttpClientTimeouts {
102
+ /** Total operation timeout (AbortController) */
103
+ totalMs?: number;
104
+ /** TCP/TLS connect timeout (undici Agent connect.timeout) */
105
+ connectMs?: number;
106
+ /** Time to receive headers after connection (undici headersTimeout) */
107
+ headersMs?: number;
108
+ /** Time to receive body after headers (undici bodyTimeout) */
109
+ bodyMs?: number;
110
+ }
111
+ /**
112
+ * HTTP client interface for dependency injection
113
+ * Pattern B: returns response + close function for proper lifecycle management
114
+ */
115
+ export interface HttpClient {
116
+ /**
117
+ * Fetch a URL with optional pinned IP
118
+ * Returns response and close function to ensure proper agent cleanup
119
+ * @param url - The URL to fetch
120
+ * @param pinnedIp - The IP address to connect to (DNS pinning)
121
+ * @param options - Fetch options with phase-specific timeouts
122
+ */
123
+ fetch(url: string, pinnedIp: string | null, options?: RequestInit & {
124
+ timeouts?: HttpClientTimeouts;
125
+ timeoutMs?: number;
126
+ }): Promise<{
127
+ response: Response;
128
+ close: () => Promise<void>;
129
+ }>;
130
+ }
131
+ /**
132
+ * Redirect policy for cross-host redirects
133
+ */
134
+ export type RedirectPolicy = 'none' | 'same-origin' | 'same-registrable-domain' | 'allowlist';
135
+ /**
136
+ * Evidence redaction level for portable receipts
137
+ *
138
+ * - 'public': Safe for external sharing. Hashes IPs, removes raw addresses.
139
+ * Use this for evidence that may be included in receipts, logs, or telemetry.
140
+ *
141
+ * - 'tenant': Internal correlation with HMAC-SHA256 keyed hashing.
142
+ * Prevents cross-org linkage while allowing intra-org correlation.
143
+ * Requires `redactionKey` option to be provided.
144
+ *
145
+ * - 'private': Full details for internal audit. Includes raw IPs and hostnames.
146
+ * Only use for internal debugging or trusted audit systems.
147
+ *
148
+ * Default is 'public' for defense-in-depth (evidence may leak to external systems).
149
+ *
150
+ * @since v0.10.x
151
+ */
152
+ export type EvidenceLevel = 'public' | 'tenant' | 'private';
153
+ /**
154
+ * Redacted IP information for public evidence
155
+ *
156
+ * Raw IP addresses are replaced with family + SHA-256 hash.
157
+ * This allows correlation without exposing internal topology.
158
+ *
159
+ * For 'tenant' level, uses HMAC-SHA256 with tenant key to prevent
160
+ * cross-org correlation via rainbow tables.
161
+ */
162
+ export interface RedactedIpInfo {
163
+ /** Address family (4 or 6) */
164
+ family: 4 | 6;
165
+ /** SHA-256 or HMAC-SHA256 hash of the IP address (lowercase hex, 0x prefixed) */
166
+ hash: string;
167
+ /** Key ID used for HMAC (only present in 'tenant' mode) */
168
+ key_id?: string;
169
+ }
170
+ /**
171
+ * Acknowledgment string for allowing mixed public/private DNS resolution
172
+ */
173
+ export declare const ALLOW_MIXED_DNS_ACK: "I_UNDERSTAND_MIXED_DNS_SECURITY_RISKS";
174
+ /**
175
+ * Evidence without digest fields (for computing the digest)
176
+ *
177
+ * The digest is computed over evidence with digest fields omitted
178
+ * to avoid self-referential circularity.
179
+ */
180
+ export type SafeFetchEvidenceCore = Omit<SafeFetchEvidence, 'evidence_digest' | 'evidence_alg' | 'canonicalization'>;
181
+ /**
182
+ * Canonicalize evidence per RFC 8785 (JCS)
183
+ *
184
+ * Returns the canonical JSON string representation of the evidence object.
185
+ * This is deterministic across implementations and can be used for:
186
+ * - Computing digests for signing
187
+ * - Cross-implementation verification
188
+ * - Deduplication
189
+ *
190
+ * IMPORTANT: For digest computation, pass evidence WITHOUT digest fields
191
+ * to avoid self-referential circularity.
192
+ *
193
+ * @param evidence - SafeFetchEvidence or SafeFetchEvidenceCore object
194
+ * @returns Canonical JSON string
195
+ */
196
+ export declare function canonicalizeEvidence(evidence: SafeFetchEvidence | SafeFetchEvidenceCore): string;
197
+ /**
198
+ * Compute SHA-256 digest of evidence using JCS canonicalization
199
+ *
200
+ * Returns a 0x-prefixed lowercase hex string, consistent with other
201
+ * PEAC cryptographic identifiers (EAS anchors, IP hashes).
202
+ *
203
+ * Self-omission rule: If the evidence includes digest fields, they are
204
+ * stripped before computing the digest to avoid circularity.
205
+ *
206
+ * @param evidence - SafeFetchEvidence or SafeFetchEvidenceCore object
207
+ * @returns 0x-prefixed SHA-256 hex digest
208
+ */
209
+ export declare function computeEvidenceDigest(evidence: SafeFetchEvidence | SafeFetchEvidenceCore): string;
210
+ /**
211
+ * Event types emitted by safeFetch for audit logging
212
+ */
213
+ export type SafeFetchEventType = 'dns_start' | 'dns_success' | 'dns_blocked' | 'dns_error' | 'connect_start' | 'connect_success' | 'connect_error' | 'request_start' | 'response_headers' | 'response_body_start' | 'response_body_complete' | 'redirect' | 'error' | 'audit_overflow' | 'audit_hook_error';
214
+ /**
215
+ * Structured DNS answer for audit-clean logging
216
+ *
217
+ * Each answer includes the IP, address family, and blocked reason (if any).
218
+ * This allows downstream verifiers to replay "why blocked/allowed" decisions.
219
+ */
220
+ export interface DnsAnswer {
221
+ /** Resolved IP address */
222
+ ip: string;
223
+ /** Address family (4 or 6) */
224
+ family: 4 | 6;
225
+ /** If blocked, the reason code; undefined if allowed */
226
+ blocked_reason?: string;
227
+ }
228
+ /**
229
+ * Policy decision for audit trail
230
+ */
231
+ export type PolicyDecision = 'allow' | 'block';
232
+ /**
233
+ * Audit event with timing and metadata
234
+ *
235
+ * This is a vendor-neutral structure that can be adapted to any logging system.
236
+ * Events are emitted at key points in the fetch lifecycle.
237
+ *
238
+ * IMPORTANT: This schema is versioned via `schema_version` for forward compatibility.
239
+ * @see SAFE_FETCH_EVENT_SCHEMA_VERSION
240
+ */
241
+ export interface SafeFetchEvent {
242
+ /** Schema version for forward compatibility */
243
+ schema_version: typeof SAFE_FETCH_EVENT_SCHEMA_VERSION;
244
+ /** Event type */
245
+ type: SafeFetchEventType;
246
+ /** Timestamp (Date.now()) */
247
+ timestamp: number;
248
+ /** Original request URL (sanitized - no credentials) */
249
+ url: string;
250
+ /** Resolved hostname (after canonicalization) */
251
+ hostname?: string;
252
+ /** Selected IP address (only on success events) */
253
+ selected_ip?: string;
254
+ /** Error code if applicable */
255
+ code?: string;
256
+ /** Human-readable message (generic, non-sensitive) */
257
+ message?: string;
258
+ /** Policy decision (allow/block) for DNS events */
259
+ policy_decision?: PolicyDecision;
260
+ /** Additional metadata (vendor-neutral, may contain sensitive debug info) */
261
+ meta?: Record<string, unknown>;
262
+ }
263
+ /**
264
+ * Audit hook callback type
265
+ *
266
+ * HOOK CONTRACT (for reliable integration):
267
+ *
268
+ * - Delivery: Asynchronous via queueMicrotask (non-blocking to fetch path)
269
+ * - Ordering: FIFO within a single request (events arrive in fetch order)
270
+ * - Guarantees: At-most-once delivery (events may be dropped under load)
271
+ * - Exceptions: MUST NOT throw (exceptions are swallowed silently)
272
+ * - Side effects: MUST NOT modify request/response or call safeFetch
273
+ *
274
+ * Special events:
275
+ * - `audit_overflow`: Emitted SYNCHRONOUSLY when queue is full (rate-limited 1/sec)
276
+ * This event bypasses the queue to ensure observability of drops.
277
+ *
278
+ * For enterprise reliability, consume events idempotently and monitor
279
+ * `audit_overflow` events as a signal of capacity issues.
280
+ */
281
+ export type SafeFetchAuditHook = (event: SafeFetchEvent) => void;
282
+ /**
283
+ * Evidence object returned by safeFetch for PEAC-ready receipts
284
+ *
285
+ * This is a portable, signed-ready artifact that captures:
286
+ * - Request URL (normalized, no credentials)
287
+ * - Canonical host processing
288
+ * - DNS resolution decisions
289
+ * - Redirect chain (if any)
290
+ * - Timing summary
291
+ * - Budget enforcement
292
+ * - Final decision code
293
+ *
294
+ * Evidence is redacted based on `evidence_level`:
295
+ * - 'public': Raw IPs replaced with family + SHA-256 hash
296
+ * - 'private': Full details including raw IPs
297
+ *
298
+ * Designed for multi-agent workflow verification and dispute resolution.
299
+ */
300
+ export interface SafeFetchEvidence {
301
+ /** Schema version for forward compatibility (evidence-specific) */
302
+ schema_version: typeof SAFE_FETCH_EVIDENCE_SCHEMA_VERSION;
303
+ /**
304
+ * Evidence redaction level
305
+ * - 'public': Safe for external sharing (default)
306
+ * - 'tenant': Internal correlation with HMAC (prevents cross-org linkage)
307
+ * - 'private': Full details for internal audit
308
+ */
309
+ evidence_level: EvidenceLevel;
310
+ /**
311
+ * SHA-256 digest of canonical evidence (0x-prefixed hex)
312
+ * Computed over evidence with this field omitted (self-referential exclusion)
313
+ */
314
+ evidence_digest: string;
315
+ /**
316
+ * Hash algorithm used for digest
317
+ * @default 'sha-256'
318
+ */
319
+ evidence_alg: 'sha-256';
320
+ /**
321
+ * Canonicalization scheme used before hashing
322
+ * @default 'RFC8785-JCS'
323
+ */
324
+ canonicalization: 'RFC8785-JCS';
325
+ /** Request timestamp (start of fetch) */
326
+ request_timestamp: number;
327
+ /** Response timestamp (headers received) */
328
+ response_timestamp?: number;
329
+ /** Normalized request URL (no credentials) */
330
+ request_url: string;
331
+ /** Canonical hostname after processing */
332
+ canonical_host: string;
333
+ /** Whether host was an IP literal */
334
+ is_ip_literal: boolean;
335
+ /**
336
+ * Selected IP info (public evidence - always present on success)
337
+ * Contains family and SHA-256 hash of the selected IP
338
+ */
339
+ selected_ip_info?: RedactedIpInfo;
340
+ /**
341
+ * DNS answer summary (public evidence)
342
+ * Count of answers by family, without raw IPs
343
+ */
344
+ dns_answer_count?: {
345
+ ipv4: number;
346
+ ipv6: number;
347
+ };
348
+ /**
349
+ * DNS answers with per-IP decisions (private evidence only)
350
+ * Only populated when evidence_level is 'private'
351
+ */
352
+ dns_answers?: DnsAnswer[];
353
+ /**
354
+ * Raw selected IP for connection (private evidence only)
355
+ * Only populated when evidence_level is 'private'
356
+ */
357
+ selected_ip?: string;
358
+ /** Policy decision */
359
+ policy_decision: PolicyDecision;
360
+ /** Final decision code */
361
+ decision_code: string;
362
+ /** Redirect chain (URLs) */
363
+ redirect_chain?: string[];
364
+ /** Response status code */
365
+ response_status?: number;
366
+ /** Actual response bytes received */
367
+ response_bytes?: number;
368
+ /** Max response bytes budget */
369
+ max_response_bytes: number;
370
+ /** Total elapsed time in milliseconds */
371
+ elapsed_ms?: number;
372
+ /** Dropped headers (security enforcement) */
373
+ dropped_headers?: string[];
374
+ /**
375
+ * Audit queue statistics at time of evidence creation (per-request)
376
+ *
377
+ * Provides visibility into audit event processing health for THIS request.
378
+ * All counters are request-scoped - they reset for each safeFetch call.
379
+ * If `dropped > 0`, some events were lost due to queue overflow.
380
+ * This is a signal that evidence quality may be degraded.
381
+ */
382
+ audit_stats?: {
383
+ /** Number of audit events pending at evidence creation */
384
+ pending: number;
385
+ /** Total number of dropped audit events for this request */
386
+ dropped: number;
387
+ /** Maximum allowed pending events */
388
+ max_pending: number;
389
+ /** Number of hook errors that occurred during this request */
390
+ hook_errors: number;
391
+ /** Number of hook errors suppressed by rate limiting for this request */
392
+ hook_suppressed: number;
393
+ };
394
+ /**
395
+ * Indicates audit data was truncated due to overflow
396
+ *
397
+ * NORMATIVE PRESENCE SEMANTICS:
398
+ * - Present and `true`: Evidence is incomplete, audit events were dropped
399
+ * - Absent (undefined): Evidence is complete, no truncation occurred
400
+ *
401
+ * Producers MUST omit this field when no truncation occurred.
402
+ * Producers MUST NOT emit `audit_truncated: false`.
403
+ *
404
+ * This field uses presence semantics to ensure digest compatibility:
405
+ * `{ ...core }` and `{ ...core, audit_truncated: false }` would produce
406
+ * different digests for logically identical evidence. By requiring omission
407
+ * when false, we guarantee cross-implementation digest parity.
408
+ *
409
+ * Set to `true` when `audit_stats.dropped > 0`.
410
+ */
411
+ audit_truncated?: true;
412
+ }
413
+ /**
414
+ * Granular timeout configuration for different phases
415
+ *
416
+ * All values are in milliseconds. If not specified, defaults apply.
417
+ */
418
+ export interface TimeoutConfig {
419
+ /**
420
+ * Total request timeout (overall limit)
421
+ * @default 30000 (30 seconds)
422
+ */
423
+ totalMs?: number;
424
+ /**
425
+ * DNS resolution timeout
426
+ * @default 5000 (5 seconds)
427
+ */
428
+ dnsMs?: number;
429
+ /**
430
+ * TCP/TLS connection timeout
431
+ * @default 10000 (10 seconds)
432
+ */
433
+ connectMs?: number;
434
+ /**
435
+ * Time to receive response headers
436
+ * @default 15000 (15 seconds)
437
+ */
438
+ headersMs?: number;
439
+ /**
440
+ * Time to receive response body (after headers)
441
+ * @default 30000 (30 seconds)
442
+ */
443
+ bodyMs?: number;
444
+ }
445
+ /**
446
+ * Default timeout values
447
+ */
448
+ export declare const DEFAULT_TIMEOUTS: Required<TimeoutConfig>;
449
+ /**
450
+ * Options for safeFetch
451
+ */
452
+ export interface SafeFetchOptions extends Omit<RequestInit, 'signal'> {
453
+ /**
454
+ * SSRF policy configuration
455
+ * @default DEFAULT_SSRF_POLICY
456
+ */
457
+ ssrfPolicy?: SSRFPolicy;
458
+ /**
459
+ * Request timeout in milliseconds
460
+ * @default 30000 (30 seconds)
461
+ */
462
+ timeoutMs?: number;
463
+ /**
464
+ * Maximum number of redirects to follow
465
+ * Set to 0 to disable redirects
466
+ * @default 5
467
+ */
468
+ maxRedirects?: number;
469
+ /**
470
+ * Redirect policy for handling cross-host redirects
471
+ * @default 'same-origin'
472
+ */
473
+ redirectPolicy?: RedirectPolicy;
474
+ /**
475
+ * Allowed hosts for redirects when redirectPolicy is 'allowlist'
476
+ * Also used for 'same-registrable-domain' as additional allowed hosts
477
+ */
478
+ redirectAllowHosts?: string[];
479
+ /**
480
+ * @deprecated Use redirectPolicy instead
481
+ * Allow redirects to different hosts
482
+ */
483
+ allowCrossHostRedirects?: boolean;
484
+ /**
485
+ * Allowed HTTP methods (SSRF side-effect prevention)
486
+ * @default ['GET', 'HEAD']
487
+ */
488
+ allowedMethods?: string[];
489
+ /**
490
+ * Maximum response body size in bytes (DoS protection)
491
+ * @default 2097152 (2 MB)
492
+ */
493
+ maxResponseBytes?: number;
494
+ /**
495
+ * Disable Accept-Encoding: identity default.
496
+ *
497
+ * By default, we send Accept-Encoding: identity to prevent decompression bombs.
498
+ * Setting this to true allows compressed responses (gzip, deflate, br).
499
+ *
500
+ * WARNING: Only set this if you trust the server AND have streaming
501
+ * decompression with size limits in place.
502
+ *
503
+ * @default false
504
+ */
505
+ allowCompression?: boolean;
506
+ /**
507
+ * Allow DNS resolution that returns BOTH public and private IPs.
508
+ *
509
+ * By default, we REJECT requests where ANY resolved IP is private,
510
+ * even if there are also public IPs. This prevents attackers from
511
+ * hiding a private IP alongside public ones.
512
+ *
513
+ * In some corporate environments, split DNS legitimately returns
514
+ * both internal and external IPs. This escape hatch allows such
515
+ * configurations, but requires explicit acknowledgment.
516
+ *
517
+ * SECURITY WARNING: This weakens SSRF protection. Only use if:
518
+ * 1. You control the DNS infrastructure
519
+ * 2. You understand the risks of mixed DNS responses
520
+ * 3. You have other mitigations in place (network segmentation, etc.)
521
+ *
522
+ * @default false
523
+ */
524
+ allowMixedPublicAndPrivateDns?: boolean;
525
+ /**
526
+ * Required acknowledgment for allowMixedPublicAndPrivateDns.
527
+ * Set to ALLOW_MIXED_DNS_ACK ('I_UNDERSTAND_MIXED_DNS_SECURITY_RISKS').
528
+ */
529
+ ack_allow_mixed_dns?: typeof ALLOW_MIXED_DNS_ACK;
530
+ /**
531
+ * Allow connections to dangerous ports (admin, databases, service mesh).
532
+ *
533
+ * By default, we BLOCK connections to ports in DANGEROUS_PORTS set
534
+ * (SSH, databases, container orchestration, etc.) even on public IPs.
535
+ * This prevents SSRF attacks targeting internal services on non-standard ports.
536
+ *
537
+ * SECURITY WARNING: This weakens SSRF protection. Only use if:
538
+ * 1. You specifically need to connect to services on these ports
539
+ * 2. You understand the risks of allowing these connections
540
+ * 3. You have network-level controls in place
541
+ *
542
+ * @default false
543
+ */
544
+ allowDangerousPorts?: boolean;
545
+ /**
546
+ * Required acknowledgment for allowDangerousPorts.
547
+ * Set to ALLOW_DANGEROUS_PORTS_ACK ('I_UNDERSTAND_DANGEROUS_PORTS_RISK').
548
+ */
549
+ ack_allow_dangerous_ports?: typeof ALLOW_DANGEROUS_PORTS_ACK;
550
+ /**
551
+ * Granular timeout configuration for different phases.
552
+ * If provided, overrides timeoutMs for specific phases.
553
+ * @see TimeoutConfig
554
+ */
555
+ timeouts?: TimeoutConfig;
556
+ /**
557
+ * Audit hook for observing fetch lifecycle events.
558
+ *
559
+ * Called at key points (DNS, connect, request, response, error).
560
+ * Useful for logging, metrics, and debugging.
561
+ *
562
+ * MUST NOT throw or modify request/response.
563
+ * Hooks are invoked via queueMicrotask for performance isolation.
564
+ * @see SafeFetchAuditHook
565
+ */
566
+ onEvent?: SafeFetchAuditHook;
567
+ /**
568
+ * Custom sanitizer for hook error messages.
569
+ *
570
+ * By default, error messages are sanitized to:
571
+ * - Truncate to 200 characters
572
+ * - Redact Bearer tokens, passwords, keys, secrets
573
+ *
574
+ * Use this option to provide a custom sanitizer or disable sanitization:
575
+ * - Pass a function to customize: `(msg: string) => sanitizedMsg`
576
+ * - Pass `'off'` to disable sanitization entirely (NOT RECOMMENDED in production)
577
+ *
578
+ * @default Built-in sanitizer (truncate + redact secrets)
579
+ */
580
+ sanitizeHookError?: ((message: string) => string) | 'off';
581
+ /**
582
+ * Enable debug mode to include sensitive details in evidence and events.
583
+ *
584
+ * When false (default), error messages and evidence are generic to avoid
585
+ * leaking internal IPs or hostnames through logs/telemetry.
586
+ *
587
+ * When true:
588
+ * - Evidence includes detailed `dns_answers` with per-IP decisions
589
+ * - Event metadata includes blocked IPs and addresses
590
+ *
591
+ * WARNING: Only enable in trusted environments (local dev, internal logging).
592
+ * @default false
593
+ * @deprecated Use evidenceLevel: 'private' instead for evidence, keep debug for events
594
+ */
595
+ debug?: boolean;
596
+ /**
597
+ * Evidence redaction level for portable receipts.
598
+ *
599
+ * - 'public' (default): Raw IPs replaced with family + SHA-256 hash.
600
+ * Safe for including in receipts, logs, or external telemetry.
601
+ * Note: SHA-256 of IPv4 is rainbow-table vulnerable (only 2^32 inputs).
602
+ *
603
+ * - 'tenant': HMAC-SHA256 with tenant-specific key. Prevents cross-org
604
+ * correlation via rainbow tables while allowing intra-org correlation.
605
+ * Requires `redactionKey` and `redactionKeyId` options.
606
+ *
607
+ * - 'private': Full details including raw IPs and DNS answers.
608
+ * Only use for internal audit systems you control.
609
+ *
610
+ * Defense-in-depth: default 'public' ensures evidence is safe even
611
+ * if accidentally exposed through logs or telemetry pipelines.
612
+ *
613
+ * @default 'public'
614
+ * @since v0.10.x
615
+ */
616
+ evidenceLevel?: EvidenceLevel;
617
+ /**
618
+ * Tenant-specific key for HMAC-based IP hashing (tenant mode only).
619
+ *
620
+ * Must be at least 32 bytes for security. Used with HMAC-SHA256 to
621
+ * prevent cross-tenant correlation via rainbow tables.
622
+ *
623
+ * Required when evidenceLevel is 'tenant'.
624
+ *
625
+ * @since v0.10.x
626
+ */
627
+ redactionKey?: Uint8Array;
628
+ /**
629
+ * Key identifier for tenant mode redaction.
630
+ *
631
+ * Included in evidence to identify which key was used for hashing.
632
+ * Allows key rotation while maintaining audit trails.
633
+ *
634
+ * Required when evidenceLevel is 'tenant'.
635
+ *
636
+ * @since v0.10.x
637
+ */
638
+ redactionKeyId?: string;
639
+ /**
640
+ * Custom DNS resolver for dependency injection (testing)
641
+ * @internal
642
+ */
643
+ _dnsResolver?: DnsResolver;
644
+ /**
645
+ * Custom HTTP client for dependency injection (testing)
646
+ * @internal
647
+ */
648
+ _httpClient?: HttpClient;
649
+ }
650
+ /**
651
+ * Default timeout for fetch requests (30 seconds)
652
+ */
653
+ export declare const DEFAULT_TIMEOUT_MS = 30000;
654
+ /**
655
+ * Default maximum redirects
656
+ */
657
+ export declare const DEFAULT_MAX_REDIRECTS = 5;
658
+ /**
659
+ * Default allowed HTTP methods (GET/HEAD only for SSRF safety)
660
+ */
661
+ export declare const DEFAULT_ALLOWED_METHODS: string[];
662
+ /**
663
+ * Default maximum response size (2 MB)
664
+ *
665
+ * COMPRESSION SEMANTICS (P0.4 - Protocol-Grade Evidence):
666
+ *
667
+ * This limit measures DECODED bytes (after decompression), NOT wire bytes.
668
+ * The limit applies to what `response.body.getReader().read()` returns.
669
+ *
670
+ * By default, we request `Accept-Encoding: identity` to prevent compression.
671
+ * This means:
672
+ * - Wire bytes === Decoded bytes (no compression)
673
+ * - Content-Length matches actual body size
674
+ * - No decompression bomb risk
675
+ *
676
+ * If `allowCompression: true`, compression is allowed, and:
677
+ * - Wire bytes may be smaller than decoded bytes
678
+ * - Content-Length reflects compressed size (if set)
679
+ * - We measure DECODED bytes (after browser/runtime decompresses)
680
+ * - Decompression bombs are possible (1KB compressed -> 1GB decoded)
681
+ *
682
+ * WARNING: Only use `allowCompression: true` if you trust the server AND have
683
+ * additional decoded size limits in place.
684
+ *
685
+ * For evidence purposes, `response_bytes` in SafeFetchEvidence reflects
686
+ * decoded bytes (what we actually read), not wire bytes.
687
+ */
688
+ export declare const DEFAULT_MAX_RESPONSE_BYTES: number;
689
+ /**
690
+ * Whether maxResponseBytes measures wire or decoded bytes.
691
+ * Always 'decoded' - we measure after potential decompression.
692
+ */
693
+ export declare const RESPONSE_BYTES_MEASUREMENT: "decoded";
694
+ /**
695
+ * Maximum response size for JWKS/OIDC endpoints (512 KB)
696
+ */
697
+ export declare const MAX_JWKS_RESPONSE_BYTES: number;
698
+ /**
699
+ * Dangerous ports that are ALWAYS blocked regardless of configuration.
700
+ *
701
+ * These ports commonly host:
702
+ * - SSH (22), SMTP (25), DNS (53), FTP (21)
703
+ * - Database services (3306 MySQL, 5432 PostgreSQL, 6379 Redis, 27017 MongoDB)
704
+ * - Kubernetes (6443, 10250), Envoy (15000), Prometheus (9090)
705
+ * - Internal admin panels, debug ports
706
+ *
707
+ * Blocking these prevents SSRF to internal services even on attacker-controlled hosts.
708
+ * To allow these ports, you MUST provide explicit acknowledgment.
709
+ */
710
+ export declare const DANGEROUS_PORTS: ReadonlySet<number>;
711
+ /**
712
+ * Acknowledgment string for allowing dangerous ports
713
+ */
714
+ export declare const ALLOW_DANGEROUS_PORTS_ACK: "I_UNDERSTAND_DANGEROUS_PORTS_RISK";
715
+ /**
716
+ * Default DNS resolver using Node.js dns.promises module
717
+ * Resolves both A (IPv4) and AAAA (IPv6) records separately for Happy Eyeballs
718
+ */
719
+ export declare const defaultDnsResolver: DnsResolver;
720
+ /**
721
+ * Default HTTP client using undici with DNS pinning
722
+ * Returns close function for proper agent lifecycle management
723
+ * P1.3: Supports phase-specific timeouts via undici options
724
+ */
725
+ export declare const defaultHttpClient: HttpClient;
726
+ /**
727
+ * SSRF-safe raw fetch with proper DNS resolution pinning
728
+ * Returns response stream with close function for proper lifecycle management
729
+ *
730
+ * Use this when you need to process the response stream directly.
731
+ * IMPORTANT: You MUST call the close() function after consuming the response.
732
+ *
733
+ * Security features:
734
+ * - DNS resolution pinning (prevents rebinding attacks)
735
+ * - Private IP blocking (RFC 6890 compliant)
736
+ * - Host header enforcement (prevents SSRF via Host manipulation)
737
+ * - Hop-by-hop header stripping (RFC 7230)
738
+ * - Response size enforcement
739
+ * - Redirect chain validation
740
+ *
741
+ * Returns an evidence object for PEAC-ready receipts and audit trails.
742
+ */
743
+ export declare function safeFetchRaw(url: string, options?: SafeFetchOptions): Promise<SafeFetchRawResult>;
744
+ /**
745
+ * SSRF-safe JSON fetch with proper DNS resolution pinning and size limits
746
+ *
747
+ * This is the recommended function for fetching JSON APIs (JWKS, OIDC, etc.)
748
+ */
749
+ export declare function safeFetchJson<T = unknown>(url: string, options?: SafeFetchOptions): Promise<SafeFetchResult<T>>;
750
+ /**
751
+ * SSRF-safe fetch with JSON parsing (legacy API, use safeFetchJson for new code)
752
+ *
753
+ * @deprecated Use safeFetchJson for better type safety and structured errors
754
+ */
755
+ export declare function safeFetch<T = unknown>(url: string, options?: SafeFetchOptions): Promise<SafeFetchResult<T>>;
756
+ /**
757
+ * SSRF-safe fetch for JWKS endpoints
758
+ *
759
+ * Convenience wrapper around safeFetchJson with JWKS-specific defaults:
760
+ * - Shorter timeout (10 seconds)
761
+ * - No redirects by default
762
+ * - Smaller response size limit (512 KB)
763
+ * - Type-safe JWKS response
764
+ */
765
+ export declare function safeFetchJWKS(url: string, options?: Omit<SafeFetchOptions, 'maxRedirects'>): Promise<SafeFetchResult<{
766
+ keys: unknown[];
767
+ }>>;
768
+ export { resolveReceiptUrl, verifyReceiptRef } from './receipt-resolver.js';
769
+ export type { ResolveReceiptUrlOptions, ResolveReceiptUrlResult, ResolveReceiptUrlSuccess, ResolveReceiptUrlFailure, } from './receipt-resolver.js';
770
+ export { createReceiptUrlResolver } from './receipt-url-middleware.js';
771
+ export type { ReceiptUrlResolverOptions, RetrievalMetadata, ReceiptUrlMiddleware, } from './receipt-url-middleware.js';
772
+ //# sourceMappingURL=index.d.ts.map