@yedoma-labs/tanur-ssrf 0.0.0-stage → 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +130 -2
- package/dist/index.cjs +720 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +61 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +717 -0
- package/dist/index.js.map +1 -0
- package/package.json +84 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["request","httpsRequest","httpRequest"],"sources":["../src/errors.ts","../src/body.ts","../src/ip.ts","../src/types.ts","../src/policy.ts","../src/transport.ts","../src/safe-fetch.ts"],"sourcesContent":["export type SafeFetchErrorCode =\n\t| \"PROTOCOL_NOT_ALLOWED\"\n\t| \"PORT_NOT_ALLOWED\"\n\t| \"INVALID_URL\"\n\t| \"BLOCKED_HOSTNAME\"\n\t| \"BLOCKED_ADDRESS\"\n\t| \"DNS_FAILURE\"\n\t| \"REDIRECT_LIMIT\"\n\t| \"REDIRECT_WITHOUT_LOCATION\"\n\t| \"TIMEOUT\"\n\t| \"BODY_TOO_LARGE\"\n\t| \"NETWORK_ERROR\";\n\n/**\n * Typed error thrown by the safe fetch. Messages never contain the URL, its\n * query string or any credentials; they only name the blocked host or address.\n */\nexport class SafeFetchError extends Error {\n\treadonly code: SafeFetchErrorCode;\n\n\tconstructor(code: SafeFetchErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"SafeFetchError\";\n\t\tthis.code = code;\n\t}\n}\n","import { SafeFetchError } from \"./errors.js\";\n\n/**\n * Reads a Response body into memory, failing once more than `maxBytes` bytes arrive.\n * The stream is cancelled at the limit, so an oversized body is not buffered.\n */\nexport async function readBodyWithLimit(response: Response, maxBytes: number): Promise<Uint8Array> {\n\tif (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {\n\t\tthrow new TypeError(\"maxBytes must be a non-negative integer\");\n\t}\n\tif (response.body === null) {\n\t\treturn new Uint8Array(0);\n\t}\n\n\tconst reader = response.body.getReader();\n\tconst chunks: Uint8Array[] = [];\n\tlet total = 0;\n\tfor (;;) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) {\n\t\t\tbreak;\n\t\t}\n\t\ttotal += value.byteLength;\n\t\tif (total > maxBytes) {\n\t\t\tawait reader.cancel();\n\t\t\tthrow new SafeFetchError(\"BODY_TOO_LARGE\", `Response body exceeds ${maxBytes} bytes`);\n\t\t}\n\t\tchunks.push(value);\n\t}\n\n\tconst result = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tresult.set(chunk, offset);\n\t\toffset += chunk.byteLength;\n\t}\n\treturn result;\n}\n","/**\n * Strict IP address parsing and range classification.\n *\n * Implemented locally (no ipaddr.js). Anything that is not an unambiguous\n * IPv4 or IPv6 literal is rejected by the parsers, so callers can fail closed.\n */\n\nexport type IpFamily = 4 | 6;\n\nexport interface ParsedIp {\n\treadonly family: IpFamily;\n\treadonly bytes: Uint8Array;\n}\n\nconst IPV4_PART = /^(?:0|[1-9]\\d{0,2})$/;\nconst IPV6_GROUP = /^[0-9a-fA-F]{1,4}$/;\n\n/**\n * Parses a dotted-decimal IPv4 address. Leading zeros are rejected because\n * they are ambiguous (octal in some resolvers).\n */\nexport function parseIpv4(text: string): Uint8Array | undefined {\n\tconst parts = text.split(\".\");\n\tif (parts.length !== 4) {\n\t\treturn undefined;\n\t}\n\tconst bytes = new Uint8Array(4);\n\tfor (let index = 0; index < 4; index++) {\n\t\tconst part = parts[index];\n\t\tif (part === undefined || !IPV4_PART.test(part)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst value = Number(part);\n\t\tif (value > 255) {\n\t\t\treturn undefined;\n\t\t}\n\t\tbytes[index] = value;\n\t}\n\treturn bytes;\n}\n\n/** Parses 8 groups of 16 bits from a side of an IPv6 literal. */\nfunction parseGroups(side: string, allowIpv4Tail: boolean): number[] | undefined {\n\tif (side === \"\") {\n\t\treturn [];\n\t}\n\tconst parts = side.split(\":\");\n\tconst groups: number[] = [];\n\tfor (let index = 0; index < parts.length; index++) {\n\t\tconst part = parts[index];\n\t\tif (part === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (allowIpv4Tail && index === parts.length - 1 && part.includes(\".\")) {\n\t\t\tconst v4 = parseIpv4(part);\n\t\t\tif (v4 === undefined) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tgroups.push((byteAt(v4, 0) << 8) | byteAt(v4, 1), (byteAt(v4, 2) << 8) | byteAt(v4, 3));\n\t\t\tcontinue;\n\t\t}\n\t\tif (!IPV6_GROUP.test(part)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tgroups.push(Number.parseInt(part, 16));\n\t}\n\treturn groups;\n}\n\n/**\n * Parses an IPv6 literal (without brackets or zone id). Supports `::`\n * compression and an embedded dotted IPv4 tail. Zone ids are rejected.\n */\nexport function parseIpv6(text: string): Uint8Array | undefined {\n\tif (text.includes(\"%\") || !text.includes(\":\")) {\n\t\treturn undefined;\n\t}\n\tconst compressionAt = text.indexOf(\"::\");\n\tif (compressionAt !== text.lastIndexOf(\"::\")) {\n\t\treturn undefined;\n\t}\n\n\tlet groups: number[];\n\tif (compressionAt === -1) {\n\t\tconst all = parseGroups(text, true);\n\t\tif (all === undefined || all.length !== 8) {\n\t\t\treturn undefined;\n\t\t}\n\t\tgroups = all;\n\t} else {\n\t\tconst head = parseGroups(text.slice(0, compressionAt), false);\n\t\tconst tail = parseGroups(text.slice(compressionAt + 2), true);\n\t\tif (head === undefined || tail === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst missing = 8 - head.length - tail.length;\n\t\tif (missing < 1) {\n\t\t\treturn undefined;\n\t\t}\n\t\tgroups = [...head, ...new Array<number>(missing).fill(0), ...tail];\n\t}\n\n\tconst bytes = new Uint8Array(16);\n\tfor (let index = 0; index < 8; index++) {\n\t\tconst group = groups[index] ?? 0;\n\t\tbytes[index * 2] = group >> 8;\n\t\tbytes[index * 2 + 1] = group & 0xff;\n\t}\n\treturn bytes;\n}\n\n/** Parses an IPv4 or IPv6 literal. Returns undefined for anything else. */\nexport function parseIp(text: string): ParsedIp | undefined {\n\tif (text.includes(\":\")) {\n\t\tconst bytes = parseIpv6(text);\n\t\treturn bytes === undefined ? undefined : { family: 6, bytes };\n\t}\n\tconst bytes = parseIpv4(text);\n\treturn bytes === undefined ? undefined : { family: 4, bytes };\n}\n\nfunction byteAt(bytes: Uint8Array, index: number): number {\n\treturn bytes[index] ?? 0;\n}\n\nfunction u32(a: number, b: number, c: number, d: number): number {\n\treturn ((a << 24) | (b << 16) | (c << 8) | d) >>> 0;\n}\n\ninterface Ipv4Range {\n\treadonly network: number;\n\treadonly prefix: number;\n\treadonly name: string;\n}\n\nconst BLOCKED_IPV4: readonly Ipv4Range[] = [\n\t{ network: u32(0, 0, 0, 0), prefix: 8, name: \"unspecified network (0.0.0.0/8)\" },\n\t{ network: u32(10, 0, 0, 0), prefix: 8, name: \"private (10.0.0.0/8)\" },\n\t{ network: u32(100, 64, 0, 0), prefix: 10, name: \"shared address space (100.64.0.0/10)\" },\n\t{ network: u32(127, 0, 0, 0), prefix: 8, name: \"loopback (127.0.0.0/8)\" },\n\t{ network: u32(169, 254, 0, 0), prefix: 16, name: \"link-local (169.254.0.0/16)\" },\n\t{ network: u32(172, 16, 0, 0), prefix: 12, name: \"private (172.16.0.0/12)\" },\n\t{ network: u32(192, 0, 0, 0), prefix: 24, name: \"IETF protocol assignments (192.0.0.0/24)\" },\n\t{ network: u32(192, 0, 2, 0), prefix: 24, name: \"documentation (192.0.2.0/24)\" },\n\t{ network: u32(192, 168, 0, 0), prefix: 16, name: \"private (192.168.0.0/16)\" },\n\t{ network: u32(198, 18, 0, 0), prefix: 15, name: \"benchmarking (198.18.0.0/15)\" },\n\t{ network: u32(198, 51, 100, 0), prefix: 24, name: \"documentation (198.51.100.0/24)\" },\n\t{ network: u32(203, 0, 113, 0), prefix: 24, name: \"documentation (203.0.113.0/24)\" },\n\t{ network: u32(224, 0, 0, 0), prefix: 4, name: \"multicast (224.0.0.0/4)\" },\n\t{ network: u32(240, 0, 0, 0), prefix: 4, name: \"reserved (240.0.0.0/4)\" },\n\t{ network: u32(255, 255, 255, 255), prefix: 32, name: \"broadcast (255.255.255.255)\" },\n];\n\nfunction ipv4Value(bytes: Uint8Array): number {\n\treturn u32(byteAt(bytes, 0), byteAt(bytes, 1), byteAt(bytes, 2), byteAt(bytes, 3));\n}\n\nfunction inPrefix(value: number, network: number, prefix: number): boolean {\n\tconst mask = (0xffffffff << (32 - prefix)) >>> 0;\n\treturn (value & mask) >>> 0 === (network & mask) >>> 0;\n}\n\n/** Returns the name of the blocked IPv4 range, or undefined if the address is public. */\nfunction classifyIpv4(bytes: Uint8Array): string | undefined {\n\tconst value = ipv4Value(bytes);\n\tfor (const range of BLOCKED_IPV4) {\n\t\tif (inPrefix(value, range.network, range.prefix)) {\n\t\t\treturn range.name;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/** True if bytes[from..to) are all zero. */\nfunction zeroBetween(bytes: Uint8Array, from: number, to: number): boolean {\n\tfor (let index = from; index < to; index++) {\n\t\tif (byteAt(bytes, index) !== 0) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nfunction bytesEqual(bytes: Uint8Array, expected: readonly number[]): boolean {\n\treturn expected.every((value, index) => byteAt(bytes, index) === value);\n}\n\nfunction classifyEmbeddedIpv4(bytes: Uint8Array, start: number, label: string): string | undefined {\n\tconst reason = classifyIpv4(bytes.subarray(start, start + 4));\n\treturn reason === undefined ? undefined : `${label}, embedded address in ${reason}`;\n}\n\n/**\n * Returns the name of the blocked IPv6 range, or undefined if the address is public.\n * Embedded IPv4 forms (mapped, NAT64, 6to4) are classified by their IPv4 address.\n */\nfunction classifyIpv6(bytes: Uint8Array): string | undefined {\n\tif (zeroBetween(bytes, 0, 12)) {\n\t\tif (zeroBetween(bytes, 12, 15) && byteAt(bytes, 15) === 1) {\n\t\t\treturn \"loopback (::1)\";\n\t\t}\n\t\treturn \"unspecified or IPv4-compatible (::/96)\";\n\t}\n\t// IPv4-mapped ::ffff:0:0/96 is judged by the IPv4 address it wraps.\n\tif (zeroBetween(bytes, 0, 10) && bytesEqual(bytes.subarray(10, 12), [0xff, 0xff])) {\n\t\tconst reason = classifyIpv4(bytes.subarray(12, 16));\n\t\treturn reason === undefined ? undefined : `IPv4-mapped, ${reason}`;\n\t}\n\t// IPv4-translated ::ffff:0:0:0/96 (SIIT) is never routed to public addresses.\n\tif (zeroBetween(bytes, 0, 8) && bytesEqual(bytes.subarray(8, 12), [0xff, 0xff, 0x00, 0x00])) {\n\t\treturn \"IPv4-translated (::ffff:0:0:0/96)\";\n\t}\n\t// NAT64 well-known prefix 64:ff9b::/96.\n\tif (bytesEqual(bytes, [0x00, 0x64, 0xff, 0x9b]) && zeroBetween(bytes, 4, 12)) {\n\t\treturn classifyEmbeddedIpv4(bytes, 12, \"NAT64 (64:ff9b::/96)\");\n\t}\n\t// 6to4 2002::/16 carries an IPv4 address in bytes 2..5.\n\tif (bytesEqual(bytes, [0x20, 0x02])) {\n\t\treturn classifyEmbeddedIpv4(bytes, 2, \"6to4 (2002::/16)\");\n\t}\n\t// Teredo 2001::/32 obfuscates client addresses; not needed by server-side fetches.\n\tif (bytesEqual(bytes, [0x20, 0x01, 0x00, 0x00])) {\n\t\treturn \"Teredo (2001::/32)\";\n\t}\n\tif (bytesEqual(bytes, [0x01, 0x00]) && zeroBetween(bytes, 2, 8)) {\n\t\treturn \"discard-only (100::/64)\";\n\t}\n\tif (bytesEqual(bytes, [0x20, 0x01, 0x0d, 0xb8])) {\n\t\treturn \"documentation (2001:db8::/32)\";\n\t}\n\tconst first = byteAt(bytes, 0);\n\tconst second = byteAt(bytes, 1);\n\tif ((first & 0xfe) === 0xfc) {\n\t\treturn \"unique local (fc00::/7)\";\n\t}\n\tif (first === 0xfe && (second & 0xc0) === 0x80) {\n\t\treturn \"link-local (fe80::/10)\";\n\t}\n\tif (first === 0xfe && (second & 0xc0) === 0xc0) {\n\t\treturn \"site-local (fec0::/10)\";\n\t}\n\tif (first === 0xff) {\n\t\treturn \"multicast (ff00::/8)\";\n\t}\n\treturn undefined;\n}\n\n/**\n * Returns a description of the blocked range the address falls into, or\n * undefined if the address is globally routable (as far as this list knows).\n */\nexport function getBlockedRange(ip: ParsedIp): string | undefined {\n\treturn ip.family === 4 ? classifyIpv4(ip.bytes) : classifyIpv6(ip.bytes);\n}\n","/** One address returned by a resolver. Compatible with `dns.lookup(name, { all: true })`. */\nexport interface ResolvedAddress {\n\treadonly address: string;\n\treadonly family?: number;\n}\n\n/**\n * Resolves a hostname to all of its addresses. Called exactly once per\n * request hop; the connection is pinned to an address from that single answer.\n */\nexport type Resolver = (hostname: string) => Promise<readonly ResolvedAddress[]>;\n\nexport interface SafeFetchOptions {\n\t/** URL protocols that are accepted. Only http: and https: can actually be dispatched. */\n\treadonly allowedProtocols?: readonly string[];\n\t/**\n\t * Allow connections to private, loopback, link-local and other reserved addresses.\n\t * Intended for tests and for known internal targets only.\n\t */\n\treadonly allowPrivateNetworks?: boolean;\n\t/** Ports that may be contacted. Default: [80, 443]. */\n\treadonly allowedPorts?: readonly number[];\n\t/**\n\t * Hostnames that are rejected before DNS resolution. Entries starting with \".\"\n\t * match the suffix (and the bare name). Replaces the default list, so keep the\n\t * defaults if you pass your own. Default: localhost, metadata.google.internal,\n\t * .localhost, .internal.\n\t */\n\treadonly blockedHostnames?: readonly string[];\n\t/** Maximum number of redirects that are followed. Default: 5. */\n\treadonly maxRedirects?: number;\n\t/** Deadline in milliseconds for the whole call, including DNS and body consumption. Default: 10000. */\n\treadonly timeoutMs?: number;\n\t/** Maximum number of decoded response body bytes. No limit when omitted. */\n\treadonly maxBytes?: number;\n\t/** Hostname resolver. Default: node:dns/promises lookup with all: true. */\n\treadonly resolver?: Resolver;\n}\n\nexport type SafeFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;\n\nexport const DEFAULT_ALLOWED_PROTOCOLS: readonly string[] = [\"https:\", \"http:\"];\nexport const DEFAULT_ALLOWED_PORTS: readonly number[] = [80, 443];\nexport const DEFAULT_BLOCKED_HOSTNAMES: readonly string[] = [\n\t\"localhost\",\n\t\"metadata.google.internal\",\n\t\".localhost\",\n\t\".internal\",\n];\nexport const DEFAULT_MAX_REDIRECTS = 5;\nexport const DEFAULT_TIMEOUT_MS = 10_000;\n","import { lookup } from \"node:dns/promises\";\nimport { SafeFetchError } from \"./errors.js\";\nimport { getBlockedRange, parseIp } from \"./ip.js\";\nimport {\n\tDEFAULT_ALLOWED_PORTS,\n\tDEFAULT_ALLOWED_PROTOCOLS,\n\tDEFAULT_BLOCKED_HOSTNAMES,\n\tDEFAULT_MAX_REDIRECTS,\n\tDEFAULT_TIMEOUT_MS,\n\ttype Resolver,\n\ttype SafeFetchOptions,\n} from \"./types.js\";\n\nexport interface ResolvedOptions {\n\treadonly allowedProtocols: readonly string[];\n\treadonly allowPrivateNetworks: boolean;\n\treadonly allowedPorts: readonly number[];\n\treadonly blockedHostnames: readonly string[];\n\treadonly maxRedirects: number;\n\treadonly timeoutMs: number;\n\treadonly maxBytes: number | undefined;\n\treadonly resolver: Resolver;\n}\n\n/** A validated connection target. `address` is the only IP the socket may connect to. */\nexport interface Target {\n\treadonly url: URL;\n\treadonly protocol: \"http:\" | \"https:\";\n\t/** Normalized hostname without brackets or trailing dot. Used for SNI and the Host header. */\n\treadonly hostname: string;\n\treadonly isIpLiteral: boolean;\n\treadonly port: number;\n\treadonly path: string;\n\treadonly address: string;\n\treadonly family: 4 | 6;\n}\n\nconst DEFAULT_PORTS: Readonly<Record<\"http:\" | \"https:\", number>> = {\n\t\"http:\": 80,\n\t\"https:\": 443,\n};\n\nconst PROTOCOL_PATTERN = /^[a-z][a-z0-9+.-]*:$/;\n\nconst defaultResolver: Resolver = (hostname) => lookup(hostname, { all: true, verbatim: true });\n\n/** Validates caller options and applies defaults. Invalid options throw TypeError. */\nexport function resolveOptions(options: SafeFetchOptions): ResolvedOptions {\n\tconst allowedProtocols = [...(options.allowedProtocols ?? DEFAULT_ALLOWED_PROTOCOLS)];\n\tfor (const protocol of allowedProtocols) {\n\t\tif (!PROTOCOL_PATTERN.test(protocol)) {\n\t\t\tthrow new TypeError(`Invalid protocol in allowedProtocols: ${protocol}`);\n\t\t}\n\t}\n\n\tconst allowedPorts = [...(options.allowedPorts ?? DEFAULT_ALLOWED_PORTS)];\n\tfor (const port of allowedPorts) {\n\t\tif (!Number.isInteger(port) || port < 1 || port > 65535) {\n\t\t\tthrow new TypeError(`Invalid port in allowedPorts: ${String(port)}`);\n\t\t}\n\t}\n\n\tconst blockedHostnames = (options.blockedHostnames ?? DEFAULT_BLOCKED_HOSTNAMES).map((entry) =>\n\t\tentry.toLowerCase().replace(/\\.$/, \"\"),\n\t);\n\n\tconst maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;\n\tif (!Number.isSafeInteger(maxRedirects) || maxRedirects < 0) {\n\t\tthrow new TypeError(\"maxRedirects must be a non-negative integer\");\n\t}\n\n\tconst timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2_147_483_647) {\n\t\tthrow new TypeError(\"timeoutMs must be a positive number of milliseconds\");\n\t}\n\n\tconst maxBytes = options.maxBytes;\n\tif (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes < 0)) {\n\t\tthrow new TypeError(\"maxBytes must be a non-negative integer\");\n\t}\n\n\tconst resolver = options.resolver ?? defaultResolver;\n\tif (typeof resolver !== \"function\") {\n\t\tthrow new TypeError(\"resolver must be a function\");\n\t}\n\n\treturn {\n\t\tallowedProtocols,\n\t\tallowPrivateNetworks: options.allowPrivateNetworks ?? false,\n\t\tallowedPorts,\n\t\tblockedHostnames,\n\t\tmaxRedirects,\n\t\ttimeoutMs,\n\t\tmaxBytes,\n\t\tresolver,\n\t};\n}\n\n/** Parses a URL, with or without base. Never echoes the input in the error. */\nexport function parseUrl(input: string, base?: URL): URL {\n\ttry {\n\t\treturn new URL(input, base);\n\t} catch {\n\t\tthrow new SafeFetchError(\"INVALID_URL\", \"Invalid URL\");\n\t}\n}\n\n/**\n * Lowercases, strips IPv6 brackets and one trailing dot. Rejects names that\n * are empty after normalization.\n */\nexport function normalizeHostname(rawHostname: string): string {\n\tlet hostname = rawHostname.toLowerCase();\n\tif (hostname.startsWith(\"[\") && hostname.endsWith(\"]\")) {\n\t\thostname = hostname.slice(1, -1);\n\t}\n\tif (hostname.endsWith(\".\")) {\n\t\thostname = hostname.slice(0, -1);\n\t}\n\tif (hostname === \"\" || hostname.endsWith(\".\")) {\n\t\tthrow new SafeFetchError(\"INVALID_URL\", \"Invalid URL\");\n\t}\n\treturn hostname;\n}\n\nexport function isBlockedHostname(hostname: string, blocked: readonly string[]): boolean {\n\treturn blocked.some((entry) => {\n\t\tif (entry.startsWith(\".\")) {\n\t\t\treturn hostname === entry.slice(1) || hostname.endsWith(entry);\n\t\t}\n\t\treturn hostname === entry;\n\t});\n}\n\nfunction assertNoCredentials(url: URL): void {\n\tif (url.username !== \"\" || url.password !== \"\") {\n\t\tthrow new SafeFetchError(\"INVALID_URL\", \"URLs with credentials are not allowed\");\n\t}\n}\n\nfunction raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n\tif (signal.aborted) {\n\t\tpromise.catch(() => undefined);\n\t\treturn Promise.reject(new DOMException(\"The operation was aborted\", \"AbortError\"));\n\t}\n\treturn new Promise<T>((resolve, reject) => {\n\t\tconst onAbort = () => reject(new DOMException(\"The operation was aborted\", \"AbortError\"));\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tpromise.then(\n\t\t\t(value) => {\n\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\tresolve(value);\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\treject(error);\n\t\t\t},\n\t\t);\n\t});\n}\n\nasync function resolveAddresses(\n\toptions: ResolvedOptions,\n\thostname: string,\n\tsignal: AbortSignal,\n): Promise<string[]> {\n\tlet answers: readonly { readonly address?: unknown }[];\n\ttry {\n\t\tanswers = await raceAbort(options.resolver(hostname), signal);\n\t} catch (error) {\n\t\tif (signal.aborted) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow new SafeFetchError(\"DNS_FAILURE\", `DNS lookup failed for ${hostname}`);\n\t}\n\tif (!Array.isArray(answers) || answers.length === 0) {\n\t\tthrow new SafeFetchError(\"DNS_FAILURE\", `DNS lookup returned no addresses for ${hostname}`);\n\t}\n\treturn answers.map((answer) => {\n\t\tif (typeof answer?.address !== \"string\") {\n\t\t\tthrow new SafeFetchError(\n\t\t\t\t\"BLOCKED_ADDRESS\",\n\t\t\t\t`Resolver returned a non-IP value for ${hostname}`,\n\t\t\t);\n\t\t}\n\t\treturn answer.address;\n\t});\n}\n\n/**\n * Validates one URL hop and resolves it to exactly one pinned address.\n *\n * Every address returned for the hostname is checked. If any of them is blocked,\n * the whole hop is rejected, so a mixed answer cannot be used to smuggle a private\n * address past the check.\n */\nexport async function resolveTarget(\n\turl: URL,\n\toptions: ResolvedOptions,\n\tsignal: AbortSignal,\n): Promise<Target> {\n\tassertNoCredentials(url);\n\n\tconst protocol = url.protocol;\n\tif (\n\t\t(protocol !== \"http:\" && protocol !== \"https:\") ||\n\t\t!options.allowedProtocols.includes(protocol)\n\t) {\n\t\tthrow new SafeFetchError(\"PROTOCOL_NOT_ALLOWED\", `Protocol not allowed: ${protocol}`);\n\t}\n\n\tconst port = url.port === \"\" ? DEFAULT_PORTS[protocol] : Number(url.port);\n\tif (!options.allowedPorts.includes(port)) {\n\t\tthrow new SafeFetchError(\"PORT_NOT_ALLOWED\", `Port not allowed: ${String(port)}`);\n\t}\n\n\tconst hostname = normalizeHostname(url.hostname);\n\tconst literal = parseIp(hostname);\n\tif (literal === undefined && isBlockedHostname(hostname, options.blockedHostnames)) {\n\t\tthrow new SafeFetchError(\"BLOCKED_HOSTNAME\", `Hostname not allowed: ${hostname}`);\n\t}\n\n\tconst candidates =\n\t\tliteral === undefined ? await resolveAddresses(options, hostname, signal) : [hostname];\n\tif (candidates.length === 0) {\n\t\tthrow new SafeFetchError(\"DNS_FAILURE\", `DNS lookup returned no addresses for ${hostname}`);\n\t}\n\n\tlet pinned: { address: string; family: 4 | 6 } | undefined;\n\tfor (const candidate of candidates) {\n\t\tconst ip = parseIp(candidate);\n\t\tif (ip === undefined) {\n\t\t\tthrow new SafeFetchError(\"BLOCKED_ADDRESS\", `Not a valid IP address for ${hostname}`);\n\t\t}\n\t\tif (!options.allowPrivateNetworks) {\n\t\t\tconst range = getBlockedRange(ip);\n\t\t\tif (range !== undefined) {\n\t\t\t\tthrow new SafeFetchError(\n\t\t\t\t\t\"BLOCKED_ADDRESS\",\n\t\t\t\t\t`Address ${candidate} of ${hostname} is in a blocked range: ${range}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tpinned ??= { address: candidate, family: ip.family };\n\t}\n\tif (pinned === undefined) {\n\t\tthrow new SafeFetchError(\"DNS_FAILURE\", `DNS lookup returned no addresses for ${hostname}`);\n\t}\n\n\treturn {\n\t\turl,\n\t\tprotocol,\n\t\thostname,\n\t\tisIpLiteral: literal !== undefined,\n\t\tport,\n\t\tpath: `${url.pathname}${url.search}`,\n\t\taddress: pinned.address,\n\t\tfamily: pinned.family,\n\t};\n}\n","import type { ClientRequest, IncomingMessage, RequestOptions } from \"node:http\";\nimport { request as httpRequest } from \"node:http\";\nimport { request as httpsRequest } from \"node:https\";\nimport type { LookupFunction } from \"node:net\";\nimport type { Readable, Transform } from \"node:stream\";\nimport { createBrotliDecompress, createGunzip, createInflate } from \"node:zlib\";\nimport { SafeFetchError } from \"./errors.js\";\nimport type { Target } from \"./policy.js\";\n\nexport interface OutgoingRequest {\n\treadonly method: string;\n\treadonly headers: Readonly<Record<string, string>>;\n\treadonly body: Uint8Array | undefined;\n}\n\nexport interface ResponseContext {\n\treadonly url: URL;\n\treadonly redirected: boolean;\n\treadonly method: string;\n\treadonly maxBytes: number | undefined;\n\t/** Called once when the body is fully consumed, fails or is cancelled. Idempotent. */\n\treadonly onSettled: () => void;\n\t/** Maps any failure to the public error type. */\n\treadonly toError: (error: unknown) => unknown;\n}\n\ntype ContentEncoding = \"gzip\" | \"deflate\" | \"br\";\n\nconst NULL_BODY_STATUS = new Set([204, 205, 304]);\nconst HOP_BY_HOP_REQUEST_HEADERS = [\"host\", \"content-length\", \"transfer-encoding\", \"connection\"];\n\n/** Lookup that returns exactly the address validated in resolveTarget. Node never re-resolves. */\nfunction pinnedLookup(target: Target): LookupFunction {\n\treturn ((_hostname: string, options: { all?: boolean }, callback: unknown) => {\n\t\tconst done = callback as (\n\t\t\terror: null,\n\t\t\taddress: string | { address: string; family: number }[],\n\t\t\tfamily?: number,\n\t\t) => void;\n\t\tif (options.all === true) {\n\t\t\tdone(null, [{ address: target.address, family: target.family }]);\n\t\t} else {\n\t\t\tdone(null, target.address, target.family);\n\t\t}\n\t}) as LookupFunction;\n}\n\nfunction hostHeaderValue(target: Target): string {\n\tconst host = target.hostname.includes(\":\") ? `[${target.hostname}]` : target.hostname;\n\tconst isDefaultPort = target.port === (target.protocol === \"https:\" ? 443 : 80);\n\treturn isDefaultPort ? host : `${host}:${target.port}`;\n}\n\n/**\n * Sends one request to the pinned address. `agent: false` prevents socket reuse,\n * so a socket validated under one policy can never serve a request under another.\n */\nexport function sendRequest(\n\ttarget: Target,\n\trequest: OutgoingRequest,\n\tsignal: AbortSignal,\n): Promise<IncomingMessage> {\n\treturn new Promise<IncomingMessage>((resolve, reject) => {\n\t\tconst headers: Record<string, string> = { ...request.headers, host: hostHeaderValue(target) };\n\t\tif (request.body !== undefined) {\n\t\t\theaders[\"content-length\"] = String(request.body.byteLength);\n\t\t}\n\t\tconst options: RequestOptions & { lookup: LookupFunction; servername?: string } = {\n\t\t\tprotocol: target.protocol,\n\t\t\thostname: target.hostname,\n\t\t\tport: target.port,\n\t\t\tpath: target.path,\n\t\t\tmethod: request.method,\n\t\t\theaders,\n\t\t\tagent: false,\n\t\t\tsignal,\n\t\t\tlookup: pinnedLookup(target),\n\t\t};\n\t\tif (target.protocol === \"https:\" && !target.isIpLiteral) {\n\t\t\toptions.servername = target.hostname;\n\t\t}\n\t\tconst requestFn = (target.protocol === \"https:\" ? httpsRequest : httpRequest) as (\n\t\t\toptions: RequestOptions,\n\t\t\tcallback: (response: IncomingMessage) => void,\n\t\t) => ClientRequest;\n\t\tconst outgoing = requestFn(options, resolve);\n\t\toutgoing.on(\"error\", reject);\n\t\toutgoing.end(request.body);\n\t});\n}\n\nfunction parseContentEncoding(value: string | undefined): ContentEncoding | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\tconst token = value.trim().toLowerCase();\n\tif (token === \"\" || token === \"identity\") {\n\t\treturn undefined;\n\t}\n\tif (token === \"gzip\" || token === \"x-gzip\") {\n\t\treturn \"gzip\";\n\t}\n\tif (token === \"deflate\") {\n\t\treturn \"deflate\";\n\t}\n\tif (token === \"br\") {\n\t\treturn \"br\";\n\t}\n\tthrow new SafeFetchError(\"NETWORK_ERROR\", \"Unsupported content-encoding in response\");\n}\n\nfunction createDecoder(encoding: ContentEncoding): Transform {\n\tswitch (encoding) {\n\t\tcase \"gzip\":\n\t\t\treturn createGunzip();\n\t\tcase \"deflate\":\n\t\t\treturn createInflate();\n\t\tcase \"br\":\n\t\t\treturn createBrotliDecompress();\n\t}\n}\n\nfunction safeStatusText(message: string | undefined): string {\n\treturn message !== undefined && /^[\\x20-\\x7e]*$/.test(message) ? message : \"\";\n}\n\nfunction createBodyStream(source: Readable, ctx: ResponseContext): ReadableStream<Uint8Array> {\n\tconst iterator = source[Symbol.asyncIterator]();\n\tlet received = 0;\n\treturn new ReadableStream<Uint8Array>({\n\t\tasync pull(controller) {\n\t\t\ttry {\n\t\t\t\tconst next = await iterator.next();\n\t\t\t\tif (next.done === true) {\n\t\t\t\t\tctx.onSettled();\n\t\t\t\t\tcontroller.close();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst chunk = next.value as Uint8Array;\n\t\t\t\treceived += chunk.byteLength;\n\t\t\t\tif (ctx.maxBytes !== undefined && received > ctx.maxBytes) {\n\t\t\t\t\tthrow new SafeFetchError(\"BODY_TOO_LARGE\", `Response body exceeds ${ctx.maxBytes} bytes`);\n\t\t\t\t}\n\t\t\t\tcontroller.enqueue(chunk);\n\t\t\t} catch (error) {\n\t\t\t\tctx.onSettled();\n\t\t\t\tsource.destroy();\n\t\t\t\tcontroller.error(ctx.toError(error));\n\t\t\t}\n\t\t},\n\t\tcancel() {\n\t\t\tctx.onSettled();\n\t\t\tsource.destroy();\n\t\t},\n\t});\n}\n\n/**\n * Converts a Node response into a fetch-compatible Response. Redirects must\n * already have been handled by the caller. Decodes gzip, deflate and br bodies.\n */\nexport function toResponse(incoming: IncomingMessage, ctx: ResponseContext): Response {\n\tconst status = incoming.statusCode ?? 0;\n\tif (status < 200 || status > 599) {\n\t\tincoming.destroy();\n\t\tthrow new SafeFetchError(\"NETWORK_ERROR\", \"Invalid HTTP status in response\");\n\t}\n\n\tconst headers = new Headers();\n\tfor (const [name, value] of Object.entries(incoming.headers)) {\n\t\tif (value === undefined) {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const item of Array.isArray(value) ? value : [value]) {\n\t\t\theaders.append(name, item);\n\t\t}\n\t}\n\n\tconst bodyless = ctx.method === \"HEAD\" || NULL_BODY_STATUS.has(status);\n\tif (bodyless) {\n\t\tincoming.resume();\n\t\tctx.onSettled();\n\t\treturn buildResponse(null, status, incoming.statusMessage, headers, ctx);\n\t}\n\n\tconst encoding = parseContentEncoding(incoming.headers[\"content-encoding\"]);\n\tif (encoding === undefined) {\n\t\tconst declared = Number(incoming.headers[\"content-length\"]);\n\t\tif (ctx.maxBytes !== undefined && Number.isFinite(declared) && declared > ctx.maxBytes) {\n\t\t\tincoming.destroy();\n\t\t\tthrow new SafeFetchError(\"BODY_TOO_LARGE\", `Response body exceeds ${ctx.maxBytes} bytes`);\n\t\t}\n\t} else {\n\t\t// The body is decoded below, so the encoding and length headers no longer describe it.\n\t\theaders.delete(\"content-encoding\");\n\t\theaders.delete(\"content-length\");\n\t}\n\n\t// Errors are handled by the body stream; this listener only prevents an uncaught 'error' event.\n\tincoming.on(\"error\", () => undefined);\n\tlet source: Readable = incoming;\n\tif (encoding !== undefined) {\n\t\tconst decoder = createDecoder(encoding);\n\t\tdecoder.on(\"error\", () => incoming.destroy());\n\t\tsource = incoming.pipe(decoder);\n\t\tincoming.on(\"error\", (error) => decoder.destroy(error));\n\t}\n\n\treturn buildResponse(createBodyStream(source, ctx), status, incoming.statusMessage, headers, ctx);\n}\n\nfunction buildResponse(\n\tbody: ReadableStream<Uint8Array> | null,\n\tstatus: number,\n\tstatusMessage: string | undefined,\n\theaders: Headers,\n\tctx: ResponseContext,\n): Response {\n\tconst response = new Response(body, {\n\t\tstatus,\n\t\tstatusText: safeStatusText(statusMessage),\n\t\theaders,\n\t});\n\tObject.defineProperty(response, \"url\", { value: ctx.url.href });\n\tObject.defineProperty(response, \"redirected\", { value: ctx.redirected });\n\treturn response;\n}\n","import { SafeFetchError } from \"./errors.js\";\nimport { parseUrl, type ResolvedOptions, resolveOptions, resolveTarget } from \"./policy.js\";\nimport { type OutgoingRequest, sendRequest, toResponse } from \"./transport.js\";\nimport type { SafeFetch, SafeFetchOptions } from \"./types.js\";\n\nconst REDIRECT_STATUSES: ReadonlySet<number> = new Set([301, 302, 303, 307, 308]);\n/** Request headers that are never forwarded. Host is always set to the validated target. */\nconst DROPPED_REQUEST_HEADERS: ReadonlySet<string> = new Set([\n\t\"host\",\n\t\"content-length\",\n\t\"transfer-encoding\",\n\t\"connection\",\n]);\n/** Credentials are not sent to a different origin after a redirect, matching fetch. */\nconst CROSS_ORIGIN_HEADERS: readonly string[] = [\"authorization\", \"cookie\", \"proxy-authorization\"];\n\ninterface PreparedRequest {\n\treadonly url: URL;\n\treadonly method: string;\n\treadonly headers: Record<string, string>;\n\treadonly body: Uint8Array | undefined;\n}\n\n/**\n * Creates a fetch-compatible function that validates every URL, resolves each\n * hostname once, connects to the validated IP only, and validates every redirect.\n *\n * Throws SafeFetchError for policy violations. Throws TypeError for invalid options\n * or unsupported body types.\n */\nexport function createSafeFetch(options: SafeFetchOptions = {}): SafeFetch {\n\tconst config = resolveOptions(options);\n\treturn (input, init) => fetchSafely(config, input, init);\n}\n\nasync function toBytes(body: BodyInit): Promise<Uint8Array> {\n\tif (typeof body === \"string\") {\n\t\treturn new TextEncoder().encode(body);\n\t}\n\tif (body instanceof ArrayBuffer) {\n\t\treturn new Uint8Array(body.slice(0));\n\t}\n\tif (ArrayBuffer.isView(body)) {\n\t\treturn new Uint8Array(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength));\n\t}\n\tif (body instanceof URLSearchParams) {\n\t\treturn new TextEncoder().encode(body.toString());\n\t}\n\tif (body instanceof Blob) {\n\t\treturn new Uint8Array(await body.arrayBuffer());\n\t}\n\tthrow new TypeError(\n\t\t\"Unsupported request body; use a string, ArrayBuffer, ArrayBufferView, URLSearchParams or Blob\",\n\t);\n}\n\nasync function prepareRequest(\n\tinput: string | URL | Request,\n\tinit: RequestInit | undefined,\n): Promise<PreparedRequest> {\n\tconst rawUrl = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n\tconst url = parseUrl(rawUrl);\n\tconst method = (init?.method ?? (input instanceof Request ? input.method : \"GET\")).toUpperCase();\n\n\tconst merged = new Headers(input instanceof Request ? input.headers : undefined);\n\tif (init?.headers !== undefined) {\n\t\tnew Headers(init.headers).forEach((value, key) => merged.set(key, value));\n\t}\n\tconst headers: Record<string, string> = {};\n\tmerged.forEach((value, key) => {\n\t\tif (!DROPPED_REQUEST_HEADERS.has(key)) {\n\t\t\theaders[key] = value;\n\t\t}\n\t});\n\t// Compressed responses would otherwise reach the caller undecoded when the caller did not ask for them.\n\theaders[\"accept-encoding\"] ??= \"identity\";\n\n\tlet body: Uint8Array | undefined =\n\t\tinit?.body === undefined || init.body === null ? undefined : await toBytes(init.body);\n\tif (body === undefined && input instanceof Request && method !== \"GET\" && method !== \"HEAD\") {\n\t\tbody = new Uint8Array(await input.arrayBuffer());\n\t}\n\tif (body !== undefined && (method === \"GET\" || method === \"HEAD\")) {\n\t\tthrow new TypeError(\"Request with GET or HEAD method cannot have a body\");\n\t}\n\treturn { url, method, headers, body };\n}\n\nasync function fetchSafely(\n\tconfig: ResolvedOptions,\n\tinput: string | URL | Request,\n\tinit: RequestInit | undefined,\n): Promise<Response> {\n\tconst request = await prepareRequest(input, init);\n\n\tconst callerSignal = init?.signal ?? undefined;\n\tconst controller = new AbortController();\n\tlet timedOut = false;\n\tlet settled = false;\n\tconst onCallerAbort = () => controller.abort();\n\tconst timer = setTimeout(() => {\n\t\ttimedOut = true;\n\t\tcontroller.abort();\n\t}, config.timeoutMs);\n\tcallerSignal?.addEventListener(\"abort\", onCallerAbort, { once: true });\n\tif (callerSignal?.aborted === true) {\n\t\tcontroller.abort();\n\t}\n\n\t// Releases the deadline and the caller listener. Called when the body is done or the call failed.\n\tconst settle = (): void => {\n\t\tif (settled) {\n\t\t\treturn;\n\t\t}\n\t\tsettled = true;\n\t\tclearTimeout(timer);\n\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t};\n\n\tconst toError = (error: unknown): unknown => {\n\t\tif (error instanceof SafeFetchError) {\n\t\t\treturn error;\n\t\t}\n\t\tif (callerSignal?.aborted === true) {\n\t\t\treturn callerSignal.reason;\n\t\t}\n\t\tif (timedOut) {\n\t\t\treturn new SafeFetchError(\"TIMEOUT\", `Request timed out after ${config.timeoutMs} ms`);\n\t\t}\n\t\treturn new SafeFetchError(\"NETWORK_ERROR\", \"Network request failed\");\n\t};\n\n\ttry {\n\t\tlet url = request.url;\n\t\tlet method = request.method;\n\t\tlet headers = request.headers;\n\t\tlet body = request.body;\n\t\tlet redirects = 0;\n\n\t\tfor (;;) {\n\t\t\tconst target = await resolveTarget(url, config, controller.signal);\n\t\t\tconst outgoing: OutgoingRequest = { method, headers, body };\n\t\t\tconst incoming = await sendRequest(target, outgoing, controller.signal);\n\t\t\tconst status = incoming.statusCode ?? 0;\n\n\t\t\tif (!REDIRECT_STATUSES.has(status)) {\n\t\t\t\treturn toResponse(incoming, {\n\t\t\t\t\turl,\n\t\t\t\t\tredirected: redirects > 0,\n\t\t\t\t\tmethod,\n\t\t\t\t\tmaxBytes: config.maxBytes,\n\t\t\t\t\tonSettled: settle,\n\t\t\t\t\ttoError,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst location = incoming.headers.location;\n\t\t\tincoming.destroy();\n\t\t\tif (location === undefined) {\n\t\t\t\tthrow new SafeFetchError(\n\t\t\t\t\t\"REDIRECT_WITHOUT_LOCATION\",\n\t\t\t\t\t\"Redirect response has no Location header\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (redirects >= config.maxRedirects) {\n\t\t\t\tthrow new SafeFetchError(\n\t\t\t\t\t\"REDIRECT_LIMIT\",\n\t\t\t\t\t`Redirect limit of ${config.maxRedirects} exceeded`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tredirects++;\n\n\t\t\t// The next hop is validated from the top of the loop, including protocol, port, DNS and IP checks.\n\t\t\tconst next = parseUrl(location, url);\n\t\t\tconst nextHeaders: Record<string, string> = { ...headers };\n\t\t\tif (\n\t\t\t\t(status === 303 && method !== \"HEAD\") ||\n\t\t\t\t((status === 301 || status === 302) && method === \"POST\")\n\t\t\t) {\n\t\t\t\tmethod = \"GET\";\n\t\t\t\tbody = undefined;\n\t\t\t\tdelete nextHeaders[\"content-type\"];\n\t\t\t}\n\t\t\tif (next.origin !== url.origin) {\n\t\t\t\tfor (const name of CROSS_ORIGIN_HEADERS) {\n\t\t\t\t\tdelete nextHeaders[name];\n\t\t\t\t}\n\t\t\t}\n\t\t\theaders = nextHeaders;\n\t\t\turl = next;\n\t\t}\n\t} catch (error) {\n\t\tsettle();\n\t\tthrow toError(error);\n\t}\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,85 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@yedoma-labs/tanur-ssrf",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SSRF-safe fetch for Node.js: pins the DNS result, validates every resolved IP and every redirect hop. Zero dependencies.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20"
|
|
26
|
+
},
|
|
27
|
+
"packageManager": "pnpm@12.15.0",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsdown",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"test:coverage": "vitest run --coverage",
|
|
33
|
+
"lint": "biome check src tests",
|
|
34
|
+
"lint:fix": "biome check --write src tests",
|
|
35
|
+
"format": "biome format --write src tests",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"prepublishOnly": "pnpm run build"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"ssrf",
|
|
41
|
+
"fetch",
|
|
42
|
+
"security",
|
|
43
|
+
"dns-rebinding",
|
|
44
|
+
"redirect",
|
|
45
|
+
"url-validation",
|
|
46
|
+
"node"
|
|
47
|
+
],
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/yedoma-labs/tanur-ssrf"
|
|
51
|
+
},
|
|
52
|
+
"author": "",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"simple-git-hooks": {
|
|
55
|
+
"pre-commit": "pnpm exec lint-staged",
|
|
56
|
+
"pre-push": "pnpm run typecheck && pnpm run test",
|
|
57
|
+
"commit-msg": "pnpm exec commitlint --edit $1"
|
|
58
|
+
},
|
|
59
|
+
"lint-staged": {
|
|
60
|
+
"*.ts": [
|
|
61
|
+
"biome check --write"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@biomejs/biome": "2.6.3",
|
|
66
|
+
"@commitlint/cli": "^21.2.4",
|
|
67
|
+
"@commitlint/config-conventional": "^21.2.4",
|
|
68
|
+
"@semantic-release/changelog": "^7.0.0",
|
|
69
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
70
|
+
"@semantic-release/exec": "^7.1.0",
|
|
71
|
+
"@semantic-release/git": "^11.0.1",
|
|
72
|
+
"@semantic-release/github": "^12.0.10",
|
|
73
|
+
"@semantic-release/npm": "^13.2.0",
|
|
74
|
+
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
75
|
+
"@types/node": "26.6.5",
|
|
76
|
+
"@typescript/typescript6": "^6.0.2",
|
|
77
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
78
|
+
"lint-staged": "^17.6.0",
|
|
79
|
+
"semantic-release": "^25.0.9",
|
|
80
|
+
"simple-git-hooks": "^2.14.0",
|
|
81
|
+
"tsdown": "^0.23.2",
|
|
82
|
+
"typescript": "7.0.2",
|
|
83
|
+
"vitest": "5.0.5"
|
|
84
|
+
}
|
|
85
|
+
}
|