@pho9ubenaa/siro 0.6.0 → 0.6.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.2] — 2026-10-03
4
+
5
+ - Report oversized custom rule groups as finding-limit errors (CLI exit 2), rather
6
+ than JavaScript argument-limit failures (exit 70). Larger caller-supplied finding
7
+ budgets also work without truncating results.
8
+ - Reduce distribution size and repeated configuration work during scans.
9
+
10
+ Public API, CLI options, decoded JSON schema 3, Node requirements, inspection scope
11
+ and [safety limits](docs/threat-model.md) remain unchanged.
12
+
13
+ ## [0.6.1] — 2026-10-02
14
+
15
+ - Add `--no-config` to skip executable repository configuration, and
16
+ `--strict-filesystem` / API `rejectSymlinks` to reject native input-path symlinks.
17
+ Defaults retain automatic config and symlink resolution; neither option is a sandbox.
18
+ - Add finite scan/output budgets, caller overrides (`limits`, CLI `--max-*`) and
19
+ `DEFAULT_SCAN_LIMITS` / `ScanLimits` exports. Oversized/deep inputs now fail with
20
+ exit 2; output overflow exits 70 rather than producing a truncated success report.
21
+ - Omit source excerpts from JSON/YAML/TOML syntax diagnostics. Observed finding
22
+ values are not secret-redacted.
23
+ - Fix argument-limit failures when inspecting large Deno scoped import mappings.
24
+ - Update locked transitive dependencies to brace-expansion 5.0.12, markdown-it 14.3.1
25
+ and fast-uri 3.1.8 for upstream security fixes.
26
+ - Correct first-run safety examples to use `npx` without a global install.
27
+ - Separate build/verification from OIDC staging of the verified artifact, with
28
+ release identity, digest and main-ancestry checks.
29
+
30
+ Node requirements, direct dependency versions and decoded JSON schema 3 remain unchanged.
31
+
3
32
  ## [0.6.0] — 2026-09-29
4
33
 
5
34
  ### Breaking changes and inspection scope
package/README.md CHANGED
@@ -33,8 +33,9 @@ Discovery does not imply that every package's installation settings were inspect
33
33
  siro detects managers from `packageManager`, lockfiles, and configuration files. If it cannot
34
34
  detect one, choose it explicitly, for example `npx @pho9ubenaa/siro lint --pm npm`.
35
35
  The CLI may download code through `npx` and imports a repository's `siro.config.*` as executable
36
- code. Review configurations before running it on an unfamiliar project; see the
37
- [threat model](docs/threat-model.md).
36
+ code by default. For an unfamiliar project, use `npx @pho9ubenaa/siro lint --no-config --strict-filesystem`
37
+ in an isolated environment without credentials. These flags do not sandbox npx or
38
+ extensions; see the [threat model](docs/threat-model.md).
38
39
 
39
40
  ## Read findings and add CI
40
41
 
@@ -70,6 +71,11 @@ For exclusions and rule overrides, see the [configuration examples](docs/configu
70
71
  | `--severity <error\|warn\|info>` | Set both the display and CI failure threshold; default failure threshold is `error`. |
71
72
  | `--reporter <pretty\|json\|github>` | Choose terminal, JSON, or GitHub Actions output; `--json` is a JSON shortcut. |
72
73
 
74
+ `--no-config` disables executable configuration; `--strict-filesystem` rejects
75
+ symlink input paths in native data reads. Scans also have finite caller-controlled
76
+ [file, tree, nesting, finding and output budgets](docs/configuration.md#strict-filesystem-and-scan-budgets).
77
+ Overflow fails the check rather than silently skipping inputs.
78
+
73
79
  For a walkthrough and deeper reference, use these guides:
74
80
 
75
81
  - [Getting started](docs/getting-started.md) walks through findings and CI; [configuration](docs/configuration.md) covers local PM/version selection, discovery and explicit installation scope, executable config, exit codes, and migration from the removed `--workspaces` flag.
package/dist/cli.mjs CHANGED
@@ -1,271 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { C as isSupportedNodeVersion, E as assertDirectory, M as SiroError, N as UsageError, O as isNodeError, S as isSeverity, T as version, _ as PROJECT_TYPES, a as DEFAULT_REPORTER_NAME, b as SEVERITIES, i as BUILTIN_REPORTER_NAMES, k as asAbsPath, l as safeText, m as loadConfig, o as JSON_REPORTER_NAME, r as lintCommand, t as nodeIO, v as isProjectType, w as SUPPORTED_NODE_RANGE, x as isPM, y as PMS } from "./node-io-CAszdmko.mjs";
3
- import path from "node:path";
4
- import { pathToFileURL } from "node:url";
5
- import { parseArgs } from "node:util";
6
- //#region src/cli/parsers.ts
7
- const parsePmFlag = (raw) => {
8
- if (typeof raw === "undefined") return;
9
- if (raw === true) throw new UsageError(`--pm requires a value (expected one of: ${PMS.join(", ")})`);
10
- if (typeof raw !== "string" || !isPM(raw)) throw new UsageError(`Unknown package manager: ${raw} (expected one of: ${PMS.join(", ")})`);
11
- return raw;
12
- };
13
- const parseProjectTypeFlag = (raw) => {
14
- if (typeof raw === "undefined") return;
15
- if (raw === true) throw new UsageError(`--project-type requires a value (expected ${PROJECT_TYPES.join("|")})`);
16
- if (typeof raw === "string" && isProjectType(raw)) return raw;
17
- throw new UsageError(`Unknown project type: ${String(raw)} (expected ${PROJECT_TYPES.join("|")})`);
18
- };
19
- const parseSeverityFlag = (raw) => {
20
- if (typeof raw === "undefined") return;
21
- if (raw === true) throw new UsageError(`--severity requires a value (expected ${SEVERITIES.join("|")})`);
22
- if (typeof raw !== "string" || !isSeverity(raw)) throw new UsageError(`Invalid severity: ${String(raw)} (expected ${SEVERITIES.join("|")})`);
23
- return raw;
24
- };
25
- const ensureNodeVersion = (nodeVersion) => {
26
- if (!isSupportedNodeVersion(nodeVersion)) throw new UsageError(`Node ${SUPPORTED_NODE_RANGE} required (you are on ${nodeVersion}). Upgrade your runtime to use siro.`);
27
- };
28
- //#endregion
29
- //#region src/cli/commands.ts
30
- const COMMANDS = ["lint", "check"];
31
- const isCommandName = (value) => COMMANDS.some((cmd) => cmd === value);
32
- //#endregion
33
- //#region src/cli/parse-args.ts
34
- const REPEATABLE_FLAGS = new Set(["exclude", "installation-root"]);
35
- const VALUE_FLAGS = new Set([
36
- "pm",
37
- "pm-version",
38
- "project-type",
39
- "reporter",
40
- "severity",
41
- ...REPEATABLE_FLAGS
42
- ]);
43
- const BOOLEAN_FLAGS = new Set([
44
- "help",
45
- "version",
46
- "json"
47
- ]);
48
- const parseCommand = (argv) => {
49
- const { tokens } = parseArgs({
50
- args: [...argv],
51
- options: {
52
- help: {
53
- type: "boolean",
54
- short: "h"
55
- },
56
- version: {
57
- type: "boolean",
58
- short: "v"
59
- }
60
- },
61
- strict: false,
62
- tokens: true
63
- });
64
- const flags = /* @__PURE__ */ new Map();
65
- const repeated = /* @__PURE__ */ new Map();
66
- const positionals = [];
67
- let error;
68
- for (let index = 0; index < tokens.length; index += 1) {
69
- const token = tokens[index];
70
- if (!token) break;
71
- if (token.kind === "option-terminator") {
72
- if (index + 1 < tokens.length) error ??= "siro takes no passthrough arguments after `--`.";
73
- break;
74
- }
75
- if (token.kind === "positional") {
76
- positionals.push(token.value);
77
- continue;
78
- }
79
- if (BOOLEAN_FLAGS.has(token.name)) if (token.value !== void 0) error ??= `Flag ${token.rawName} does not accept a value.`;
80
- else {
81
- if (token.name === "json" && flags.has("json")) error ??= "--json must be specified only once.";
82
- flags.set(token.name, true);
83
- }
84
- else if (VALUE_FLAGS.has(token.name)) {
85
- let value = token.value;
86
- const next = tokens[index + 1];
87
- if (value === void 0 && next?.kind === "positional" && next.index === token.index + 1) {
88
- value = next.value;
89
- index += 1;
90
- }
91
- if (value === void 0 || value === "") error ??= `${token.rawName} requires a value.`;
92
- else if (REPEATABLE_FLAGS.has(token.name)) repeated.set(token.name, [...repeated.get(token.name) ?? [], value]);
93
- else {
94
- if (flags.has(token.name)) error ??= `${token.rawName} must be specified only once.`;
95
- flags.set(token.name, value);
96
- }
97
- } else error ??= token.name === "workspaces" ? "The --workspaces flag was removed in 0.6.0; discovery is recursive by default. Use --exclude and --installation-root." : `Unknown flag: ${token.rawName}`;
98
- }
99
- const [command, cwd, ...extra] = positionals;
100
- if (flags.has("help")) return {
101
- kind: "help",
102
- target: command && isCommandName(command) ? command : void 0
103
- };
104
- if (flags.has("version")) return { kind: "version" };
105
- if (error) throw new UsageError(error);
106
- if (command === void 0) return { kind: "usage" };
107
- if (command === "init") return {
108
- kind: "usage",
109
- reason: "The 'init' command was removed: siro is lint-only. Run `siro lint --reporter json` and review each finding's remediation."
110
- };
111
- if (!isCommandName(command)) return {
112
- kind: "usage",
113
- reason: `Unknown command: ${command}`
114
- };
115
- if (extra.length > 0) throw new UsageError(`Unexpected extra argument: ${extra.join(" ")}`);
116
- if (flags.has("reporter") && flags.has("json")) throw new UsageError("Invalid reporter selection: use either --reporter or --json.");
117
- const reporter = flags.get("reporter");
118
- const pmVersion = flags.get("pm-version");
119
- return {
120
- kind: "lint",
121
- cwd: asAbsPath(path.resolve(cwd ?? process.cwd())),
122
- pm: parsePmFlag(flags.get("pm")),
123
- pmVersion: typeof pmVersion === "string" ? pmVersion : void 0,
124
- exclude: repeated.get("exclude"),
125
- installationRoots: repeated.get("installation-root"),
126
- projectType: parseProjectTypeFlag(flags.get("project-type")),
127
- severity: parseSeverityFlag(flags.get("severity")),
128
- reporter: typeof reporter === "string" ? reporter : flags.has("json") ? JSON_REPORTER_NAME : DEFAULT_REPORTER_NAME
129
- };
130
- };
131
- //#endregion
132
- //#region src/cli/help.ts
133
- const PMS_LIST = PMS.join("|");
134
- const REPORTERS_LIST = BUILTIN_REPORTER_NAMES.join("|");
135
- const PROJECT_TYPES_LIST = PROJECT_TYPES.join("|");
136
- const SEVERITIES_LIST = SEVERITIES.join("|");
137
- const FLAG_LINES = {
138
- json: " --json Shortcut for --reporter json",
139
- pm: ` --pm <name> Target a specific package manager (${PMS_LIST})`,
140
- pmVersion: " --pm-version <x.y.z> Target an exact stable PM version (requires --pm)",
141
- inspection: " --exclude <pattern> Exclude directories from recursive discovery (repeatable)\n --installation-root <path> Inspect local install policy here (repeatable; default .)",
142
- projectType: ` --project-type <type> Project type (${PROJECT_TYPES_LIST}; default auto)`,
143
- reporter: ` --reporter <name> Reporter (${REPORTERS_LIST}; additional reporters can be registered via siro.config.ts)`,
144
- severity: ` --severity <level> Show + fail on findings at or above this level (${SEVERITIES_LIST})`
145
- };
146
- const HELP_ROOT = [
147
- "siro — security best-practices for the npm ecosystem",
148
- "",
149
- "USAGE",
150
- " siro <command> [path] [flags]",
151
- "",
152
- "COMMANDS",
153
- " lint Report best-practice violations (alias: check)",
154
- "",
155
- "GLOBAL FLAGS",
156
- FLAG_LINES.pm,
157
- FLAG_LINES.pmVersion,
158
- FLAG_LINES.inspection,
159
- FLAG_LINES.projectType,
160
- " --version Print the siro version",
161
- " --help Show help for siro or a command",
162
- "",
163
- "LINT FLAGS",
164
- FLAG_LINES.reporter,
165
- FLAG_LINES.json,
166
- FLAG_LINES.severity,
167
- "",
168
- "EXAMPLES",
169
- " $ siro lint # report violations in cwd",
170
- " $ siro lint --reporter github # GitHub Actions annotations",
171
- " $ siro lint --severity warn # also fail on warnings",
172
- "",
173
- "LEARN MORE",
174
- " Quickstart https://github.com/pHo9UBenaA/siro/blob/main/docs/getting-started.md",
175
- " Rule reference https://github.com/pHo9UBenaA/siro/blob/main/docs/rules.md",
176
- " Config options https://github.com/pHo9UBenaA/siro/blob/main/docs/configuration.md"
177
- ].join("\n");
178
- const HELP_LINT = [
179
- "siro lint — report best-practice violations",
180
- "",
181
- "USAGE",
182
- " siro lint [path] [flags]",
183
- " siro check [path] [flags] (alias)",
184
- "",
185
- "FLAGS",
186
- FLAG_LINES.pm,
187
- FLAG_LINES.pmVersion,
188
- FLAG_LINES.inspection,
189
- FLAG_LINES.projectType,
190
- FLAG_LINES.reporter,
191
- FLAG_LINES.json,
192
- FLAG_LINES.severity,
193
- "",
194
- "EXIT CODES",
195
- " 0 No findings at or above the active threshold",
196
- " 1 Findings at or above the threshold (default: error)",
197
- " 2 Usage error (bad flag, broken siro.config.ts, unreadable path, …)",
198
- " 70 Uncaught exception (a siro bug, or a reporter / custom rule that threw)",
199
- "",
200
- "EXAMPLES",
201
- " $ siro lint # default: pretty reporter, fail on errors",
202
- " $ siro lint --reporter github # GitHub Actions annotations",
203
- " $ siro lint --severity warn # tighten the gate"
204
- ].join("\n");
205
- const renderHelp = (target) => {
206
- if (typeof target === "undefined") return HELP_ROOT;
207
- if (target === "lint" || target === "check") return HELP_LINT;
208
- return target;
209
- };
210
- //#endregion
211
- //#region src/cli.ts
212
- const EXIT_SUCCESS = 0;
213
- const EXIT_USAGE = 2;
214
- const EXIT_CRASH = 70;
215
- const dispatch = async (cmd, io) => {
216
- switch (cmd.kind) {
217
- case "version":
218
- await io.stdout(version);
219
- return EXIT_SUCCESS;
220
- case "help":
221
- await io.stdout(renderHelp(cmd.target));
222
- return EXIT_SUCCESS;
223
- case "usage":
224
- if (cmd.reason) await io.stderr(`${safeText(cmd.reason)}\n`);
225
- await io.stderr(renderHelp());
226
- return EXIT_USAGE;
227
- case "lint":
228
- assertDirectory(cmd.cwd);
229
- return loadConfig(cmd.cwd).then((config) => lintCommand({
230
- ...cmd,
231
- config
232
- }, io));
233
- default: throw new Error(`Unhandled command kind: ${String(cmd)}`);
234
- }
235
- };
236
- const handleError = async (error, io) => {
237
- if (error instanceof SiroError) {
238
- await io.stderr(safeText(error.message));
239
- return error.exitCode;
240
- }
241
- if (isNodeError(error) && "errno" in error && typeof error.errno === "number") {
242
- await io.stderr(safeText(`File system error: ${error.message}`));
243
- return EXIT_USAGE;
244
- }
245
- throw error;
246
- };
247
- const run = async (argv, io = nodeIO) => {
248
- try {
249
- ensureNodeVersion(process.versions.node);
250
- return await dispatch(parseCommand(argv), io);
251
- } catch (error) {
252
- return handleError(error, io);
253
- }
254
- };
255
- const runMain = async (argv) => {
256
- try {
257
- process.exitCode = await run(argv);
258
- } catch (error) {
259
- const errStr = error instanceof Error ? error.stack ?? error.message : String(error);
260
- process.exitCode = EXIT_CRASH;
261
- try {
262
- await nodeIO.stderr(safeText(errStr));
263
- } catch {}
264
- }
265
- };
266
- const [, invokedPath] = process.argv;
267
- if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) await runMain(process.argv.slice(2));
268
- //#endregion
269
- export { run, runMain };
270
-
2
+ import{C as isSeverity,D as asAbsPath,E as version,I as SiroError,L as UsageError,M as DEFAULT_SCAN_LIMITS,N as resolveScanLimits,P as isOwnKey,S as isPM,T as SUPPORTED_NODE_RANGE,_ as PROJECT_TYPES,a as DEFAULT_REPORTER_NAME,b as PMS,i as BUILTIN_REPORTER_NAMES,j as isNodeError,k as assertDirectory,l as safeText,m as loadConfig,o as JSON_REPORTER_NAME,r as lintCommand,t as nodeIO,v as isProjectType,w as isSupportedNodeVersion,x as SEVERITIES,y as isStableVersion}from"./node-io-DE7Nzlal.mjs";import path from"node:path";import{pathToFileURL}from"node:url";import{parseArgs}from"node:util";const parsePmFlag=raw=>{if(typeof raw===`undefined`)return;if(typeof raw!==`string`||!isPM(raw))throw new UsageError(`Unknown package manager: ${raw} (expected one of: ${PMS.join(`, `)})`);return raw};const parseProjectTypeFlag=raw=>{if(typeof raw===`undefined`)return;if(typeof raw===`string`&&isProjectType(raw))return raw;throw new UsageError(`Unknown project type: ${String(raw)} (expected ${PROJECT_TYPES.join(`|`)})`)};const parseSeverityFlag=raw=>{if(typeof raw===`undefined`)return;if(typeof raw!==`string`||!isSeverity(raw))throw new UsageError(`Invalid severity: ${String(raw)} (expected ${SEVERITIES.join(`|`)})`);return raw};const ensureNodeVersion=nodeVersion=>{if(!isSupportedNodeVersion(nodeVersion))throw new UsageError(`Node ${SUPPORTED_NODE_RANGE} required (you are on ${nodeVersion}). Upgrade your runtime to use siro.`)};const COMMANDS=[`lint`,`check`];const isCommandName=value=>COMMANDS.some(command=>command===value);const options={maxFileBytes:{flag:`max-file-bytes`,description:`Bound each input file`},maxTotalBytes:{flag:`max-total-bytes`,description:`Bound all input reads`,descriptionColumn:25},maxEntries:{flag:`max-entries`,description:`Bound native directory entries`},maxDirectories:{flag:`max-directories`,description:`Bound discovered directories`},maxDirectoryDepth:{flag:`max-directory-depth`,description:`Bound discovery depth`},maxConfigDepth:{flag:`max-config-depth`,description:`Bound configuration nesting`},maxFindings:{flag:`max-findings`,description:`Bound unfiltered findings`},maxOutputBytes:{flag:`max-output-bytes`,description:`Bound report output`}};const limitOptions=[];for(const key of Object.keys(options)){if(!isOwnKey(options,key))continue;limitOptions.push({key,...options[key]})}const LIMIT_OPTIONS=limitOptions;const limitHelp=LIMIT_OPTIONS.map(({key,flag,description,descriptionColumn=24})=>`${` --${flag} <n> `.padEnd(descriptionColumn)}${description} (default ${DEFAULT_SCAN_LIMITS[key]})`).join(`
3
+ `);const REPEATABLE_FLAGS=new Set([`exclude`,`installation-root`]);const VALUE_FLAGS=new Set([`pm`,`pm-version`,`project-type`,`reporter`,`severity`,...REPEATABLE_FLAGS,...LIMIT_OPTIONS.map(({flag})=>flag)]);const BOOLEAN_FLAGS=new Set([`help`,`version`,`json`,`no-config`,`strict-filesystem`]);const collectArguments=argv=>{const{tokens}=parseArgs({args:[...argv],options:{help:{type:`boolean`,short:`h`},version:{type:`boolean`,short:`v`}},strict:false,tokens:true});const flags=new Set;const values=new Map;const repeatedFlags=new Map;const positionals=[];let firstError;const remainingTokens=tokens.entries();for(const[index,token]of remainingTokens){if(token.kind===`option-terminator`){if(index+1<tokens.length)firstError??="siro takes no passthrough arguments after `--`.";break}if(token.kind===`positional`){positionals.push(token.value);continue}if(BOOLEAN_FLAGS.has(token.name)){if(token.value!==void 0){firstError??=`Flag ${token.rawName} does not accept a value.`;continue}if([`json`,`no-config`,`strict-filesystem`].includes(token.name)&&flags.has(token.name))firstError??=`${token.rawName} must be specified only once.`;flags.add(token.name);continue}if(!VALUE_FLAGS.has(token.name)){firstError??=token.name===`workspaces`?`The --workspaces flag was removed in 0.6.0; discovery is recursive by default. Use --exclude and --installation-root.`:`Unknown flag: ${token.rawName}`;continue}const next=tokens[index+1];const consumesNext=token.value===void 0&&next?.kind===`positional`&&next.index===token.index+1;const value=consumesNext?next.value:token.value;if(consumesNext)remainingTokens.next();if(value===void 0||value===``){firstError??=`${token.rawName} requires a value.`;continue}if(REPEATABLE_FLAGS.has(token.name)){const occurrences=repeatedFlags.get(token.name)??[];occurrences.push(value);repeatedFlags.set(token.name,occurrences)}else{if(values.has(token.name))firstError??=`${token.rawName} must be specified only once.`;values.set(token.name,value)}}return{flags,values,repeatedFlags,positionals,firstError}};const parseLimitOverrides=values=>{const limits={};for(const{flag,key}of LIMIT_OPTIONS){const value=values.get(flag);if(value===void 0)continue;if(!/^\d+$/u.test(value))throw new UsageError(`${flag} must be a positive safe integer.`);limits[key]=Number(value)}resolveScanLimits(limits);return limits};const parseCommand=argv=>{const{flags,values,repeatedFlags,positionals,firstError}=collectArguments(argv);const[command,cwd,...extra]=positionals;if(flags.has(`help`))return{kind:`help`,target:command&&isCommandName(command)?command:void 0};if(flags.has(`version`))return{kind:`version`};if(firstError)throw new UsageError(firstError);if(command===void 0)return{kind:`usage`};if(command===`init`)return{kind:`usage`,reason:"The 'init' command was removed: siro is lint-only. Run `siro lint --reporter json` and review each finding's remediation."};if(!isCommandName(command))return{kind:`usage`,reason:`Unknown command: ${command}`};if(extra.length>0)throw new UsageError(`Unexpected extra argument: ${extra.join(` `)}`);if(values.has(`reporter`)&&flags.has(`json`))throw new UsageError(`Invalid reporter selection: use either --reporter or --json.`);const reporter=values.get(`reporter`)??(flags.has(`json`)?JSON_REPORTER_NAME:DEFAULT_REPORTER_NAME);const pmVersion=values.get(`pm-version`);const pm=parsePmFlag(values.get(`pm`));if(pmVersion!==void 0&&(!pm||!isStableVersion(pmVersion)))throw new UsageError(`--pm-version requires --pm and an exact stable version.`);const limits=parseLimitOverrides(values);return{kind:`lint`,cwd:asAbsPath(path.resolve(cwd??process.cwd())),pm,pmVersion,exclude:repeatedFlags.get(`exclude`),installationRoots:repeatedFlags.get(`installation-root`),projectType:parseProjectTypeFlag(values.get(`project-type`)),severity:parseSeverityFlag(values.get(`severity`)),...flags.has(`no-config`)?{noConfig:true}:{},...flags.has(`strict-filesystem`)?{rejectSymlinks:true}:{},...Object.keys(limits).length?{limits}:{},reporter}};const PMS_LIST=PMS.join(`|`);const REPORTERS_LIST=BUILTIN_REPORTER_NAMES.join(`|`);const PROJECT_TYPES_LIST=PROJECT_TYPES.join(`|`);const SEVERITIES_LIST=SEVERITIES.join(`|`);const FLAG_LINES={json:` --json Shortcut for --reporter json`,safety:` --no-config Do not probe or execute repository siro.config.*
4
+ --strict-filesystem Reject symlink input paths (not a containment sandbox)`,limits:limitHelp,pm:` --pm <name> Target a specific package manager (${PMS_LIST})`,pmVersion:` --pm-version <x.y.z> Target an exact stable PM version (requires --pm)`,inspection:` --exclude <pattern> Exclude directories from recursive discovery (repeatable)
5
+ --installation-root <path> Inspect local install policy here (repeatable; default .)`,projectType:` --project-type <type> Project type (${PROJECT_TYPES_LIST}; default auto)`,reporter:` --reporter <name> Reporter (${REPORTERS_LIST}; additional reporters can be registered via siro.config.ts)`,severity:` --severity <level> Show + fail on findings at or above this level (${SEVERITIES_LIST})`};const HELP_ROOT=[`siro — security best-practices for the npm ecosystem`,``,`USAGE`,` siro <command> [path] [flags]`,``,`COMMANDS`,` lint Report best-practice violations (alias: check)`,``,`GLOBAL FLAGS`,FLAG_LINES.pm,FLAG_LINES.pmVersion,FLAG_LINES.inspection,FLAG_LINES.projectType,` --version Print the siro version`,` --help Show help for siro or a command`,``,`LINT FLAGS`,FLAG_LINES.safety,FLAG_LINES.limits,FLAG_LINES.reporter,FLAG_LINES.json,FLAG_LINES.severity,``,`EXAMPLES`,` $ siro lint # report violations in cwd`,` $ siro lint --no-config --strict-filesystem # data-only inspection`,` $ siro lint --reporter github # GitHub Actions annotations`,` $ siro lint --severity warn # also fail on warnings`,``,`LEARN MORE`,` Quickstart https://github.com/pHo9UBenaA/siro/blob/main/docs/getting-started.md`,` Rule reference https://github.com/pHo9UBenaA/siro/blob/main/docs/rules.md`,` Config options https://github.com/pHo9UBenaA/siro/blob/main/docs/configuration.md`].join(`
6
+ `);const HELP_LINT=[`siro lint — report best-practice violations`,``,`USAGE`,` siro lint [path] [flags]`,` siro check [path] [flags] (alias)`,``,`FLAGS`,FLAG_LINES.safety,FLAG_LINES.limits,FLAG_LINES.pm,FLAG_LINES.pmVersion,FLAG_LINES.inspection,FLAG_LINES.projectType,FLAG_LINES.reporter,FLAG_LINES.json,FLAG_LINES.severity,``,`EXIT CODES`,` 0 No findings at or above the active threshold`,` 1 Findings at or above the threshold (default: error)`,` 2 Usage error (bad flag, broken siro.config.ts, unreadable path, …)`,` 70 Output failure/limit, or unexpected exception (including trusted extensions)`,``,`EXAMPLES`,` $ siro lint # default: pretty reporter, fail on errors`,` $ siro lint --no-config --strict-filesystem # data-only inspection`,` $ siro lint --reporter github # GitHub Actions annotations`,` $ siro lint --severity warn # tighten the gate`].join(`
7
+ `);const renderHelp=target=>{if(typeof target===`undefined`)return HELP_ROOT;if(target===`lint`||target===`check`)return HELP_LINT;return target};const EXIT_SUCCESS=0;const EXIT_USAGE=2;const EXIT_CRASH=70;const dispatch=async(command,io)=>{switch(command.kind){case`version`:await io.stdout(version);return EXIT_SUCCESS;case`help`:await io.stdout(renderHelp(command.target));return EXIT_SUCCESS;case`usage`:if(command.reason)await io.stderr(`${safeText(command.reason)}\n`);await io.stderr(renderHelp());return EXIT_USAGE;case`lint`:{assertDirectory(command.cwd);const{noConfig,...options}=command;if(noConfig)return lintCommand(options,io);const config=await loadConfig(command.cwd);return lintCommand({...options,config},io)}default:throw new Error(`Unhandled command kind: ${String(command)}`)}};const handleError=async(error,io)=>{if(error instanceof SiroError){await io.stderr(safeText(error.message));return error.exitCode}if(isNodeError(error)&&`errno`in error&&typeof error.errno===`number`){await io.stderr(safeText(`File system error: ${error.message}`));return EXIT_USAGE}throw error};const run=async(argv,io=nodeIO)=>{try{ensureNodeVersion(process.versions.node);return await dispatch(parseCommand(argv),io)}catch(error){return handleError(error,io)}};const runMain=async argv=>{try{process.exitCode=await run(argv)}catch(error){const diagnostic=error instanceof Error?error.stack??error.message:String(error);process.exitCode=EXIT_CRASH;try{await nodeIO.stderr(safeText(diagnostic))}catch{}}};const[,invokedPath]=process.argv;if(invokedPath&&import.meta.url===pathToFileURL(invokedPath).href)await runMain(process.argv.slice(2));export{run,runMain};
271
8
  //# sourceMappingURL=cli.mjs.map
package/dist/index.d.mts CHANGED
@@ -35,12 +35,7 @@ interface ConfigFileRef {
35
35
  }
36
36
  //#endregion
37
37
  //#region src/core/contracts/pms.d.ts
38
- /**
39
- * Single source of truth for package managers and severities.
40
- * Types, runtime sets, and ordering derive from these tuples. Adding a manager
41
- * still requires local detection signals, applicable rule bindings and verified
42
- * policy/availability evidence; the tuple alone does not implement support.
43
- */
38
+ /** Supported identifiers in stable evaluation/display order. */
44
39
  declare const PMS: readonly ["npm", "pnpm", "yarn", "bun", "deno", "aube"];
45
40
  type PM = (typeof PMS)[number];
46
41
  declare const SEVERITIES: readonly ["error", "warn", "info"];
@@ -146,7 +141,7 @@ interface Rule<Id extends string = string> {
146
141
  declare const defineRule: <const Id extends string>(rule: Rule<Id>) => Rule<Id>;
147
142
  //#endregion
148
143
  //#region src/core/contracts/file-system.d.ts
149
- /** IO boundary: swap in memfs (or any other backend) for tests. */
144
+ /** Read-only filesystem boundary for repository inspection. */
150
145
  interface FileSystem {
151
146
  /** Ordinary native child directory names, excluding symlinks. Required for recursive discovery. Errors must propagate. */
152
147
  readDirectories: (path: AbsPath) => readonly string[];
@@ -201,9 +196,24 @@ interface LintResult {
201
196
  readonly inspection: Inspection;
202
197
  }
203
198
  //#endregion
199
+ //#region src/core/contracts/scan-limits.d.ts
200
+ /** Finite per-scan budgets, controlled by the caller, never repository config. */
201
+ interface ScanLimits {
202
+ readonly maxFileBytes: number;
203
+ readonly maxTotalBytes: number;
204
+ readonly maxEntries: number;
205
+ readonly maxDirectories: number;
206
+ readonly maxDirectoryDepth: number;
207
+ readonly maxConfigDepth: number;
208
+ readonly maxFindings: number;
209
+ readonly maxOutputBytes: number;
210
+ }
211
+ declare const DEFAULT_SCAN_LIMITS: Readonly<ScanLimits>;
212
+ //#endregion
204
213
  //#region src/core/contracts/reporter.d.ts
205
214
  interface ReportContext {
206
215
  readonly cwd: AbsPath;
216
+ readonly limits?: ScanLimits;
207
217
  }
208
218
  interface Reporter<Name extends string = string> {
209
219
  readonly name: Name;
@@ -220,7 +230,9 @@ type InstallationRootInput = string | {
220
230
  //#region src/core/contracts/date-time.d.ts
221
231
  /** Host time and native date parsing, including the host timezone for offsetless input. */
222
232
  interface DateTime {
233
+ /** Current epoch milliseconds. */
223
234
  now: () => number;
235
+ /** Epoch milliseconds, or NaN for invalid input; offsetless dates use host semantics. */
224
236
  parse: (value: string) => number;
225
237
  }
226
238
  //#endregion
@@ -260,6 +272,10 @@ declare const defineConfig: (config: SiroConfig) => SiroConfig;
260
272
  interface LintOptions {
261
273
  readonly cwd: AbsPath;
262
274
  readonly fs?: FileSystem;
275
+ /** Caller-controlled finite budgets; omitted values use DEFAULT_SCAN_LIMITS. */
276
+ readonly limits?: Partial<ScanLimits>;
277
+ /** Reject file/path symlinks in the native adapter; not an atomic containment sandbox. */
278
+ readonly rejectSymlinks?: boolean;
263
279
  /** Applies only to cwd, never to discovered children or additional installation roots. */
264
280
  readonly pm?: PM;
265
281
  readonly pmVersion?: string;
@@ -282,32 +298,13 @@ interface LintCommandOptions extends LintOptions {
282
298
  declare const lint: (options: LintOptions) => LintResult;
283
299
  declare const lintCommand: (options: LintCommandOptions, io: IO) => Promise<number>;
284
300
  //#endregion
301
+ //#region src/version.d.ts
302
+ declare const version: string;
303
+ //#endregion
285
304
  //#region src/load-config.d.ts
286
305
  /** Load the first matching config; executable imports always use the real filesystem. */
287
306
  declare const loadConfig: (cwd: AbsPath, nodeVersion?: string) => Promise<SiroConfig | undefined>;
288
307
  //#endregion
289
- //#region src/adapters/node-file-system.d.ts
290
- declare const nodeFileSystem: FileSystem;
291
- //#endregion
292
- //#region src/adapters/node-io.d.ts
293
- declare const nodeIO: IO;
294
- //#endregion
295
- //#region src/adapters/reporters/github.d.ts
296
- /** Emit GitHub Actions workflow commands (annotations on PRs). */
297
- declare const githubReporter: Reporter<'github'>;
298
- //#endregion
299
- //#region src/adapters/reporters/json.d.ts
300
- declare const jsonReporter: Reporter<'json'>;
301
- //#endregion
302
- //#region src/adapters/reporters/pretty.d.ts
303
- declare const prettyReporter: Reporter<'pretty'>;
304
- //#endregion
305
- //#region src/adapters/reporters/registry.d.ts
306
- /** Names of every built-in reporter, in stable display order. */
307
- declare const BUILTIN_REPORTER_NAMES: readonly BuiltinReporterName[];
308
- /** Literal union of every built-in reporter name. */
309
- type BuiltinReporterName = (typeof prettyReporter | typeof jsonReporter | typeof githubReporter)['name'];
310
- //#endregion
311
308
  //#region src/core/config-files.d.ts
312
309
  /** Known configuration locations and their parsers. */
313
310
  declare const CONFIG_FILES: {
@@ -357,7 +354,7 @@ interface RequireConfigKeySpec {
357
354
  /** Only unconditional defaults may reduce severity; omitted means conditional. */
358
355
  readonly defaultSafety?: 'unconditional' | 'conditional';
359
356
  /**
360
- * Severity used when `documentedDefault` satisfies the requirement. Defaults
357
+ * Severity when an unconditional `documentedDefault` satisfies the requirement. Defaults
361
358
  * to `'info'` (advisory). Set `'off'` to silence the finding entirely.
362
359
  */
363
360
  readonly defaultSatisfiedSeverity?: Severity | 'off';
@@ -379,6 +376,28 @@ declare const overrideBindings: <Id extends string>(rule: Rule<Id>, overrides: P
379
376
  /** Build a Rule from a per-PM table of {file, keyPath, value, message}. */
380
377
  declare const requireConfigKey: <const Id extends string>(options: RequireConfigKeyOptions<Id>) => Rule<Id>;
381
378
  //#endregion
379
+ //#region src/adapters/node-file-system.d.ts
380
+ declare const nodeFileSystem: FileSystem;
381
+ //#endregion
382
+ //#region src/adapters/node-io.d.ts
383
+ declare const nodeIO: IO;
384
+ //#endregion
385
+ //#region src/adapters/reporters/github.d.ts
386
+ /** Emit GitHub Actions workflow commands (annotations on PRs). */
387
+ declare const githubReporter: Reporter<'github'>;
388
+ //#endregion
389
+ //#region src/adapters/reporters/json.d.ts
390
+ declare const jsonReporter: Reporter<'json'>;
391
+ //#endregion
392
+ //#region src/adapters/reporters/pretty.d.ts
393
+ declare const prettyReporter: Reporter<'pretty'>;
394
+ //#endregion
395
+ //#region src/adapters/reporters/registry.d.ts
396
+ /** Names of every built-in reporter, in stable display order. */
397
+ declare const BUILTIN_REPORTER_NAMES: readonly BuiltinReporterName[];
398
+ /** Literal union of every built-in reporter name. */
399
+ type BuiltinReporterName = (typeof prettyReporter | typeof jsonReporter | typeof githubReporter)['name'];
400
+ //#endregion
382
401
  //#region src/core/contracts/errors.d.ts
383
402
  declare class SiroError extends Error {
384
403
  readonly exitCode: number;
@@ -391,11 +410,8 @@ declare class UsageError extends SiroError {
391
410
  constructor(message: string);
392
411
  }
393
412
  //#endregion
394
- //#region src/version.d.ts
395
- declare const version: string;
396
- //#endregion
397
413
  //#region src/adapters/node-paths.d.ts
398
414
  declare const asAbsPath: (value: string) => AbsPath;
399
415
  //#endregion
400
- export { type AbsPath, BUILTIN_REPORTER_NAMES, type BuiltinReporterName, CONFIG_FILES, type CheckStatus, ConfigError, type ConfigFileRef, type ConfigReadValue, type ConfigValue, type FileSystem, type Finding, type IO, type Inspection, type InstallationRootInput, type KeyPath, type LintCommandOptions, type LintOptions, type LintResult, type PM, PMS, PROJECT_TYPES, type ParsedConfig, type PolicyTarget, type ProjectType, type RelPath, type Remediation, type RepoContext, type ReportContext, type Reporter, type RequireConfigKeyOptions, type RequireConfigKeySpec, type Rule, type RuleBinding, type RuleContext, type RuleSetting, SEVERITIES, type SetKeyOperation, type Severity, type SiroConfig, SiroError, UsageError, type VersionNote, type ViolationStatus, asAbsPath, asRelPath, defineConfig, defineRule, getByPath, githubReporter, isPM, isSeverity, jsonReporter, lint, lintCommand, loadConfig, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, requireConfigKey, version };
416
+ export { type AbsPath, BUILTIN_REPORTER_NAMES, type BuiltinReporterName, CONFIG_FILES, type CheckStatus, ConfigError, type ConfigFileRef, type ConfigReadValue, type ConfigValue, DEFAULT_SCAN_LIMITS, type FileSystem, type Finding, type IO, type Inspection, type InstallationRootInput, type KeyPath, type LintCommandOptions, type LintOptions, type LintResult, type PM, PMS, PROJECT_TYPES, type ParsedConfig, type PolicyTarget, type ProjectType, type RelPath, type Remediation, type RepoContext, type ReportContext, type Reporter, type RequireConfigKeyOptions, type RequireConfigKeySpec, type Rule, type RuleBinding, type RuleContext, type RuleSetting, SEVERITIES, type ScanLimits, type SetKeyOperation, type Severity, type SiroConfig, SiroError, UsageError, type VersionNote, type ViolationStatus, asAbsPath, asRelPath, defineConfig, defineRule, getByPath, githubReporter, isPM, isSeverity, jsonReporter, lint, lintCommand, loadConfig, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, requireConfigKey, version };
401
417
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,8 +1,2 @@
1
- import { A as asRelPath, D as nodeFileSystem, M as SiroError, N as UsageError, S as isSeverity, T as version, _ as PROJECT_TYPES, b as SEVERITIES, c as jsonReporter, d as overrideBindings, f as requireConfigKey, g as getByPath, h as defineRule, i as BUILTIN_REPORTER_NAMES, j as ConfigError, k as asAbsPath, m as loadConfig, n as lint, p as CONFIG_FILES, r as lintCommand, s as prettyReporter, t as nodeIO, u as githubReporter, x as isPM, y as PMS } from "./node-io-CAszdmko.mjs";
2
- //#region src/core/siro-config.ts
3
- /** Identity helper for type-checked config files. */
4
- const defineConfig = (config) => config;
5
- //#endregion
6
- export { BUILTIN_REPORTER_NAMES, CONFIG_FILES, ConfigError, PMS, PROJECT_TYPES, SEVERITIES, SiroError, UsageError, asAbsPath, asRelPath, defineConfig, defineRule, getByPath, githubReporter, isPM, isSeverity, jsonReporter, lint, lintCommand, loadConfig, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, requireConfigKey, version };
7
-
1
+ import{A as nodeFileSystem,C as isSeverity,D as asAbsPath,E as version,F as ConfigError,I as SiroError,L as UsageError,M as DEFAULT_SCAN_LIMITS,O as asRelPath,S as isPM,_ as PROJECT_TYPES,b as PMS,c as jsonReporter,d as overrideBindings,f as requireConfigKey,g as getByPath,h as defineRule,i as BUILTIN_REPORTER_NAMES,m as loadConfig,n as lint,p as CONFIG_FILES,r as lintCommand,s as prettyReporter,t as nodeIO,u as githubReporter,x as SEVERITIES}from"./node-io-DE7Nzlal.mjs";const defineConfig=config=>config;export{BUILTIN_REPORTER_NAMES,CONFIG_FILES,ConfigError,DEFAULT_SCAN_LIMITS,PMS,PROJECT_TYPES,SEVERITIES,SiroError,UsageError,asAbsPath,asRelPath,defineConfig,defineRule,getByPath,githubReporter,isPM,isSeverity,jsonReporter,lint,lintCommand,loadConfig,nodeFileSystem,nodeIO,overrideBindings,prettyReporter,requireConfigKey,version};
8
2
  //# sourceMappingURL=index.mjs.map