@pho9ubenaa/siro 0.5.1 → 0.6.0

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,67 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.0] — 2026-09-29
4
+
5
+ ### Breaking changes and inspection scope
6
+
7
+ - Discover package.json and strict deno.json recursively by default, independently
8
+ of PM workspace declarations. Remove `--workspaces` / API `workspaces`, including
9
+ false; legacy calls receive migration errors. PM exclusions no longer hide
10
+ fixtures, vendor or dist. Use `exclude: ['test/fixtures', 'vendor', 'dist']`, or
11
+ `exclude: ['**']` to keep only cwd.
12
+ - Add common exclusions and explicit `installationRoots` (default `['.']`). Arrays
13
+ replace config values; `[]` disables installation checks, not publication checks.
14
+ Example: `siro lint . --installation-root . --installation-root tools/standalone`.
15
+ Additional independent installation projects are not automatically inferred.
16
+ - Resolve PM/version locally: root options stay at cwd, additional roots use their
17
+ entry/local detection, and other packages use manifest-local evidence. Unknown
18
+ PMs still receive generic publication checks; unknown availability is not safety.
19
+ - JSON schema 3 adds `inspection`, required finding `directory`, and optional `pm`.
20
+ Generic publication findings run once per manifest without a synthetic PM.
21
+ - Require injected `FileSystem.readDirectories`; remove `resolveDirectory`. Skip
22
+ directory symlinks, `.git`, node_modules and explicit exclusions before reads.
23
+ No implicit vendor/dist/fixture exclusions or native filesystem fallback.
24
+ - `requireConfigKey.defaultSafety` explicitly controls safe-default downgrades;
25
+ omitted safety is conservative. VersionNote has no policy effect.
26
+ - Reporter calls require scan cwd as a third argument:
27
+ `await reporter.format(result, io, { cwd })`. Await built-in reporters and direct
28
+ IO writes, which may now complete asynchronously; handle output rejections.
29
+ Two-argument custom reporter implementations can ignore the additional context.
30
+
31
+ ### Fixes
32
+
33
+ - Validate consumed Deno publication metadata before applicability and check exact
34
+ registry pins in both inline imports and scopes. Reject unrepresentable npmrc age
35
+ cutoffs rather than treating any positive integer as protection.
36
+ - Respect known pre-12 npm targets when checking npm-shrinkwrap.json; explain removed
37
+ or unknown-version lockfiles instead of incorrectly reporting that no file exists.
38
+ - GitHub annotations reference the correct absolute file using the supplied scan cwd.
39
+ API/JSON paths and schema 3 retain their existing meaning.
40
+ - Escape untrusted display controls and workflow markers without changing API paths
41
+ or parsed JSON values. Observe actual stream writes and preserve exit 70 on output
42
+ failure. IO may complete asynchronously; synchronous return values remain ignored.
43
+ - Reject Promise/thenable config exports and check results without an unhandled
44
+ rejection overriding the configuration-error exit. Keep lint synchronous.
45
+ - Deno empty/age-null/exclude-only objects no longer falsely satisfy release age.
46
+ Valid omitted ages use active local npmrc fallback, retaining zero opt-out and
47
+ explicit-age precedence. Leaf remedies preserve valid exclusions.
48
+ - npm own publishConfig.provenance overrides npmrc, including false. Validate its
49
+ consumed boolean type; align finding/remedy with the responsible file and keep
50
+ both locations' old-target availability guards.
51
+ - Preserve legal POSIX backslash and colon directory names without confusing them
52
+ with separators or portable user input. Reject malformed/traversing adapter names.
53
+ - Make every remediation operation path relative to scan cwd, including multi-file
54
+ remedies. Findings without a responsible file remain file-less.
55
+ - Fix Windows package verification for paths with spaces and shell metacharacters.
56
+ - Make one-shot and locally installed usage explicit, restore rule configuration
57
+ examples, and update moved documentation anchors in findings and the rule reference.
58
+
59
+ ### Inspection limits
60
+
61
+ Installation checks use local settings, not inherited effective policy. Manifest-only
62
+ children have no provenance-policy checks, and child executable configs are not loaded.
63
+ See [scope and migration](docs/configuration.md) and [schema 3](docs/json-output.md).
64
+
3
65
  ## [0.5.1]
4
66
 
5
67
  ### Refactoring
package/README.md CHANGED
@@ -4,115 +4,78 @@
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
19
 
20
- ```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)
23
- ```
20
+ ## Try it
24
21
 
25
- ## A concrete example
22
+ Requires Node.js `^22.18.0` or `^24.0.0`. From your repository:
26
23
 
27
- For maintainers and CI owners, siro makes package-manager policy gaps visible during review. For example, change an unsafe npm script setting:
28
-
29
- ```diff
30
- # .npmrc
31
- -ignore-scripts=false
32
- +ignore-scripts=true
24
+ ```sh
25
+ npx @pho9ubenaa/siro lint
33
26
  ```
34
27
 
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
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.
78
32
 
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
- ```
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. Review configurations before running it on an unfamiliar project; see the
37
+ [threat model](docs/threat-model.md).
91
38
 
92
- `check` is an alias of `lint` (same flags, same exit codes) — provided so `siro check` reads naturally in CI scripts.
39
+ ## Read findings and add CI
93
40
 
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).
41
+ Findings have `error`, `warn`, or `info` severity. Exit `0` means no findings at or above the
42
+ selected threshold; exit `1` means there are findings. Usage/configuration errors exit `2`
43
+ without completing the check; unexpected failures exit `70`. Errors fail CI by default. siro
44
+ suggests fixes but **does not edit files**: review changes and rerun the linter.
95
45
 
96
- ## How is this different from `npm audit` / `osv-scanner`?
46
+ For regular use, install it with `npm install --save-dev --save-exact @pho9ubenaa/siro`
47
+ and add a package script:
48
+
49
+ ```json
50
+ {
51
+ "scripts": { "lint:security": "siro lint" }
52
+ }
53
+ ```
97
54
 
98
- Different layer of the supply-chain pipeline; you want both.
55
+ After installing dependencies in CI, run `npm run lint:security`. A local install
56
+ makes `siro` available to package scripts, not to every shell or Git hook.
57
+ For exclusions and rule overrides, see the [configuration examples](docs/configuration.md).
99
58
 
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` |
59
+ ## Common CLI options
104
60
 
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.
61
+ `check` is an alias for `lint`. Run `npx @pho9ubenaa/siro lint --help` for the complete CLI syntax.
106
62
 
107
- ## Contributing
63
+ | Option | Use |
64
+ | ----------------------------------------- | ----------------------------------------------------------------------------------------- |
65
+ | `--pm <npm\|pnpm\|yarn\|bun\|deno\|aube>` | Select one manager at cwd; additional installation roots retain their own targets. |
66
+ | `--pm-version <x.y.z>` | Supply an exact stable target version (requires `--pm`); it does not run an installed PM. |
67
+ | `--project-type <application\|package>` | Choose whether publication safeguards apply; omitted means infer from publish metadata. |
68
+ | `--exclude <pattern>` | Prune directories from recursive discovery (repeatable). |
69
+ | `--installation-root <path>` | Replace the default cwd installation scope (repeatable; include `.` to retain cwd). |
70
+ | `--severity <error\|warn\|info>` | Set both the display and CI failure threshold; default failure threshold is `error`. |
71
+ | `--reporter <pretty\|json\|github>` | Choose terminal, JSON, or GitHub Actions output; `--json` is a JSON shortcut. |
108
72
 
109
- Adding a rule or a package manager is a localized change — see
110
- [docs/contributing.md](docs/contributing.md).
73
+ For a walkthrough and deeper reference, use these guides:
111
74
 
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).
75
+ - [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.
76
+ - The [rule reference](docs/rules.md) and [PM comparison](docs/comparison.md) show what is checked for each manager.
77
+ - [JSON output](docs/json-output.md) documents the machine-readable remediation contract.
78
+ - [Contributing](docs/contributing.md) covers development setup, the source map, and verification.
116
79
 
117
80
  ## License
118
81
 
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 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
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { parseArgs } from "node:util";
@@ -31,18 +31,19 @@ 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 REPEATABLE_FLAGS = new Set(["exclude", "installation-root"]);
34
35
  const VALUE_FLAGS = new Set([
35
36
  "pm",
36
37
  "pm-version",
37
38
  "project-type",
38
39
  "reporter",
39
- "severity"
40
+ "severity",
41
+ ...REPEATABLE_FLAGS
40
42
  ]);
41
43
  const BOOLEAN_FLAGS = new Set([
42
44
  "help",
43
45
  "version",
44
- "json",
45
- "workspaces"
46
+ "json"
46
47
  ]);
47
48
  const parseCommand = (argv) => {
48
49
  const { tokens } = parseArgs({
@@ -61,6 +62,7 @@ const parseCommand = (argv) => {
61
62
  tokens: true
62
63
  });
63
64
  const flags = /* @__PURE__ */ new Map();
65
+ const repeated = /* @__PURE__ */ new Map();
64
66
  const positionals = [];
65
67
  let error;
66
68
  for (let index = 0; index < tokens.length; index += 1) {
@@ -87,11 +89,12 @@ const parseCommand = (argv) => {
87
89
  index += 1;
88
90
  }
89
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]);
90
93
  else {
91
94
  if (flags.has(token.name)) error ??= `${token.rawName} must be specified only once.`;
92
95
  flags.set(token.name, value);
93
96
  }
94
- } else error ??= `Unknown flag: ${token.rawName}`;
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}`;
95
98
  }
96
99
  const [command, cwd, ...extra] = positionals;
97
100
  if (flags.has("help")) return {
@@ -118,7 +121,8 @@ const parseCommand = (argv) => {
118
121
  cwd: asAbsPath(path.resolve(cwd ?? process.cwd())),
119
122
  pm: parsePmFlag(flags.get("pm")),
120
123
  pmVersion: typeof pmVersion === "string" ? pmVersion : void 0,
121
- workspaces: flags.has("workspaces") || void 0,
124
+ exclude: repeated.get("exclude"),
125
+ installationRoots: repeated.get("installation-root"),
122
126
  projectType: parseProjectTypeFlag(flags.get("project-type")),
123
127
  severity: parseSeverityFlag(flags.get("severity")),
124
128
  reporter: typeof reporter === "string" ? reporter : flags.has("json") ? JSON_REPORTER_NAME : DEFAULT_REPORTER_NAME
@@ -134,7 +138,7 @@ const FLAG_LINES = {
134
138
  json: " --json Shortcut for --reporter json",
135
139
  pm: ` --pm <name> Target a specific package manager (${PMS_LIST})`,
136
140
  pmVersion: " --pm-version <x.y.z> Target an exact stable PM version (requires --pm)",
137
- workspaces: " --workspaces Also check workspace members' publication metadata",
141
+ inspection: " --exclude <pattern> Exclude directories from recursive discovery (repeatable)\n --installation-root <path> Inspect local install policy here (repeatable; default .)",
138
142
  projectType: ` --project-type <type> Project type (${PROJECT_TYPES_LIST}; default auto)`,
139
143
  reporter: ` --reporter <name> Reporter (${REPORTERS_LIST}; additional reporters can be registered via siro.config.ts)`,
140
144
  severity: ` --severity <level> Show + fail on findings at or above this level (${SEVERITIES_LIST})`
@@ -151,7 +155,7 @@ const HELP_ROOT = [
151
155
  "GLOBAL FLAGS",
152
156
  FLAG_LINES.pm,
153
157
  FLAG_LINES.pmVersion,
154
- FLAG_LINES.workspaces,
158
+ FLAG_LINES.inspection,
155
159
  FLAG_LINES.projectType,
156
160
  " --version Print the siro version",
157
161
  " --help Show help for siro or a command",
@@ -181,7 +185,7 @@ const HELP_LINT = [
181
185
  "FLAGS",
182
186
  FLAG_LINES.pm,
183
187
  FLAG_LINES.pmVersion,
184
- FLAG_LINES.workspaces,
188
+ FLAG_LINES.inspection,
185
189
  FLAG_LINES.projectType,
186
190
  FLAG_LINES.reporter,
187
191
  FLAG_LINES.json,
@@ -208,17 +212,17 @@ const renderHelp = (target) => {
208
212
  const EXIT_SUCCESS = 0;
209
213
  const EXIT_USAGE = 2;
210
214
  const EXIT_CRASH = 70;
211
- const dispatch = (cmd, io) => {
215
+ const dispatch = async (cmd, io) => {
212
216
  switch (cmd.kind) {
213
217
  case "version":
214
- io.stdout(version);
218
+ await io.stdout(version);
215
219
  return EXIT_SUCCESS;
216
220
  case "help":
217
- io.stdout(renderHelp(cmd.target));
221
+ await io.stdout(renderHelp(cmd.target));
218
222
  return EXIT_SUCCESS;
219
223
  case "usage":
220
- if (cmd.reason) io.stderr(`${cmd.reason}\n`);
221
- io.stderr(renderHelp());
224
+ if (cmd.reason) await io.stderr(`${safeText(cmd.reason)}\n`);
225
+ await io.stderr(renderHelp());
222
226
  return EXIT_USAGE;
223
227
  case "lint":
224
228
  assertDirectory(cmd.cwd);
@@ -229,13 +233,13 @@ const dispatch = (cmd, io) => {
229
233
  default: throw new Error(`Unhandled command kind: ${String(cmd)}`);
230
234
  }
231
235
  };
232
- const handleError = (error, io) => {
236
+ const handleError = async (error, io) => {
233
237
  if (error instanceof SiroError) {
234
- io.stderr(error.message);
238
+ await io.stderr(safeText(error.message));
235
239
  return error.exitCode;
236
240
  }
237
241
  if (isNodeError(error) && "errno" in error && typeof error.errno === "number") {
238
- io.stderr(`File system error: ${error.message}`);
242
+ await io.stderr(safeText(`File system error: ${error.message}`));
239
243
  return EXIT_USAGE;
240
244
  }
241
245
  throw error;
@@ -253,8 +257,10 @@ const runMain = async (argv) => {
253
257
  process.exitCode = await run(argv);
254
258
  } catch (error) {
255
259
  const errStr = error instanceof Error ? error.stack ?? error.message : String(error);
256
- process.stderr.write(`${errStr}\n`);
257
260
  process.exitCode = EXIT_CRASH;
261
+ try {
262
+ await nodeIO.stderr(safeText(errStr));
263
+ } catch {}
258
264
  }
259
265
  };
260
266
  const [, invokedPath] = process.argv;