laxy-verify 1.3.0 → 1.3.1

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,7 @@
1
+ export interface RuntimeRouteDiscoveryResult {
2
+ routes: string[];
3
+ scriptUrls: string[];
4
+ }
5
+ export declare function extractScriptUrlsFromHtml(html: string, baseUrl: string): string[];
6
+ export declare function extractRoutesFromText(content: string): string[];
7
+ export declare function discoverRuntimeRoutes(baseUrl: string): Promise<RuntimeRouteDiscoveryResult>;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractScriptUrlsFromHtml = extractScriptUrlsFromHtml;
4
+ exports.extractRoutesFromText = extractRoutesFromText;
5
+ exports.discoverRuntimeRoutes = discoverRuntimeRoutes;
6
+ const SCRIPT_SRC_REGEX = /<script[^>]+src=["']([^"'#?]+(?:\?[^"'#]*)?)["']/gi;
7
+ const HTML_ROUTE_REGEX = /(?:href|data-href)=["'](\/[^"'#? ]*)/gi;
8
+ const ROUTE_SNIPPET_REGEXES = [
9
+ /(?:path|pathname|href|to|route|router\.push|navigate)\s*[:=(]\s*["'`]((?:\/(?!\/)[^"'`?#]+))["'`]/g,
10
+ /["'`]((?:\/(?!\/)[a-z0-9][^"'`?#]*))["'`]/gi,
11
+ ];
12
+ function normalizeRoute(candidate) {
13
+ const decoded = candidate
14
+ .replace(/\\u002F/gi, "/")
15
+ .replace(/\\\//g, "/")
16
+ .trim();
17
+ if (!decoded.startsWith("/"))
18
+ return null;
19
+ const normalized = decoded
20
+ .split("?")[0]
21
+ ?.split("#")[0]
22
+ ?.replace(/\/+/g, "/")
23
+ ?.replace(/\/$/, "") || "/";
24
+ if (normalized === "/" || normalized.length < 2)
25
+ return null;
26
+ if (normalized.startsWith("/_next/") || normalized.startsWith("/api/"))
27
+ return null;
28
+ if (/[.*:[\]]/.test(normalized))
29
+ return null;
30
+ if (/\.[a-z0-9]{2,8}$/i.test(normalized))
31
+ return null;
32
+ if (/\s/.test(normalized))
33
+ return null;
34
+ if (!/^\/[a-z0-9/_-]+$/i.test(normalized))
35
+ return null;
36
+ return normalized;
37
+ }
38
+ function extractScriptUrlsFromHtml(html, baseUrl) {
39
+ const urls = [];
40
+ const seen = new Set();
41
+ for (const match of html.matchAll(SCRIPT_SRC_REGEX)) {
42
+ const raw = match[1];
43
+ if (!raw)
44
+ continue;
45
+ try {
46
+ const scriptUrl = new URL(raw, baseUrl);
47
+ if (scriptUrl.origin !== new URL(baseUrl).origin)
48
+ continue;
49
+ const href = scriptUrl.href;
50
+ if (!seen.has(href)) {
51
+ seen.add(href);
52
+ urls.push(href);
53
+ }
54
+ }
55
+ catch {
56
+ // Ignore malformed URLs.
57
+ }
58
+ }
59
+ return urls;
60
+ }
61
+ function extractRoutesFromText(content) {
62
+ const routes = [];
63
+ const seen = new Set();
64
+ for (const regex of ROUTE_SNIPPET_REGEXES) {
65
+ for (const match of content.matchAll(regex)) {
66
+ const route = normalizeRoute(match[1] ?? "");
67
+ if (!route || seen.has(route))
68
+ continue;
69
+ seen.add(route);
70
+ routes.push(route);
71
+ }
72
+ }
73
+ return routes;
74
+ }
75
+ async function discoverRuntimeRoutes(baseUrl) {
76
+ const htmlRes = await fetch(baseUrl, {
77
+ signal: AbortSignal.timeout(8000),
78
+ headers: { accept: "text/html,application/xhtml+xml" },
79
+ });
80
+ const html = await htmlRes.text();
81
+ const routes = new Set(extractRoutesFromText(html));
82
+ for (const match of html.matchAll(HTML_ROUTE_REGEX)) {
83
+ const route = normalizeRoute(match[1] ?? "");
84
+ if (route)
85
+ routes.add(route);
86
+ }
87
+ const scriptUrls = extractScriptUrlsFromHtml(html, baseUrl)
88
+ .filter((url) => /\/_next\/static\/chunks\/|assets\/|static\/|build\//i.test(url))
89
+ .slice(0, 10);
90
+ await Promise.all(scriptUrls.map(async (scriptUrl) => {
91
+ try {
92
+ const res = await fetch(scriptUrl, { signal: AbortSignal.timeout(5000) });
93
+ if (!res.ok)
94
+ return;
95
+ const content = await res.text();
96
+ for (const route of extractRoutesFromText(content)) {
97
+ routes.add(route);
98
+ }
99
+ }
100
+ catch {
101
+ // Skip chunk fetch failures. This is best-effort coverage expansion.
102
+ }
103
+ }));
104
+ return {
105
+ routes: Array.from(routes).sort((a, b) => a.localeCompare(b)),
106
+ scriptUrls,
107
+ };
108
+ }
@@ -1,17 +1,17 @@
1
- export interface SecurityAuditResult {
2
- totalVulnerabilities: number;
3
- critical: number;
4
- high: number;
5
- moderate: number;
6
- low: number;
7
- summary: string;
8
- missingHeaders: string[];
9
- headerCheckUrl?: string;
10
- headerCheckError?: string;
11
- }
12
- export declare function auditSecurityHeaders(url: string): Promise<{
13
- missingHeaders: string[];
14
- checkedUrl: string;
15
- error?: string;
16
- }>;
17
- export declare function runSecurityAudit(cwd: string, appUrl?: string, timeoutMs?: number): Promise<SecurityAuditResult>;
1
+ export interface SecurityAuditResult {
2
+ totalVulnerabilities: number;
3
+ critical: number;
4
+ high: number;
5
+ moderate: number;
6
+ low: number;
7
+ summary: string;
8
+ missingHeaders: string[];
9
+ headerCheckUrl?: string;
10
+ headerCheckError?: string;
11
+ }
12
+ export declare function auditSecurityHeaders(url: string): Promise<{
13
+ missingHeaders: string[];
14
+ checkedUrl: string;
15
+ error?: string;
16
+ }>;
17
+ export declare function runSecurityAudit(cwd: string, appUrl?: string, timeoutMs?: number): Promise<SecurityAuditResult>;
@@ -1,127 +1,127 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.auditSecurityHeaders = auditSecurityHeaders;
4
- exports.runSecurityAudit = runSecurityAudit;
5
- /**
6
- * npm audit wrapper plus runtime security-header checks.
7
- *
8
- * The package audit catches known dependency vulnerabilities.
9
- * The header audit adds shallow runtime coverage for common missing
10
- * browser-enforced protections on the running app.
11
- */
12
- const node_child_process_1 = require("node:child_process");
13
- async function runNpmAudit(cwd, timeoutMs) {
14
- return new Promise((resolve) => {
15
- const chunks = [];
16
- const proc = process.platform === "win32"
17
- ? (0, node_child_process_1.spawn)(process.env.ComSpec || "cmd.exe", ["/d", "/c", "npm audit --json"], {
18
- stdio: ["ignore", "pipe", "pipe"],
19
- cwd,
20
- })
21
- : (0, node_child_process_1.spawn)("npm", ["audit", "--json"], {
22
- shell: true,
23
- stdio: ["ignore", "pipe", "pipe"],
24
- cwd,
25
- });
26
- const timer = setTimeout(() => {
27
- try {
28
- proc.kill();
29
- }
30
- catch { }
31
- resolve({ totalVulnerabilities: 0, critical: 0, high: 0, moderate: 0, low: 0 });
32
- }, timeoutMs);
33
- proc.stdout?.on("data", (chunk) => chunks.push(chunk.toString()));
34
- proc.stderr?.on("data", () => { });
35
- proc.on("exit", () => {
36
- clearTimeout(timer);
37
- try {
38
- const json = JSON.parse(chunks.join(""));
39
- const meta = json.metadata?.vulnerabilities ?? json.vulnerabilities ?? {};
40
- const critical = meta.critical ?? 0;
41
- const high = meta.high ?? 0;
42
- const moderate = meta.moderate ?? 0;
43
- const low = meta.low ?? 0;
44
- resolve({
45
- totalVulnerabilities: critical + high + moderate + low,
46
- critical,
47
- high,
48
- moderate,
49
- low,
50
- });
51
- }
52
- catch {
53
- resolve({ totalVulnerabilities: 0, critical: 0, high: 0, moderate: 0, low: 0 });
54
- }
55
- });
56
- });
57
- }
58
- async function auditSecurityHeaders(url) {
59
- const requiredHeaders = ["X-Frame-Options", "Content-Security-Policy"];
60
- const urlObj = new URL(url);
61
- if (urlObj.protocol === "https:") {
62
- requiredHeaders.push("Strict-Transport-Security");
63
- }
64
- let response;
65
- try {
66
- response = await fetch(url, {
67
- method: "HEAD",
68
- redirect: "follow",
69
- signal: AbortSignal.timeout(5000),
70
- });
71
- }
72
- catch {
73
- try {
74
- response = await fetch(url, {
75
- method: "GET",
76
- redirect: "follow",
77
- signal: AbortSignal.timeout(5000),
78
- });
79
- }
80
- catch (error) {
81
- return {
82
- missingHeaders: [],
83
- checkedUrl: url,
84
- error: error instanceof Error ? error.message : String(error),
85
- };
86
- }
87
- }
88
- const missingHeaders = requiredHeaders.filter((headerName) => !response.headers.get(headerName));
89
- return {
90
- missingHeaders,
91
- checkedUrl: response.url || url,
92
- };
93
- }
94
- async function runSecurityAudit(cwd, appUrl, timeoutMs = 30000) {
95
- console.log(" Running security audit (npm audit + runtime headers)...");
96
- const [npmAudit, headerAudit] = await Promise.all([
97
- runNpmAudit(cwd, timeoutMs),
98
- appUrl ? auditSecurityHeaders(appUrl) : Promise.resolve(null),
99
- ]);
100
- const parts = [];
101
- if (npmAudit.critical > 0)
102
- parts.push(`${npmAudit.critical} critical`);
103
- if (npmAudit.high > 0)
104
- parts.push(`${npmAudit.high} high`);
105
- if (npmAudit.moderate > 0)
106
- parts.push(`${npmAudit.moderate} moderate`);
107
- if (npmAudit.low > 0)
108
- parts.push(`${npmAudit.low} low`);
109
- const missingHeaders = headerAudit?.missingHeaders ?? [];
110
- if (missingHeaders.length > 0) {
111
- parts.push(`missing headers: ${missingHeaders.join(", ")}`);
112
- }
113
- if (headerAudit?.error) {
114
- parts.push(`header check skipped: ${headerAudit.error}`);
115
- }
116
- const summary = parts.length > 0
117
- ? parts.join(" | ")
118
- : "No known vulnerabilities or missing security headers";
119
- console.log(` Security: ${summary}`);
120
- return {
121
- ...npmAudit,
122
- summary,
123
- missingHeaders,
124
- headerCheckUrl: headerAudit?.checkedUrl,
125
- headerCheckError: headerAudit?.error,
126
- };
127
- }
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.auditSecurityHeaders = auditSecurityHeaders;
4
+ exports.runSecurityAudit = runSecurityAudit;
5
+ /**
6
+ * npm audit wrapper plus runtime security-header checks.
7
+ *
8
+ * The package audit catches known dependency vulnerabilities.
9
+ * The header audit adds shallow runtime coverage for common missing
10
+ * browser-enforced protections on the running app.
11
+ */
12
+ const node_child_process_1 = require("node:child_process");
13
+ async function runNpmAudit(cwd, timeoutMs) {
14
+ return new Promise((resolve) => {
15
+ const chunks = [];
16
+ const proc = process.platform === "win32"
17
+ ? (0, node_child_process_1.spawn)(process.env.ComSpec || "cmd.exe", ["/d", "/c", "npm audit --json"], {
18
+ stdio: ["ignore", "pipe", "pipe"],
19
+ cwd,
20
+ })
21
+ : (0, node_child_process_1.spawn)("npm", ["audit", "--json"], {
22
+ shell: true,
23
+ stdio: ["ignore", "pipe", "pipe"],
24
+ cwd,
25
+ });
26
+ const timer = setTimeout(() => {
27
+ try {
28
+ proc.kill();
29
+ }
30
+ catch { }
31
+ resolve({ totalVulnerabilities: 0, critical: 0, high: 0, moderate: 0, low: 0 });
32
+ }, timeoutMs);
33
+ proc.stdout?.on("data", (chunk) => chunks.push(chunk.toString()));
34
+ proc.stderr?.on("data", () => { });
35
+ proc.on("exit", () => {
36
+ clearTimeout(timer);
37
+ try {
38
+ const json = JSON.parse(chunks.join(""));
39
+ const meta = json.metadata?.vulnerabilities ?? json.vulnerabilities ?? {};
40
+ const critical = meta.critical ?? 0;
41
+ const high = meta.high ?? 0;
42
+ const moderate = meta.moderate ?? 0;
43
+ const low = meta.low ?? 0;
44
+ resolve({
45
+ totalVulnerabilities: critical + high + moderate + low,
46
+ critical,
47
+ high,
48
+ moderate,
49
+ low,
50
+ });
51
+ }
52
+ catch {
53
+ resolve({ totalVulnerabilities: 0, critical: 0, high: 0, moderate: 0, low: 0 });
54
+ }
55
+ });
56
+ });
57
+ }
58
+ async function auditSecurityHeaders(url) {
59
+ const requiredHeaders = ["X-Frame-Options", "Content-Security-Policy"];
60
+ const urlObj = new URL(url);
61
+ if (urlObj.protocol === "https:") {
62
+ requiredHeaders.push("Strict-Transport-Security");
63
+ }
64
+ let response;
65
+ try {
66
+ response = await fetch(url, {
67
+ method: "HEAD",
68
+ redirect: "follow",
69
+ signal: AbortSignal.timeout(5000),
70
+ });
71
+ }
72
+ catch {
73
+ try {
74
+ response = await fetch(url, {
75
+ method: "GET",
76
+ redirect: "follow",
77
+ signal: AbortSignal.timeout(5000),
78
+ });
79
+ }
80
+ catch (error) {
81
+ return {
82
+ missingHeaders: [],
83
+ checkedUrl: url,
84
+ error: error instanceof Error ? error.message : String(error),
85
+ };
86
+ }
87
+ }
88
+ const missingHeaders = requiredHeaders.filter((headerName) => !response.headers.get(headerName));
89
+ return {
90
+ missingHeaders,
91
+ checkedUrl: response.url || url,
92
+ };
93
+ }
94
+ async function runSecurityAudit(cwd, appUrl, timeoutMs = 30000) {
95
+ console.log(" Running security audit (npm audit + runtime headers)...");
96
+ const [npmAudit, headerAudit] = await Promise.all([
97
+ runNpmAudit(cwd, timeoutMs),
98
+ appUrl ? auditSecurityHeaders(appUrl) : Promise.resolve(null),
99
+ ]);
100
+ const parts = [];
101
+ if (npmAudit.critical > 0)
102
+ parts.push(`${npmAudit.critical} critical`);
103
+ if (npmAudit.high > 0)
104
+ parts.push(`${npmAudit.high} high`);
105
+ if (npmAudit.moderate > 0)
106
+ parts.push(`${npmAudit.moderate} moderate`);
107
+ if (npmAudit.low > 0)
108
+ parts.push(`${npmAudit.low} low`);
109
+ const missingHeaders = headerAudit?.missingHeaders ?? [];
110
+ if (missingHeaders.length > 0) {
111
+ parts.push(`missing headers: ${missingHeaders.join(", ")}`);
112
+ }
113
+ if (headerAudit?.error) {
114
+ parts.push(`header check skipped: ${headerAudit.error}`);
115
+ }
116
+ const summary = parts.length > 0
117
+ ? parts.join(" | ")
118
+ : "No known vulnerabilities or missing security headers";
119
+ console.log(` Security: ${summary}`);
120
+ return {
121
+ ...npmAudit,
122
+ summary,
123
+ missingHeaders,
124
+ headerCheckUrl: headerAudit?.checkedUrl,
125
+ headerCheckError: headerAudit?.error,
126
+ };
127
+ }