@pho9ubenaa/siro 0.5.1 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,86 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.1] — 2026-10-02
4
+
5
+ - Add `--no-config` to skip executable repository configuration, and
6
+ `--strict-filesystem` / API `rejectSymlinks` to reject native input-path symlinks.
7
+ Defaults retain automatic config and symlink resolution; neither option is a sandbox.
8
+ - Add finite scan/output budgets, caller overrides (`limits`, CLI `--max-*`) and
9
+ `DEFAULT_SCAN_LIMITS` / `ScanLimits` exports. Oversized/deep inputs now fail with
10
+ exit 2; output overflow exits 70 rather than producing a truncated success report.
11
+ - Omit source excerpts from JSON/YAML/TOML syntax diagnostics. Observed finding
12
+ values are not secret-redacted.
13
+ - Fix argument-limit failures when inspecting large Deno scoped import mappings.
14
+ - Update locked transitive dependencies to brace-expansion 5.0.12, markdown-it 14.3.1
15
+ and fast-uri 3.1.8 for upstream security fixes.
16
+ - Correct first-run safety examples to use `npx` without a global install.
17
+ - Separate build/verification from OIDC staging of the verified artifact, with
18
+ release identity, digest and main-ancestry checks.
19
+
20
+ Node requirements, direct dependency versions and decoded JSON schema 3 remain unchanged.
21
+
22
+ ## [0.6.0] — 2026-09-29
23
+
24
+ ### Breaking changes and inspection scope
25
+
26
+ - Discover package.json and strict deno.json recursively by default, independently
27
+ of PM workspace declarations. Remove `--workspaces` / API `workspaces`, including
28
+ false; legacy calls receive migration errors. PM exclusions no longer hide
29
+ fixtures, vendor or dist. Use `exclude: ['test/fixtures', 'vendor', 'dist']`, or
30
+ `exclude: ['**']` to keep only cwd.
31
+ - Add common exclusions and explicit `installationRoots` (default `['.']`). Arrays
32
+ replace config values; `[]` disables installation checks, not publication checks.
33
+ Example: `siro lint . --installation-root . --installation-root tools/standalone`.
34
+ Additional independent installation projects are not automatically inferred.
35
+ - Resolve PM/version locally: root options stay at cwd, additional roots use their
36
+ entry/local detection, and other packages use manifest-local evidence. Unknown
37
+ PMs still receive generic publication checks; unknown availability is not safety.
38
+ - JSON schema 3 adds `inspection`, required finding `directory`, and optional `pm`.
39
+ Generic publication findings run once per manifest without a synthetic PM.
40
+ - Require injected `FileSystem.readDirectories`; remove `resolveDirectory`. Skip
41
+ directory symlinks, `.git`, node_modules and explicit exclusions before reads.
42
+ No implicit vendor/dist/fixture exclusions or native filesystem fallback.
43
+ - `requireConfigKey.defaultSafety` explicitly controls safe-default downgrades;
44
+ omitted safety is conservative. VersionNote has no policy effect.
45
+ - Reporter calls require scan cwd as a third argument:
46
+ `await reporter.format(result, io, { cwd })`. Await built-in reporters and direct
47
+ IO writes, which may now complete asynchronously; handle output rejections.
48
+ Two-argument custom reporter implementations can ignore the additional context.
49
+
50
+ ### Fixes
51
+
52
+ - Validate consumed Deno publication metadata before applicability and check exact
53
+ registry pins in both inline imports and scopes. Reject unrepresentable npmrc age
54
+ cutoffs rather than treating any positive integer as protection.
55
+ - Respect known pre-12 npm targets when checking npm-shrinkwrap.json; explain removed
56
+ or unknown-version lockfiles instead of incorrectly reporting that no file exists.
57
+ - GitHub annotations reference the correct absolute file using the supplied scan cwd.
58
+ API/JSON paths and schema 3 retain their existing meaning.
59
+ - Escape untrusted display controls and workflow markers without changing API paths
60
+ or parsed JSON values. Observe actual stream writes and preserve exit 70 on output
61
+ failure. IO may complete asynchronously; synchronous return values remain ignored.
62
+ - Reject Promise/thenable config exports and check results without an unhandled
63
+ rejection overriding the configuration-error exit. Keep lint synchronous.
64
+ - Deno empty/age-null/exclude-only objects no longer falsely satisfy release age.
65
+ Valid omitted ages use active local npmrc fallback, retaining zero opt-out and
66
+ explicit-age precedence. Leaf remedies preserve valid exclusions.
67
+ - npm own publishConfig.provenance overrides npmrc, including false. Validate its
68
+ consumed boolean type; align finding/remedy with the responsible file and keep
69
+ both locations' old-target availability guards.
70
+ - Preserve legal POSIX backslash and colon directory names without confusing them
71
+ with separators or portable user input. Reject malformed/traversing adapter names.
72
+ - Make every remediation operation path relative to scan cwd, including multi-file
73
+ remedies. Findings without a responsible file remain file-less.
74
+ - Fix Windows package verification for paths with spaces and shell metacharacters.
75
+ - Make one-shot and locally installed usage explicit, restore rule configuration
76
+ examples, and update moved documentation anchors in findings and the rule reference.
77
+
78
+ ### Inspection limits
79
+
80
+ Installation checks use local settings, not inherited effective policy. Manifest-only
81
+ children have no provenance-policy checks, and child executable configs are not loaded.
82
+ See [scope and migration](docs/configuration.md) and [schema 3](docs/json-output.md).
83
+
3
84
  ## [0.5.1]
4
85
 
5
86
  ### Refactoring
package/README.md CHANGED
@@ -4,115 +4,84 @@
4
4
  [![npm](https://img.shields.io/npm/v/@pho9ubenaa/siro)](https://www.npmjs.com/package/@pho9ubenaa/siro)
5
5
  [![license](https://img.shields.io/github/license/pHo9UBenaA/siro)](https://github.com/pHo9UBenaA/siro/blob/main/LICENSE)
6
6
 
7
- > Security best-practices linter for the npm ecosystem — npm, pnpm, yarn, bun, deno, [aube](https://github.com/aubepkg/aube).
7
+ A security-configuration linter for npm, pnpm, Yarn, Bun, Deno, and Aube.
8
+ It reports supported dependency-installation and publication policy gaps, such as permissive
9
+ lifecycle scripts, unpinned versions, and missing publication safeguards. It does not install
10
+ packages or change your files.
8
11
 
9
- [Getting started](docs/getting-started.md) ·
10
- [Rules](docs/rules.md) ·
11
- [Comparison](docs/comparison.md) ·
12
- [Configuration](docs/configuration.md)
12
+ | Approach | Primary question | Typical input |
13
+ | ----------------------------- | --------------------------------------------- | -------------------------------------------- |
14
+ | siro: configuration lint | Are supported install/publish settings risky? | Repository manifests and configuration files |
15
+ | Dependency vulnerability scan | Do dependencies match known advisories? | Dependency inventory and vulnerability data |
16
+ | Dependency update automation | Which dependencies can be updated? | Manifests, lockfiles, and package registries |
13
17
 
14
- `siro` is **inspired by** the community-maintained [npm security best practices](https://github.com/bodadotsh/npm-security-best-practices)
15
- and turns those recommendations into something you can run: it **lints** repos for supply-chain risks,
16
- graded `error` / `warn` / `info`, and emits machine-readable remediation so an editor or agent skill
17
- can apply the fixes. The rule selection and severities reflect siro's own opinions; it is not
18
- affiliated with the upstream doc.
18
+ These approaches complement one another; a clean result does not guarantee safety.
19
+
20
+ ## Try it
21
+
22
+ Requires Node.js `^22.18.0` or `^24.0.0`. From your repository:
19
23
 
20
24
  ```sh
21
- npx @pho9ubenaa/siro lint # report best-practice violations in the current repo
22
- npx @pho9ubenaa/siro lint --reporter json # machine-readable remediation (see docs/json-output.md)
25
+ npx @pho9ubenaa/siro lint
23
26
  ```
24
27
 
25
- ## A concrete example
28
+ siro recursively discovers package.json and strict deno.json below cwd, independently of PM
29
+ workspace declarations. It checks local installation policy at cwd by default; add independent
30
+ projects with `--installation-root`. Exclude intentional fixtures with `--exclude test/fixtures`.
31
+ Discovery does not imply that every package's installation settings were inspected.
26
32
 
27
- For maintainers and CI owners, siro makes package-manager policy gaps visible during review. For example, change an unsafe npm script setting:
33
+ siro detects managers from `packageManager`, lockfiles, and configuration files. If it cannot
34
+ detect one, choose it explicitly, for example `npx @pho9ubenaa/siro lint --pm npm`.
35
+ The CLI may download code through `npx` and imports a repository's `siro.config.*` as executable
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).
28
39
 
29
- ```diff
30
- # .npmrc
31
- -ignore-scripts=false
32
- +ignore-scripts=true
33
- ```
40
+ ## Read findings and add CI
34
41
 
35
- Run `siro lint --pm npm --project-type application` before and after the edit. The `disable-lifecycle-scripts` error clears when this setting is enabled; other findings remain until addressed. Review required build scripts before changing their execution policy.
36
-
37
- The CLI reads `siro.config.*` as executable code. Review repository configuration before running it, particularly in CI. See the [threat model](docs/threat-model.md) and [security reporting policy](SECURITY.md).
38
-
39
- ## Features
40
-
41
- - **Rules across six managers.** 28 rules covering lifecycle scripts, version pinning, lockfiles
42
- (`commit`/`frozen`), release age, publish provenance, `files`/`publishConfig`, SSL enforcement,
43
- checksum verification, exotic subdependency blocking, audit suppression review, store integrity,
44
- Bun's security scanner API, and Yarn 4's hardened-mode — each mapped to the right setting per
45
- package manager (`.npmrc`, `pnpm-workspace.yaml`, `.yarnrc.yml`, `bunfig.toml`, `deno.json`,
46
- `aube-workspace.yaml`, `package.json`).
47
- - **PM-aware severities.** When a manager's documented default satisfies a rule across every
48
- supported version and target environment, the finding is demoted to `info`. Installed versions
49
- and CI conditions are not checked, so defaults that depend on either retain the rule's severity.
50
- - **Machine-readable remediation.** A finding can carry automatic key operations or manual
51
- instructions. Review proposed changes and rerun the linter after editing.
52
- See [docs/json-output.md](docs/json-output.md).
53
- - **Target PM versions.** Flag settings introduced after the declared or explicit stable PM
54
- version. See [checked settings and sources](docs/rules.md#checked-introduction-versions)
55
- for the npm, pnpm, Yarn, Bun, and Deno coverage.
56
- - **Workspace members.** `--workspaces` adds publication-metadata checks for declared npm,
57
- pnpm, Yarn, Bun, Deno, and Aube members, including public packages under a private root.
58
- See [workspace inspection](docs/configuration.md#workspace-members) for scope and exclusions.
59
- - **Lint with severities.** `error` fails CI by default; `--severity warn` tightens the gate.
60
- - **Reporters.** `pretty` (default), `json` for CI, `github` for PR annotations; register your own.
61
- - **Configurable.** Drop a `siro.config.ts` to disable rules, override severities, restrict PMs,
62
- or plug in custom rules and reporters.
63
-
64
- See the [rule reference](docs/rules.md) for what each check does and why, and the
65
- [comparison matrix](docs/comparison.md) for per-manager support at a glance.
66
-
67
- ## Versioning policy
68
-
69
- siro evaluates the recorded policy snapshot in [docs/policy-sources.md](docs/policy-sources.md).
70
- It detects package-manager names and reads exact stable targets from `packageManager`,
71
- `config.pmVersions`, or `--pm-version`. It does not inspect installed binaries. The
72
- `unsupported-settings` rule checks recorded introduction versions; unlisted settings and
73
- unknown targets are not evaluated for availability. A version-dependent safe-default annotation prevents an unverified
74
- severity downgrade; it does not prove that the current version satisfies the rule. See
75
- [configuration](docs/configuration.md) for defaults, applicability, and limits.
76
-
77
- ## Usage
42
+ Findings have `error`, `warn`, or `info` severity. Exit `0` means no findings at or above the
43
+ selected threshold; exit `1` means there are findings. Usage/configuration errors exit `2`
44
+ without completing the check; unexpected failures exit `70`. Errors fail CI by default. siro
45
+ suggests fixes but **does not edit files**: review changes and rerun the linter.
78
46
 
79
- ```
80
- siro <lint|check> [path] [options]
81
-
82
- --pm <npm|pnpm|yarn|bun|deno|aube> Target a specific package manager (auto-detected; required if detection finds nothing)
83
- --pm-version <x.y.z> Exact stable target version (requires --pm)
84
- --workspaces Also inspect workspace members' publication metadata
85
- --project-type <application|package> Select application or published-package policy (default auto)
86
- --reporter <pretty|json|github> Output format (default pretty)
87
- --severity <error|warn|info> Show and fail on findings at or above this level
88
- --json Shortcut for --reporter json
89
- --version, --help
90
- ```
91
-
92
- `check` is an alias of `lint` (same flags, same exit codes) — provided so `siro check` reads naturally in CI scripts.
47
+ For regular use, install it with `npm install --save-dev --save-exact @pho9ubenaa/siro`
48
+ and add a package script:
93
49
 
94
- Exit codes: `0` no findings at/above threshold · `1` findings at/above threshold · `2` usage or configuration error · `70` uncaught exception (a siro bug or a throwing reporter/custom rule).
50
+ ```json
51
+ {
52
+ "scripts": { "lint:security": "siro lint" }
53
+ }
54
+ ```
95
55
 
96
- ## How is this different from `npm audit` / `osv-scanner`?
56
+ After installing dependencies in CI, run `npm run lint:security`. A local install
57
+ makes `siro` available to package scripts, not to every shell or Git hook.
58
+ For exclusions and rule overrides, see the [configuration examples](docs/configuration.md).
97
59
 
98
- Different layer of the supply-chain pipeline; you want both.
60
+ ## Common CLI options
99
61
 
100
- | Tool | What it checks | Where the data comes from |
101
- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
102
- | `npm audit` · [osv-scanner](https://github.com/google/osv-scanner) · Snyk · Dependabot | **Known CVEs** in your installed dependency tree | GHSA / OSV.dev / vendor feeds |
103
- | `siro` | **Your install pipeline's configuration** — postinstall scripts, version ranges, lockfile policy, publish provenance, files allow-list, etc. | Static analysis of `.npmrc`, `pnpm-workspace.yaml`, `.yarnrc.yml`, `bunfig.toml`, `deno.json`, `aube-workspace.yaml`, `package.json` |
62
+ `check` is an alias for `lint`. Run `npx @pho9ubenaa/siro lint --help` for the complete CLI syntax.
104
63
 
105
- `npm audit` reports known vulnerabilities. `siro` reports supported configuration gaps that can increase exposure to malicious dependencies. Neither a clean result nor a cooldown window guarantees safety. Run both in CI.
64
+ | Option | Use |
65
+ | ----------------------------------------- | ----------------------------------------------------------------------------------------- |
66
+ | `--pm <npm\|pnpm\|yarn\|bun\|deno\|aube>` | Select one manager at cwd; additional installation roots retain their own targets. |
67
+ | `--pm-version <x.y.z>` | Supply an exact stable target version (requires `--pm`); it does not run an installed PM. |
68
+ | `--project-type <application\|package>` | Choose whether publication safeguards apply; omitted means infer from publish metadata. |
69
+ | `--exclude <pattern>` | Prune directories from recursive discovery (repeatable). |
70
+ | `--installation-root <path>` | Replace the default cwd installation scope (repeatable; include `.` to retain cwd). |
71
+ | `--severity <error\|warn\|info>` | Set both the display and CI failure threshold; default failure threshold is `error`. |
72
+ | `--reporter <pretty\|json\|github>` | Choose terminal, JSON, or GitHub Actions output; `--json` is a JSON shortcut. |
106
73
 
107
- ## Contributing
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.
108
78
 
109
- Adding a rule or a package manager is a localized change — see
110
- [docs/contributing.md](docs/contributing.md).
79
+ For a walkthrough and deeper reference, use these guides:
111
80
 
112
- If siro is useful to you, a [GitHub Star](https://github.com/pHo9UBenaA/siro) would be appreciated.
113
- Stars help me decide how much time to devote to future features and maintenance.
114
- Please share feedback from real-world use and feature requests in
115
- [GitHub Issues](https://github.com/pHo9UBenaA/siro/issues).
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.
82
+ - The [rule reference](docs/rules.md) and [PM comparison](docs/comparison.md) show what is checked for each manager.
83
+ - [JSON output](docs/json-output.md) documents the machine-readable remediation contract.
84
+ - [Contributing](docs/contributing.md) covers development setup, the source map, and verification.
116
85
 
117
86
  ## License
118
87
 
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as SUPPORTED_NODE_RANGE, D as isNodeError, M as UsageError, O as asAbsPath, S as isSupportedNodeVersion, T as assertDirectory, _ as isProjectType, a as DEFAULT_REPORTER_NAME, b as isPM, g as PROJECT_TYPES, i as BUILTIN_REPORTER_NAMES, j as SiroError, o as JSON_REPORTER_NAME, p as loadConfig, r as lintCommand, t as nodeIO, v as PMS, w as version, x as isSeverity, y as SEVERITIES } from "./node-io-BDHg4J2w.mjs";
2
+ import { C as isSeverity, D as asAbsPath, E as version, F as SiroError, I as UsageError, M as DEFAULT_SCAN_LIMITS, N as resolveScanLimits, 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-BpAHyeBG.mjs";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { parseArgs } from "node:util";
@@ -31,18 +31,23 @@ const COMMANDS = ["lint", "check"];
31
31
  const isCommandName = (value) => COMMANDS.some((cmd) => cmd === value);
32
32
  //#endregion
33
33
  //#region src/cli/parse-args.ts
34
+ const LIMIT_FLAGS = new Map(Object.keys(DEFAULT_SCAN_LIMITS).map((key) => [key.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`), key]));
35
+ const REPEATABLE_FLAGS = new Set(["exclude", "installation-root"]);
34
36
  const VALUE_FLAGS = new Set([
35
37
  "pm",
36
38
  "pm-version",
37
39
  "project-type",
38
40
  "reporter",
39
- "severity"
41
+ "severity",
42
+ ...REPEATABLE_FLAGS,
43
+ ...LIMIT_FLAGS.keys()
40
44
  ]);
41
45
  const BOOLEAN_FLAGS = new Set([
42
46
  "help",
43
47
  "version",
44
48
  "json",
45
- "workspaces"
49
+ "no-config",
50
+ "strict-filesystem"
46
51
  ]);
47
52
  const parseCommand = (argv) => {
48
53
  const { tokens } = parseArgs({
@@ -61,6 +66,7 @@ const parseCommand = (argv) => {
61
66
  tokens: true
62
67
  });
63
68
  const flags = /* @__PURE__ */ new Map();
69
+ const repeated = /* @__PURE__ */ new Map();
64
70
  const positionals = [];
65
71
  let error;
66
72
  for (let index = 0; index < tokens.length; index += 1) {
@@ -76,7 +82,11 @@ const parseCommand = (argv) => {
76
82
  }
77
83
  if (BOOLEAN_FLAGS.has(token.name)) if (token.value !== void 0) error ??= `Flag ${token.rawName} does not accept a value.`;
78
84
  else {
79
- if (token.name === "json" && flags.has("json")) error ??= "--json must be specified only once.";
85
+ if ([
86
+ "json",
87
+ "no-config",
88
+ "strict-filesystem"
89
+ ].includes(token.name) && flags.has(token.name)) error ??= `${token.rawName} must be specified only once.`;
80
90
  flags.set(token.name, true);
81
91
  }
82
92
  else if (VALUE_FLAGS.has(token.name)) {
@@ -87,11 +97,12 @@ const parseCommand = (argv) => {
87
97
  index += 1;
88
98
  }
89
99
  if (value === void 0 || value === "") error ??= `${token.rawName} requires a value.`;
100
+ else if (REPEATABLE_FLAGS.has(token.name)) repeated.set(token.name, [...repeated.get(token.name) ?? [], value]);
90
101
  else {
91
102
  if (flags.has(token.name)) error ??= `${token.rawName} must be specified only once.`;
92
103
  flags.set(token.name, value);
93
104
  }
94
- } else error ??= `Unknown flag: ${token.rawName}`;
105
+ } 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}`;
95
106
  }
96
107
  const [command, cwd, ...extra] = positionals;
97
108
  if (flags.has("help")) return {
@@ -113,14 +124,29 @@ const parseCommand = (argv) => {
113
124
  if (flags.has("reporter") && flags.has("json")) throw new UsageError("Invalid reporter selection: use either --reporter or --json.");
114
125
  const reporter = flags.get("reporter");
115
126
  const pmVersion = flags.get("pm-version");
127
+ const pm = parsePmFlag(flags.get("pm"));
128
+ if (pmVersion !== void 0 && (!pm || !isStableVersion(pmVersion))) throw new UsageError("--pm-version requires --pm and an exact stable version.");
129
+ const limits = {};
130
+ for (const [flag, key] of LIMIT_FLAGS) {
131
+ const value = flags.get(flag);
132
+ if (value !== void 0) {
133
+ if (typeof value !== "string" || !/^\d+$/u.test(value)) throw new UsageError(`${flag} must be a positive safe integer.`);
134
+ limits[key] = Number(value);
135
+ }
136
+ }
137
+ resolveScanLimits(limits);
116
138
  return {
117
139
  kind: "lint",
118
140
  cwd: asAbsPath(path.resolve(cwd ?? process.cwd())),
119
- pm: parsePmFlag(flags.get("pm")),
141
+ pm,
120
142
  pmVersion: typeof pmVersion === "string" ? pmVersion : void 0,
121
- workspaces: flags.has("workspaces") || void 0,
143
+ exclude: repeated.get("exclude"),
144
+ installationRoots: repeated.get("installation-root"),
122
145
  projectType: parseProjectTypeFlag(flags.get("project-type")),
123
146
  severity: parseSeverityFlag(flags.get("severity")),
147
+ ...flags.has("no-config") ? { noConfig: true } : {},
148
+ ...flags.has("strict-filesystem") ? { rejectSymlinks: true } : {},
149
+ ...Object.keys(limits).length ? { limits } : {},
124
150
  reporter: typeof reporter === "string" ? reporter : flags.has("json") ? JSON_REPORTER_NAME : DEFAULT_REPORTER_NAME
125
151
  };
126
152
  };
@@ -132,9 +158,11 @@ const PROJECT_TYPES_LIST = PROJECT_TYPES.join("|");
132
158
  const SEVERITIES_LIST = SEVERITIES.join("|");
133
159
  const FLAG_LINES = {
134
160
  json: " --json Shortcut for --reporter json",
161
+ safety: " --no-config Do not probe or execute repository siro.config.*\n --strict-filesystem Reject symlink input paths (not a containment sandbox)",
162
+ limits: " --max-file-bytes <n> Bound each input file (default 8388608)\n --max-total-bytes <n> Bound all input reads (default 67108864)\n --max-entries <n> Bound native directory entries (default 100000)\n --max-directories <n> Bound discovered directories (default 10000)\n --max-directory-depth <n> Bound discovery depth (default 128)\n --max-config-depth <n> Bound configuration nesting (default 128)\n --max-findings <n> Bound unfiltered findings (default 50000)\n --max-output-bytes <n> Bound report output (default 33554432)",
135
163
  pm: ` --pm <name> Target a specific package manager (${PMS_LIST})`,
136
164
  pmVersion: " --pm-version <x.y.z> Target an exact stable PM version (requires --pm)",
137
- workspaces: " --workspaces Also check workspace members' publication metadata",
165
+ inspection: " --exclude <pattern> Exclude directories from recursive discovery (repeatable)\n --installation-root <path> Inspect local install policy here (repeatable; default .)",
138
166
  projectType: ` --project-type <type> Project type (${PROJECT_TYPES_LIST}; default auto)`,
139
167
  reporter: ` --reporter <name> Reporter (${REPORTERS_LIST}; additional reporters can be registered via siro.config.ts)`,
140
168
  severity: ` --severity <level> Show + fail on findings at or above this level (${SEVERITIES_LIST})`
@@ -151,18 +179,21 @@ const HELP_ROOT = [
151
179
  "GLOBAL FLAGS",
152
180
  FLAG_LINES.pm,
153
181
  FLAG_LINES.pmVersion,
154
- FLAG_LINES.workspaces,
182
+ FLAG_LINES.inspection,
155
183
  FLAG_LINES.projectType,
156
184
  " --version Print the siro version",
157
185
  " --help Show help for siro or a command",
158
186
  "",
159
187
  "LINT FLAGS",
188
+ FLAG_LINES.safety,
189
+ FLAG_LINES.limits,
160
190
  FLAG_LINES.reporter,
161
191
  FLAG_LINES.json,
162
192
  FLAG_LINES.severity,
163
193
  "",
164
194
  "EXAMPLES",
165
195
  " $ siro lint # report violations in cwd",
196
+ " $ siro lint --no-config --strict-filesystem # data-only inspection",
166
197
  " $ siro lint --reporter github # GitHub Actions annotations",
167
198
  " $ siro lint --severity warn # also fail on warnings",
168
199
  "",
@@ -179,9 +210,11 @@ const HELP_LINT = [
179
210
  " siro check [path] [flags] (alias)",
180
211
  "",
181
212
  "FLAGS",
213
+ FLAG_LINES.safety,
214
+ FLAG_LINES.limits,
182
215
  FLAG_LINES.pm,
183
216
  FLAG_LINES.pmVersion,
184
- FLAG_LINES.workspaces,
217
+ FLAG_LINES.inspection,
185
218
  FLAG_LINES.projectType,
186
219
  FLAG_LINES.reporter,
187
220
  FLAG_LINES.json,
@@ -191,10 +224,11 @@ const HELP_LINT = [
191
224
  " 0 No findings at or above the active threshold",
192
225
  " 1 Findings at or above the threshold (default: error)",
193
226
  " 2 Usage error (bad flag, broken siro.config.ts, unreadable path, …)",
194
- " 70 Uncaught exception (a siro bug, or a reporter / custom rule that threw)",
227
+ " 70 Output failure/limit, or unexpected exception (including trusted extensions)",
195
228
  "",
196
229
  "EXAMPLES",
197
230
  " $ siro lint # default: pretty reporter, fail on errors",
231
+ " $ siro lint --no-config --strict-filesystem # data-only inspection",
198
232
  " $ siro lint --reporter github # GitHub Actions annotations",
199
233
  " $ siro lint --severity warn # tighten the gate"
200
234
  ].join("\n");
@@ -208,34 +242,38 @@ const renderHelp = (target) => {
208
242
  const EXIT_SUCCESS = 0;
209
243
  const EXIT_USAGE = 2;
210
244
  const EXIT_CRASH = 70;
211
- const dispatch = (cmd, io) => {
245
+ const dispatch = async (cmd, io) => {
212
246
  switch (cmd.kind) {
213
247
  case "version":
214
- io.stdout(version);
248
+ await io.stdout(version);
215
249
  return EXIT_SUCCESS;
216
250
  case "help":
217
- io.stdout(renderHelp(cmd.target));
251
+ await io.stdout(renderHelp(cmd.target));
218
252
  return EXIT_SUCCESS;
219
253
  case "usage":
220
- if (cmd.reason) io.stderr(`${cmd.reason}\n`);
221
- io.stderr(renderHelp());
254
+ if (cmd.reason) await io.stderr(`${safeText(cmd.reason)}\n`);
255
+ await io.stderr(renderHelp());
222
256
  return EXIT_USAGE;
223
- case "lint":
257
+ case "lint": {
224
258
  assertDirectory(cmd.cwd);
225
- return loadConfig(cmd.cwd).then((config) => lintCommand({
226
- ...cmd,
259
+ const { noConfig, ...options } = cmd;
260
+ if (noConfig) return lintCommand(options, io);
261
+ const config = await loadConfig(cmd.cwd);
262
+ return lintCommand({
263
+ ...options,
227
264
  config
228
- }, io));
265
+ }, io);
266
+ }
229
267
  default: throw new Error(`Unhandled command kind: ${String(cmd)}`);
230
268
  }
231
269
  };
232
- const handleError = (error, io) => {
270
+ const handleError = async (error, io) => {
233
271
  if (error instanceof SiroError) {
234
- io.stderr(error.message);
272
+ await io.stderr(safeText(error.message));
235
273
  return error.exitCode;
236
274
  }
237
275
  if (isNodeError(error) && "errno" in error && typeof error.errno === "number") {
238
- io.stderr(`File system error: ${error.message}`);
276
+ await io.stderr(safeText(`File system error: ${error.message}`));
239
277
  return EXIT_USAGE;
240
278
  }
241
279
  throw error;
@@ -253,8 +291,10 @@ const runMain = async (argv) => {
253
291
  process.exitCode = await run(argv);
254
292
  } catch (error) {
255
293
  const errStr = error instanceof Error ? error.stack ?? error.message : String(error);
256
- process.stderr.write(`${errStr}\n`);
257
294
  process.exitCode = EXIT_CRASH;
295
+ try {
296
+ await nodeIO.stderr(safeText(errStr));
297
+ } catch {}
258
298
  }
259
299
  };
260
300
  const [, invokedPath] = process.argv;