sfscanner 0.1.0 → 0.2.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.
- package/README.md +141 -214
- package/THIRD_PARTY_NOTICES.md +385 -48
- package/dist/_types/contracts/preflight-prerequisite-types.d.ts +73 -0
- package/dist/_types/packages/scanner-cli/src/index.d.ts +6 -0
- package/dist/_types/packages/scanner-cli/src/v2/arguments.d.ts +102 -0
- package/dist/_types/packages/scanner-cli/src/v2/cli-runner.d.ts +22 -0
- package/dist/{v2 → _types/packages/scanner-cli/src/v2}/commands.d.ts +10 -5
- package/dist/{v2 → _types/packages/scanner-cli/src/v2}/contracts.d.ts +53 -9
- package/dist/_types/packages/scanner-cli/src/v2/help.d.ts +64 -0
- package/dist/_types/packages/scanner-cli/src/v2/metadata-output.d.ts +37 -0
- package/dist/_types/packages/scanner-cli/src/v2/preflight-readiness.d.ts +108 -0
- package/dist/_types/packages/scanner-cli/src/v2/privacy-prepare.d.ts +39 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +26129 -0
- package/dist/index.js.map +7 -0
- package/dist/main.js +26874 -12830
- package/dist/main.js.map +4 -4
- package/dist/mcp.js +19357 -7477
- package/dist/mcp.js.map +4 -4
- package/dist/scan-worker.js +28702 -0
- package/dist/scan-worker.js.map +7 -0
- package/dist/v2/arguments.d.ts +1 -86
- package/dist/v2/arguments.js +220 -21
- package/dist/v2/arguments.js.map +3 -3
- package/dist/v2/cli-runner.d.ts +1 -28
- package/dist/v2/cli-runner.js +19080 -7387
- package/dist/v2/cli-runner.js.map +4 -4
- package/dist/v2/help.d.ts +1 -27
- package/dist/v2/help.js +77 -9
- package/dist/v2/help.js.map +2 -2
- package/dist/v2/metadata-output.d.ts +1 -37
- package/dist/v2/metadata-output.js +20917 -276
- package/dist/v2/metadata-output.js.map +4 -4
- package/dist/v2/preflight-readiness.d.ts +1 -0
- package/dist/v2/preflight-readiness.js +447 -0
- package/dist/v2/preflight-readiness.js.map +7 -0
- package/dist/v2/privacy-prepare.d.ts +1 -0
- package/dist/v2/privacy-prepare.js +232 -0
- package/dist/v2/privacy-prepare.js.map +7 -0
- package/package.json +82 -67
- package/dist/v2/atomic-output.d.ts +0 -34
- package/dist/v2/batch-runner.d.ts +0 -44
- package/dist/v2/config.d.ts +0 -16
- package/dist/v2/exit-reducer.d.ts +0 -23
- package/dist/v2/input-read.d.ts +0 -26
- package/dist/v2/input-selection.d.ts +0 -38
- package/dist/v2/json-output.d.ts +0 -97
- package/dist/v2/metadata-validation.d.ts +0 -4
- package/dist/v2/ndjson-output.d.ts +0 -3
- package/dist/v2/sarif-helpers.d.ts +0 -69
- package/dist/v2/sarif-output.d.ts +0 -40
- package/dist/v2/table-output.d.ts +0 -2
- package/dist/v2/worker-host.d.ts +0 -24
package/dist/v2/arguments.d.ts
CHANGED
|
@@ -1,86 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { V2CommandName } from './help';
|
|
3
|
-
export type CliFormatV2 = 'json' | 'ndjson' | 'sarif' | 'table';
|
|
4
|
-
export type CliProfileV2 = 'beta-default' | 'beta-hardened';
|
|
5
|
-
export type CliTargetEnumV2 = 'UNKNOWN' | 'COMMUNITY' | 'ENTERPRISE' | 'CLOUD' | 'SELF_HOSTED' | 'OWNER' | 'ADMIN' | 'MEMBER' | 'LEGACY' | 'NATIVE';
|
|
6
|
-
export type CliParseErrorCodeV2 = 'NO_COMMAND' | 'UNKNOWN_COMMAND' | 'UNKNOWN_OPTION' | 'MISSING_VALUE' | 'INVALID_VALUE' | 'DUPLICATE_OPTION' | 'UNEXPECTED_ARGUMENT' | 'REMOTE_PATH' | 'RULE_PACK_INVALID' | 'SEMANTIC_CATALOGUE_INVALID' | 'PROFILE_NOT_ADMITTED';
|
|
7
|
-
export type CliParseErrorV2 = Readonly<{
|
|
8
|
-
code: CliParseErrorCodeV2;
|
|
9
|
-
message: string;
|
|
10
|
-
argument?: string;
|
|
11
|
-
}>;
|
|
12
|
-
type ScanCommandV2 = Readonly<{
|
|
13
|
-
kind: 'scan';
|
|
14
|
-
schema: 'v2';
|
|
15
|
-
inputs: readonly string[];
|
|
16
|
-
format: CliFormatV2;
|
|
17
|
-
profile: CliProfileV2;
|
|
18
|
-
failOn: ScanSeverityV2;
|
|
19
|
-
requireComplete: boolean;
|
|
20
|
-
concurrency: number;
|
|
21
|
-
timeoutMs?: number;
|
|
22
|
-
output?: string;
|
|
23
|
-
configPath?: string;
|
|
24
|
-
rules: 'builtin';
|
|
25
|
-
catalogue: 'builtin';
|
|
26
|
-
limits: string;
|
|
27
|
-
target?: string;
|
|
28
|
-
n8nVersion?: string;
|
|
29
|
-
edition: 'COMMUNITY' | 'ENTERPRISE' | 'CLOUD' | 'UNKNOWN';
|
|
30
|
-
deployment: 'SELF_HOSTED' | 'CLOUD' | 'UNKNOWN';
|
|
31
|
-
role: 'OWNER' | 'ADMIN' | 'MEMBER' | 'UNKNOWN';
|
|
32
|
-
taskRunner: 'LEGACY' | 'NATIVE' | 'UNKNOWN';
|
|
33
|
-
communityPackages: readonly string[];
|
|
34
|
-
configuration: readonly string[];
|
|
35
|
-
include: readonly string[];
|
|
36
|
-
exclude: readonly string[];
|
|
37
|
-
allowUnsignedDevelopmentRules: boolean;
|
|
38
|
-
noColor: boolean;
|
|
39
|
-
cliOverrides: Readonly<Partial<ScanConfigV2>>;
|
|
40
|
-
}>;
|
|
41
|
-
export type CliHelpCommandV2 = Readonly<{
|
|
42
|
-
kind: 'help';
|
|
43
|
-
schema: 'v2';
|
|
44
|
-
command?: V2CommandName;
|
|
45
|
-
}>;
|
|
46
|
-
export type CliCommandV2 = ScanCommandV2 | Readonly<{
|
|
47
|
-
kind: 'rules';
|
|
48
|
-
schema: 'v2';
|
|
49
|
-
format: 'json' | 'table';
|
|
50
|
-
}> | Readonly<{
|
|
51
|
-
kind: 'explain';
|
|
52
|
-
schema: 'v2';
|
|
53
|
-
format: 'json' | 'table';
|
|
54
|
-
ruleId: string;
|
|
55
|
-
}> | Readonly<{
|
|
56
|
-
kind: 'profiles';
|
|
57
|
-
schema: 'v2';
|
|
58
|
-
format: 'json' | 'table';
|
|
59
|
-
}> | Readonly<{
|
|
60
|
-
kind: 'validate-config';
|
|
61
|
-
schema: 'v2';
|
|
62
|
-
format: 'json' | 'table';
|
|
63
|
-
path: string;
|
|
64
|
-
}> | Readonly<{
|
|
65
|
-
kind: 'version';
|
|
66
|
-
schema: 'v2';
|
|
67
|
-
format: 'json' | 'table';
|
|
68
|
-
}>;
|
|
69
|
-
type ParsedCommandV2 = CliCommandV2 | CliHelpCommandV2;
|
|
70
|
-
export type CliParseResultV2 = Readonly<{
|
|
71
|
-
ok: true;
|
|
72
|
-
command: ParsedCommandV2;
|
|
73
|
-
} | {
|
|
74
|
-
ok: false;
|
|
75
|
-
error: CliParseErrorV2;
|
|
76
|
-
}>;
|
|
77
|
-
export type CliArgumentDispatch = Readonly<{
|
|
78
|
-
mode: 'v1';
|
|
79
|
-
argv: readonly string[];
|
|
80
|
-
} | {
|
|
81
|
-
mode: 'v2';
|
|
82
|
-
result: CliParseResultV2;
|
|
83
|
-
}>;
|
|
84
|
-
export declare function parseArgumentsV2(argv: readonly string[]): CliParseResultV2;
|
|
85
|
-
export declare function parseCliArguments(argv: readonly string[]): CliArgumentDispatch;
|
|
86
|
-
export {};
|
|
1
|
+
export * from "../_types/packages/scanner-cli/src/v2/arguments.js";
|
package/dist/v2/arguments.js
CHANGED
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
// src/v2/contracts.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
// ../scanner-core/src/canonical-json.ts
|
|
5
|
+
function canonicalizeJson(value) {
|
|
6
|
+
return encodeCanonical(value, /* @__PURE__ */ new Set());
|
|
7
|
+
}
|
|
8
|
+
function encodeCanonical(value, seen) {
|
|
9
|
+
if (value === null) return "null";
|
|
10
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
11
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
12
|
+
if (typeof value === "number") {
|
|
13
|
+
if (!Number.isFinite(value)) throw new TypeError("Cannot encode non-finite number as canonical JSON");
|
|
14
|
+
return JSON.stringify(Object.is(value, -0) ? 0 : value);
|
|
15
|
+
}
|
|
16
|
+
if (typeof value !== "object" || value === void 0) {
|
|
17
|
+
throw new TypeError(`Cannot encode ${typeof value} as canonical JSON`);
|
|
18
|
+
}
|
|
19
|
+
if (seen.has(value)) throw new TypeError("Cannot encode cyclic value as canonical JSON");
|
|
20
|
+
seen.add(value);
|
|
21
|
+
try {
|
|
22
|
+
if (Array.isArray(value)) return encodeArray(value, seen);
|
|
23
|
+
return encodeObject(value, seen);
|
|
24
|
+
} finally {
|
|
25
|
+
seen.delete(value);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function encodeArray(value, seen) {
|
|
29
|
+
const values = Array.from({ length: value.length }, (_, index) => {
|
|
30
|
+
if (!Object.prototype.hasOwnProperty.call(value, index)) {
|
|
31
|
+
throw new TypeError("Cannot encode sparse array as canonical JSON");
|
|
32
|
+
}
|
|
33
|
+
return encodeCanonical(value[index], seen);
|
|
34
|
+
});
|
|
35
|
+
return `[${values.join(",")}]`;
|
|
36
|
+
}
|
|
37
|
+
function encodeObject(value, seen) {
|
|
38
|
+
const prototype = Object.getPrototypeOf(value);
|
|
39
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
40
|
+
throw new TypeError("Cannot encode a non-plain object as canonical JSON");
|
|
41
|
+
}
|
|
42
|
+
if (Object.getOwnPropertySymbols(value).some((symbol) => Object.prototype.propertyIsEnumerable.call(value, symbol))) {
|
|
43
|
+
throw new TypeError("Cannot encode symbol properties as canonical JSON");
|
|
44
|
+
}
|
|
45
|
+
const pairs = Object.keys(value).sort(compareCanonicalStrings).map((key) => `${JSON.stringify(key)}:${encodeCanonical(value[key], seen)}`);
|
|
46
|
+
return `{${pairs.join(",")}}`;
|
|
47
|
+
}
|
|
48
|
+
function compareCanonicalStrings(left, right) {
|
|
49
|
+
if (left === right) return 0;
|
|
50
|
+
return left < right ? -1 : 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
1
53
|
// src/v2/contracts.ts
|
|
2
54
|
var SCAN_CONFIG_SCHEMA_V2 = "n8n-scan-config-v2";
|
|
3
55
|
var ADMITTED_PROFILE_V2 = Object.freeze({ id: "beta-default", version: "2.0.0", digest: "3ca5d69db2554383e22c44afe507cc98e381ea58a7e42aaac0ee8f7497fb4f44" });
|
|
@@ -10,8 +62,8 @@ function isAdmittedProfileSpecifierV2(value) {
|
|
|
10
62
|
}
|
|
11
63
|
var BUILTIN_AUTHORITIES_V2 = Object.freeze({
|
|
12
64
|
profile: ADMITTED_PROFILE_V2,
|
|
13
|
-
rules: Object.freeze({ id: "scanner-core-builtins", version: "2.0.0", digest: "
|
|
14
|
-
semantics: Object.freeze({ id: "scanner-core-builtins", version: "2.
|
|
65
|
+
rules: Object.freeze({ id: "scanner-core-builtins", version: "2.0.0", digest: "30bdc8343582b6a4efc13538d3475db87d07fa28bea4027f9e93b813213fd510", signatureStatus: "VERIFIED" }),
|
|
66
|
+
semantics: Object.freeze({ id: "scanner-core-builtins", version: "2.2.0", digest: "58419339dcf99ed398d2955282662e6f6d37208f62393cafb56818d4a38182d7" }),
|
|
15
67
|
limits: Object.freeze({ schemaVersion: "scanner-limits-v2", digest: "fca787753ca88f71cf0d174427f4ac730a7aef1c823aaf73aa1ea72af92b4077" })
|
|
16
68
|
});
|
|
17
69
|
var PROFILE_MANIFEST_DIGEST_V2 = ADMITTED_PROFILE_V2.digest;
|
|
@@ -20,6 +72,26 @@ var BUILTIN_SEMANTICS_AUTHORITY_V2 = BUILTIN_AUTHORITIES_V2.semantics;
|
|
|
20
72
|
var BUILTIN_LIMITS_AUTHORITY_V2 = BUILTIN_AUTHORITIES_V2.limits;
|
|
21
73
|
var RULE_PACK_DIGEST_V2 = BUILTIN_RULESET_AUTHORITY_V2.digest;
|
|
22
74
|
var SEMANTIC_CATALOGUE_DIGEST_V2 = BUILTIN_SEMANTICS_AUTHORITY_V2.digest;
|
|
75
|
+
var BUILTIN_SCOPED_SEMANTICS_AUTHORITY_V2 = Object.freeze({
|
|
76
|
+
id: "reviewed-gmail-message-v1",
|
|
77
|
+
version: "1",
|
|
78
|
+
digest: "2b21f771d2cf172e33e8f78e5d1deaaefc0a5e8ff45f7d9f252cb8ec321ad56a",
|
|
79
|
+
sourceReceiptSha256: "2e8b7a2666cfc07c7609fdc9a2bdf01be291195a8b7da0af0f69836a180f8685",
|
|
80
|
+
sourceReceiptCanonicalDigest: "965de9fabde482dd5a6bcc67299fce8c33232e78c9fdc70053ad588b0f62ba2a"
|
|
81
|
+
});
|
|
82
|
+
var CLI_VERSION_V2 = "0.1.0";
|
|
83
|
+
var CLI_TOOL_V2 = Object.freeze({
|
|
84
|
+
name: "n8n-scan",
|
|
85
|
+
version: CLI_VERSION_V2,
|
|
86
|
+
buildDigest: createHash("sha256").update(canonicalizeJson({
|
|
87
|
+
engine: "secureflow-scanner",
|
|
88
|
+
version: "2.0.0",
|
|
89
|
+
profile: ADMITTED_PROFILE_V2,
|
|
90
|
+
ruleset: BUILTIN_RULESET_AUTHORITY_V2.digest,
|
|
91
|
+
semantics: BUILTIN_SEMANTICS_AUTHORITY_V2.digest,
|
|
92
|
+
scopedSemantics: BUILTIN_SCOPED_SEMANTICS_AUTHORITY_V2
|
|
93
|
+
})).digest("hex")
|
|
94
|
+
});
|
|
23
95
|
var DEFAULT_SCAN_CONFIG_V2 = Object.freeze({
|
|
24
96
|
schemaVersion: SCAN_CONFIG_SCHEMA_V2,
|
|
25
97
|
format: "json",
|
|
@@ -32,6 +104,7 @@ var DEFAULT_SCAN_CONFIG_V2 = Object.freeze({
|
|
|
32
104
|
limits: "builtin",
|
|
33
105
|
include: Object.freeze(["**/*.json"]),
|
|
34
106
|
exclude: Object.freeze([".git/**", "node_modules/**"]),
|
|
107
|
+
approvedDomains: Object.freeze([]),
|
|
35
108
|
allowUnsignedDevelopmentRules: false,
|
|
36
109
|
noColor: false
|
|
37
110
|
});
|
|
@@ -51,18 +124,63 @@ var SCAN_CONFIG_V2_FIELDS = Object.freeze([
|
|
|
51
124
|
"target",
|
|
52
125
|
"include",
|
|
53
126
|
"exclude",
|
|
127
|
+
"approvedDomains",
|
|
54
128
|
"allowUnsignedDevelopmentRules",
|
|
55
129
|
"noColor"
|
|
56
130
|
]);
|
|
131
|
+
var DOMAIN_V2 = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
132
|
+
function isApprovedDomainV2(value) {
|
|
133
|
+
return typeof value === "string" && DOMAIN_V2.test(value);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/v2/path-admission.ts
|
|
137
|
+
var CONTROL = /[\u0000-\u001f\u007f]/u;
|
|
138
|
+
var SCHEME = /^[a-z][a-z\d+.-]*:/iu;
|
|
139
|
+
var DRIVE = /^[a-z]:\//iu;
|
|
140
|
+
var MAX_PATH = 4096;
|
|
141
|
+
function isAbsoluteAdmittedPathV2(value) {
|
|
142
|
+
const normalized = value.replaceAll("\\", "/");
|
|
143
|
+
if (normalized.startsWith("//")) return false;
|
|
144
|
+
return normalized.startsWith("/") || DRIVE.test(normalized);
|
|
145
|
+
}
|
|
146
|
+
function isAdmittedLocalPath(value) {
|
|
147
|
+
return admitLocal(value, false) !== void 0;
|
|
148
|
+
}
|
|
149
|
+
function isAdmittedRelativePath(value) {
|
|
150
|
+
return admitLocal(value, true) !== void 0;
|
|
151
|
+
}
|
|
152
|
+
function normalizeAdmittedPathV2(value, relativeOnly = false) {
|
|
153
|
+
return admitLocal(value, relativeOnly);
|
|
154
|
+
}
|
|
155
|
+
function admitLocal(value, relativeOnly) {
|
|
156
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_PATH || CONTROL.test(value)) return void 0;
|
|
157
|
+
const normalized = value.replaceAll("\\", "/");
|
|
158
|
+
if (normalized.startsWith("//")) return void 0;
|
|
159
|
+
const drive = /^([a-z]):(?:\/|$)/iu.exec(normalized);
|
|
160
|
+
if (SCHEME.test(normalized) && drive === null) return void 0;
|
|
161
|
+
const absolute = drive !== null || normalized.startsWith("/");
|
|
162
|
+
if (relativeOnly && absolute) return void 0;
|
|
163
|
+
const body = drive === null ? normalized : normalized.slice(2);
|
|
164
|
+
const parts = [];
|
|
165
|
+
for (const part of body.split("/")) {
|
|
166
|
+
if (part === "" || part === ".") continue;
|
|
167
|
+
if (part === "..") return void 0;
|
|
168
|
+
parts.push(part);
|
|
169
|
+
}
|
|
170
|
+
if (drive !== null) return parts.length === 0 ? `${drive[1].toUpperCase()}:/` : `${drive[1].toUpperCase()}:/${parts.join("/")}`;
|
|
171
|
+
if (normalized.startsWith("/")) return parts.length === 0 ? "/" : `/${parts.join("/")}`;
|
|
172
|
+
if (relativeOnly && parts.length === 0) return void 0;
|
|
173
|
+
return parts.length === 0 ? "." : parts.join("/");
|
|
174
|
+
}
|
|
57
175
|
|
|
58
176
|
// src/v2/arguments.ts
|
|
59
177
|
var severities = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"];
|
|
60
|
-
var commands = Object.freeze({ scan: true, rules: true, explain: true, profiles: true, "validate-config": true, version: true, "--version": true });
|
|
61
|
-
var implicitV2Commands = Object.freeze({ rules: true, explain: true, profiles: true, "validate-config": true, version: true });
|
|
178
|
+
var commands = Object.freeze({ scan: true, fix: true, rules: true, explain: true, profiles: true, "validate-config": true, version: true, "--version": true });
|
|
179
|
+
var implicitV2Commands = Object.freeze({ fix: true, rules: true, explain: true, profiles: true, "validate-config": true, version: true });
|
|
62
180
|
var scalarOptions = Object.freeze({ "--format": true, "--profile": true, "--fail-on": true, "--max-concurrency": true, "--concurrency": true, "--timeout": true, "--output": true, "--config": true, "--rules": true, "--catalogue": true, "--limits": true, "--target": true, "--n8n-version": true, "--edition": true, "--deployment": true, "--role": true, "--task-runner": true });
|
|
63
|
-
var repeatableOptions = Object.freeze({ "--community-package": true, "--configuration": true, "--include": true, "--exclude": true });
|
|
64
|
-
var configFields = Object.freeze({ "--format": "format", "--profile": "profile", "--fail-on": "failOn", "--max-concurrency": "concurrency", "--concurrency": "concurrency", "--output": "output", "--config": "configPath", "--rules": "rules", "--catalogue": "catalogue", "--limits": "limits", "--target": "target", "--include": "include", "--exclude": "exclude", "inputs": "inputs", "--require-complete": "requireComplete", "--allow-unsigned-development-rules": "allowUnsignedDevelopmentRules", "--no-color": "noColor" });
|
|
65
|
-
var overrideOrder = ["format", "profile", "output", "inputs", "configPath", "failOn", "requireComplete", "concurrency", "rules", "catalogue", "limits", "target", "include", "exclude", "allowUnsignedDevelopmentRules", "noColor"];
|
|
181
|
+
var repeatableOptions = Object.freeze({ "--community-package": true, "--configuration": true, "--approved-domain": true, "--include": true, "--exclude": true });
|
|
182
|
+
var configFields = Object.freeze({ "--format": "format", "--profile": "profile", "--fail-on": "failOn", "--max-concurrency": "concurrency", "--concurrency": "concurrency", "--output": "output", "--config": "configPath", "--rules": "rules", "--catalogue": "catalogue", "--limits": "limits", "--target": "target", "--approved-domain": "approvedDomains", "--include": "include", "--exclude": "exclude", "inputs": "inputs", "--require-complete": "requireComplete", "--allow-unsigned-development-rules": "allowUnsignedDevelopmentRules", "--no-color": "noColor" });
|
|
183
|
+
var overrideOrder = ["format", "profile", "output", "inputs", "configPath", "failOn", "requireComplete", "concurrency", "rules", "catalogue", "limits", "target", "include", "exclude", "approvedDomains", "allowUnsignedDevelopmentRules", "noColor"];
|
|
66
184
|
function parseArgumentsV2(argv) {
|
|
67
185
|
const stripped = stripSchema(argv);
|
|
68
186
|
if (!stripped.ok) return stripped.result;
|
|
@@ -72,6 +190,7 @@ function parseArgumentsV2(argv) {
|
|
|
72
190
|
if (raw === "--help") return success({ kind: "help", schema: "v2" });
|
|
73
191
|
if (commands[raw] !== true) return failure("UNKNOWN_COMMAND", "Unknown v2 command.");
|
|
74
192
|
const kind = raw === "--version" ? "version" : raw;
|
|
193
|
+
if (kind === "fix") return parseFix(args.slice(1));
|
|
75
194
|
if (kind !== "scan") return parseMetadata(kind, args.slice(1));
|
|
76
195
|
if (args.length === 2 && args[1] === "--help") return success({ kind: "help", schema: "v2", command: "scan" });
|
|
77
196
|
const value = {
|
|
@@ -95,24 +214,40 @@ function parseArgumentsV2(argv) {
|
|
|
95
214
|
include: ["**/*.json"],
|
|
96
215
|
exclude: [".git/**", "node_modules/**"],
|
|
97
216
|
allowUnsignedDevelopmentRules: false,
|
|
98
|
-
noColor: false
|
|
217
|
+
noColor: false,
|
|
218
|
+
auditSupplyChain: false,
|
|
219
|
+
includeReadiness: false
|
|
99
220
|
};
|
|
100
221
|
const overrides = {};
|
|
101
222
|
const inputs = [];
|
|
102
|
-
const repeatable = { "--community-package": [], "--configuration": [], "--include": [], "--exclude": [] };
|
|
223
|
+
const repeatable = { "--community-package": [], "--configuration": [], "--approved-domain": [], "--include": [], "--exclude": [] };
|
|
103
224
|
const seen = /* @__PURE__ */ new Set();
|
|
104
225
|
for (let index = 1; index < args.length; index += 1) {
|
|
105
226
|
const token = args[index];
|
|
106
227
|
if (token === "--") {
|
|
107
|
-
|
|
228
|
+
for (const rawInput of args.slice(index + 1)) {
|
|
229
|
+
const input = rawInput === "-" ? "-" : normalizeAdmittedPathV2(rawInput);
|
|
230
|
+
if (input === void 0) return failure("REMOTE_PATH", "Input path is not admitted.");
|
|
231
|
+
inputs.push(input);
|
|
232
|
+
}
|
|
108
233
|
break;
|
|
109
234
|
}
|
|
110
235
|
if (token === "-" || !token.startsWith("-")) {
|
|
111
|
-
|
|
112
|
-
|
|
236
|
+
const input = token === "-" ? "-" : normalizeAdmittedPathV2(token);
|
|
237
|
+
if (input === void 0) return failure("REMOTE_PATH", "Input path is not admitted.");
|
|
238
|
+
inputs.push(input);
|
|
113
239
|
continue;
|
|
114
240
|
}
|
|
115
241
|
const [name, inline] = splitOption(token);
|
|
242
|
+
if (name === "--audit-supply-chain" || name === "--include-readiness") {
|
|
243
|
+
if (inline !== void 0) return failure("INVALID_VALUE", "Boolean option does not accept a value.", name);
|
|
244
|
+
if (seen.has(name)) return failure("DUPLICATE_OPTION", "Option supplied more than once.", name);
|
|
245
|
+
const next = args[index + 1];
|
|
246
|
+
if (next === "true" || next === "false") return failure("INVALID_VALUE", "Boolean option does not accept a value.", name);
|
|
247
|
+
seen.add(name);
|
|
248
|
+
value[name === "--include-readiness" ? "includeReadiness" : "auditSupplyChain"] = true;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
116
251
|
if (name === "--require-complete" || name === "--allow-unsigned-development-rules" || name === "--no-color") {
|
|
117
252
|
if (inline !== void 0 || seen.has(name)) return failure("DUPLICATE_OPTION", "Option supplied more than once.", name);
|
|
118
253
|
seen.add(name);
|
|
@@ -127,7 +262,8 @@ function parseArgumentsV2(argv) {
|
|
|
127
262
|
if (read === void 0 || read.length === 0 || inline === void 0 && read.startsWith("-") && !(name === "--output" && read === "-")) return failure("MISSING_VALUE", "Option requires a value.", name);
|
|
128
263
|
if (inline === void 0) index += 1;
|
|
129
264
|
if (repeatableOptions[name] === true) {
|
|
130
|
-
if (!
|
|
265
|
+
if (name === "--approved-domain" && !isApprovedDomainV2(read)) return failure("INVALID_VALUE", "Approved domain is invalid.", name);
|
|
266
|
+
if (!isAdmittedRelativePath(read)) return failure("REMOTE_PATH", "Option value is not admitted.", name);
|
|
131
267
|
repeatable[name].push(read);
|
|
132
268
|
if (configFields[name]) overrides[configFields[name]] = Object.freeze([...repeatable[name]]);
|
|
133
269
|
continue;
|
|
@@ -143,16 +279,18 @@ function parseArgumentsV2(argv) {
|
|
|
143
279
|
if (name === "--task-runner" && !["LEGACY", "NATIVE", "UNKNOWN"].includes(read)) return failure("INVALID_VALUE", "Task runner is invalid.", name);
|
|
144
280
|
if (name === "--rules" || name === "--catalogue") {
|
|
145
281
|
if (read !== "builtin") return failure(name === "--rules" ? "RULE_PACK_INVALID" : "SEMANTIC_CATALOGUE_INVALID", "Only built-in authority is available.", name);
|
|
146
|
-
} else if (["--limits", "--target", "--config"].includes(name) && (read === "-" || !
|
|
147
|
-
else if (name === "--output" && read !== "-" && !
|
|
282
|
+
} else if (["--limits", "--target", "--config"].includes(name) && (read === "-" || !isAdmittedRelativePath(read))) return failure("REMOTE_PATH", "Option value is not admitted.", name);
|
|
283
|
+
else if (name === "--output" && read !== "-" && !isAdmittedRelativePath(read)) return failure("REMOTE_PATH", "Option value is not admitted.", name);
|
|
148
284
|
else if (["--n8n-version", "--edition", "--deployment", "--role", "--task-runner"].includes(name) && !safeText(read)) return failure("INVALID_VALUE", "Option value is not admitted.", name);
|
|
149
285
|
seen.add(duplicateKey);
|
|
150
286
|
assign(value, name, read);
|
|
287
|
+
if (name === "--profile") value.profile = ADMITTED_PROFILE_V2.id;
|
|
151
288
|
if (configFields[name]) overrides[configFields[name]] = ["--max-concurrency", "--concurrency"].includes(name) ? Number(read) : read;
|
|
152
289
|
}
|
|
290
|
+
if (value.includeReadiness === true && value.format === "sarif") return failure("INVALID_VALUE", "Readiness assessment cannot be projected into SARIF.", "--include-readiness");
|
|
153
291
|
if (value.allowUnsignedDevelopmentRules === true) return failure("RULE_PACK_INVALID", "External rule packs are not admitted in this release.", "--allow-unsigned-development-rules");
|
|
154
292
|
if (inputs.length === 0) return failure("UNEXPECTED_ARGUMENT", "At least one workflow input is required.");
|
|
155
|
-
if (inputs.some((input) => !
|
|
293
|
+
if (inputs.some((input) => input !== "-" && !isAdmittedLocalPath(input))) return failure("REMOTE_PATH", "Input path is not admitted.");
|
|
156
294
|
if (inputs.includes("-") && inputs.length > 1) return failure("UNEXPECTED_ARGUMENT", "Standard input cannot be combined with file inputs.");
|
|
157
295
|
overrides.inputs = Object.freeze([...inputs]);
|
|
158
296
|
const cliOverrides = {};
|
|
@@ -163,7 +301,7 @@ function parseArgumentsV2(argv) {
|
|
|
163
301
|
schema: "v2",
|
|
164
302
|
inputs: Object.freeze([...inputs]),
|
|
165
303
|
format: value.format,
|
|
166
|
-
profile:
|
|
304
|
+
profile: ADMITTED_PROFILE_V2.id,
|
|
167
305
|
failOn: value.failOn,
|
|
168
306
|
requireComplete: value.requireComplete,
|
|
169
307
|
concurrency: value.concurrency,
|
|
@@ -181,10 +319,13 @@ function parseArgumentsV2(argv) {
|
|
|
181
319
|
taskRunner: value.taskRunner,
|
|
182
320
|
communityPackages: arrayFor(repeatable["--community-package"]),
|
|
183
321
|
configuration: arrayFor(repeatable["--configuration"]),
|
|
322
|
+
approvedDomains: arrayFor(repeatable["--approved-domain"]),
|
|
184
323
|
include: repeatable["--include"].length > 0 ? arrayFor(repeatable["--include"]) : Object.freeze(["**/*.json"]),
|
|
185
324
|
exclude: repeatable["--exclude"].length > 0 ? arrayFor(repeatable["--exclude"]) : Object.freeze([".git/**", "node_modules/**"]),
|
|
186
325
|
allowUnsignedDevelopmentRules: value.allowUnsignedDevelopmentRules,
|
|
187
326
|
noColor: value.noColor,
|
|
327
|
+
auditSupplyChain: value.auditSupplyChain === true,
|
|
328
|
+
includeReadiness: value.includeReadiness,
|
|
188
329
|
cliOverrides
|
|
189
330
|
};
|
|
190
331
|
return success(command);
|
|
@@ -197,6 +338,67 @@ function parseCliArguments(argv) {
|
|
|
197
338
|
const implicit = argv[0] !== void 0 && implicitV2Commands[argv[0]] === true;
|
|
198
339
|
return hasSchema || implicit ? freeze({ mode: "v2", result: parseArgumentsV2(argv) }) : freeze({ mode: "v1", argv: Object.freeze([...argv]) });
|
|
199
340
|
}
|
|
341
|
+
var FIX_OPTIONS = Object.freeze({ "--root": true, "--input": true, "--fix": true, "--mapping": true, "--endpoint-receipt": true });
|
|
342
|
+
var FIX_SELECTION = /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$/u;
|
|
343
|
+
var SUBSTITUTION_LITERAL = /(?:\{\{|\}\}|\$env\b|\$\{|\$[A-Za-z_][A-Za-z0-9_]*\b|%[A-Za-z_][A-Za-z0-9_]*%)/u;
|
|
344
|
+
function fixPathValue(name, read) {
|
|
345
|
+
if (read === "-") return failure("INVALID_VALUE", "Fix commands require a file path; standard input cannot be re-read.", name);
|
|
346
|
+
if (SUBSTITUTION_LITERAL.test(read)) return failure("REMOTE_PATH", "Option value is not admitted.", name);
|
|
347
|
+
const value = normalizeAdmittedPathV2(read, name !== "--root");
|
|
348
|
+
if (value === void 0) return failure("REMOTE_PATH", "Option value is not admitted.", name);
|
|
349
|
+
if (name === "--root" && !isAbsoluteAdmittedPathV2(value)) return failure("INVALID_VALUE", "Root must be an absolute local directory.", name);
|
|
350
|
+
return value;
|
|
351
|
+
}
|
|
352
|
+
function parseFix(args) {
|
|
353
|
+
const action = args[0];
|
|
354
|
+
if (action !== "suggest" && action !== "apply") return failure("UNKNOWN_COMMAND", "fix requires the suggest or apply subcommand.");
|
|
355
|
+
const selected = {};
|
|
356
|
+
const seen = /* @__PURE__ */ new Set();
|
|
357
|
+
let confirm = false;
|
|
358
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
359
|
+
const token = args[index];
|
|
360
|
+
if (!token.startsWith("-") || token === "-") return failure("UNEXPECTED_ARGUMENT", "fix accepts options only; positional arguments are not admitted.");
|
|
361
|
+
const [name, inline] = splitOption(token);
|
|
362
|
+
if (name === "--confirm") {
|
|
363
|
+
if (inline !== void 0) return failure("INVALID_VALUE", "Boolean option does not accept a value.", name);
|
|
364
|
+
if (action !== "apply") return failure("UNEXPECTED_ARGUMENT", "The suggest action does not accept --confirm.", name);
|
|
365
|
+
if (seen.has(name)) return failure("DUPLICATE_OPTION", "Option supplied more than once.", name);
|
|
366
|
+
seen.add(name);
|
|
367
|
+
confirm = true;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (FIX_OPTIONS[name] !== true) return failure("UNKNOWN_OPTION", "Unknown option.", name);
|
|
371
|
+
if (seen.has(name)) return failure("DUPLICATE_OPTION", "Option supplied more than once.", name);
|
|
372
|
+
const read = inline ?? args[index + 1];
|
|
373
|
+
if (read === void 0 || read.length === 0 || inline === void 0 && read.startsWith("-") && read !== "-") return failure("MISSING_VALUE", "Option requires a value.", name);
|
|
374
|
+
if (inline === void 0) index += 1;
|
|
375
|
+
seen.add(name);
|
|
376
|
+
if (name === "--fix") {
|
|
377
|
+
if (!FIX_SELECTION.test(read) || /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(read)) return failure("INVALID_VALUE", "Fix selection is invalid.", name);
|
|
378
|
+
selected.fix = read;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const path = fixPathValue(name, read);
|
|
382
|
+
if (typeof path !== "string") return path;
|
|
383
|
+
selected[name === "--root" ? "root" : name === "--input" ? "input" : name === "--mapping" ? "mapping" : "endpointReceipt"] = path;
|
|
384
|
+
}
|
|
385
|
+
if (selected.root === void 0) return failure("UNEXPECTED_ARGUMENT", "fix requires exactly one --root.");
|
|
386
|
+
if (selected.input === void 0) return failure("UNEXPECTED_ARGUMENT", "fix requires exactly one --input.");
|
|
387
|
+
if (action === "apply" && selected.fix === void 0) return failure("UNEXPECTED_ARGUMENT", "fix apply requires one selected fix.");
|
|
388
|
+
if (action === "apply" && !confirm) return failure("UNEXPECTED_ARGUMENT", "fix apply requires --confirm.", "--confirm");
|
|
389
|
+
return success({
|
|
390
|
+
kind: "fix",
|
|
391
|
+
schema: "v2",
|
|
392
|
+
action,
|
|
393
|
+
format: "json",
|
|
394
|
+
root: selected.root,
|
|
395
|
+
input: selected.input,
|
|
396
|
+
...selected.fix === void 0 ? {} : { fix: selected.fix },
|
|
397
|
+
...selected.mapping === void 0 ? {} : { mapping: selected.mapping },
|
|
398
|
+
...selected.endpointReceipt === void 0 ? {} : { endpointReceipt: selected.endpointReceipt },
|
|
399
|
+
confirm
|
|
400
|
+
});
|
|
401
|
+
}
|
|
200
402
|
function parseMetadata(kind, args) {
|
|
201
403
|
if (args.length === 1 && args[0] === "--help") return success({ kind: "help", schema: "v2", command: kind });
|
|
202
404
|
let format = "json";
|
|
@@ -219,7 +421,7 @@ function parseMetadata(kind, args) {
|
|
|
219
421
|
return success({ kind, schema: "v2", format });
|
|
220
422
|
}
|
|
221
423
|
if (kind === "explain") return rest.length === 1 && safeText(rest[0]) ? success({ kind, schema: "v2", format, ruleId: rest[0] }) : failure("UNEXPECTED_ARGUMENT", "explain requires one rule ID.");
|
|
222
|
-
if (rest.length !== 1 || !
|
|
424
|
+
if (rest.length !== 1 || !isAdmittedRelativePath(rest[0])) return failure("UNEXPECTED_ARGUMENT", "validate-config requires one local file.");
|
|
223
425
|
return success({ kind, schema: "v2", format, path: rest[0] });
|
|
224
426
|
}
|
|
225
427
|
function assign(value, name, read) {
|
|
@@ -238,9 +440,6 @@ function positiveInteger(value, maximum) {
|
|
|
238
440
|
function safeText(value) {
|
|
239
441
|
return value.length > 0 && value.length <= 4096 && !/[\u0000-\u001f\u007f]/.test(value) && !/^(?:[a-z][a-z\d+.-]*:|\/|[a-z]:|\\\\)/i.test(value);
|
|
240
442
|
}
|
|
241
|
-
function local(value) {
|
|
242
|
-
return safeText(value) && !/^(?:[a-z][a-z\d+.-]*:|\/|[a-z]:|\\\\)/i.test(value);
|
|
243
|
-
}
|
|
244
443
|
function hasSchemaMarker(argv) {
|
|
245
444
|
for (const token of argv) {
|
|
246
445
|
if (token === "--") break;
|