milieu-cli 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.
- package/LICENSE +200 -0
- package/README.md +153 -0
- package/dist/bridges/index.d.ts +5 -0
- package/dist/bridges/index.d.ts.map +1 -0
- package/dist/bridges/index.js +6 -0
- package/dist/bridges/index.js.map +1 -0
- package/dist/bridges/reachability/crawler-policy.d.ts +36 -0
- package/dist/bridges/reachability/crawler-policy.d.ts.map +1 -0
- package/dist/bridges/reachability/crawler-policy.js +110 -0
- package/dist/bridges/reachability/crawler-policy.js.map +1 -0
- package/dist/bridges/reachability/http-status.d.ts +7 -0
- package/dist/bridges/reachability/http-status.d.ts.map +1 -0
- package/dist/bridges/reachability/http-status.js +74 -0
- package/dist/bridges/reachability/http-status.js.map +1 -0
- package/dist/bridges/reachability/https-check.d.ts +14 -0
- package/dist/bridges/reachability/https-check.d.ts.map +1 -0
- package/dist/bridges/reachability/https-check.js +38 -0
- package/dist/bridges/reachability/https-check.js.map +1 -0
- package/dist/bridges/reachability/index.d.ts +13 -0
- package/dist/bridges/reachability/index.d.ts.map +1 -0
- package/dist/bridges/reachability/index.js +115 -0
- package/dist/bridges/reachability/index.js.map +1 -0
- package/dist/bridges/reachability/meta-robots.d.ts +16 -0
- package/dist/bridges/reachability/meta-robots.d.ts.map +1 -0
- package/dist/bridges/reachability/meta-robots.js +119 -0
- package/dist/bridges/reachability/meta-robots.js.map +1 -0
- package/dist/bridges/reachability/robots-parser.d.ts +26 -0
- package/dist/bridges/reachability/robots-parser.d.ts.map +1 -0
- package/dist/bridges/reachability/robots-parser.js +105 -0
- package/dist/bridges/reachability/robots-parser.js.map +1 -0
- package/dist/bridges/reachability/robots-txt.d.ts +14 -0
- package/dist/bridges/reachability/robots-txt.d.ts.map +1 -0
- package/dist/bridges/reachability/robots-txt.js +80 -0
- package/dist/bridges/reachability/robots-txt.js.map +1 -0
- package/dist/bridges/separation/api-presence.d.ts +14 -0
- package/dist/bridges/separation/api-presence.d.ts.map +1 -0
- package/dist/bridges/separation/api-presence.js +96 -0
- package/dist/bridges/separation/api-presence.js.map +1 -0
- package/dist/bridges/separation/developer-docs.d.ts +21 -0
- package/dist/bridges/separation/developer-docs.d.ts.map +1 -0
- package/dist/bridges/separation/developer-docs.js +81 -0
- package/dist/bridges/separation/developer-docs.js.map +1 -0
- package/dist/bridges/separation/index.d.ts +20 -0
- package/dist/bridges/separation/index.d.ts.map +1 -0
- package/dist/bridges/separation/index.js +63 -0
- package/dist/bridges/separation/index.js.map +1 -0
- package/dist/bridges/separation/sdk-references.d.ts +12 -0
- package/dist/bridges/separation/sdk-references.d.ts.map +1 -0
- package/dist/bridges/separation/sdk-references.js +93 -0
- package/dist/bridges/separation/sdk-references.js.map +1 -0
- package/dist/bridges/separation/webhook-support.d.ts +19 -0
- package/dist/bridges/separation/webhook-support.d.ts.map +1 -0
- package/dist/bridges/separation/webhook-support.js +94 -0
- package/dist/bridges/separation/webhook-support.js.map +1 -0
- package/dist/bridges/standards/index.d.ts +13 -0
- package/dist/bridges/standards/index.d.ts.map +1 -0
- package/dist/bridges/standards/index.js +79 -0
- package/dist/bridges/standards/index.js.map +1 -0
- package/dist/bridges/standards/json-ld.d.ts +16 -0
- package/dist/bridges/standards/json-ld.d.ts.map +1 -0
- package/dist/bridges/standards/json-ld.js +63 -0
- package/dist/bridges/standards/json-ld.js.map +1 -0
- package/dist/bridges/standards/llms-txt.d.ts +19 -0
- package/dist/bridges/standards/llms-txt.d.ts.map +1 -0
- package/dist/bridges/standards/llms-txt.js +64 -0
- package/dist/bridges/standards/llms-txt.js.map +1 -0
- package/dist/bridges/standards/mcp.d.ts +13 -0
- package/dist/bridges/standards/mcp.d.ts.map +1 -0
- package/dist/bridges/standards/mcp.js +72 -0
- package/dist/bridges/standards/mcp.js.map +1 -0
- package/dist/bridges/standards/openapi.d.ts +14 -0
- package/dist/bridges/standards/openapi.d.ts.map +1 -0
- package/dist/bridges/standards/openapi.js +424 -0
- package/dist/bridges/standards/openapi.js.map +1 -0
- package/dist/bridges/standards/schema-org.d.ts +12 -0
- package/dist/bridges/standards/schema-org.d.ts.map +1 -0
- package/dist/bridges/standards/schema-org.js +101 -0
- package/dist/bridges/standards/schema-org.js.map +1 -0
- package/dist/bridges/standards/well-known.d.ts +16 -0
- package/dist/bridges/standards/well-known.d.ts.map +1 -0
- package/dist/bridges/standards/well-known.js +77 -0
- package/dist/bridges/standards/well-known.js.map +1 -0
- package/dist/bridges/stubs.d.ts +4 -0
- package/dist/bridges/stubs.d.ts.map +1 -0
- package/dist/bridges/stubs.js +25 -0
- package/dist/bridges/stubs.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +83 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/explanations.d.ts +11 -0
- package/dist/core/explanations.d.ts.map +1 -0
- package/dist/core/explanations.js +128 -0
- package/dist/core/explanations.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/scan.d.ts +3 -0
- package/dist/core/scan.d.ts.map +1 -0
- package/dist/core/scan.js +89 -0
- package/dist/core/scan.js.map +1 -0
- package/dist/core/types.d.ts +119 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +7 -0
- package/dist/core/version.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/render/colors.d.ts +7 -0
- package/dist/render/colors.d.ts.map +1 -0
- package/dist/render/colors.js +28 -0
- package/dist/render/colors.js.map +1 -0
- package/dist/render/format-bridge.d.ts +3 -0
- package/dist/render/format-bridge.d.ts.map +1 -0
- package/dist/render/format-bridge.js +39 -0
- package/dist/render/format-bridge.js.map +1 -0
- package/dist/render/format-scan.d.ts +3 -0
- package/dist/render/format-scan.d.ts.map +1 -0
- package/dist/render/format-scan.js +44 -0
- package/dist/render/format-scan.js.map +1 -0
- package/dist/render/format-verbose.d.ts +3 -0
- package/dist/render/format-verbose.d.ts.map +1 -0
- package/dist/render/format-verbose.js +14 -0
- package/dist/render/format-verbose.js.map +1 -0
- package/dist/render/index.d.ts +7 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +8 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/progress-bar.d.ts +10 -0
- package/dist/render/progress-bar.d.ts.map +1 -0
- package/dist/render/progress-bar.js +21 -0
- package/dist/render/progress-bar.js.map +1 -0
- package/dist/render/symbols.d.ts +10 -0
- package/dist/render/symbols.d.ts.map +1 -0
- package/dist/render/symbols.js +21 -0
- package/dist/render/symbols.js.map +1 -0
- package/dist/utils/http-client.d.ts +25 -0
- package/dist/utils/http-client.d.ts.map +1 -0
- package/dist/utils/http-client.js +235 -0
- package/dist/utils/http-client.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/ssrf.d.ts +29 -0
- package/dist/utils/ssrf.d.ts.map +1 -0
- package/dist/utils/ssrf.js +134 -0
- package/dist/utils/ssrf.js.map +1 -0
- package/dist/utils/url.d.ts +53 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/url.js +64 -0
- package/dist/utils/url.js.map +1 -0
- package/package.json +74 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
function isColorEnabled() {
|
|
3
|
+
const noColor = process.env["NO_COLOR"];
|
|
4
|
+
if (noColor !== undefined && noColor !== "") {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
const colorEnabled = isColorEnabled();
|
|
10
|
+
export function green(text) {
|
|
11
|
+
return colorEnabled ? chalk.green(text) : text;
|
|
12
|
+
}
|
|
13
|
+
export function yellow(text) {
|
|
14
|
+
return colorEnabled ? chalk.yellow(text) : text;
|
|
15
|
+
}
|
|
16
|
+
export function red(text) {
|
|
17
|
+
return colorEnabled ? chalk.red(text) : text;
|
|
18
|
+
}
|
|
19
|
+
export function cyan(text) {
|
|
20
|
+
return colorEnabled ? chalk.cyan(text) : text;
|
|
21
|
+
}
|
|
22
|
+
export function dim(text) {
|
|
23
|
+
return colorEnabled ? chalk.dim(text) : text;
|
|
24
|
+
}
|
|
25
|
+
export function bold(text) {
|
|
26
|
+
return colorEnabled ? chalk.bold(text) : text;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=colors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors.js","sourceRoot":"","sources":["../../src/render/colors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,SAAS,cAAc;IACrB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACxC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,YAAY,GAAG,cAAc,EAAE,CAAC;AAEtC,MAAM,UAAU,KAAK,CAAC,IAAY;IAChC,OAAO,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,OAAO,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AACD,MAAM,UAAU,GAAG,CAAC,IAAY;IAC9B,OAAO,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,IAAY;IAC/B,OAAO,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AACD,MAAM,UAAU,GAAG,CAAC,IAAY;IAC9B,OAAO,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,IAAY;IAC/B,OAAO,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-bridge.d.ts","sourceRoot":"","sources":["../../src/render/format-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAgB/D,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAQ3E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { cyan, dim, bold } from "./colors.js";
|
|
2
|
+
import { progressBar } from "./progress-bar.js";
|
|
3
|
+
const BRIDGE_QUESTIONS = {
|
|
4
|
+
1: "Is my site accessible to AI agents?",
|
|
5
|
+
2: "Does my site publish machine-readable standards?",
|
|
6
|
+
3: "Does my site expose API infrastructure?",
|
|
7
|
+
4: "Can agents use the APIs correctly?",
|
|
8
|
+
5: "Can agents trust and leverage the context?",
|
|
9
|
+
};
|
|
10
|
+
function bridgeLabel(bridge) {
|
|
11
|
+
return `${bridge.name}: ${BRIDGE_QUESTIONS[bridge.id]}`;
|
|
12
|
+
}
|
|
13
|
+
export function formatBridge(bridge, verbose) {
|
|
14
|
+
if (bridge.status === "not_evaluated") {
|
|
15
|
+
return formatStubBridge(bridge);
|
|
16
|
+
}
|
|
17
|
+
if (bridge.score !== null) {
|
|
18
|
+
return formatScoredBridge(bridge, verbose);
|
|
19
|
+
}
|
|
20
|
+
return formatDetectionBridge(bridge, verbose);
|
|
21
|
+
}
|
|
22
|
+
function formatScoredBridge(bridge, _verbose) {
|
|
23
|
+
const bar = progressBar(bridge.score);
|
|
24
|
+
const label = bridgeLabel(bridge);
|
|
25
|
+
const timing = dim(`(${bridge.durationMs}ms)`);
|
|
26
|
+
return ` ${bold(label)} ${bar} ${bridge.score} ${timing}`;
|
|
27
|
+
}
|
|
28
|
+
function formatDetectionBridge(bridge, _verbose) {
|
|
29
|
+
const detected = bridge.checks.filter((c) => c.status === "pass").length;
|
|
30
|
+
const total = bridge.checks.length;
|
|
31
|
+
const countText = `${detected} of ${total} signals detected`;
|
|
32
|
+
const label = bridgeLabel(bridge);
|
|
33
|
+
const timing = dim(`(${bridge.durationMs}ms)`);
|
|
34
|
+
return ` ${bold(label)} ${cyan(countText)} ${timing}`;
|
|
35
|
+
}
|
|
36
|
+
function formatStubBridge(bridge) {
|
|
37
|
+
return dim(` ${bridgeLabel(bridge)} not evaluated`);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=format-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-bridge.js","sourceRoot":"","sources":["../../src/render/format-bridge.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,gBAAgB,GAA6B;IACjD,CAAC,EAAE,qCAAqC;IACxC,CAAC,EAAE,kDAAkD;IACrD,CAAC,EAAE,yCAAyC;IAC5C,CAAC,EAAE,oCAAoC;IACvC,CAAC,EAAE,4CAA4C;CAChD,CAAC;AAEF,SAAS,WAAW,CAAC,MAAoB;IACvC,OAAO,GAAG,MAAM,CAAC,IAAI,KAAK,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAoB,EAAE,OAAgB;IACjE,IAAI,MAAM,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;QACtC,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAoB,EAAE,QAAiB;IACjE,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,CAAC,KAAe,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,CAAC,CAAC;IAC/C,OAAO,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,MAAM,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;AAChE,CAAC;AAED,SAAS,qBAAqB,CAC5B,MAAoB,EACpB,QAAiB;IAEjB,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IACzE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;IACnC,MAAM,SAAS,GAAG,GAAG,QAAQ,OAAO,KAAK,mBAAmB,CAAC;IAC7D,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,CAAC,CAAC;IAC/C,OAAO,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,EAAE,CAAC;AAC3D,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAoB;IAC5C,OAAO,GAAG,CAAC,KAAK,WAAW,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-scan.d.ts","sourceRoot":"","sources":["../../src/render/format-scan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAwBnD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,OAAO,EAChB,UAAU,UAAQ,GACjB,MAAM,CA4BR"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { green, yellow, red, bold, dim } from "./colors.js";
|
|
2
|
+
import { formatBridge } from "./format-bridge.js";
|
|
3
|
+
import { formatVerboseChecks } from "./format-verbose.js";
|
|
4
|
+
function formatTimestamp(isoTimestamp) {
|
|
5
|
+
const d = new Date(isoTimestamp);
|
|
6
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
7
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
|
|
8
|
+
}
|
|
9
|
+
function formatTotalTime(ms) {
|
|
10
|
+
if (ms < 1000)
|
|
11
|
+
return `${ms}ms`;
|
|
12
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
13
|
+
}
|
|
14
|
+
function scoreColorFn(label) {
|
|
15
|
+
if (label === "pass")
|
|
16
|
+
return green;
|
|
17
|
+
if (label === "partial")
|
|
18
|
+
return yellow;
|
|
19
|
+
return red;
|
|
20
|
+
}
|
|
21
|
+
export function formatScanOutput(result, verbose, explainAll = false) {
|
|
22
|
+
const lines = [];
|
|
23
|
+
// Header
|
|
24
|
+
const domain = result.url.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
25
|
+
lines.push(bold(`Milieu Scan: ${domain}`));
|
|
26
|
+
lines.push(dim(`Scanned: ${formatTimestamp(result.timestamp)}`));
|
|
27
|
+
lines.push("");
|
|
28
|
+
// Bridges
|
|
29
|
+
for (const bridge of result.bridges) {
|
|
30
|
+
lines.push(formatBridge(bridge, verbose));
|
|
31
|
+
if (verbose && bridge.checks.length > 0) {
|
|
32
|
+
lines.push(formatVerboseChecks(bridge.checks, explainAll));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
lines.push("");
|
|
36
|
+
// Overall score (colored by score label)
|
|
37
|
+
const colorize = scoreColorFn(result.overallScoreLabel);
|
|
38
|
+
const scoreLine = `Overall Score: ${colorize(String(result.overallScore))} (${result.overallScoreLabel})`;
|
|
39
|
+
lines.push(bold(scoreLine));
|
|
40
|
+
// Total time
|
|
41
|
+
lines.push(dim(`Total: ${formatTotalTime(result.durationMs)}`));
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=format-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-scan.js","sourceRoot":"","sources":["../../src/render/format-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-verbose.d.ts","sourceRoot":"","sources":["../../src/render/format-verbose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAI9C,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,UAAU,UAAQ,GAAG,MAAM,CAU/E"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { statusSymbol } from "./symbols.js";
|
|
2
|
+
import { dim } from "./colors.js";
|
|
3
|
+
export function formatVerboseChecks(checks, explainAll = false) {
|
|
4
|
+
return checks
|
|
5
|
+
.map((check) => {
|
|
6
|
+
const symbol = statusSymbol(check.status);
|
|
7
|
+
const detail = check.detail ? dim(` (${check.detail})`) : "";
|
|
8
|
+
const showWhy = check.why && (explainAll || check.status !== "pass");
|
|
9
|
+
const why = showWhy ? `\n ${dim(check.why)}` : "";
|
|
10
|
+
return ` ${symbol} ${check.label}${detail}${why}`;
|
|
11
|
+
})
|
|
12
|
+
.join("\n");
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=format-verbose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-verbose.js","sourceRoot":"","sources":["../../src/render/format-verbose.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,MAAM,UAAU,mBAAmB,CAAC,MAAe,EAAE,UAAU,GAAG,KAAK;IACrE,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,KAAK,CAAC,GAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,OAAO,OAAO,MAAM,IAAI,KAAK,CAAC,KAAK,GAAG,MAAM,GAAG,GAAG,EAAE,CAAC;IACvD,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { green, yellow, red, cyan, dim, bold } from "./colors.js";
|
|
2
|
+
export { progressBar } from "./progress-bar.js";
|
|
3
|
+
export { statusSymbol } from "./symbols.js";
|
|
4
|
+
export { formatBridge } from "./format-bridge.js";
|
|
5
|
+
export { formatVerboseChecks } from "./format-verbose.js";
|
|
6
|
+
export { formatScanOutput } from "./format-scan.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Terminal rendering and output formatting (Phase 6)
|
|
2
|
+
export { green, yellow, red, cyan, dim, bold } from "./colors.js";
|
|
3
|
+
export { progressBar } from "./progress-bar.js";
|
|
4
|
+
export { statusSymbol } from "./symbols.js";
|
|
5
|
+
export { formatBridge } from "./format-bridge.js";
|
|
6
|
+
export { formatVerboseChecks } from "./format-verbose.js";
|
|
7
|
+
export { formatScanOutput } from "./format-scan.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a 12-character progress bar for a scored bridge.
|
|
3
|
+
* Score 0-100 maps to 0-12 filled characters.
|
|
4
|
+
*
|
|
5
|
+
* Uses Unicode block characters:
|
|
6
|
+
* - U+2588 (full block) for filled
|
|
7
|
+
* - U+2591 (light shade) for empty
|
|
8
|
+
*/
|
|
9
|
+
export declare function progressBar(score: number): string;
|
|
10
|
+
//# sourceMappingURL=progress-bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress-bar.d.ts","sourceRoot":"","sources":["../../src/render/progress-bar.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASjD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { green, yellow, red } from "./colors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Render a 12-character progress bar for a scored bridge.
|
|
4
|
+
* Score 0-100 maps to 0-12 filled characters.
|
|
5
|
+
*
|
|
6
|
+
* Uses Unicode block characters:
|
|
7
|
+
* - U+2588 (full block) for filled
|
|
8
|
+
* - U+2591 (light shade) for empty
|
|
9
|
+
*/
|
|
10
|
+
export function progressBar(score) {
|
|
11
|
+
const width = 12;
|
|
12
|
+
const filled = Math.round((score / 100) * width);
|
|
13
|
+
const empty = width - filled;
|
|
14
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
15
|
+
if (score >= 80)
|
|
16
|
+
return green(bar);
|
|
17
|
+
if (score >= 40)
|
|
18
|
+
return yellow(bar);
|
|
19
|
+
return red(bar);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=progress-bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress-bar.js","sourceRoot":"","sources":["../../src/render/progress-bar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,MAAM,KAAK,GAAG,EAAE,CAAC;IACjB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7B,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE7D,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACpC,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return a colored status symbol for verbose check display.
|
|
3
|
+
*
|
|
4
|
+
* pass -> green checkmark
|
|
5
|
+
* partial -> yellow warning
|
|
6
|
+
* fail -> red x
|
|
7
|
+
* error -> red x
|
|
8
|
+
*/
|
|
9
|
+
export declare function statusSymbol(status: "pass" | "partial" | "fail" | "error"): string;
|
|
10
|
+
//# sourceMappingURL=symbols.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../../src/render/symbols.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAC5C,MAAM,CAUR"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { green, red, yellow } from "./colors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Return a colored status symbol for verbose check display.
|
|
4
|
+
*
|
|
5
|
+
* pass -> green checkmark
|
|
6
|
+
* partial -> yellow warning
|
|
7
|
+
* fail -> red x
|
|
8
|
+
* error -> red x
|
|
9
|
+
*/
|
|
10
|
+
export function statusSymbol(status) {
|
|
11
|
+
switch (status) {
|
|
12
|
+
case "pass":
|
|
13
|
+
return green("\u2714");
|
|
14
|
+
case "partial":
|
|
15
|
+
return yellow("\u26A0");
|
|
16
|
+
case "fail":
|
|
17
|
+
case "error":
|
|
18
|
+
return red("\u2718");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=symbols.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../src/render/symbols.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,MAA6C;IAE7C,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM;YACT,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzB,KAAK,SAAS;YACZ,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,KAAK,MAAM,CAAC;QACZ,KAAK,OAAO;YACV,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HttpResponse } from "../core/types.js";
|
|
2
|
+
import { type DnsCache } from "./ssrf.js";
|
|
3
|
+
/** Options for httpGet */
|
|
4
|
+
export interface HttpGetOptions {
|
|
5
|
+
/** HTTP method (default: "GET") */
|
|
6
|
+
method?: "GET" | "HEAD";
|
|
7
|
+
/** Per-request timeout in milliseconds (default: 10000) */
|
|
8
|
+
timeout?: number;
|
|
9
|
+
/** Maximum number of redirects to follow (default: 5) */
|
|
10
|
+
maxRedirects?: number;
|
|
11
|
+
/** Maximum response body size in bytes (default: 5MB) */
|
|
12
|
+
maxBodyBytes?: number;
|
|
13
|
+
/** Scan-scoped DNS cache -- caller creates and reuses across requests */
|
|
14
|
+
dnsCache?: DnsCache;
|
|
15
|
+
/** Custom headers to merge with defaults */
|
|
16
|
+
headers?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Perform an HTTP GET (or HEAD) request with SSRF protection, redirect
|
|
20
|
+
* tracking, retry logic, and discriminated union error handling.
|
|
21
|
+
*
|
|
22
|
+
* NEVER throws -- all errors are returned as HttpFailure values.
|
|
23
|
+
*/
|
|
24
|
+
export declare function httpGet(url: string, options?: Partial<HttpGetOptions>): Promise<HttpResponse>;
|
|
25
|
+
//# sourceMappingURL=http-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../src/utils/http-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGvD,0BAA0B;AAC1B,MAAM,WAAW,cAAc;IAC7B,mCAAmC;IACnC,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAkQD;;;;;GAKG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAChC,OAAO,CAAC,YAAY,CAAC,CA8BvB"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { validateDns } from "./ssrf.js";
|
|
2
|
+
import { resolveRedirectUrl } from "./url.js";
|
|
3
|
+
const DEFAULT_OPTIONS = {
|
|
4
|
+
method: "GET",
|
|
5
|
+
timeout: 10_000,
|
|
6
|
+
maxRedirects: 5,
|
|
7
|
+
maxBodyBytes: 5 * 1024 * 1024,
|
|
8
|
+
};
|
|
9
|
+
const USER_AGENT = "milieu-cli/0.1.0";
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Error classification
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
function classifyFetchError(error, url) {
|
|
14
|
+
// AbortSignal.timeout produces DOMException with name "TimeoutError"
|
|
15
|
+
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
16
|
+
return { ok: false, error: { kind: "timeout", message: "Request timed out", url } };
|
|
17
|
+
}
|
|
18
|
+
if (error instanceof TypeError && error.cause) {
|
|
19
|
+
const cause = error.cause;
|
|
20
|
+
switch (cause.code) {
|
|
21
|
+
case "ENOTFOUND":
|
|
22
|
+
return { ok: false, error: { kind: "dns", message: `DNS resolution failed for ${url}`, url } };
|
|
23
|
+
case "ECONNREFUSED":
|
|
24
|
+
return { ok: false, error: { kind: "connection_refused", message: "Connection refused", url } };
|
|
25
|
+
case "CERT_HAS_EXPIRED":
|
|
26
|
+
case "DEPTH_ZERO_SELF_SIGNED_CERT":
|
|
27
|
+
case "UNABLE_TO_VERIFY_LEAF_SIGNATURE":
|
|
28
|
+
case "ERR_TLS_CERT_ALTNAME_INVALID":
|
|
29
|
+
return { ok: false, error: { kind: "ssl_error", message: `SSL error: ${cause.code}`, url } };
|
|
30
|
+
default:
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { ok: false, error: { kind: "unknown", message: String(error), url } };
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Bot protection detection
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
function isBotProtected(status, headers) {
|
|
40
|
+
const server = (headers["server"] ?? "").toLowerCase();
|
|
41
|
+
// Cloudflare 403 with server header or cf-ray
|
|
42
|
+
if (status === 403 && (server.includes("cloudflare") || headers["cf-ray"] !== undefined)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// 429 rate limit from any server
|
|
46
|
+
if (status === 429)
|
|
47
|
+
return true;
|
|
48
|
+
// Cloudflare 503 challenge
|
|
49
|
+
if (status === 503 && server.includes("cloudflare"))
|
|
50
|
+
return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Convert Response headers to plain object
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
function headersToRecord(headers) {
|
|
57
|
+
const result = {};
|
|
58
|
+
headers.forEach((value, key) => {
|
|
59
|
+
result[key] = value;
|
|
60
|
+
});
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Single fetch attempt (no retry)
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
async function fetchOnce(url, options) {
|
|
67
|
+
let currentUrl = url;
|
|
68
|
+
const redirects = [];
|
|
69
|
+
// Validate initial URL
|
|
70
|
+
try {
|
|
71
|
+
new URL(currentUrl);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return { ok: false, error: { kind: "unknown", message: "Invalid URL", url } };
|
|
75
|
+
}
|
|
76
|
+
for (let hop = 0; hop <= options.maxRedirects; hop++) {
|
|
77
|
+
// SSRF pre-flight at every hop
|
|
78
|
+
const hostname = new URL(currentUrl).hostname;
|
|
79
|
+
const ssrfResult = await validateDns(hostname, options.dnsCache);
|
|
80
|
+
if (!ssrfResult.safe) {
|
|
81
|
+
// Distinguish DNS failure from SSRF block
|
|
82
|
+
const kind = ssrfResult.error.startsWith("DNS resolution failed")
|
|
83
|
+
? "dns"
|
|
84
|
+
: "ssrf_blocked";
|
|
85
|
+
return { ok: false, error: { kind, message: ssrfResult.error, url: currentUrl } };
|
|
86
|
+
}
|
|
87
|
+
let response;
|
|
88
|
+
try {
|
|
89
|
+
response = await fetch(currentUrl, {
|
|
90
|
+
method: options.method,
|
|
91
|
+
// Manual redirects: required for SSRF re-validation at each hop and redirect chain tracking
|
|
92
|
+
redirect: "manual",
|
|
93
|
+
signal: AbortSignal.timeout(options.timeout),
|
|
94
|
+
headers: options.headers,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
return classifyFetchError(err, currentUrl);
|
|
99
|
+
}
|
|
100
|
+
// Handle redirects (3xx)
|
|
101
|
+
if (response.status >= 300 && response.status < 400) {
|
|
102
|
+
const location = response.headers.get("location");
|
|
103
|
+
if (!location) {
|
|
104
|
+
// No Location header -- treat as final response
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
const resolved = resolveRedirectUrl(location, currentUrl);
|
|
108
|
+
if (!resolved.ok) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: { kind: "http_error", message: `Invalid redirect: ${resolved.error}`, statusCode: response.status, url: currentUrl },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
redirects.push(currentUrl);
|
|
115
|
+
// Check redirect limit BEFORE following
|
|
116
|
+
if (redirects.length >= options.maxRedirects) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
error: { kind: "http_error", message: `Too many redirects (max ${options.maxRedirects})`, url: currentUrl },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
currentUrl = resolved.url;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// Convert headers
|
|
126
|
+
const headerRecord = headersToRecord(response.headers);
|
|
127
|
+
// Bot protection detection
|
|
128
|
+
if (isBotProtected(response.status, headerRecord)) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
error: { kind: "bot_protected", message: "Bot protection detected", statusCode: response.status, url: currentUrl },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// 4xx/5xx errors (non-bot)
|
|
135
|
+
if (response.status >= 400) {
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
error: { kind: "http_error", message: `HTTP ${response.status} ${response.statusText}`, statusCode: response.status, url: currentUrl },
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Success (2xx) -- read body
|
|
142
|
+
let body = "";
|
|
143
|
+
if (options.method !== "HEAD") {
|
|
144
|
+
// Check Content-Length before reading
|
|
145
|
+
const contentLength = response.headers.get("content-length");
|
|
146
|
+
if (contentLength && parseInt(contentLength, 10) > options.maxBodyBytes) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
error: { kind: "body_too_large", message: `Response body exceeds ${options.maxBodyBytes} bytes`, url: currentUrl },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
body = await response.text();
|
|
153
|
+
// Truncate if body exceeds limit (no Content-Length header case)
|
|
154
|
+
if (body.length > options.maxBodyBytes) {
|
|
155
|
+
body = body.slice(0, options.maxBodyBytes);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const success = {
|
|
159
|
+
ok: true,
|
|
160
|
+
url: currentUrl,
|
|
161
|
+
status: response.status,
|
|
162
|
+
headers: headerRecord,
|
|
163
|
+
body,
|
|
164
|
+
redirects,
|
|
165
|
+
durationMs: 0, // Set by outer wrapper
|
|
166
|
+
};
|
|
167
|
+
return success;
|
|
168
|
+
}
|
|
169
|
+
// Fell through without returning -- shouldn't happen, but handle gracefully
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
error: { kind: "http_error", message: `Too many redirects (max ${options.maxRedirects})`, url: currentUrl },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Retry wrapper
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
function isRetriable(result) {
|
|
179
|
+
if (result.ok)
|
|
180
|
+
return false;
|
|
181
|
+
const { kind } = result.error;
|
|
182
|
+
// Retry on timeout or connection_refused
|
|
183
|
+
if (kind === "timeout" || kind === "connection_refused")
|
|
184
|
+
return true;
|
|
185
|
+
// Retry on 5xx server errors
|
|
186
|
+
if (kind === "http_error" && result.error.statusCode !== undefined && result.error.statusCode >= 500) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
async function fetchWithRetry(url, options) {
|
|
192
|
+
const result = await fetchOnce(url, options);
|
|
193
|
+
if (isRetriable(result)) {
|
|
194
|
+
// Wait 2 seconds before retry
|
|
195
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
196
|
+
return fetchOnce(url, options);
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Public API
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
/**
|
|
204
|
+
* Perform an HTTP GET (or HEAD) request with SSRF protection, redirect
|
|
205
|
+
* tracking, retry logic, and discriminated union error handling.
|
|
206
|
+
*
|
|
207
|
+
* NEVER throws -- all errors are returned as HttpFailure values.
|
|
208
|
+
*/
|
|
209
|
+
export async function httpGet(url, options) {
|
|
210
|
+
const method = options?.method ?? DEFAULT_OPTIONS.method;
|
|
211
|
+
const timeout = options?.timeout ?? DEFAULT_OPTIONS.timeout;
|
|
212
|
+
const maxRedirects = options?.maxRedirects ?? DEFAULT_OPTIONS.maxRedirects;
|
|
213
|
+
const maxBodyBytes = options?.maxBodyBytes ?? DEFAULT_OPTIONS.maxBodyBytes;
|
|
214
|
+
const dnsCache = options?.dnsCache ?? new Map();
|
|
215
|
+
const headers = {
|
|
216
|
+
"User-Agent": USER_AGENT,
|
|
217
|
+
...(options?.headers ?? {}),
|
|
218
|
+
};
|
|
219
|
+
const start = performance.now();
|
|
220
|
+
const result = await fetchWithRetry(url, {
|
|
221
|
+
method,
|
|
222
|
+
timeout,
|
|
223
|
+
maxRedirects,
|
|
224
|
+
maxBodyBytes,
|
|
225
|
+
dnsCache,
|
|
226
|
+
headers,
|
|
227
|
+
});
|
|
228
|
+
const durationMs = Math.round(performance.now() - start);
|
|
229
|
+
// Attach durationMs to success results
|
|
230
|
+
if (result.ok) {
|
|
231
|
+
return { ...result, durationMs };
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=http-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/utils/http-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { normalizeUrl, extractDomain, resolveRedirectUrl } from "./url.js";
|
|
2
|
+
export { isPrivateIp, validateDns } from "./ssrf.js";
|
|
3
|
+
export type { DnsCache, SsrfResult } from "./ssrf.js";
|
|
4
|
+
export { httpGet } from "./http-client.js";
|
|
5
|
+
export type { HttpGetOptions } from "./http-client.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAG3E,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACrD,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGtD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// URL normalization and domain extraction
|
|
2
|
+
export { normalizeUrl, extractDomain, resolveRedirectUrl } from "./url.js";
|
|
3
|
+
// SSRF protection
|
|
4
|
+
export { isPrivateIp, validateDns } from "./ssrf.js";
|
|
5
|
+
// HTTP client
|
|
6
|
+
export { httpGet } from "./http-client.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE3E,kBAAkB;AAClB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGrD,cAAc;AACd,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Scan-scoped DNS cache: hostname -> resolved IP */
|
|
2
|
+
export type DnsCache = Map<string, string>;
|
|
3
|
+
/** SSRF validation result -- discriminated union */
|
|
4
|
+
export type SsrfResult = {
|
|
5
|
+
safe: true;
|
|
6
|
+
ip: string;
|
|
7
|
+
} | {
|
|
8
|
+
safe: false;
|
|
9
|
+
error: string;
|
|
10
|
+
ip?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Check whether an IP address falls in a private/reserved range.
|
|
14
|
+
*
|
|
15
|
+
* Covers all RFC 1918 ranges, loopback, link-local, CGNAT, IPv6 ULA,
|
|
16
|
+
* and IPv4-mapped IPv6 addresses.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isPrivateIp(ip: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Validate that a hostname does not resolve to a private/reserved IP.
|
|
21
|
+
*
|
|
22
|
+
* - Checks cache first (scan-scoped, avoids duplicate lookups)
|
|
23
|
+
* - Skips DNS if hostname is already an IP literal
|
|
24
|
+
* - Resolves ALL addresses and rejects if ANY is private
|
|
25
|
+
* - Caches the first resolved IP on success
|
|
26
|
+
* - Uses a 3-second DNS timeout via AbortSignal
|
|
27
|
+
*/
|
|
28
|
+
export declare function validateDns(hostname: string, cache: DnsCache): Promise<SsrfResult>;
|
|
29
|
+
//# sourceMappingURL=ssrf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssrf.d.ts","sourceRoot":"","sources":["../../src/utils/ssrf.ts"],"names":[],"mappings":"AAGA,qDAAqD;AACrD,MAAM,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE3C,oDAAoD;AACpD,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA2DhD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAI/C;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,QAAQ,GACd,OAAO,CAAC,UAAU,CAAC,CAuDrB"}
|