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.
Files changed (53) hide show
  1. package/README.md +141 -214
  2. package/THIRD_PARTY_NOTICES.md +385 -48
  3. package/dist/_types/contracts/preflight-prerequisite-types.d.ts +73 -0
  4. package/dist/_types/packages/scanner-cli/src/index.d.ts +6 -0
  5. package/dist/_types/packages/scanner-cli/src/v2/arguments.d.ts +102 -0
  6. package/dist/_types/packages/scanner-cli/src/v2/cli-runner.d.ts +22 -0
  7. package/dist/{v2 → _types/packages/scanner-cli/src/v2}/commands.d.ts +10 -5
  8. package/dist/{v2 → _types/packages/scanner-cli/src/v2}/contracts.d.ts +53 -9
  9. package/dist/_types/packages/scanner-cli/src/v2/help.d.ts +64 -0
  10. package/dist/_types/packages/scanner-cli/src/v2/metadata-output.d.ts +37 -0
  11. package/dist/_types/packages/scanner-cli/src/v2/preflight-readiness.d.ts +108 -0
  12. package/dist/_types/packages/scanner-cli/src/v2/privacy-prepare.d.ts +39 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +26129 -0
  15. package/dist/index.js.map +7 -0
  16. package/dist/main.js +26874 -12830
  17. package/dist/main.js.map +4 -4
  18. package/dist/mcp.js +19357 -7477
  19. package/dist/mcp.js.map +4 -4
  20. package/dist/scan-worker.js +28702 -0
  21. package/dist/scan-worker.js.map +7 -0
  22. package/dist/v2/arguments.d.ts +1 -86
  23. package/dist/v2/arguments.js +220 -21
  24. package/dist/v2/arguments.js.map +3 -3
  25. package/dist/v2/cli-runner.d.ts +1 -28
  26. package/dist/v2/cli-runner.js +19080 -7387
  27. package/dist/v2/cli-runner.js.map +4 -4
  28. package/dist/v2/help.d.ts +1 -27
  29. package/dist/v2/help.js +77 -9
  30. package/dist/v2/help.js.map +2 -2
  31. package/dist/v2/metadata-output.d.ts +1 -37
  32. package/dist/v2/metadata-output.js +20917 -276
  33. package/dist/v2/metadata-output.js.map +4 -4
  34. package/dist/v2/preflight-readiness.d.ts +1 -0
  35. package/dist/v2/preflight-readiness.js +447 -0
  36. package/dist/v2/preflight-readiness.js.map +7 -0
  37. package/dist/v2/privacy-prepare.d.ts +1 -0
  38. package/dist/v2/privacy-prepare.js +232 -0
  39. package/dist/v2/privacy-prepare.js.map +7 -0
  40. package/package.json +82 -67
  41. package/dist/v2/atomic-output.d.ts +0 -34
  42. package/dist/v2/batch-runner.d.ts +0 -44
  43. package/dist/v2/config.d.ts +0 -16
  44. package/dist/v2/exit-reducer.d.ts +0 -23
  45. package/dist/v2/input-read.d.ts +0 -26
  46. package/dist/v2/input-selection.d.ts +0 -38
  47. package/dist/v2/json-output.d.ts +0 -97
  48. package/dist/v2/metadata-validation.d.ts +0 -4
  49. package/dist/v2/ndjson-output.d.ts +0 -3
  50. package/dist/v2/sarif-helpers.d.ts +0 -69
  51. package/dist/v2/sarif-output.d.ts +0 -40
  52. package/dist/v2/table-output.d.ts +0 -2
  53. package/dist/v2/worker-host.d.ts +0 -24
@@ -1,86 +1 @@
1
- import { type ScanConfigV2, type ScanSeverityV2 } from './contracts';
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";
@@ -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: "a36d00564bbf8e8c38f05820480782cdc53fdbd3712558850066c4e3f5c3413d", signatureStatus: "VERIFIED" }),
14
- semantics: Object.freeze({ id: "scanner-core-builtins", version: "2.0.0", digest: "1bb11f63269d427cb28e141dfe0a6a1512e53c298abdb4d6450545ceb56d11c4" }),
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
- inputs.push(...args.slice(index + 1));
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
- if (!local(token)) return failure("REMOTE_PATH", "Input path is not admitted.");
112
- inputs.push(token);
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 (!local(read)) return failure("REMOTE_PATH", "Option value is not admitted.", name);
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 === "-" || !local(read))) return failure("REMOTE_PATH", "Option value is not admitted.", name);
147
- else if (name === "--output" && read !== "-" && !local(read)) return failure("REMOTE_PATH", "Option value is not admitted.", name);
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) => !local(input))) return failure("REMOTE_PATH", "Input path is not admitted.");
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: value.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 || !local(rest[0])) return failure("UNEXPECTED_ARGUMENT", "validate-config requires one local file.");
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;