{"_id":"@ariada-org/url-guard","name":"@ariada-org/url-guard","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@ariada-org/url-guard","version":"0.1.0","description":"Shared SSRF guard — reject non-http(s) schemes and resolve+validate hostnames against loopback/private/link-local/reserved ranges, returning a pinned IP so callers can close DNS-rebinding. Open source under EUPL-1.2.","license":"EUPL-1.2","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"dependencies":{"neverthrow":"^8.2.0"},"engines":{"node":">=22"},"repository":{"type":"git","url":"git+https://github.com/ariada-org/ariada.git","directory":"packages/url-guard"},"devDependencies":{"@types/node":"^22.10.2","rimraf":"^6.0.1","typescript":"^5.7.2","vitest":"^4.1.0"},"publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","test":"vitest run","clean":"rimraf dist coverage","lint":"eslint src"},"_id":"@ariada-org/url-guard@0.1.0","bugs":{"url":"https://github.com/ariada-org/ariada/issues"},"homepage":"https://github.com/ariada-org/ariada#readme","_integrity":"sha512-/w39JIdzTcs8MaMKKrhWNafTLKHqa/0IAvJS03v6txZCzIkn5RNuB19b/irnRI+kSNoZup/25EMjiL+APtaxNw==","_resolved":"/private/var/folders/2c/_42xj0l179z8yc7wmp5k91s00000gn/T/1bfbedfc6b135e0f87103004241d0f55/ariada-org-url-guard-0.1.0.tgz","_from":"file:ariada-org-url-guard-0.1.0.tgz","_nodeVersion":"26.5.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-/w39JIdzTcs8MaMKKrhWNafTLKHqa/0IAvJS03v6txZCzIkn5RNuB19b/irnRI+kSNoZup/25EMjiL+APtaxNw==","shasum":"eddef28cefb508f3c52f2165d5d711292b805219","tarball":"https://registry.npmjs.org/@ariada-org/url-guard/-/url-guard-0.1.0.tgz","fileCount":11,"unpackedSize":38413,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDguqd/DSWTqG/rndTfTX8APy/h3U3rrFiMKT2gvEewLwIgItAP6RUpySXSaspmCFzp2RtlWFma+KEGLPs6eOZDE+Q="}]},"_npmUser":{"name":"agonist","email":"ab@agonist.ai"},"directories":{},"maintainers":[{"name":"agonist","email":"ab@agonist.ai"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/url-guard_0.1.0_1784942578406_0.7383944449276225"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-25T01:22:58.254Z","0.1.0":"2026-07-25T01:22:58.558Z","modified":"2026-07-25T01:22:58.769Z"},"maintainers":[{"name":"agonist","email":"ab@agonist.ai"}],"description":"Shared SSRF guard — reject non-http(s) schemes and resolve+validate hostnames against loopback/private/link-local/reserved ranges, returning a pinned IP so callers can close DNS-rebinding. Open source under EUPL-1.2.","homepage":"https://github.com/ariada-org/ariada#readme","repository":{"type":"git","url":"git+https://github.com/ariada-org/ariada.git","directory":"packages/url-guard"},"bugs":{"url":"https://github.com/ariada-org/ariada/issues"},"license":"EUPL-1.2","readme":"<!--\nSPDX-FileCopyrightText: 2026 Agonist Development AB\nSPDX-License-Identifier: EUPL-1.2\n-->\n\n# @ariada-org/url-guard\n\nShared server-side request-forgery (SSRF) guard for every place the scanner\nfetches a user-supplied URL. It rejects non-`http(s)` schemes, resolves the\nhostname to all of its addresses, and refuses the request if any resolved\naddress is loopback, private (RFC 1918), link-local, unique-local, carrier-grade\nNAT, or otherwise reserved — including the IPv4-mapped IPv6 form\n(`::ffff:a.b.c.d`) that a naive prefix check misses. It returns the validated\naddress so the caller can pin the connection and close DNS-rebinding.\n\n## Install\n\n```sh\npnpm add @ariada-org/url-guard\n```\n\n## Usage\n\n```ts\nimport { resolveAndGuard, guardRedirect } from '@ariada-org/url-guard';\n\nconst guarded = await resolveAndGuard(userUrl);\nif (guarded.isErr()) {\n  // guarded.error.kind: 'scheme_not_allowed' | 'private_literal'\n  //   | 'private_resolved' | 'resolution_failed' | 'unparseable'\n  throw new Error(`refused: ${guarded.error.kind}`);\n}\n// guarded.value.url          — the validated URL\n// guarded.value.pinnedAddress — pin the socket to this IP before fetching\n\n// On each redirect hop, re-check the Location header:\nconst next = await guardRedirect(locationHeader, currentUrl);\n```\n\n`assertSafeUrl` is the synchronous scheme + IP-literal check (no DNS);\n`resolveAndGuard` adds hostname resolution and returns the pinned address;\n`guardRedirect` resolves a relative `Location` against its base and re-guards it.\n\n## Options\n\n- `allowPrivate` — when `true`, private/loopback destinations are allowed (for a\n  CLI `--allow-private` opt-in or local development). Defaults to `false`.\n\n## Ranges refused by default\n\n`10/8`, `172.16/12`, `192.168/16`, `127/8`, `169.254/16` (cloud metadata),\n`0.0.0.0/8`, `100.64/10` (CGNAT), `::1`, `fc00::/7`, `fe80::/10`, and any\nIPv4-mapped IPv6 whose embedded IPv4 falls in the above.\n\n## License\n\nEUPL-1.2. Copyright Agonist Development AB. See `LICENSE`.\n","readmeFilename":"README.md","_rev":"1-4141ab42a6f94542869ba8fb7497fb00"}