@nifrajs/link-safety 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ export declare const LINK_SAFETY_VERDICTS: readonly ["benign", "suspicious", "malicious", "unknown"];
2
+ export type LinkSafetyVerdict = (typeof LINK_SAFETY_VERDICTS)[number];
3
+ export type ClassifiedLinkVerdict = Exclude<LinkSafetyVerdict, "unknown">;
4
+ export declare const LINK_SAFETY_ACTIONS: readonly ["allow", "review", "block"];
5
+ export type LinkSafetyAction = (typeof LINK_SAFETY_ACTIONS)[number];
6
+ export declare const LINK_SAFETY_REASONS: readonly ["corpus_benign_match", "corpus_suspicious_match", "corpus_malicious_match", "corpus_conflict", "punycode_hostname", "ip_literal", "private_network_candidate", "non_default_port", "long_hostname", "many_subdomains", "encoded_path_separator", "long_path", "query_present", "semantic_classifier"];
7
+ export type LinkSafetyReason = (typeof LINK_SAFETY_REASONS)[number];
8
+ export declare const LINK_SAFETY_ERROR_CODES: readonly ["invalid_url", "corpus_invalid", "corpus_unavailable", "classifier_unavailable", "classifier_timeout", "classifier_rate_limited", "classifier_unauthorized", "classifier_response_invalid", "cancelled", "policy_invalid", "internal"];
9
+ export type LinkSafetyErrorCode = (typeof LINK_SAFETY_ERROR_CODES)[number];
10
+ export type CorpusLabel = Exclude<LinkSafetyVerdict, "unknown">;
11
+ export type CorpusIndicatorType = "hostname" | "hostname_path_prefix" | "url_prefix";
12
+ export interface NormalizedLink {
13
+ readonly protocol: "http" | "https";
14
+ readonly hostname: string;
15
+ readonly port?: number;
16
+ readonly pathname: string;
17
+ readonly canonical: string;
18
+ readonly hasQuery: boolean;
19
+ readonly queryParameterCount: number;
20
+ readonly queryLength: number;
21
+ readonly flags: readonly LinkSafetyReason[];
22
+ }
23
+ export interface CorpusEntry {
24
+ readonly id: string;
25
+ readonly indicatorType: CorpusIndicatorType;
26
+ readonly indicator: string;
27
+ readonly label: CorpusLabel;
28
+ readonly category?: string;
29
+ readonly source?: string;
30
+ readonly updatedAt?: string;
31
+ }
32
+ export interface CorpusMatch {
33
+ readonly entry: CorpusEntry;
34
+ readonly matchType: CorpusIndicatorType;
35
+ }
36
+ export interface CorpusExamplesOptions {
37
+ readonly limit?: number;
38
+ }
39
+ export interface LinkCorpus {
40
+ readonly version: string;
41
+ lookup(link: NormalizedLink): PromiseLike<readonly CorpusMatch[]>;
42
+ examples?(link: NormalizedLink, options?: CorpusExamplesOptions): PromiseLike<readonly CorpusEntry[]>;
43
+ }
44
+ export interface LinkClassifierInput {
45
+ readonly link: NormalizedLink;
46
+ readonly matches: readonly CorpusMatch[];
47
+ readonly examples: readonly CorpusEntry[];
48
+ readonly signal: AbortSignal;
49
+ }
50
+ export interface LinkClassifierSuccess {
51
+ readonly ok: true;
52
+ readonly verdict: ClassifiedLinkVerdict;
53
+ readonly riskScore: number;
54
+ readonly confidence: number;
55
+ readonly reasons?: readonly LinkSafetyReason[];
56
+ readonly provider?: string;
57
+ readonly model?: string;
58
+ }
59
+ export interface LinkClassifierFailure {
60
+ readonly ok: false;
61
+ readonly error: LinkSafetyErrorCode;
62
+ }
63
+ export type LinkClassifierResult = LinkClassifierSuccess | LinkClassifierFailure;
64
+ export interface LinkClassifier {
65
+ readonly id: string;
66
+ classify(input: LinkClassifierInput): PromiseLike<LinkClassifierResult>;
67
+ }
68
+ export interface LinkSafetyPolicy {
69
+ /** Automatic allow/block recommendations are disabled unless explicitly true. */
70
+ readonly allowAct?: boolean;
71
+ /** Required confidence for semantic recommendations. Defaults to 0.92. */
72
+ readonly minimumConfidence?: number;
73
+ /** An exact malicious corpus match can recommend block when action is enabled. */
74
+ readonly blockKnownMalicious?: boolean;
75
+ }
76
+ export interface LinkScanOptions {
77
+ readonly corpus?: LinkCorpus;
78
+ readonly classifier?: LinkClassifier;
79
+ readonly policy?: LinkSafetyPolicy;
80
+ readonly signal?: AbortSignal;
81
+ }
82
+ export interface LinkScanEvidence {
83
+ readonly corpusVersion?: string;
84
+ readonly corpusMatchCount: number;
85
+ readonly provider?: string;
86
+ readonly model?: string;
87
+ readonly deterministicReasons: readonly LinkSafetyReason[];
88
+ readonly latencyMs: number;
89
+ }
90
+ export interface LinkScanSuccess {
91
+ readonly ok: true;
92
+ readonly link: NormalizedLink;
93
+ readonly verdict: LinkSafetyVerdict;
94
+ readonly riskScore: number;
95
+ readonly confidence: number;
96
+ readonly reasons: readonly LinkSafetyReason[];
97
+ readonly recommendedAction: LinkSafetyAction;
98
+ readonly policyReason: "automatic_action_not_enabled" | "confidence_below_threshold" | "conflicting_signals" | "no_reliable_signal" | "passed";
99
+ readonly evidence: LinkScanEvidence;
100
+ }
101
+ export interface LinkScanFailure {
102
+ readonly ok: false;
103
+ readonly error: LinkSafetyErrorCode;
104
+ readonly recommendedAction: "review";
105
+ readonly link?: NormalizedLink;
106
+ readonly latencyMs: number;
107
+ }
108
+ export type LinkScanResult = LinkScanSuccess | LinkScanFailure;
109
+ export interface LinkSafetyScanner {
110
+ scan(input: unknown, options?: LinkScanOptions): Promise<LinkScanResult>;
111
+ }
112
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,37 @@
1
+ export const LINK_SAFETY_VERDICTS = [
2
+ "benign",
3
+ "suspicious",
4
+ "malicious",
5
+ "unknown",
6
+ ];
7
+ export const LINK_SAFETY_ACTIONS = ["allow", "review", "block"];
8
+ export const LINK_SAFETY_REASONS = [
9
+ "corpus_benign_match",
10
+ "corpus_suspicious_match",
11
+ "corpus_malicious_match",
12
+ "corpus_conflict",
13
+ "punycode_hostname",
14
+ "ip_literal",
15
+ "private_network_candidate",
16
+ "non_default_port",
17
+ "long_hostname",
18
+ "many_subdomains",
19
+ "encoded_path_separator",
20
+ "long_path",
21
+ "query_present",
22
+ "semantic_classifier",
23
+ ];
24
+ export const LINK_SAFETY_ERROR_CODES = [
25
+ "invalid_url",
26
+ "corpus_invalid",
27
+ "corpus_unavailable",
28
+ "classifier_unavailable",
29
+ "classifier_timeout",
30
+ "classifier_rate_limited",
31
+ "classifier_unauthorized",
32
+ "classifier_response_invalid",
33
+ "cancelled",
34
+ "policy_invalid",
35
+ "internal",
36
+ ];
37
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,QAAQ;IACR,YAAY;IACZ,WAAW;IACX,SAAS;CACD,CAAC;AAKX,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAC;AAGzE,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,qBAAqB;IACrB,yBAAyB;IACzB,wBAAwB;IACxB,iBAAiB;IACjB,mBAAmB;IACnB,YAAY;IACZ,2BAA2B;IAC3B,kBAAkB;IAClB,eAAe;IACf,iBAAiB;IACjB,wBAAwB;IACxB,WAAW;IACX,eAAe;IACf,qBAAqB;CACb,CAAC;AAIX,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,aAAa;IACb,gBAAgB;IAChB,oBAAoB;IACpB,wBAAwB;IACxB,oBAAoB;IACpB,yBAAyB;IACzB,yBAAyB;IACzB,6BAA6B;IAC7B,WAAW;IACX,gBAAgB;IAChB,UAAU;CACF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@nifrajs/link-safety",
3
+ "version": "0.1.0",
4
+ "description": "Local-first, fail-closed URL risk classification for TypeScript applications.",
5
+ "keywords": [
6
+ "nifra",
7
+ "url",
8
+ "security",
9
+ "phishing",
10
+ "abuse-detection",
11
+ "classification"
12
+ ],
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src"
27
+ ],
28
+ "license": "MIT",
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/nifrajs/nifra-link-safety.git",
35
+ "directory": "packages/link-safety"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.build.json",
39
+ "test": "bun test"
40
+ }
41
+ }
package/src/corpus.ts ADDED
@@ -0,0 +1,210 @@
1
+ import { normalizeLink } from "./normalize.js";
2
+ import type {
3
+ CorpusEntry,
4
+ CorpusExamplesOptions,
5
+ CorpusIndicatorType,
6
+ CorpusLabel,
7
+ CorpusMatch,
8
+ LinkCorpus,
9
+ NormalizedLink,
10
+ } from "./types.js";
11
+
12
+ const MAX_CORPUS_BYTES = 64 * 1024 * 1024;
13
+ const MAX_CORPUS_ENTRIES = 100_000;
14
+ const MAX_LINE_BYTES = 32 * 1024;
15
+ const MAX_ID_LENGTH = 128;
16
+ const MAX_INDICATOR_LENGTH = 4096;
17
+ const MAX_TEXT_LENGTH = 256;
18
+
19
+ const LABELS = new Set<CorpusLabel>(["benign", "suspicious", "malicious"]);
20
+ const INDICATOR_TYPES = new Set<CorpusIndicatorType>([
21
+ "hostname",
22
+ "hostname_path_prefix",
23
+ "url_prefix",
24
+ ]);
25
+
26
+ interface IndexedEntry {
27
+ readonly entry: CorpusEntry;
28
+ readonly host: string;
29
+ readonly pathPrefix?: string;
30
+ readonly canonicalPrefix?: string;
31
+ }
32
+
33
+ export interface MemoryCorpusOptions {
34
+ readonly version?: string;
35
+ }
36
+
37
+ export function createMemoryCorpus(
38
+ entries: readonly unknown[],
39
+ options: MemoryCorpusOptions = {},
40
+ ): LinkCorpus {
41
+ if (!Array.isArray(entries) || entries.length > MAX_CORPUS_ENTRIES) {
42
+ throw new RangeError("corpus: too many entries");
43
+ }
44
+ const version = validateText(options.version ?? "memory", "version", 128);
45
+ const ids = new Set<string>();
46
+ const indexed: IndexedEntry[] = [];
47
+ for (const [index, raw] of entries.entries()) {
48
+ const entry = validateEntry(raw, index);
49
+ if (ids.has(entry.id)) throw new TypeError("corpus: duplicate entry id");
50
+ ids.add(entry.id);
51
+ indexed.push(indexEntry(entry));
52
+ }
53
+
54
+ return Object.freeze({
55
+ version,
56
+ lookup: async (link: NormalizedLink): Promise<readonly CorpusMatch[]> => {
57
+ const matches = indexed
58
+ .filter((item) => matchesLink(item, link))
59
+ .map((item) =>
60
+ Object.freeze({
61
+ entry: item.entry,
62
+ matchType: item.entry.indicatorType,
63
+ }),
64
+ );
65
+ return Object.freeze(matches);
66
+ },
67
+ examples: async (
68
+ link: NormalizedLink,
69
+ exampleOptions: CorpusExamplesOptions = {},
70
+ ): Promise<readonly CorpusEntry[]> => {
71
+ const limit = validateLimit(exampleOptions.limit);
72
+ const hostMatches = indexed
73
+ .filter((item) => item.host === link.hostname)
74
+ .slice(0, limit)
75
+ .map((item) => item.entry);
76
+ return Object.freeze(hostMatches);
77
+ },
78
+ });
79
+ }
80
+
81
+ export function parseCorpusJsonl(
82
+ input: string,
83
+ options: MemoryCorpusOptions = {},
84
+ ): LinkCorpus {
85
+ if (typeof input !== "string") throw new TypeError("corpus: input is invalid");
86
+ if (new TextEncoder().encode(input).byteLength > MAX_CORPUS_BYTES) {
87
+ throw new RangeError("corpus: input is too large");
88
+ }
89
+ const entries: unknown[] = [];
90
+ for (const [index, line] of input.split(/\r?\n/u).entries()) {
91
+ if (line.trim() === "") continue;
92
+ if (new TextEncoder().encode(line).byteLength > MAX_LINE_BYTES) {
93
+ throw new RangeError(`corpus: line ${index + 1} is too large`);
94
+ }
95
+ let value: unknown;
96
+ try {
97
+ value = JSON.parse(line);
98
+ } catch {
99
+ throw new TypeError(`corpus: line ${index + 1} is not valid JSON`);
100
+ }
101
+ entries.push(value);
102
+ }
103
+ return createMemoryCorpus(entries, options);
104
+ }
105
+
106
+ function validateEntry(value: unknown, index: number): CorpusEntry {
107
+ if (!isRecord(value)) throw new TypeError(`corpus: entry ${index + 1} is invalid`);
108
+ const id = validateText(value.id, "entry id", MAX_ID_LENGTH);
109
+ const indicatorType = value.indicatorType;
110
+ if (
111
+ typeof indicatorType !== "string" ||
112
+ !INDICATOR_TYPES.has(indicatorType as CorpusIndicatorType)
113
+ ) {
114
+ throw new TypeError(`corpus: entry ${index + 1} has an invalid indicator type`);
115
+ }
116
+ const indicator = validateText(value.indicator, "indicator", MAX_INDICATOR_LENGTH);
117
+ const label = value.label;
118
+ if (typeof label !== "string" || !LABELS.has(label as CorpusLabel)) {
119
+ throw new TypeError(`corpus: entry ${index + 1} has an invalid label`);
120
+ }
121
+ const category = optionalText(value.category, "category", MAX_TEXT_LENGTH);
122
+ const source = optionalText(value.source, "source", MAX_TEXT_LENGTH);
123
+ const updatedAt = optionalText(value.updatedAt, "updatedAt", 64);
124
+ const entry: CorpusEntry = {
125
+ id,
126
+ indicatorType: indicatorType as CorpusIndicatorType,
127
+ indicator,
128
+ label: label as CorpusLabel,
129
+ ...(category === undefined ? {} : { category }),
130
+ ...(source === undefined ? {} : { source }),
131
+ ...(updatedAt === undefined ? {} : { updatedAt }),
132
+ };
133
+ return Object.freeze(entry);
134
+ }
135
+
136
+ function indexEntry(entry: CorpusEntry): IndexedEntry {
137
+ if (entry.indicatorType === "hostname") {
138
+ return Object.freeze({ entry, host: normalizeHost(entry.indicator) });
139
+ }
140
+ if (entry.indicatorType === "hostname_path_prefix") {
141
+ const slash = entry.indicator.indexOf("/");
142
+ if (slash <= 0) {
143
+ throw new TypeError("corpus: hostname path indicator is invalid");
144
+ }
145
+ const host = normalizeHost(entry.indicator.slice(0, slash));
146
+ const pathPrefix = normalizePathPrefix(entry.indicator.slice(slash));
147
+ return Object.freeze({ entry, host, pathPrefix });
148
+ }
149
+ const normalized = normalizeLink(entry.indicator);
150
+ if (!normalized.ok) throw new TypeError("corpus: URL indicator is invalid");
151
+ return Object.freeze({
152
+ entry,
153
+ host: normalized.link.hostname,
154
+ canonicalPrefix: normalized.link.canonical,
155
+ });
156
+ }
157
+
158
+ function matchesLink(item: IndexedEntry, link: NormalizedLink): boolean {
159
+ if (item.host !== link.hostname) return false;
160
+ if (item.pathPrefix !== undefined && !link.pathname.startsWith(item.pathPrefix)) {
161
+ return false;
162
+ }
163
+ return (
164
+ item.canonicalPrefix === undefined ||
165
+ link.canonical.startsWith(item.canonicalPrefix)
166
+ );
167
+ }
168
+
169
+ function normalizeHost(value: string): string {
170
+ const host = value.trim().toLowerCase().replace(/\.$/u, "");
171
+ if (host.length === 0 || host.includes("/") || host.includes("@")) {
172
+ throw new TypeError("corpus: hostname indicator is invalid");
173
+ }
174
+ return host;
175
+ }
176
+
177
+ function normalizePathPrefix(value: string): string {
178
+ if (!value.startsWith("/") || value.length > 2048) {
179
+ throw new TypeError("corpus: path prefix is invalid");
180
+ }
181
+ return value;
182
+ }
183
+
184
+ function validateText(value: unknown, label: string, maxLength: number): string {
185
+ if (typeof value !== "string" || value.trim() === "" || value.length > maxLength) {
186
+ throw new TypeError(`corpus: ${label} is invalid`);
187
+ }
188
+ return value;
189
+ }
190
+
191
+ function optionalText(
192
+ value: unknown,
193
+ label: string,
194
+ maxLength: number,
195
+ ): string | undefined {
196
+ if (value === undefined) return undefined;
197
+ return validateText(value, label, maxLength);
198
+ }
199
+
200
+ function validateLimit(value: number | undefined): number {
201
+ if (value === undefined) return 8;
202
+ if (!Number.isSafeInteger(value) || value < 0 || value > 32) {
203
+ throw new RangeError("corpus: example limit must be between 0 and 32");
204
+ }
205
+ return value;
206
+ }
207
+
208
+ function isRecord(value: unknown): value is Record<string, unknown> {
209
+ return typeof value === "object" && value !== null && !Array.isArray(value);
210
+ }
package/src/index.ts ADDED
@@ -0,0 +1,35 @@
1
+ export { createMemoryCorpus, parseCorpusJsonl } from "./corpus.js";
2
+ export { normalizeLink } from "./normalize.js";
3
+ export { createLinkSafetyScanner, scanLink } from "./scan.js";
4
+ export type {
5
+ ClassifiedLinkVerdict,
6
+ CorpusEntry,
7
+ CorpusExamplesOptions,
8
+ CorpusIndicatorType,
9
+ CorpusLabel,
10
+ CorpusMatch,
11
+ LinkClassifier,
12
+ LinkClassifierFailure,
13
+ LinkClassifierInput,
14
+ LinkClassifierResult,
15
+ LinkClassifierSuccess,
16
+ LinkCorpus,
17
+ LinkSafetyAction,
18
+ LinkSafetyErrorCode,
19
+ LinkSafetyPolicy,
20
+ LinkSafetyReason,
21
+ LinkSafetyScanner,
22
+ LinkSafetyVerdict,
23
+ LinkScanEvidence,
24
+ LinkScanFailure,
25
+ LinkScanOptions,
26
+ LinkScanResult,
27
+ LinkScanSuccess,
28
+ NormalizedLink,
29
+ } from "./types.js";
30
+ export {
31
+ LINK_SAFETY_ACTIONS,
32
+ LINK_SAFETY_ERROR_CODES,
33
+ LINK_SAFETY_REASONS,
34
+ LINK_SAFETY_VERDICTS,
35
+ } from "./types.js";
@@ -0,0 +1,118 @@
1
+ import type { LinkSafetyErrorCode, LinkSafetyReason, NormalizedLink } from "./types.js";
2
+
3
+ const MAX_URL_LENGTH = 4096;
4
+ const MAX_PATH_LENGTH = 2048;
5
+ const MAX_QUERY_LENGTH = 4096;
6
+
7
+ const IPV4 = /^(?:\d{1,3}\.){3}\d{1,3}$/;
8
+ const IPV6 = /^\[[0-9a-f:]+\]$/i;
9
+
10
+ export type NormalizeResult =
11
+ | { readonly ok: true; readonly link: NormalizedLink }
12
+ | { readonly ok: false; readonly error: LinkSafetyErrorCode };
13
+
14
+ export function normalizeLink(input: unknown): NormalizeResult {
15
+ if (typeof input !== "string") return { ok: false, error: "invalid_url" };
16
+ const value = input.trim();
17
+ if (value.length === 0 || value.length > MAX_URL_LENGTH) {
18
+ return { ok: false, error: "invalid_url" };
19
+ }
20
+
21
+ let parsed: URL;
22
+ try {
23
+ parsed = new URL(value);
24
+ } catch {
25
+ return { ok: false, error: "invalid_url" };
26
+ }
27
+
28
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
29
+ return { ok: false, error: "invalid_url" };
30
+ }
31
+ if (parsed.username || parsed.password || parsed.hostname.length === 0) {
32
+ return { ok: false, error: "invalid_url" };
33
+ }
34
+ if (
35
+ parsed.pathname.length > MAX_PATH_LENGTH ||
36
+ parsed.search.length > MAX_QUERY_LENGTH
37
+ ) {
38
+ return { ok: false, error: "invalid_url" };
39
+ }
40
+
41
+ const hostname = parsed.hostname.toLowerCase().replace(/\.$/u, "");
42
+ const pathname = parsed.pathname.length === 0 ? "/" : parsed.pathname;
43
+ const port = parsed.port === "" ? undefined : Number(parsed.port);
44
+ if (port !== undefined && (!Number.isSafeInteger(port) || port < 1 || port > 65535)) {
45
+ return { ok: false, error: "invalid_url" };
46
+ }
47
+
48
+ const flags: LinkSafetyReason[] = [];
49
+ if (hostname.includes("xn--")) flags.push("punycode_hostname");
50
+ if (isIpLiteral(hostname)) flags.push("ip_literal");
51
+ if (isPrivateAddress(hostname)) flags.push("private_network_candidate");
52
+ if (port !== undefined && port !== (parsed.protocol === "https:" ? 443 : 80)) {
53
+ flags.push("non_default_port");
54
+ }
55
+ if (hostname.length > 100) flags.push("long_hostname");
56
+ if (hostname.split(".").length > 5) flags.push("many_subdomains");
57
+ if (/%(?:2f|5c)/i.test(pathname)) flags.push("encoded_path_separator");
58
+ if (pathname.length > 1024) flags.push("long_path");
59
+ if (parsed.search.length > 0) flags.push("query_present");
60
+
61
+ const canonical = `${parsed.protocol}//${hostname}${
62
+ port === undefined ? "" : `:${port}`
63
+ }${pathname}`;
64
+ return {
65
+ ok: true,
66
+ link: Object.freeze({
67
+ protocol: parsed.protocol === "https:" ? "https" : "http",
68
+ hostname,
69
+ ...(port === undefined ? {} : { port }),
70
+ pathname,
71
+ canonical,
72
+ hasQuery: parsed.search.length > 0,
73
+ queryParameterCount: countQueryParameters(parsed.search),
74
+ queryLength: parsed.search.length,
75
+ flags: Object.freeze(flags),
76
+ }),
77
+ };
78
+ }
79
+
80
+ function countQueryParameters(search: string): number {
81
+ if (search.length === 0) return 0;
82
+ try {
83
+ return [...new URLSearchParams(search)].length;
84
+ } catch {
85
+ return 0;
86
+ }
87
+ }
88
+
89
+ function isIpLiteral(hostname: string): boolean {
90
+ return IPV4.test(hostname) || IPV6.test(hostname);
91
+ }
92
+
93
+ function isPrivateAddress(hostname: string): boolean {
94
+ const unwrapped = hostname.replace(/^\[/, "").replace(/\]$/u, "");
95
+ if (
96
+ unwrapped === "localhost" ||
97
+ unwrapped === "::1" ||
98
+ unwrapped.startsWith("fc") ||
99
+ unwrapped.startsWith("fd") ||
100
+ unwrapped.startsWith("fe80:")
101
+ ) {
102
+ return true;
103
+ }
104
+ if (!IPV4.test(unwrapped)) return false;
105
+ const octets = unwrapped.split(".").map(Number);
106
+ if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) {
107
+ return false;
108
+ }
109
+ const first = octets[0] ?? -1;
110
+ const second = octets[1] ?? -1;
111
+ return (
112
+ first === 10 ||
113
+ first === 127 ||
114
+ (first === 169 && second === 254) ||
115
+ (first === 172 && second >= 16 && second <= 31) ||
116
+ (first === 192 && second === 168)
117
+ );
118
+ }