@pho9ubenaa/siro 0.1.0-rc

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 ADDED
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0]
4
+
5
+ Initial public release. 27 rules across 6 package managers (npm, pnpm, yarn, bun, deno, aube).
6
+
7
+ ### Features
8
+
9
+ - **27 security rules** covering lifecycle scripts, version pinning, lockfiles (`commit`/`frozen`), release age, publish provenance, `files`/`publishConfig`, SSL enforcement, checksum verification, exotic subdependency blocking, audit suppression review, store integrity, and Bun's security scanner API.
10
+ - **6 package managers**: npm, pnpm, yarn, bun, deno, aube — auto-detected from `packageManager` field, lockfiles, and config files.
11
+ - **PM-aware severities**: findings demoted to `info` when the PM's documented default already satisfies the rule.
12
+ - **Machine-readable JSON output** with `fix` operations and `manualSteps` for automated remediation.
13
+ - **Reporters**: `pretty` (default), `json` (CI), `github` (PR annotations); extensible via custom reporters.
14
+ - **Configuration file** (`siro.config.ts`): disable rules, override severities, restrict PMs, plug in custom rules and reporters.
15
+ - **`--severity` flag**: gate CI on `error` (default) or `warn` for stricter checks.
16
+ - **`--pm` flag**: target a specific package manager.
17
+ - **`--json` flag**: shortcut for `--reporter json`.
18
+
19
+ ### Rules added
20
+
21
+ - `advisory-check` — aube `security.advisoryCheck`
22
+ - `approved-git-repos` — yarn `npmScopes.*.approvedGitRepos`
23
+ - `audit-suppression` — yarn `npmAuditExclude`
24
+ - `block-auto-install` — pnpm `autoInstallPeers`, aube `peerDependencies.autoInstall`
25
+ - `block-exotic-subdeps` — aube `blockExoticSubdeps`
26
+ - `bun-security-scanner` — bun `install.security`
27
+ - `checksum-verification` — pnpm `verifyStoreIntegrity`, aube `checksumVerification`
28
+ - `commit-lockfile` — npm `lockfileVersion` (lockfile committed to git)
29
+ - `dependency-overrides` — pnpm `pnpm.overrides`, aube `overrides`
30
+ - `disable-lifecycle-scripts` — npm `ignore-scripts`, pnpm `strictDepBuilds`, yarn `enableScripts`, bun `install.ignoreScripts`, deno `deno.json#/tasks`, aube `jailBuilds`
31
+ - `enforce-strict-ssl` — npm/yarn/pnpm/bun/aube `strict-ssl`
32
+ - `files-field` — npm `files` in package.json
33
+ - `frozen-lockfile` — npm `ci` enforcement, pnpm `frozen-lockfile`, yarn `freezeLockfile`, aube `preferFrozenLockfile`
34
+ - `frozen-store` — pnpm `store.frozen`
35
+ - `hardened-mode` — yarn `enableHardenedMode`
36
+ - `minimum-release-age` — pnpm `fetchRetries`, aube `minimumReleaseAge`
37
+ - `named-registries` — pnpm `registries.*.registry`
38
+ - `paranoid-mode` — aube `paranoid`
39
+ - `patched-dependencies` — pnpm `patchedDependencies`
40
+ - `pin-exact-versions` — npm `save-exact`, pnpm `save-exact`, deno `deno.json#/imports`
41
+ - `provenance` — npm/pnpm `publishConfig.provenance`
42
+ - `publish-access` — npm/pnpm `publishConfig.access`
43
+ - `publishable` — package.json `private`
44
+ - `store-server` — pnpm `store.server`
45
+ - `strict-allow-scripts` — aube `strictAllowScripts`
46
+ - `strict-release-age` — aube `strictReleaseAge`
47
+ - `strict-store-integrity` — pnpm `store.integrity`
48
+ - `trust-policy` — aube `trustPolicy`
49
+
50
+ ### Tooling
51
+
52
+ - CI via GitHub Actions (Node 20, 22, 24 matrix)
53
+ - Pre-commit hooks: lint (oxlint), format (oxfmt), typecheck, related tests
54
+ - Pre-push hooks: full lint, typecheck, test coverage
55
+ - Commit message linting via commitlint (conventional commits)
56
+ - Dead code detection via knip
57
+ - Rule scaffold generator (`pnpm gen:rule`)
58
+ - Documentation generators (`pnpm gen:rules`, `pnpm gen:comparison`)
59
+ - Version module auto-generation from package.json
60
+ - Layering enforcement script
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-Present pHo9UBenaA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # siro
2
+
3
+ [![CI](https://img.shields.io/github/actions/workflow/status/pHo9UBenaA/siro/ci.yaml?branch=main)](https://github.com/pHo9UBenaA/siro/actions)
4
+ [![npm](https://img.shields.io/npm/v/@pho9ubenaa/siro)](https://www.npmjs.com/package/@pho9ubenaa/siro)
5
+ [![license](https://img.shields.io/github/license/pHo9UBenaA/siro)](https://github.com/pHo9UBenaA/siro/blob/main/LICENSE)
6
+
7
+ > Security best-practices linter for the npm ecosystem — npm, pnpm, yarn, bun, deno, aube.
8
+
9
+ [Getting started](docs/getting-started.md) ·
10
+ [Rules](docs/rules.md) ·
11
+ [Comparison](docs/comparison.md) ·
12
+ [Configuration](docs/configuration.md)
13
+
14
+ `siro` **lints** repos for supply-chain risks, graded `error` / `warn` / `info`,
15
+ and emits machine-readable remediation so an editor or agent skill
16
+ can apply the fixes.
17
+
18
+ ```sh
19
+ npx @pho9ubenaa/siro lint
20
+ npx @pho9ubenaa/siro lint --reporter json # machine-readable output with fix ops (see docs/json-output.md)
21
+ ```
22
+
23
+ ## Features
24
+
25
+ - **27 rules across 6 managers.** Covering lifecycle scripts, version pinning, lockfiles
26
+ (`commit`/`frozen`), release age, publish provenance, `files`/`publishConfig`, SSL enforcement,
27
+ checksum verification, exotic subdependency blocking, audit suppression review, store integrity,
28
+ Bun's security scanner API, and Yarn 4's hardened-mode — each mapped to the right setting per
29
+ package manager (`.npmrc`, `pnpm-workspace.yaml`, `.yarnrc.yml`, `bunfig.toml`, `deno.json`,
30
+ `aube-workspace.yaml`, `package.json`).
31
+ - **PM-aware severities.** When a manager's documented default already satisfies a rule (e.g.
32
+ Yarn's `enableScripts: false`, aube's `preferFrozenLockfile: true`), the finding is demoted
33
+ to `info` so CI noise stays proportional to real risk.
34
+ - **Machine-readable remediation.** Every finding carries `fix` operations and `manualSteps` in the
35
+ JSON output — hand it to an agent skill or editor plugin that edits the files and re-runs
36
+ `siro lint` until it exits `0`. See [JSON output](docs/json-output.md).
37
+ - **Lint with severities.** `error` fails CI by default; `--severity warn` tightens the gate.
38
+ - **Reporters.** `pretty` (default), `json` for CI, `github` for PR annotations; register your own.
39
+ - **Configurable.** Drop a `siro.config.ts` to disable rules, override severities, restrict PMs,
40
+ or plug in custom rules and reporters.
41
+
42
+ See the [rule reference](docs/rules.md) for what each check does and why, and the
43
+ [comparison matrix](docs/comparison.md) for per-manager support at a glance.
44
+
45
+ ## Versioning policy
46
+
47
+ `siro` is **version-agnostic**: it never inspects the actual version of npm / pnpm / yarn / bun /
48
+ deno your project is running. Every rule is written against the **latest stable major** of each
49
+ manager — that's the configuration surface we lint and the defaults we trust.
50
+
51
+ When a rule's binding knows that a key was introduced or had its default tightened in a specific
52
+ version, the finding's message carries a `(available since pnpm 10.16.0; default safe since pnpm 11.0.0 (1440 minutes))`
53
+ suffix as documentation — but siro does not branch on it. If your project pins an older PM, the
54
+ right move is usually to upgrade; the lint output stays the same either way.
55
+
56
+ See [version matrix](docs/version-matrix.md) for the per-rule × per-PM version table
57
+ that backs those suffixes.
58
+
59
+ ## Usage
60
+
61
+ ```
62
+ siro <lint|check> [path] [options]
63
+
64
+ --pm <npm|pnpm|yarn|bun|deno|aube> Target a specific package manager (auto-detected; required if detection finds nothing)
65
+ --reporter <pretty|json|github> Output format (default pretty)
66
+ --severity <error|warn|info> Show and fail on findings at or above this level
67
+ --json Shortcut for --reporter json
68
+ --version, --help
69
+ ```
70
+
71
+ `check` is an alias of `lint` (same flags, same exit codes) — provided so `siro check` reads naturally in CI scripts.
72
+
73
+ Exit codes: `0` clean · `1` findings at/above threshold · `2` usage error.
74
+
75
+ ## How is this different from `npm audit` / `osv-scanner`?
76
+
77
+ Different layer of the supply-chain pipeline; you want both.
78
+
79
+ | Tool | What it checks | Where the data comes from |
80
+ | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
81
+ | `npm audit` · osv-scanner · Snyk · Dependabot | **Known CVEs** in your installed dependency tree | GHSA / OSV.dev / vendor feeds |
82
+ | `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` |
83
+
84
+ `npm audit` tells you "this dependency has a known vulnerability". `siro` tells you "even if a brand-new vulnerability lands tomorrow, your install settings can't trust it without review". Run both in CI.
85
+
86
+ ## Contributing
87
+
88
+ Adding a rule or a package manager is a localized change — see
89
+ [contributing guide](docs/contributing.md).
90
+
91
+ ## License
92
+
93
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { run } from './cli.mjs';
3
+
4
+ const code = await run(process.argv.slice(2));
5
+ process.exitCode = code;
package/dist/cli.mjs ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import{C as e,E as t,O as n,S as r,k as i,m as a,n as o,p as s,t as c,v as l,w as u,x as d}from"./node-io-C_ERSDGJ.mjs";import ee from"node:path";import{cac as f}from"cac";import{pathToFileURL as p}from"node:url";const m=[`lint`,`check`],h=e=>m.some(t=>t===e),g=new Set(Object.entries({json:{takesValue:!1},pm:{takesValue:!0},reporter:{takesValue:!0},severity:{takesValue:!0}}).filter(([,e])=>e.takesValue).map(([e])=>e)),_=[`pm`,`reporter`,`json`,`severity`],te={check:_,lint:_},ne=[`version`,`help`],v=[`v`,`h`,`--`],y=e=>new Set([...te[e],...ne,...v]),b=(()=>{let e=[];for(let t of m)for(let n of y(t))e.push(n);return new Set(e)})(),x=(e,t)=>e.startsWith(`--`)&&!e.includes(`=`)&&g.has(e.slice(2))&&t!==void 0&&!t.startsWith(`-`),S=e=>e===`--help`||e===`-h`,C=(e,t,n)=>e===`--`?`break`:(S(e)?n.asksHelp=!0:x(e,t)?n.index+=1:!e.startsWith(`-`)&&n.first===``&&(n.first=e),`continue`),w=e=>{let t={asksHelp:!1,first:``,index:0};for(;t.index<e.length;t.index+=1){let n=e[t.index];if(typeof n==`string`&&C(n,e[t.index+1],t)===`break`)break}return{asksHelp:t.asksHelp,first:t.first||[].pop()}},T=e=>{if(e!==void 0&&h(e))return e},E=e=>{let{asksHelp:t,first:n}=w(e);return t?{target:T(n)}:[].pop()},D=e=>{for(let t=0;t<e.length;t+=1){let n=e[t];if(typeof n==`string`){if(n===`--`)return!1;if(n===`--version`||n===`-v`)return!0;x(n,e[t+1])&&(t+=1)}}return!1},O=(e,n,r)=>{for(let i of Object.keys(e))if(!n.has(i)){let e=``;throw r&&(e=` for '${r}'`),new t(`Unknown flag${e}: --${i}`)}},k=n=>{if(n!==void 0){if(n===!0)throw new t(`--pm requires a value (expected one of: ${d.join(`, `)})`);if(typeof n!=`string`||!e(n))throw new t(`Unknown package manager: ${n} (expected one of: ${d.join(`, `)})`);return n}},A=e=>{if(e!==void 0){if(e===!0)throw new t(`--severity requires a value (expected ${r.join(`|`)})`);if(typeof e!=`string`||!u(e))throw new t(`Invalid severity: ${String(e)} (expected ${r.join(`|`)})`);return e}},j=e=>{if(e.reporter===!0)throw new t(`--reporter requires a value (expected one of: ${a.join(`, `)}, or a reporter registered via siro.config.ts)`);if(typeof e.reporter==`string`){if(e.json===!0)throw new t(`Use either --reporter or --json, not both.`);return e.reporter}return e.json===!0?`json`:`pretty`},M=e=>{let n=Number(e.replace(/^v/u,``).split(`.`)[0]);if(Number.isFinite(n)&&n<20)throw new t(`Node 20+ required (you are on ${e}). Upgrade your runtime to use siro.`)},N=()=>{let e=f(`siro`);e.option(`--pm <name>`,`Target a specific package manager`).option(`--reporter <name>`,`Reporter (${a.join(`|`)})`).option(`--json`,`Shortcut for --reporter json`).option(`--severity <level>`,`Display/fail threshold (${r.join(`|`)})`).help().version(l);for(let t of m){let n=`${t} a repository`;t===`check`&&(n=`Alias of lint`),e.command(`${t} [cwd]`,n)}return e},P=(e,t)=>e?t[0]:t[1],F=(e,t)=>e||t[0],I=(e,t)=>e?t.slice(1):t.slice(2),L=e=>{let t=N(),n=t.parse([`node`,`siro`,...e],{run:!1}),r=n.options,i=t.matchedCommandName,a=P(i,n.args);return{commandCandidate:F(i,n.args),extraPositionals:I(i,n.args),flags:r,matchedCommand:i,positionalCwd:a}},re=d.join(`|`),R=a.join(`|`),z=r.join(`|`),B={json:` --json Shortcut for --reporter json`,pm:` --pm <name> Target a specific package manager (${re})`,reporter:` --reporter <name> Reporter (${R}; additional reporters can be registered via siro.config.ts)`,severity:` --severity <level> Show + fail on findings at or above this level (${z})`},V=[`siro — security best-practices for the npm ecosystem`,``,`USAGE`,` siro <command> [path] [flags]`,``,`COMMANDS`,` lint Report best-practice violations (alias: check)`,``,`GLOBAL FLAGS`,B.pm,` --version Print the siro version`,` --help Show help for siro or a command`,``,`LINT FLAGS`,B.reporter,B.json,B.severity,``,`EXAMPLES`,` $ siro lint # report violations in cwd`,` $ 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(`
3
+ `),H=[`siro lint — report best-practice violations`,``,`USAGE`,` siro lint [path] [flags]`,` siro check [path] [flags] (alias)`,``,`FLAGS`,B.pm,B.reporter,B.json,B.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 Uncaught exception (a siro bug, or a reporter / custom rule that threw)`,``,`EXAMPLES`,` $ siro lint # default: pretty reporter, fail on errors`,` $ siro lint --reporter github # GitHub Actions annotations`,` $ siro lint --severity warn # tighten the gate`].join(`
4
+ `),U=e=>e===void 0?V:e===`lint`||e===`check`?H:e,W=e=>{let n=e[`--`];if(Array.isArray(n)&&n.length>0)throw new t("siro takes no passthrough arguments after `--`.")},G=e=>e===void 0?{kind:`usage`}:e===`init`?{kind:`usage`,reason:"The 'init' command was removed: siro is lint-only. Run `siro lint --reporter json` and apply each finding's `fix` operations with your editor or an agent skill."}:typeof e!=`string`||!h(e)?{kind:`usage`,reason:`Unknown command: ${String(e)}`}:{kind:`command`,name:e},K=e=>{if(e.length>0){let n=``;throw e.length>1&&(n=`s`),new t(`Unexpected extra argument${n}: ${e.join(` `)}`)}},q=e=>{let t=process.cwd();return typeof e==`string`&&(t=e),i(ee.resolve(t))},J=e=>{let t=E(e);if(t!==void 0)return{kind:`help`,target:t.target};if(D(e))return{kind:`version`}},Y=(e,t,n)=>(O(e,y(n),n),{cwd:q(t),kind:`lint`,pm:k(e.pm),reporter:j(e),severity:A(e.severity)}),X=e=>{O(e,b),W(e)},ie=e=>{let t=J(e);if(t!==void 0)return t;let{commandCandidate:n,extraPositionals:r,flags:i,positionalCwd:a}=L(e);X(i);let o=G(n);return o.kind===`usage`?o:(K(r),Y(i,a,o.name))},ae=e=>(e.stdout(l),Promise.resolve(0)),oe=(e,t)=>(t.stdout(U(e)),Promise.resolve(0)),se=(e,t)=>(e!==void 0&&t.stderr(`${e}\n`),t.stderr(U()),Promise.resolve(2)),ce=(e,t)=>{switch(e.kind){case`version`:return ae(t);case`help`:return oe(e.target,t);case`usage`:return se(e.reason,t);case`lint`:return o(e,t);default:throw Error(`Unhandled command kind: ${String(e)}`)}},Z=(e,t)=>{if(e instanceof n)return t.stderr(e.message),e.exitCode;if(s(e)&&typeof e.errno==`number`)return t.stderr(`File system error: ${e.message}`),2;throw e},le=(e,t)=>e instanceof n?(t.stderr(e.message),Promise.resolve(e.exitCode)):s(e)&&typeof e.errno==`number`?(t.stderr(`File system error: ${e.message}`),Promise.resolve(2)):Promise.reject(e),Q=(e,t=c)=>{try{return M(process.versions.node),ce(ie(e),t).catch(e=>Z(e,t))}catch(e){return le(e,t)}},ue=e=>{process.exitCode=e},de=e=>{let t=String(e);e instanceof Error&&(t=e.stack??e.message),process.stderr.write(`${t}\n`),process.exitCode=70},[,$]=process.argv;if($&&import.meta.url===p($).href)try{ue(await Q(process.argv.slice(2)))}catch(e){de(e)}export{Q as run};
5
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1,605 @@
1
+ import * as vb from "valibot";
2
+
3
+ //#region src/shared/paths.d.ts
4
+ /**
5
+ * Branded path types. Surface the distinction between
6
+ * - `AbsPath`: absolute filesystem paths handed to the FS boundary
7
+ * (`FileSystem.readText` etc.), and
8
+ * - `RelPath`: paths relative to a repo root, accepted by
9
+ * `RepoContext` and resolved via `resolveIn`.
10
+ *
11
+ * The brand is a structural tag with no runtime footprint — the cast in
12
+ * `asAbsPath` / `asRelPath` is the only place either type is minted, so
13
+ * passing a raw `string` to an FS or repo API now requires the caller to
14
+ * declare intent rather than silently mixing the two flavours.
15
+ */
16
+ declare const AbsPathBrand: unique symbol;
17
+ declare const RelPathBrand: unique symbol;
18
+ type AbsPath = string & {
19
+ readonly [AbsPathBrand]: true;
20
+ };
21
+ type RelPath = string & {
22
+ readonly [RelPathBrand]: true;
23
+ };
24
+ /** Tag a string as absolute. The caller vouches for the shape. */
25
+ declare const asAbsPath: (path: string) => AbsPath;
26
+ /** Tag a string as repo-root-relative. The caller vouches for the shape. */
27
+ declare const asRelPath: (path: string) => RelPath;
28
+ //#endregion
29
+ //#region src/domain/ports/file-system.d.ts
30
+ /** IO boundary: swap in memfs (or any other backend) for tests. */
31
+ interface FileSystem {
32
+ readText: (path: AbsPath) => string | undefined;
33
+ exists: (path: AbsPath) => boolean;
34
+ }
35
+ //#endregion
36
+ //#region src/adapters/node-file-system.d.ts
37
+ declare const nodeFileSystem: FileSystem;
38
+ //#endregion
39
+ //#region src/domain/ports/io.d.ts
40
+ /** Output sink for commands; injectable so tests can drive a string buffer. */
41
+ interface IO {
42
+ stdout: (line: string) => void;
43
+ stderr: (line: string) => void;
44
+ }
45
+ //#endregion
46
+ //#region src/adapters/node-io.d.ts
47
+ declare const nodeIO: IO;
48
+ //#endregion
49
+ //#region src/domain/entities/config-value.d.ts
50
+ type CodecKind = 'npmrc' | 'yaml' | 'toml' | 'json';
51
+ /** Scalar value that can be written back to a config file. */
52
+ type ConfigValue = string | number | boolean;
53
+ /** Scalar value as it appears when reading (codecs may produce `null`). */
54
+ type ConfigScalar = ConfigValue | null;
55
+ /** Recursive, structurally typed view of a parsed config file. */
56
+ interface ParsedConfig {
57
+ readonly [key: string]: ConfigScalar | readonly ConfigScalar[] | ParsedConfig;
58
+ }
59
+ /** A (possibly nested) key path, guaranteed to have at least one segment. */
60
+ type KeyPath = readonly [string, ...string[]];
61
+ /** A single key to set. */
62
+ interface KeyAssignment {
63
+ readonly keyPath: KeyPath;
64
+ readonly value: ConfigValue;
65
+ }
66
+ /**
67
+ * Value a rule's `check` saw at the target key, as returned by `getByPath`.
68
+ * Runtime caveat: YAML/TOML parsers can yield host values (e.g. `Date`)
69
+ * nested inside `ParsedConfig` even though the type does not name them —
70
+ * compare timestamps by `valueOf()`, not by type narrowing on this union.
71
+ */
72
+ type ConfigReadValue = ConfigScalar | readonly ConfigScalar[] | ParsedConfig | undefined;
73
+ /**
74
+ * Look up a nested value by key path; `undefined` if any segment is missing.
75
+ *
76
+ * Part of the public surface (re-exported from `src/index.ts`): embedders
77
+ * writing custom rules receive the same `config: ParsedConfig` shape that
78
+ * built-in rules see, so they need the canonical traversal helper rather
79
+ * than re-implementing `null`-vs-missing semantics per rule.
80
+ */
81
+ declare const getByPath: (config: ParsedConfig, keyPath: KeyPath) => ConfigScalar | readonly ConfigScalar[] | ParsedConfig | undefined;
82
+ //#endregion
83
+ //#region src/domain/entities/pms.d.ts
84
+ /**
85
+ * Single source of truth for package managers and severities.
86
+ * Types, runtime sets, and ordering are all derived from these tuples — add a
87
+ * package manager here and the rest of the codebase follows.
88
+ */
89
+ declare const PMS: readonly ["npm", "pnpm", "yarn", "bun", "deno", "aube"];
90
+ type PM = (typeof PMS)[number];
91
+ declare const SEVERITIES: readonly ["error", "warn", "info"];
92
+ type Severity = (typeof SEVERITIES)[number];
93
+ declare const isPM: (value: string) => value is PM;
94
+ declare const isSeverity: (value: string) => value is Severity;
95
+ //#endregion
96
+ //#region src/domain/schemas/package-json.d.ts
97
+ declare const PackageJsonSchema: vb.LooseObjectSchema<{
98
+ readonly files: vb.SchemaWithFallback<vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>, undefined>;
99
+ readonly name: vb.SchemaWithFallback<vb.OptionalSchema<vb.StringSchema<undefined>, undefined>, undefined>;
100
+ readonly packageManager: vb.SchemaWithFallback<vb.OptionalSchema<vb.StringSchema<undefined>, undefined>, undefined>;
101
+ readonly private: vb.OptionalSchema<vb.SchemaWithFallback<vb.BooleanSchema<undefined>, true>, undefined>;
102
+ readonly publishConfig: vb.SchemaWithFallback<vb.OptionalSchema<vb.LooseObjectSchema<{
103
+ readonly access: vb.SchemaWithFallback<vb.OptionalSchema<vb.UnionSchema<[vb.LiteralSchema<"public", undefined>, vb.LiteralSchema<"restricted", undefined>], undefined>, undefined>, undefined>;
104
+ }, undefined>, undefined>, undefined>;
105
+ readonly trustedDependencies: vb.SchemaWithFallback<vb.OptionalSchema<vb.ArraySchema<vb.StringSchema<undefined>, undefined>, undefined>, undefined>;
106
+ }, undefined>;
107
+ type PackageJson = vb.InferOutput<typeof PackageJsonSchema>;
108
+ //#endregion
109
+ //#region src/domain/ports/repo-context.d.ts
110
+ /** Read-only view of a repository, passed to every rule's `check` and `fix`. */
111
+ interface RepoContext {
112
+ readonly root: AbsPath;
113
+ exists: (relPath: RelPath) => boolean;
114
+ readText: (relPath: RelPath) => string | undefined;
115
+ readonly packageJson: PackageJson | undefined;
116
+ }
117
+ //#endregion
118
+ //#region src/domain/entities/rule.d.ts
119
+ /**
120
+ * What a rule binding points at on disk: a parsed config file or an existence
121
+ * check (e.g. "a lockfile is committed"). `path` is a branded `RelPath` minted
122
+ * once at the ref's definition (CONFIG_FILES), so the readText boundary is
123
+ * type-checked and a stray absolute path can't slip in as a string.
124
+ */
125
+ type ConfigFileRef = {
126
+ readonly kind: CodecKind;
127
+ readonly path: RelPath;
128
+ } | {
129
+ readonly kind: 'fileGlob';
130
+ readonly path: RelPath;
131
+ };
132
+ /** Result of evaluating a single rule binding against a repo. */
133
+ type CheckStatus = {
134
+ readonly state: 'ok';
135
+ } | {
136
+ readonly state: 'violation';
137
+ readonly message: string;
138
+ readonly expected?: ConfigValue;
139
+ readonly actual?: ConfigReadValue;
140
+ /**
141
+ * Dynamic severity for this specific violation, overriding both
142
+ * {@link AutoRuleBinding.severity} and {@link Rule.severity}. Lets a
143
+ * `check` distinguish "unset but PM-default safe" (advisory) from
144
+ * "explicitly weakened" (full severity). A user `rules` override in
145
+ * config still wins (see `applyConfig`).
146
+ */
147
+ readonly severity?: Severity;
148
+ /**
149
+ * Manual remediation steps surfaced verbatim on the finding. Lets a
150
+ * `check` signal "the setKey fix ops cannot resolve this state; the
151
+ * user has to intervene" — e.g. when an explicit user override would
152
+ * have to be deleted first. When non-empty, `Finding.fix` is emitted
153
+ * empty so an external fixer never writes ops the user's existing
154
+ * config would defeat.
155
+ */
156
+ readonly manualSteps?: readonly string[];
157
+ } | {
158
+ readonly state: 'na';
159
+ };
160
+ /** A remediation step produced by a rule's `fix`. */
161
+ type FixOp = {
162
+ readonly op: 'setKey';
163
+ readonly file: ConfigFileRef;
164
+ readonly keyPath: KeyPath;
165
+ readonly value: ConfigValue;
166
+ } | {
167
+ readonly op: 'ensureFileTracked';
168
+ readonly file: ConfigFileRef;
169
+ readonly message: string;
170
+ } | {
171
+ readonly op: 'note';
172
+ readonly message: string;
173
+ readonly file?: ConfigFileRef;
174
+ };
175
+ /**
176
+ * - `auto`: `fix` produces setKey ops an external fixer can apply
177
+ * mechanically (surfaced via `Finding.fix`; see docs/json-output.md).
178
+ * - `advisory`: `fix` only produces notes/ensureFileTracked hints.
179
+ */
180
+ type FixKind = 'auto' | 'advisory';
181
+ type SetKeyOp = Extract<FixOp, {
182
+ op: 'setKey';
183
+ }>;
184
+ type AdvisoryOp = Extract<FixOp, {
185
+ op: 'note' | 'ensureFileTracked';
186
+ }>;
187
+ interface AutoRuleBinding {
188
+ readonly file: ConfigFileRef;
189
+ readonly fixKind: 'auto';
190
+ /** Official package-manager doc URL for the setting this binding writes. */
191
+ readonly docs?: string;
192
+ /**
193
+ * Per-binding severity. When set, it shadows {@link Rule.severity} for this
194
+ * PM only — used when a package manager's safe default already mitigates
195
+ * the threat (so the binding is informational) while another PM with the
196
+ * same rule still warrants the rule-wide level. A user config `rules`
197
+ * override always wins over both.
198
+ */
199
+ readonly severity?: Severity;
200
+ check: (ctx: RepoContext, config: ParsedConfig) => CheckStatus;
201
+ fix: (ctx: RepoContext) => readonly SetKeyOp[];
202
+ }
203
+ interface AdvisoryRuleBinding {
204
+ readonly file: ConfigFileRef;
205
+ readonly fixKind: 'advisory';
206
+ /** Official package-manager doc URL for the setting this binding describes. */
207
+ readonly docs?: string;
208
+ /** See {@link AutoRuleBinding.severity}. */
209
+ readonly severity?: Severity;
210
+ check: (ctx: RepoContext, config: ParsedConfig) => CheckStatus;
211
+ fix: (ctx: RepoContext) => readonly AdvisoryOp[];
212
+ }
213
+ type RuleBinding = AutoRuleBinding | AdvisoryRuleBinding;
214
+ /** A package-manager-agnostic security intent, realized per PM via `bindings`. */
215
+ interface Rule {
216
+ readonly id: string;
217
+ readonly title: string;
218
+ readonly description: string;
219
+ readonly severity: Severity;
220
+ readonly docs?: string;
221
+ /** Absence of a PM key means the rule does not apply (N/A) to that PM. */
222
+ readonly bindings: Partial<Record<PM, RuleBinding>>;
223
+ }
224
+ //#endregion
225
+ //#region src/domain/entities/lint-result.d.ts
226
+ interface Finding {
227
+ readonly ruleId: string;
228
+ readonly pm: PM;
229
+ readonly severity: Severity;
230
+ readonly message: string;
231
+ readonly file?: string;
232
+ readonly fixable: boolean;
233
+ /**
234
+ * Machine-readable remediation: the binding's fix ops, verbatim. Empty
235
+ * when `manualSteps` is present (writing the ops would be defeated by the
236
+ * user state that produced the steps). Advisory bindings contribute their
237
+ * `note` / `ensureFileTracked` ops here so external fixers can surface
238
+ * them. Part of the json output contract — see docs/json-output.md.
239
+ */
240
+ readonly fix: readonly FixOp[];
241
+ /** Manual remediation steps, verbatim from the check (see CheckStatus). */
242
+ readonly manualSteps?: readonly string[];
243
+ /**
244
+ * Pinned to {@link ConfigValue} (no `null`) so it stays in lockstep with
245
+ * `CheckStatus.violation.expected`. Widening to ConfigScalar would have
246
+ * advertised a `null` outcome that no current rule can produce.
247
+ */
248
+ readonly expected?: ConfigValue;
249
+ readonly actual?: ConfigReadValue;
250
+ /** Official PM doc anchor for the setting this finding is about, if any. */
251
+ readonly docs?: string;
252
+ }
253
+ interface LintResult {
254
+ readonly findings: readonly Finding[];
255
+ readonly summary: Readonly<Record<Severity, number>>;
256
+ }
257
+ //#endregion
258
+ //#region src/domain/ports/reporter.d.ts
259
+ /**
260
+ * Renderer from `LintResult` to user-facing output.
261
+ *
262
+ * Reporters never touch the filesystem and never spawn processes — every
263
+ * output byte flows through the injected `IO` port. They MAY however
264
+ * consult `process.env` for environmental shape (TTY colour support,
265
+ * NO_COLOR / FORCE_COLOR, CI detection) because that information has no
266
+ * `IO`-port equivalent and treating it as I/O would force the application
267
+ * layer to thread a snapshot through every call site for a value the
268
+ * adapter can read in a single line.
269
+ *
270
+ * Implementations live in the adapter layer (src/adapters/reporters/),
271
+ * so the env access stays out of the domain.
272
+ */
273
+ interface Reporter<Name extends string = string> {
274
+ readonly name: Name;
275
+ format: (result: LintResult, io: IO) => void;
276
+ }
277
+ /**
278
+ * Structural guard for an embedder-supplied reporter. The `Reporter` contract
279
+ * is compile-time only (`defineConfig` / typed options), so a hand-written JS
280
+ * config or a dynamically-built object can still pass a malformed value; this
281
+ * lets each boundary reject it with its own error type before `format` is
282
+ * called on something that isn't a function.
283
+ */
284
+ declare const isReporterShape: (value: unknown) => value is Reporter;
285
+ //#endregion
286
+ //#region src/adapters/reporters/github.d.ts
287
+ /** Emit GitHub Actions workflow commands (annotations on PRs). */
288
+ declare const githubReporter: Reporter<'github'>;
289
+ //#endregion
290
+ //#region src/adapters/reporters/json.d.ts
291
+ declare const jsonReporter: Reporter<'json'>;
292
+ //#endregion
293
+ //#region src/adapters/reporters/pretty.d.ts
294
+ declare const prettyReporter: Reporter<'pretty'>;
295
+ //#endregion
296
+ //#region src/adapters/reporters/registry.d.ts
297
+ declare const BUILTINS: readonly [Reporter<"pretty">, Reporter<"json">, Reporter<"github">];
298
+ /** Names of every built-in reporter, in stable display order. */
299
+ declare const BUILTIN_REPORTER_NAMES: readonly BuiltinReporterName[];
300
+ /** Literal union of every built-in reporter name. */
301
+ type BuiltinReporterName = (typeof BUILTINS)[number]['name'];
302
+ /** Reporter lookup table — pass an explicit value to keep calls hermetic. */
303
+ interface ReporterRegistry {
304
+ get: (name: string) => Reporter | undefined;
305
+ list: () => readonly string[];
306
+ }
307
+ /** Build a registry from the builtins plus any extras (later wins on collision). */
308
+ declare const createRegistry: (extras?: readonly Reporter[]) => ReporterRegistry;
309
+ //#endregion
310
+ //#region src/application/commands/lint.d.ts
311
+ interface LintOptions {
312
+ /**
313
+ * Absolute repo root. Callers are responsible for resolving any relative
314
+ * path before branding it; siro's CLI does this in `cli.ts`. Keeping the
315
+ * brand here lets the application layer stay free of `node:path`.
316
+ */
317
+ cwd: AbsPath;
318
+ /** Restrict to a single PM; otherwise PMs are auto-detected. */
319
+ pm?: PM;
320
+ /** Reporter name or instance; defaults to `pretty`. */
321
+ reporter?: string | Reporter;
322
+ /** Extra reporters to make available by name (merged with the user config). */
323
+ reporters?: readonly Reporter[];
324
+ /**
325
+ * Programmatic custom rules to evaluate alongside the builtins and any
326
+ * `customRules` from the user config. Mirrors `SiroConfig.customRules`
327
+ * but is supplied at the call site, so embedders can compose rulesets
328
+ * without writing a config file. Ids must be unique across builtins,
329
+ * config-supplied custom rules, and this list — collisions throw a
330
+ * `ConfigError` (exit 2), matching how `loadConfig` rejects them.
331
+ */
332
+ customRules?: readonly Rule[];
333
+ /** Show (and fail on) findings at or above this severity. */
334
+ severity?: Severity;
335
+ /**
336
+ * Inject a non-default FS (e.g. memfs in tests). Caveat: `siro.config.{ts,mjs,js}`
337
+ * is loaded by jiti from the REAL disk — only the config's existence is probed
338
+ * through this FS. A config that lives solely in an injected FS won't load.
339
+ */
340
+ fs?: FileSystem;
341
+ }
342
+ /** `siro lint`: detect PMs, evaluate rules, report findings. */
343
+ declare const lintCommand: (options: LintOptions, io: IO) => Promise<number>;
344
+ //#endregion
345
+ //#region src/domain/entities/config-files.d.ts
346
+ /**
347
+ * Canonical {@link ConfigFileRef}s for every package-manager config file siro
348
+ * knows how to read. Centralizing them keeps each rule module from hardcoding
349
+ * `kind` / `path` pairs, so renaming a file (or fixing a typo in its path) is a
350
+ * single-line change. This is also the one place `RelPath` is minted for a
351
+ * config ref — every downstream binding inherits the brand, so no rule needs
352
+ * an ad-hoc `asRelPath(ref.path)` cast at the FS boundary.
353
+ *
354
+ * Add a new entry here when introducing support for a new package manager.
355
+ */
356
+ declare const CONFIG_FILES: {
357
+ readonly aubeWorkspace: {
358
+ readonly kind: "yaml";
359
+ readonly path: RelPath;
360
+ };
361
+ readonly bunfig: {
362
+ readonly kind: "toml";
363
+ readonly path: RelPath;
364
+ };
365
+ readonly denoJson: {
366
+ readonly kind: "json";
367
+ readonly path: RelPath;
368
+ };
369
+ readonly npmrc: {
370
+ readonly kind: "npmrc";
371
+ readonly path: RelPath;
372
+ };
373
+ readonly packageJson: {
374
+ readonly kind: "json";
375
+ readonly path: RelPath;
376
+ };
377
+ readonly pnpmWorkspace: {
378
+ readonly kind: "yaml";
379
+ readonly path: RelPath;
380
+ };
381
+ readonly yarnrc: {
382
+ readonly kind: "yaml";
383
+ readonly path: RelPath;
384
+ };
385
+ };
386
+ //#endregion
387
+ //#region src/domain/entities/signals.d.ts
388
+ /**
389
+ * Filenames that identify a package manager. `lockfiles[0]` is the
390
+ * canonical/preferred one (used by lint messages and error
391
+ * messages); the rest are legacy/alternative forms the PM writes itself.
392
+ *
393
+ * `lockfiles` and `configs` are **detection evidence** — their presence means
394
+ * the PM is in use. `reusesLockfiles` is NOT evidence: it lists other PMs'
395
+ * lockfile shapes that this PM will reuse rather than writing its own (aube),
396
+ * so `commit-lockfile` accepts them but `detectPMs` ignores them — otherwise a
397
+ * plain pnpm/npm repo would false-positive as aube. Keeping the two lists
398
+ * separate is what lets detection stay a simple "any signal present?" check.
399
+ */
400
+ interface PMSignals {
401
+ readonly lockfiles: readonly [string, ...string[]];
402
+ readonly configs: readonly string[];
403
+ /** Other PMs' lockfiles this PM reuses; satisfies commit-lockfile, not detection. */
404
+ readonly reusesLockfiles?: readonly string[];
405
+ }
406
+ declare const PM_SIGNALS: {
407
+ readonly aube: {
408
+ readonly configs: readonly [RelPath];
409
+ readonly lockfiles: readonly ["aube-lock.yaml"];
410
+ readonly reusesLockfiles: readonly ["pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "bun.lock", "bun.lockb", "deno.lock"];
411
+ };
412
+ readonly bun: {
413
+ readonly configs: readonly [RelPath];
414
+ readonly lockfiles: readonly ["bun.lock", "bun.lockb"];
415
+ };
416
+ readonly deno: {
417
+ readonly configs: readonly [RelPath];
418
+ readonly lockfiles: readonly ["deno.lock"];
419
+ };
420
+ readonly npm: {
421
+ readonly configs: readonly [];
422
+ readonly lockfiles: readonly ["package-lock.json", "npm-shrinkwrap.json"];
423
+ };
424
+ readonly pnpm: {
425
+ readonly configs: readonly [RelPath];
426
+ readonly lockfiles: readonly ["pnpm-lock.yaml"];
427
+ };
428
+ readonly yarn: {
429
+ readonly configs: readonly [RelPath];
430
+ readonly lockfiles: readonly ["yarn.lock"];
431
+ };
432
+ };
433
+ //#endregion
434
+ //#region src/domain/entities/rule-id.d.ts
435
+ /**
436
+ * Closed set of built-in rule IDs. Declared in `domain/` so SiroConfig can
437
+ * type `rules` keys without depending on the engine.
438
+ */
439
+ declare const BUILTIN_RULE_IDS: readonly ["advisory-check", "approved-git-repos", "audit-suppression", "block-auto-install", "block-exotic-subdeps", "bun-security-scanner", "checksum-verification", "commit-lockfile", "dependency-overrides", "disable-lifecycle-scripts", "enforce-strict-ssl", "files-field", "frozen-lockfile", "frozen-store", "hardened-mode", "minimum-release-age", "named-registries", "paranoid-mode", "patched-dependencies", "pin-exact-versions", "provenance", "publish-access", "store-server", "strict-allow-scripts", "strict-release-age", "strict-store-integrity", "trust-policy"];
440
+ type BuiltinRuleId = (typeof BUILTIN_RULE_IDS)[number];
441
+ //#endregion
442
+ //#region src/domain/entities/siro-config.d.ts
443
+ /** Per-rule setting. `'off'` disables; a Severity overrides the default level. */
444
+ type RuleSetting = Severity | 'off';
445
+ /**
446
+ * User-facing config returned by `defineConfig` in `siro.config.{ts,mjs,js}`.
447
+ *
448
+ * defineConfig({
449
+ * pms: ['npm', 'pnpm'], // restrict detection
450
+ * rules: { provenance: 'off' }, // disable / override severity
451
+ * customRules: [myRule], // extend
452
+ * reporters: [mySarifReporter], // extend
453
+ * })
454
+ */
455
+ interface SiroConfig {
456
+ readonly pms?: readonly PM[];
457
+ readonly rules?: Readonly<Partial<Record<BuiltinRuleId | (string & Record<never, never>), RuleSetting>>>;
458
+ readonly customRules?: readonly Rule[];
459
+ readonly reporters?: readonly Reporter[];
460
+ }
461
+ /** Identity helper for type-checked config files. */
462
+ declare const defineConfig: (config: SiroConfig) => SiroConfig;
463
+ //#endregion
464
+ //#region src/domain/ports/config-codec.d.ts
465
+ /** Reads a single config-file format into siro's structural view. */
466
+ interface ConfigCodec {
467
+ parse: (text: string) => ParsedConfig;
468
+ }
469
+ /** Resolve the codec for a given file kind. Total over `CodecKind`. */
470
+ type CodecFor = (kind: CodecKind) => ConfigCodec;
471
+ //#endregion
472
+ //#region src/domain/rules/builders/require-config-key.d.ts
473
+ /**
474
+ * Display-only metadata for a binding. siro itself is intentionally
475
+ * version-agnostic (we never branch on the user's actual PM version) — these
476
+ * fields exist so the rendered message can still tell the reader *when* a key
477
+ * appeared and *when* the PM started shipping a safe default. They MUST NOT
478
+ * be consulted by any code path that decides severity or applicability.
479
+ */
480
+ interface VersionNote {
481
+ /** PM version that first shipped this config key (e.g. `'pnpm 10.16.0'`). */
482
+ readonly configAvailableSince?: string;
483
+ /** PM version whose built-in default first satisfies the rule (e.g. `'pnpm 11.0.0 (1440 minutes)'`). */
484
+ readonly defaultSafeSince?: string;
485
+ /** Free-form trailing note rendered last (e.g. `'replaces auto-install-peers'`). */
486
+ readonly note?: string;
487
+ }
488
+ interface RequireConfigKeySpec {
489
+ readonly file: ConfigFileRef;
490
+ readonly keyPath: KeyPath;
491
+ /** Value `fix` will write and (when no `accept` predicate is given) the value `check` expects. */
492
+ readonly value: ConfigValue;
493
+ readonly message: string;
494
+ readonly docs?: string;
495
+ readonly severity?: Severity;
496
+ accept?: (actual: unknown) => boolean;
497
+ /**
498
+ * Extra key writes appended after the auto-generated one (e.g. clearing
499
+ * save-prefix alongside save-exact on the same .npmrc). The target file
500
+ * is implicitly `spec.file` — extras can't redirect to a different file
501
+ * because `check` would then never validate them, leaving the fix ops
502
+ * and `lint` permanently out of step. A rule that legitimately needs to write
503
+ * across multiple files belongs in a hand-written binding (see
504
+ * `disable-lifecycle-scripts` → `overrideBindings`).
505
+ */
506
+ readonly extraFix?: readonly KeyAssignment[];
507
+ /**
508
+ * PM-documented default for this key. When the user has not set the key
509
+ * AND this default would satisfy `accept`/`value`, the finding is
510
+ * downgraded to {@link defaultSatisfiedSeverity} (default `'info'`) — the
511
+ * threat is mitigated by the PM but explicit pinning is still recommended.
512
+ * - unset: no PM-default protection (legacy behaviour).
513
+ *
514
+ * A CONDITIONAL default (e.g. CI-only: pnpm `frozenLockfile`, aube
515
+ * `preferFrozenLockfile`) may use this field, but the binding's `message`
516
+ * MUST name the condition — the downgrade then reads "covered where it
517
+ * matters most", not "covered unconditionally".
518
+ */
519
+ readonly documentedDefault?: ConfigValue;
520
+ /**
521
+ * Severity used when `documentedDefault` satisfies the requirement. Defaults
522
+ * to `'info'` (advisory). Set `'off'` to silence the finding entirely.
523
+ */
524
+ readonly defaultSatisfiedSeverity?: Severity | 'off';
525
+ readonly versionNote?: VersionNote;
526
+ }
527
+ interface RequireConfigKeyOptions {
528
+ readonly id: string;
529
+ readonly title: string;
530
+ readonly description: string;
531
+ readonly severity: Severity;
532
+ readonly docs?: string;
533
+ /** Bindings keyed by PM. PMs absent from this map are treated as N/A. */
534
+ readonly bindings: Partial<Record<PM, RequireConfigKeySpec>>;
535
+ /** Return false to short-circuit `check` as N/A (e.g. private packages). */
536
+ applies?: (ctx: RepoContext) => boolean;
537
+ }
538
+ /**
539
+ * Replace one or more bindings on an existing rule, returning a fresh Rule
540
+ * object. Used by rules whose shape outgrows {@link requireConfigKey} for a
541
+ * subset of PMs — they build the simple slots via the builder, then splice
542
+ * the hand-written bindings in via this helper rather than rebuilding from
543
+ * scratch or mutating the source rule.
544
+ */
545
+ declare const overrideBindings: (rule: Rule, overrides: Partial<Rule["bindings"]>) => Rule;
546
+ declare const renderVersionNoteMessage: (message: string, versionNote: VersionNote | undefined) => string;
547
+ /** Build a Rule from a per-PM table of {file, keyPath, value, message}. */
548
+ declare const requireConfigKey: (options: RequireConfigKeyOptions) => Rule;
549
+ //#endregion
550
+ //#region src/domain/services/detect-pms.d.ts
551
+ declare const detectPMs: (ctx: RepoContext) => PM[];
552
+ //#endregion
553
+ //#region src/domain/services/filter.d.ts
554
+ /** Keep only findings at or above `threshold`, recomputing the summary. */
555
+ declare const filterBySeverity: (result: LintResult, threshold: Severity) => LintResult;
556
+ /**
557
+ * Exit code for a lint run. Non-zero when any finding meets `threshold`
558
+ * (default: only `error` fails the run).
559
+ */
560
+ declare const exitCodeForLint: (result: LintResult, threshold?: Severity) => number;
561
+ //#endregion
562
+ //#region src/shared/siro-error.d.ts
563
+ /**
564
+ * Structured errors used across siro. The CLI maps them to exit codes in one
565
+ * place so individual commands never have to think about exit semantics.
566
+ * The exit-code table lives in docs/configuration.md S"Exit codes" and
567
+ * src/cli/help.ts (HELP_LINT) -- not restated here.
568
+ *
569
+ * One mapping note that belongs at this altitude: filesystem errno errors
570
+ * are routed to 2 by SHAPE (numeric `errno`) in cli run(), so an
571
+ * errno-bearing throw from a user extension also lands on 2 rather than the
572
+ * 70 crash path -- same remedy (fix the environment) either way.
573
+ */
574
+ declare class SiroError extends Error {
575
+ /** Recommended process exit code for this error. */
576
+ readonly exitCode: number;
577
+ constructor(message: string, exitCode: number);
578
+ }
579
+ //#endregion
580
+ //#region src/shared/config-error.d.ts
581
+ declare class ConfigError extends SiroError {
582
+ constructor(message: string);
583
+ }
584
+ //#endregion
585
+ //#region src/shared/usage-error.d.ts
586
+ declare class UsageError extends SiroError {
587
+ constructor(message: string);
588
+ }
589
+ //#endregion
590
+ //#region src/shared/errors.d.ts
591
+ /**
592
+ * Run `fn` and wrap any non-`ConfigError` failure as a `ConfigError` prefixed
593
+ * with `filePath`. Codec `parse` calls hit raw libraries that
594
+ * throw bare `Error`s; without this wrap the CLI would surface those as
595
+ * exit-1 internal errors and break CI that branches on exit codes. A
596
+ * `ConfigError` that bubbles up from a nested call is re-thrown unchanged so
597
+ * the original `path: message` framing is preserved.
598
+ */
599
+ declare const wrapCodecError: <TResult>(filePath: string, fn: () => TResult) => TResult;
600
+ //#endregion
601
+ //#region src/version.d.ts
602
+ declare const version = "0.1.0-rc";
603
+ //#endregion
604
+ export { type AbsPath, type AdvisoryRuleBinding, type AutoRuleBinding, BUILTIN_REPORTER_NAMES, type BuiltinReporterName, CONFIG_FILES, type CheckStatus, type CodecFor, type ConfigCodec, ConfigError, type ConfigFileRef, type ConfigReadValue, type ConfigScalar, type ConfigValue, type FileSystem, type Finding, type FixKind, type FixOp, type IO, type KeyAssignment, type KeyPath, type LintOptions, type LintResult, type PM, PMS, type PMSignals, PM_SIGNALS, type PackageJson, type ParsedConfig, type RelPath, type RepoContext, type Reporter, type ReporterRegistry, type RequireConfigKeyOptions, type Rule, type RuleBinding, type RuleSetting, SEVERITIES, type Severity, type SiroConfig, SiroError, UsageError, type VersionNote, asAbsPath, asRelPath, createRegistry, defineConfig, detectPMs, exitCodeForLint, filterBySeverity, getByPath, githubReporter, isPM, isReporterShape, isSeverity, jsonReporter, lintCommand, nodeFileSystem, nodeIO, overrideBindings, prettyReporter, renderVersionNoteMessage, requireConfigKey, version, wrapCodecError };
605
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import{A as e,C as t,D as n,E as r,O as i,S as a,T as o,_ as s,a as c,b as l,c as u,d,f,g as p,h as m,i as h,k as g,l as _,m as v,n as y,o as b,r as x,s as S,t as C,u as w,v as T,w as E,x as D,y as O}from"./node-io-C_ERSDGJ.mjs";const k=e=>e;export{v as BUILTIN_REPORTER_NAMES,_ as CONFIG_FILES,n as ConfigError,D as PMS,c as PM_SIGNALS,a as SEVERITIES,i as SiroError,r as UsageError,g as asAbsPath,e as asRelPath,m as createRegistry,k as defineConfig,x as detectPMs,d as exitCodeForLint,f as filterBySeverity,w as getByPath,O as githubReporter,t as isPM,l as isReporterShape,E as isSeverity,s as jsonReporter,y as lintCommand,h as nodeFileSystem,C as nodeIO,b as overrideBindings,p as prettyReporter,S as renderVersionNoteMessage,u as requireConfigKey,T as version,o as wrapCodecError};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,4 @@
1
+ import e from"picocolors";import t from"ini";import{parse as n}from"smol-toml";import{parseDocument as r}from"yaml";import*as i from"valibot";import{accessSync as a,constants as o,readFileSync as s}from"node:fs";import c from"node:path";import{createJiti as l}from"jiti";const u=e=>e,d=e=>e;var f=class extends Error{exitCode;constructor(e,t){super(e),this.name=`SiroError`,this.exitCode=t}},p=class extends f{constructor(e){super(e,2),this.name=`ConfigError`}},m=class extends f{constructor(e){super(e,2),this.name=`UsageError`}};const h=(e,t)=>{try{return t()}catch(t){if(t instanceof p)throw t;let n=String(t);throw t instanceof Error&&(n=t.message),new p(`${e}: ${n}`)}},g=[`npm`,`pnpm`,`yarn`,`bun`,`deno`,`aube`],_=[`error`,`warn`,`info`],ee={error:3,info:1,warn:2},te=new Set(g),ne=new Set(_),v=e=>te.has(e),re=e=>ne.has(e),ie=/^(?<pmName>[a-z]+)(?:@.+)?$/u,ae=e=>{let t=ie.exec(e.trim()),n;if(t&&t.groups&&(n=t.groups.pmName),n!==void 0&&v(n))return n},y=e=>typeof e!=`object`||!e||!(`name`in e)||typeof e.name!=`string`?!1:`format`in e&&typeof e.format==`function`,oe={error:`error`,info:`notice`,warn:`warning`},b=e=>e.replaceAll(`%`,`%25`).replaceAll(`\r`,`%0D`).replaceAll(`
2
+ `,`%0A`).replaceAll(`:`,`%3A`).replaceAll(`,`,`%2C`),se=e=>e.replaceAll(`%`,`%25`).replaceAll(`\r`,`%0D`).replaceAll(`
3
+ `,`%0A`),ce={format(e,t){for(let n of e.findings){let e=``;n.file&&(e=`file=${b(n.file)},`);let r=`[${n.pm}] ${n.message}`;n.docs&&(r=`[${n.pm}] ${n.message} (${n.docs})`),t.stdout(`::${oe[n.severity]} ${e}title=${b(n.ruleId)}::${se(r)}`)}},name:`github`},le=`0.1.0-rc`,ue={format(e,t){t.stdout(JSON.stringify(Object.assign({},{schemaVersion:1,siroVersion:le},e),void 0,2))},name:`json`},de={error:`✖ error`,info:`ℹ info`,warn:`⚠ warn`},fe=()=>{let{env:t}=process;return t.NO_COLOR!==void 0&&t.NO_COLOR!==``?!1:`FORCE_COLOR`in t?t.FORCE_COLOR!==`0`&&t.FORCE_COLOR!==``:e.isColorSupported},pe=(e,t)=>{let n=``;e.file&&(n=t.colors.dim(` (${e.file})`)),t.io.stdout(`${t.tag[e.severity](de[e.severity])} [${e.pm}] ${t.colors.bold(e.ruleId)}${n}`),t.io.stdout(` ${e.message}`);for(let n of e.manualSteps??[])t.io.stdout(` ↳ ${n}`);e.docs&&t.io.stdout(t.colors.dim(` → ${e.docs}`))},me=t=>{let n=e.createColors(fe());return{colors:n,io:t,tag:{error:n.red,info:n.cyan,warn:n.yellow}}},he={format(e,t){let n=me(t);if(e.findings.length===0){t.stdout(n.colors.green(`✔ No security best-practice issues found.`));return}for(let t of e.findings)pe(t,n);let{error:r,warn:i,info:a}=e.summary;t.stdout(``),t.stdout(`Summary: ${r} error, ${i} warn, ${a} info`)},name:`pretty`},ge=[he,ue,ce],_e=ge.map(e=>e.name),ve=(e=[])=>{let t=new Map;for(let n of[...ge,...e]){if(!y(n))throw TypeError(`createRegistry: each reporter needs a string 'name' and a 'format' function.`);t.set(n.name,n)}return{get:e=>t.get(e),list:()=>[...t.keys()]}},x=e=>e instanceof Error&&`code`in e&&typeof e.code==`string`,ye=(e,t)=>ee[e]>=ee[t],be=(e,t)=>{let n=[],r={error:0,info:0,warn:0};for(let i of e.findings)ye(i.severity,t)&&(n.push(i),r[i.severity]+=1);return{findings:n,summary:r}},S=(e,t=`error`)=>+!!e.findings.some(e=>ye(e.severity,t)),C=e=>{if(typeof e!=`object`||!e||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===null||t===Object.prototype},w=(e,t)=>{let n=e;for(let e of t){if(!C(n))return;n=n[e]}return n},T=e=>C(e)?e:{},xe=/^-?\d+$/u,Se=e=>{if(e===`true`)return!0;if(e===`false`)return!1},Ce=e=>{if(xe.test(e))return Number(e)},E=e=>{if(typeof e==`boolean`||typeof e==`number`)return e;let t=String(e??``);return typeof e==`string`&&(t=e),Se(t)??Ce(t)??t},we={json:{parse(e){let t=e.trim();return t===``?{}:T(JSON.parse(t))}},npmrc:{parse(e){let n=T(t.parse(e)),r={};for(let[e,t]of Object.entries(n))Array.isArray(t)?r[e]=t.map(e=>E(e)):typeof t==`object`&&t?r[e]=T(t):r[e]=E(t);return r}},toml:{parse(e){return e.trim()===``?{}:T(n(e))}},yaml:{parse(e){return e.trim()===``?{}:T(r(e).toJS())}}},Te=e=>we[e],Ee=e=>(t,n)=>{let r=e(t,n);return r.state===`violation`&&r.severity!==void 0?{actual:r.actual,expected:r.expected,manualSteps:r.manualSteps,message:r.message,state:`violation`}:r},De=(e,t)=>{let n={};for(let t of g){let r=e.bindings[t];r!==void 0&&(n[t]=Object.assign({},r,{check:Ee(r.check),severity:void 0}))}return Object.assign({},e,{bindings:n,severity:t})},Oe=(e,t,n)=>{let r=t[e.id];r!==`off`&&(r===void 0?n.push(e):n.push(De(e,r)))},ke=(e,t)=>{let n;return t&&({customRules:n}=t),Array.from(e).concat(n??[])},Ae=(e,t)=>{let n=ke(e,t),r;if(t&&(r=t.rules),r===void 0)return n;let i=[];for(let e of n)Oe(e,r,i);return i},D={aubeWorkspace:{kind:`yaml`,path:d(`aube-workspace.yaml`)},bunfig:{kind:`toml`,path:d(`bunfig.toml`)},denoJson:{kind:`json`,path:d(`deno.json`)},npmrc:{kind:`npmrc`,path:d(`.npmrc`)},packageJson:{kind:`json`,path:d(`package.json`)},pnpmWorkspace:{kind:`yaml`,path:d(`pnpm-workspace.yaml`)},yarnrc:{kind:`yaml`,path:d(`.yarnrc.yml`)}},O=(e,t)=>Object.assign({},e,{bindings:Object.assign({},e.bindings,t)}),je=e=>{let t=[];return e.configAvailableSince!==void 0&&t.push(`available since ${e.configAvailableSince}`),e.defaultSafeSince!==void 0&&t.push(`default safe since ${e.defaultSafeSince}`),e.note!==void 0&&t.push(e.note),t},k=(e,t)=>{if(t===void 0)return e;let n=je(t);return n.length===0?e:`${e} (${n.join(`; `)})`},Me=e=>e.accept?e.accept(e.documentedDefault):e.documentedDefault===e.value,Ne=(e,t,n)=>{if(t!==void 0||e.documentedDefault===void 0||!Me(e))return;let r=e.defaultSatisfiedSeverity??`info`;return r===`off`?{state:`ok`}:{actual:t,expected:e.value,message:n,severity:r,state:`violation`}},Pe=(e,t,n)=>{for(let r of e.extraFix??[]){let e=w(t,r.keyPath);if(e!==r.value)return{actual:e,expected:r.value,message:n,state:`violation`}}},Fe=(e,t,n)=>{let r=t===e.value;if(e.accept&&(r=e.accept(t)),!r)return{actual:t,expected:e.value,message:n,state:`violation`}},Ie=(e,t,n)=>{let r=w(t,e.keyPath),i=Ne(e,r,n);if(i===void 0){let t=Fe(e,r,n);if(t!==void 0)return t}return Pe(e,t,n)??i??{state:`ok`}},Le=(e,t)=>({check(n,r){return t!==void 0&&!t(n)?{state:`na`}:Ie(e,r,k(e.message,e.versionNote))},docs:e.docs,file:e.file,fix(){let t=[{file:e.file,keyPath:e.keyPath,op:`setKey`,value:e.value}];for(let n of e.extraFix??[])t.push({file:e.file,keyPath:n.keyPath,op:`setKey`,value:n.value});return t},fixKind:`auto`,severity:e.severity}),A=e=>{let t={};for(let n of g){let r=e.bindings[n];r!==void 0&&(t[n]=Le(r,e.applies))}return{bindings:t,description:e.description,docs:e.docs,id:e.id,severity:e.severity,title:e.title}},{aubeWorkspace:Re}=D,ze=A({bindings:{aube:{accept:e=>e===`on`||e===`required`,docs:`https://aube.jdx.dev/security.html`,file:Re,keyPath:[`advisoryCheck`],message:"Set `advisoryCheck: on` (or `required`) in aube-workspace.yaml to check dependencies against known-malicious advisories.",value:`on`}},description:`Query the OSV database for known-malicious packages during dependency resolution.`,docs:`https://aube.jdx.dev/security.html`,id:`advisory-check`,severity:`warn`,title:`Enable malicious-package advisory checks`}),{yarnrc:j}=D,Be=k("Set `approvedGitRepositories: []` in .yarnrc.yml to block all git: protocol dependencies (or list approved repository globs).",{configAvailableSince:`yarn 4.14.0`}),Ve={bindings:{yarn:{check(e,t){let n=w(t,[`approvedGitRepositories`]);return Array.isArray(n)?{state:`ok`}:{actual:n,expected:`[]`,message:Be,state:`violation`}},docs:`https://yarnpkg.com/configuration/yarnrc#approvedGitRepositories`,file:j,fix(){return[{file:j,message:"Add `approvedGitRepositories: []` to .yarnrc.yml to block all git deps, or list specific approved repository URL globs.",op:`note`}]},fixKind:`advisory`}},description:`Restrict git: protocol dependencies to an explicit allowlist of approved repository URL patterns.`,docs:`https://yarnpkg.com/configuration/yarnrc#approvedGitRepositories`,id:`approved-git-repos`,severity:`warn`,title:`Approve git repository dependencies`},{yarnrc:M}=D,He=e=>Array.isArray(e)&&e.length>0,Ue=[`npmAuditIgnoreAdvisories`,`npmAuditExcludePackages`],We=e=>{let t=Ue.filter(t=>He(w(e,[t]))),[n]=t;return n===void 0?{state:`ok`}:{actual:w(e,[n]),message:`Review audit suppression in .yarnrc.yml (${t.join(`, `)}). Broad glob patterns can silently hide future vulnerabilities.`,state:`violation`}},Ge={bindings:{yarn:{check(e,t){return We(t)},docs:`https://yarnpkg.com/configuration/yarnrc#npmAuditIgnoreAdvisories`,file:M,fix(){return[{file:M,message:"Review entries in `npmAuditIgnoreAdvisories` and `npmAuditExcludePackages` — remove stale suppressions and overly broad glob patterns.",op:`note`}]},fixKind:`advisory`}},description:`Flag audit advisory suppressions that may silently hide future vulnerabilities via broad glob patterns.`,docs:`https://yarnpkg.com/configuration/yarnrc#npmAuditIgnoreAdvisories`,id:`audit-suppression`,severity:`info`,title:`Review audit suppression entries`},{bunfig:Ke}=D,qe=A({bindings:{bun:{docs:`https://bun.sh/docs/runtime/bunfig#install-auto`,file:Ke,keyPath:[`install`,`auto`],message:'Set `auto = "disable"` under [install] in bunfig.toml to prevent auto-install from bypassing security guards.',value:`disable`}},description:`Disable auto-install so dependencies are only installed through an explicit install step where security guards apply.`,docs:`https://bun.sh/docs/runtime/bunfig#install-auto`,id:`block-auto-install`,severity:`warn`,title:`Block automatic package installation`}),{aubeWorkspace:Je,npmrc:N,pnpmWorkspace:Ye}=D,P=e=>e===`root`||e===`none`,F="Set `allow-git=root` and `allow-remote=root` in .npmrc to block git/tarball URLs in transitive dependencies.",Xe={check(e,t){let n=w(t,[`allow-git`]);if(!P(n))return{actual:n,expected:`root`,message:F,state:`violation`};let r=w(t,[`allow-remote`]);return P(r)?{state:`ok`}:{actual:r,expected:`root`,message:F,state:`violation`}},docs:`https://docs.npmjs.com/cli/v11/using-npm/config#allow-git`,file:N,fix(){return[{file:N,keyPath:[`allow-git`],op:`setKey`,value:`root`},{file:N,keyPath:[`allow-remote`],op:`setKey`,value:`root`}]},fixKind:`auto`},Ze=O(A({bindings:{aube:{docs:`https://aube.jdx.dev/security.html`,documentedDefault:!0,file:Je,keyPath:[`blockExoticSubdeps`],message:"Set `blockExoticSubdeps: true` in aube-workspace.yaml to block git/tarball URLs in transitive dependencies.",value:!0},pnpm:{docs:`https://pnpm.io/settings#blockexoticsubdeps`,documentedDefault:!0,file:Ye,keyPath:[`blockExoticSubdeps`],message:"Set `blockExoticSubdeps: true` in pnpm-workspace.yaml to block git/tarball URLs in transitive dependencies.",value:!0,versionNote:{configAvailableSince:`pnpm 10.26.0`}}},description:`Refuse to install transitive dependencies sourced from git or tarball URLs, which bypass registry integrity checking.`,docs:`https://pnpm.io/settings#blockexoticsubdeps`,id:`block-exotic-subdeps`,severity:`warn`,title:`Block exotic subdependencies`}),{npm:Xe}),{bunfig:I}=D,Qe={configAvailableSince:`bun 1.3.0`},$e={bindings:{bun:{check(e,t){let n=w(t,[`install`,`security`,`scanner`]);return typeof n==`string`&&n.length>0?{state:`ok`}:{actual:n,message:k('Configure `[install.security] scanner = "..."` in bunfig.toml (e.g. `@socketsecurity/bun-security-scanner`) to scan new packages on install.',Qe),state:`violation`}},docs:`https://bun.com/docs/pm/security-scanner-api`,file:I,fix(){return[{file:I,message:'Add `[install.security] scanner = "@socketsecurity/bun-security-scanner"` (or another bun-compatible scanner) to bunfig.toml.',op:`note`}]},fixKind:`advisory`}},description:`Bun supports a Security Scanner API that intercepts new packages at install time (e.g. Socket Firewall).`,docs:`https://github.com/bodadotsh/npm-security-best-practices#preinstall-scanners`,id:`bun-security-scanner`,severity:`info`,title:`Enable a bun install-time security scanner`},{yarnrc:et}=D,tt=A({bindings:{yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#checksumBehavior`,documentedDefault:`throw`,file:et,keyPath:[`checksumBehavior`],message:"Set `checksumBehavior: throw` in .yarnrc.yml to reject packages whose checksum does not match the lockfile.",value:`throw`}},description:`Throw on checksum mismatches so tampered or corrupted packages are never silently installed.`,docs:`https://yarnpkg.com/configuration/yarnrc#checksumBehavior`,id:`checksum-verification`,severity:`warn`,title:`Verify package checksums`}),L={aube:{configs:[D.aubeWorkspace.path],lockfiles:[`aube-lock.yaml`],reusesLockfiles:[`pnpm-lock.yaml`,`package-lock.json`,`npm-shrinkwrap.json`,`yarn.lock`,`bun.lock`,`bun.lockb`,`deno.lock`]},bun:{configs:[D.bunfig.path],lockfiles:[`bun.lock`,`bun.lockb`]},deno:{configs:[D.denoJson.path],lockfiles:[`deno.lock`]},npm:{configs:[],lockfiles:[`package-lock.json`,`npm-shrinkwrap.json`]},pnpm:{configs:[D.pnpmWorkspace.path],lockfiles:[`pnpm-lock.yaml`]},yarn:{configs:[D.yarnrc.path],lockfiles:[`yarn.lock`]}},nt={aube:`https://aube.en.dev/package-manager/lockfiles`,bun:`https://bun.com/docs/install/lockfile`,deno:`https://docs.deno.com/runtime/fundamentals/modules/#integrity-checking-and-lock-files`,npm:`https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json`,pnpm:`https://pnpm.io/git#lockfiles`,yarn:`https://yarnpkg.com/getting-started/qa#should-lockfiles-be-committed-to-the-repository`},R=e=>{let{lockfiles:t,reusesLockfiles:n}=L[e],[r]=t,i={kind:`fileGlob`,path:d(r)},a=[...t,...n??[]];return{check(e){return a.some(t=>e.exists(d(t)))?{state:`ok`}:{message:`No lockfile found. Generate and commit ${r}.`,state:`violation`}},docs:nt[e],file:i,fix(){return[{file:i,message:`Install dependencies to generate ${r}, then commit it.`,op:`ensureFileTracked`}]},fixKind:`advisory`}},rt={bindings:{aube:R(`aube`),bun:R(`bun`),deno:R(`deno`),npm:R(`npm`),pnpm:R(`pnpm`),yarn:R(`yarn`)},description:"Lockfiles pin the full dependency tree and integrity hashes, enabling reproducible, verifiable installs (e.g. `npm ci`).",docs:`https://github.com/bodadotsh/npm-security-best-practices#2-include-lockfiles`,id:`commit-lockfile`,severity:`error`,title:`Commit a lockfile`},{aubeWorkspace:it,pnpmWorkspace:at}=D,ot=e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&Object.keys(e).length>0,st=(e,t)=>{let n=w(e,[`overrides`]);return ot(n)?{actual:n,message:`Review \`overrides\` in ${t}. Overrides can replace transitive dependencies with arbitrary versions or forks.`,state:`violation`}:{state:`ok`}},z=(e,t,n)=>({check(e,n){return st(n,t)},docs:n,file:e,fix(){return[{file:e,message:"Review each entry in `overrides` — verify pinned versions address a known CVE and are not redirecting to untrusted packages.",op:`note`}]},fixKind:`advisory`}),ct={bindings:{aube:z(it,`aube-workspace.yaml`,`https://aube.jdx.dev/settings.html`),pnpm:z(at,`pnpm-workspace.yaml`,`https://pnpm.io/settings#overrides`)},description:`Flag dependency overrides that can replace transitive packages with arbitrary versions or forks — a supply-chain injection vector.`,docs:`https://pnpm.io/settings#overrides`,id:`dependency-overrides`,severity:`info`,title:`Review dependency overrides`},{npmrc:lt,pnpmWorkspace:B,yarnrc:ut,aubeWorkspace:V,bunfig:dt}=D,ft={configAvailableSince:`pnpm 10.3.0`,defaultSafeSince:`pnpm 11.0.0`},H=e=>k(e,ft),pt={check(e,t){let n=w(t,[`dangerouslyAllowAllBuilds`]);if(n===!0)return{actual:n,expected:!1,manualSteps:["Remove `dangerouslyAllowAllBuilds: true` from pnpm-workspace.yaml (or set it to `false`). Setting `strictDepBuilds: true` alone has no effect while the bypass remains."],message:H("`dangerouslyAllowAllBuilds: true` in pnpm-workspace.yaml bypasses strictDepBuilds — remove it (or set it to false) to restore lifecycle-script gating."),state:`violation`};let r=w(t,[`strictDepBuilds`]);return r===!0?{state:`ok`}:r===void 0?{actual:r,expected:!0,message:H("Set `strictDepBuilds: true` in pnpm-workspace.yaml to pin lifecycle-script gating across versions."),severity:`info`,state:`violation`}:{actual:r,expected:!0,message:H("Set `strictDepBuilds: true` in pnpm-workspace.yaml to block silent skips of un-approved dep builds."),state:`violation`}},docs:`https://pnpm.io/settings#strictdepbuilds`,file:B,fix(){return[{file:B,keyPath:[`strictDepBuilds`],op:`setKey`,value:!0}]},fixKind:`auto`},mt={check(e,t){let n=w(t,[`jailBuilds`]);if(n!==!0)return{actual:n,expected:!0,message:"Set `jailBuilds: true` in aube-workspace.yaml to sandbox build scripts.",state:`violation`};let r=w(t,[`strictDepBuilds`]);return r===!0?{state:`ok`}:{actual:r,expected:!0,message:"Set `strictDepBuilds: true` in aube-workspace.yaml to require explicit allow/deny for lifecycle scripts.",state:`violation`}},docs:`https://aube.jdx.dev/security.html`,file:V,fix(){return[{file:V,keyPath:[`jailBuilds`],op:`setKey`,value:!0},{file:V,keyPath:[`strictDepBuilds`],op:`setKey`,value:!0}]},fixKind:`auto`},ht=k('Set `ignoreScripts = true` under [install] in bunfig.toml — or set `"trustedDependencies": []` in package.json — to opt out of the curated allow-list (postinstall is already blocked for untrusted packages by default).',{configAvailableSince:`bun 1.2.0`}),gt={check(e,t){let n=w(t,[`install`,`ignoreScripts`]);if(n===!0)return{state:`ok`};let r;return e.packageJson&&(r=e.packageJson.trustedDependencies),r!==void 0&&r.length===0?{state:`ok`}:{actual:n,expected:!0,message:ht,state:`violation`}},docs:`https://bun.com/docs/pm/lifecycle`,file:dt,fix(){return[{file:dt,keyPath:[`install`,`ignoreScripts`],op:`setKey`,value:!0}]},fixKind:`auto`,severity:`info`},_t=O(A({bindings:{npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#ignore-scripts`,file:lt,keyPath:[`ignore-scripts`],message:"Set `ignore-scripts=true` in .npmrc to block dependency lifecycle scripts.",value:!0},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#enableScripts`,documentedDefault:!1,file:ut,keyPath:[`enableScripts`],message:"Set `enableScripts: false` in .yarnrc.yml to pin the policy across versions.",value:!1,versionNote:{configAvailableSince:`yarn 2.0.0`,defaultSafeSince:`yarn 4.14.0`}}},description:`Malicious postinstall scripts are a primary supply-chain attack vector. Prevent automatic execution of dependency lifecycle scripts.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#3-disable-lifecycle-scripts`,id:`disable-lifecycle-scripts`,severity:`error`,title:`Disable dependency lifecycle scripts`}),{aube:mt,bun:gt,pnpm:pt}),{npmrc:vt,yarnrc:yt}=D,bt="Set `enableStrictSsl: true` in .yarnrc.yml to enforce SSL certificate validation for registry connections.",xt={check(e,t){let n=w(t,[`enableStrictSsl`]);if(n===!1)return{actual:!1,expected:!0,message:bt,state:`violation`};let r=w(t,[`unsafeHttpWhitelist`]);return Array.isArray(r)&&r.length>0?{actual:r,expected:``,manualSteps:["Review and remove entries from `unsafeHttpWhitelist` in .yarnrc.yml. Each entry permits unencrypted HTTP to that hostname."],message:"`unsafeHttpWhitelist` in .yarnrc.yml allows unencrypted HTTP connections — remove entries or clear the list to enforce HTTPS-only registry access.",state:`violation`}:n===void 0?{actual:n,expected:!0,message:bt,severity:`info`,state:`violation`}:{state:`ok`}},docs:`https://yarnpkg.com/configuration/yarnrc#enableStrictSsl`,file:yt,fix(){return[{file:yt,keyPath:[`enableStrictSsl`],op:`setKey`,value:!0}]},fixKind:`auto`},St=O(A({bindings:{npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-ssl`,documentedDefault:!0,file:vt,keyPath:[`strict-ssl`],message:"Set `strict-ssl=true` in .npmrc to enforce SSL certificate validation.",value:!0}},description:`Require SSL certificate validation so registry traffic cannot be intercepted or tampered with.`,docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-ssl`,id:`enforce-strict-ssl`,severity:`warn`,title:`Enforce SSL for registry connections`}),{yarn:xt}),U=e=>{let t=e.packageJson;return t===void 0?!1:t.private!==!0&&typeof t.name==`string`&&t.name.length>0},{packageJson:Ct,denoJson:wt}=D,W={check(e){if(!U(e))return{state:`na`};let t;return e.packageJson&&({files:t}=e.packageJson),Array.isArray(t)&&t.length>0?{state:`ok`}:{actual:t,message:"Add a `files` allow-list to package.json to limit what gets published.",state:`violation`}},docs:`https://docs.npmjs.com/cli/v11/configuring-npm/package-json#files`,file:Ct,fix(){return[{file:Ct,message:'Add a `files` array to package.json (e.g. ["dist"]) and verify with `npm pack --dry-run`.',op:`note`}]},fixKind:`advisory`},Tt={bindings:{aube:W,bun:W,deno:{check(e,t){if(typeof w(t,[`name`])!=`string`)return{state:`na`};let n=w(t,[`publish`,`include`]);return Array.isArray(n)&&n.length>0?{state:`ok`}:{actual:n,message:"Add `publish.include` to deno.json to limit what gets published.",state:`violation`}},docs:`https://docs.deno.com/runtime/reference/cli/publish/#how-publishing-works`,file:wt,fix(){return[{file:wt,message:"Add `publish.include` (and/or `publish.exclude`) to deno.json.",op:`note`}]},fixKind:`advisory`},npm:W,pnpm:W,yarn:W},description:"An explicit `files` array in package.json restricts what gets published, preventing accidental inclusion of secrets or local files.",docs:`https://github.com/bodadotsh/npm-security-best-practices#11-review-published-files`,id:`files-field`,severity:`info`,title:`Declare a published files allow-list`},{pnpmWorkspace:Et,yarnrc:Dt,bunfig:Ot,denoJson:kt,aubeWorkspace:At}=D,jt={check(e,t){if(w(t,[`lock`,`frozen`])===!0)return{state:`ok`};let n=w(t,[`lock`]),r=n!==void 0&&(typeof n!=`object`||!n||Array.isArray(n)),i={actual:w(t,[`lock`,`frozen`]),expected:!0,message:"Set `lock.frozen: true` in deno.json for reproducible, verified installs.",state:`violation`};return r?Object.assign({},i,{manualSteps:["deno.json `lock` is set to a non-object value; writing `lock.frozen: true` under it would overwrite that value. Set it yourself, or make `lock` an object first."]}):i},docs:`https://docs.deno.com/runtime/fundamentals/configuration/#lock`,file:kt,fix(){return[{file:kt,keyPath:[`lock`,`frozen`],op:`setKey`,value:!0}]},fixKind:`auto`},Mt=O(A({bindings:{aube:{docs:`https://aube.en.dev/settings/`,documentedDefault:!0,file:At,keyPath:[`preferFrozenLockfile`],message:`aube defaults preferFrozenLockfile to true — set it explicitly in aube-workspace.yaml to pin the policy across versions.`,value:!0},bun:{docs:`https://bun.com/docs/runtime/bunfig#install-frozenlockfile`,file:Ot,keyPath:[`install`,`frozenLockfile`],message:"Set `frozenLockfile = true` under [install] in bunfig.toml so installs fail when the lockfile would change.",value:!0,versionNote:{configAvailableSince:`bun 0.6.10`}},pnpm:{docs:`https://pnpm.io/settings#frozenlockfile`,documentedDefault:!0,file:Et,keyPath:[`frozenLockfile`],message:`pnpm defaults frozenLockfile to true in CI — set it explicitly in pnpm-workspace.yaml to pin the policy outside CI too.`,value:!0},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#enableImmutableInstalls`,documentedDefault:!0,file:Dt,keyPath:[`enableImmutableInstalls`],message:"Set `enableImmutableInstalls: true` in .yarnrc.yml to pin the policy outside CI too.",value:!0,versionNote:{configAvailableSince:`yarn 2.0.0`,defaultSafeSince:`yarn 3.0.0 in CI`}}},description:`Refuse to mutate the lockfile on install so unexpected dependency changes fail loudly.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#2-include-lockfiles`,id:`frozen-lockfile`,severity:`warn`,title:`Freeze the lockfile`}),{deno:jt}),{pnpmWorkspace:G}=D,Nt={bindings:{pnpm:{check(e,t){let n=w(t,[`frozenStore`]);return n===!0?{state:`ok`}:{actual:n,message:"Consider enabling `frozenStore` in pnpm-workspace.yaml. Without it, the content-addressable store can be mutated after initial population, weakening integrity guarantees.",state:`violation`}},docs:`https://pnpm.io/settings#frozenstore`,file:G,fix(){return[{file:G,message:"Set `frozenStore: true` to prevent writes to the store after initial population — especially useful in CI and deployment pipelines.",op:`note`}]},fixKind:`advisory`}},description:`Recommend enabling frozenStore to prevent mutations to the content-addressable store, strengthening supply-chain integrity in CI and deploy environments.`,docs:`https://pnpm.io/settings#frozenstore`,id:`frozen-store`,severity:`info`,title:`Enable frozen store`},{yarnrc:Pt}=D,Ft=A({bindings:{yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#enableHardenedMode`,documentedDefault:!0,file:Pt,keyPath:[`enableHardenedMode`],message:"yarn enables hardened mode automatically only for pull requests on public repositories — set `enableHardenedMode: true` in .yarnrc.yml to extend end-to-end install verification to every install.",value:!0,versionNote:{configAvailableSince:`yarn 4.0.0`,defaultSafeSince:`yarn 4.0.0 (conditional: auto-enabled for PRs on public repositories)`}}},description:`Yarn 4's enableHardenedMode performs end-to-end checksum, lockfile, and version verification at install time.`,docs:`https://yarnpkg.com/configuration/yarnrc#enableHardenedMode`,id:`hardened-mode`,severity:`warn`,title:`Enable hardened mode (yarn)`}),It=1440,K=3*It,Lt=3*86400,q=It,{npmrc:Rt,pnpmWorkspace:zt,yarnrc:Bt,bunfig:Vt,denoJson:Ht,aubeWorkspace:Ut}=D,J=e=>typeof e==`number`&&e>0,Wt=A({bindings:{aube:{accept:J,docs:`https://aube.en.dev/settings/`,documentedDefault:q,file:Ut,keyPath:[`minimumReleaseAge`],message:`Set minimumReleaseAge (~${K} minutes for a 3-day cooldown) in aube-workspace.yaml.`,value:K},bun:{accept:J,docs:`https://bun.com/docs/runtime/bunfig#install-minimumreleaseage`,file:Vt,keyPath:[`install`,`minimumReleaseAge`],message:`Set install.minimumReleaseAge (bun) to ~${Lt} seconds to quarantine brand-new releases.`,value:Lt,versionNote:{configAvailableSince:`bun 1.3.0`}},deno:{accept:e=>typeof e==`number`?e>0:typeof e==`string`?e!==``&&e!==`0`:typeof e==`object`&&!!e,docs:`https://docs.deno.com/runtime/reference/deno_json/`,file:Ht,keyPath:[`minimumDependencyAge`],message:`Set minimumDependencyAge (e.g. "P3D" for a 3-day cooldown) in deno.json.`,value:`P3D`},npm:{accept:J,docs:`https://docs.npmjs.com/cli/v11/using-npm/config#min-release-age`,file:Rt,keyPath:[`min-release-age`],message:`Set min-release-age to ~3 days to quarantine brand-new releases.`,value:3,versionNote:{configAvailableSince:`npm 11.10.0`}},pnpm:{accept:J,docs:`https://pnpm.io/settings#minimumreleaseage`,documentedDefault:q,file:zt,keyPath:[`minimumReleaseAge`],message:`Set minimumReleaseAge (~${K} minutes for a 3-day cooldown) in pnpm-workspace.yaml.`,value:K,versionNote:{configAvailableSince:`pnpm 10.16.0`,defaultSafeSince:`pnpm 11.0.0 (1440 minutes)`}},yarn:{accept:J,docs:`https://yarnpkg.com/configuration/yarnrc#npmMinimalAgeGate`,documentedDefault:q,file:Bt,keyPath:[`npmMinimalAgeGate`],message:`Set npmMinimalAgeGate (~${K} minutes for a 3-day cooldown) in .yarnrc.yml.`,value:K,versionNote:{configAvailableSince:`yarn 4.10.0`,defaultSafeSince:`yarn 4.15.0 (1440 minutes)`}}},description:`Refuse to install releases newer than a cooldown window so freshly published (possibly compromised) versions are skipped.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#set-minimal-release-age`,id:`minimum-release-age`,severity:`warn`,title:`Set a minimum release age`}),{pnpmWorkspace:Gt}=D,Kt=e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&Object.keys(e).length>0,qt={bindings:{pnpm:{check(e,t){let n=w(t,[`namedRegistries`]);return Kt(n)?{actual:n,message:"Review `namedRegistries` in pnpm-workspace.yaml. Custom registry mappings can redirect package resolution away from the public registry, enabling dependency confusion attacks.",state:`violation`}:{state:`ok`}},docs:`https://pnpm.io/settings#namedregistries`,file:Gt,fix(){return[{file:Gt,message:`Verify each named registry points to a trusted source and that scoped packages cannot be hijacked via a public registry of the same name.`,op:`note`}]},fixKind:`advisory`}},description:`Flag named registry mappings that redirect package resolution to custom registries, which may enable dependency confusion attacks.`,docs:`https://pnpm.io/settings#namedregistries`,id:`named-registries`,severity:`info`,title:`Review named registry mappings`},{aubeWorkspace:Jt}=D,Yt=A({bindings:{aube:{docs:`https://aube.jdx.dev/security.html`,file:Jt,keyPath:[`paranoid`],message:"Set `paranoid: true` in aube-workspace.yaml to enable the strict-security bundle (trustPolicy, jailBuilds, minimumReleaseAgeStrict, strictStoreIntegrity, strictDepBuilds, advisoryCheck).",value:!0}},description:`Activate the strict-security setting bundle that forces trustPolicy, jailBuilds, minimumReleaseAgeStrict, strictStoreIntegrity, strictDepBuilds, and advisoryCheck on in one switch.`,docs:`https://aube.jdx.dev/security.html`,id:`paranoid-mode`,severity:`info`,title:`Enable paranoid security mode`}),{pnpmWorkspace:Xt}=D,Zt=e=>typeof e==`object`&&!!e&&!Array.isArray(e)&&Object.keys(e).length>0,Qt={bindings:{pnpm:{check(e,t){let n=w(t,[`patchedDependencies`]);return Zt(n)?{actual:n,message:"Review `patchedDependencies` in pnpm-workspace.yaml. Patches modify dependency source code directly and bypass registry integrity checks.",state:`violation`}:{state:`ok`}},docs:`https://pnpm.io/settings#patcheddependencies`,file:Xt,fix(){return[{file:Xt,message:"Review each entry in `patchedDependencies` — verify patches address a known issue and have not been tampered with.",op:`note`}]},fixKind:`advisory`}},description:`Flag patched dependencies whose source code is modified by local patch files, bypassing registry integrity verification.`,docs:`https://pnpm.io/settings#patcheddependencies`,id:`patched-dependencies`,severity:`info`,title:`Review patched dependencies`},{npmrc:$t,pnpmWorkspace:en,yarnrc:tn,bunfig:nn,denoJson:rn}=D,an=e=>!Array.isArray(e),on=e=>(e.split(`-`,1)[0]??e).split(`.`).some(e=>e===`x`||e===`X`||e===`*`),sn=/[\^~]|>=|<=|<|>|\|\||\s/u,cn=e=>e===``||e===`*`||sn.test(e)||on(e),ln=e=>{if(e.startsWith(`http://`)||e.startsWith(`https://`))return!1;let t=e.lastIndexOf(`@`);return t<=0?!1:cn(e.slice(t+1))},un=e=>{let t=[];for(let[n,r]of Object.entries(e))typeof r==`string`&&ln(r)&&t.push(`${n}=${r}`);return t},dn=e=>{let t=e.slice(0,3).join(`, `),n=``;return e.length>3&&(n=` (and ${e.length-3} more)`),{message:`${e.length} deno imports use semver ranges: ${t}${n}. Use \`deno add --save-exact\` or pin manually.`,state:`violation`}},fn={state:`ok`},pn=e=>{let t=w(e,[`imports`]);if(!(typeof t!=`object`||!t||!an(t)))return t},mn={check(e,t){let n=pn(t);if(!n)return fn;let r=un(n);return r.length===0?fn:dn(r)},docs:`https://docs.deno.com/runtime/reference/cli/add/`,file:rn,fix(){return[{file:rn,message:"Run `deno add --save-exact <pkg>` for each ranged import in deno.json, or rewrite the `imports` entries to use exact versions.",op:`note`}]},fixKind:`advisory`},hn=O(A({bindings:{bun:{docs:`https://bun.com/docs/runtime/bunfig#install-exact`,file:nn,keyPath:[`install`,`exact`],message:"Set `exact = true` under [install] in bunfig.toml to pin exact versions.",value:!0,versionNote:{configAvailableSince:`bun 0.6.10`}},npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#save-exact`,extraFix:[{keyPath:[`save-prefix`],value:``}],file:$t,keyPath:[`save-exact`],message:"Set `save-exact=true` (and `save-prefix=`) in .npmrc to pin exact versions.",value:!0},pnpm:{docs:`https://pnpm.io/settings#saveprefix`,file:en,keyPath:[`savePrefix`],message:"Set `savePrefix: ''` in pnpm-workspace.yaml to pin exact versions.",value:``},yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#defaultSemverRangePrefix`,file:tn,keyPath:[`defaultSemverRangePrefix`],message:"Set `defaultSemverRangePrefix: ''` in .yarnrc.yml to pin exact versions.",value:``,versionNote:{configAvailableSince:`yarn 2.0.0`}}},description:`Semver ranges (^, ~) auto-adopt new releases, including compromised ones. Save exact versions by default.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#1-pin-dependency-versions`,id:`pin-exact-versions`,severity:`error`,title:`Pin exact dependency versions`}),{deno:mn}),{npmrc:gn,yarnrc:_n}=D,Y={file:gn,keyPath:[`provenance`],message:"Set `provenance=true` (and publish from CI) to attest releases.",value:!0},vn=A({applies:U,bindings:{bun:Object.assign({},Y,{docs:`https://github.com/oven-sh/bun/issues/15601`,message:"Set `provenance=true` in .npmrc and publish via `bunx npm publish` from CI — `bun publish` does not emit provenance attestations natively (tracking: oven-sh/bun#15601)."}),npm:Object.assign({},Y,{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#provenance`,versionNote:{configAvailableSince:`npm 9.5.0`}}),pnpm:Object.assign({},Y,{docs:`https://pnpm.io/cli/publish`}),yarn:{docs:`https://yarnpkg.com/configuration/yarnrc#npmPublishProvenance`,file:_n,keyPath:[`npmPublishProvenance`],message:"Set `npmPublishProvenance: true` (and publish from CI) to attest releases.",value:!0}},description:`Provenance statements (via Sigstore) tie a release to its source and build, letting consumers verify it was not tampered with.`,docs:`https://github.com/bodadotsh/npm-security-best-practices#10-generate-provenance-statements`,id:`provenance`,severity:`warn`,title:`Publish with provenance`}),{packageJson:yn}=D,X={check(e){if(!U(e))return{state:`na`};let t;return e.packageJson&&e.packageJson.publishConfig&&({access:t}=e.packageJson.publishConfig),t===`public`||t===`restricted`?{state:`ok`}:{actual:t,message:"Set `publishConfig.access` in package.json to `public` or `restricted` to declare publish scope explicitly.",state:`violation`}},docs:`https://docs.npmjs.com/cli/v11/configuring-npm/package-json#publishconfig`,file:yn,fix(){return[{file:yn,message:'Add `"publishConfig": { "access": "public" }` (or `"restricted"`) to package.json so publishes never default unexpectedly.',op:`note`}]},fixKind:`advisory`},bn={bindings:{aube:X,bun:X,npm:X,pnpm:X,yarn:X},description:"Set `publishConfig.access` so a misconfigured scope or registry never accidentally publishes an internal package publicly.",docs:`https://github.com/bodadotsh/npm-security-best-practices#for-maintainers`,id:`publish-access`,severity:`info`,title:`Declare publish access explicitly`},{pnpmWorkspace:xn}=D,Sn={bindings:{pnpm:{check(e,t){let n=w(t,[`useRunningStoreServer`]);return n===!0?{actual:n,message:"Review `useRunningStoreServer` in pnpm-workspace.yaml. Delegating store operations to an external process introduces a trust boundary — the server process can serve tampered packages.",state:`violation`}:{state:`ok`}},docs:`https://pnpm.io/settings#userunningStoreserver`,file:xn,fix(){return[{file:xn,message:`Verify the store server is running in a trusted environment and that its communication channel is not exposed to untrusted networks.`,op:`note`}]},fixKind:`advisory`}},description:`Flag use of an external store server process, which introduces a trust boundary where tampered packages could be served.`,docs:`https://pnpm.io/settings#userunningStoreserver`,id:`store-server`,severity:`info`,title:`Review store server usage`},{npmrc:Cn}=D,wn=A({bindings:{npm:{docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-allow-scripts`,file:Cn,keyPath:[`strict-allow-scripts`],message:"Set `strict-allow-scripts=true` in .npmrc to block packages with unapproved install scripts.",value:!0}},description:`Turn install-script policy warnings into hard errors so unapproved lifecycle scripts block installation.`,docs:`https://docs.npmjs.com/cli/v11/using-npm/config#strict-allow-scripts`,id:`strict-allow-scripts`,severity:`warn`,title:`Require explicit script allow-listing`}),{aubeWorkspace:Tn}=D,En=A({bindings:{aube:{docs:`https://aube.jdx.dev/settings/`,file:Tn,keyPath:[`minimumReleaseAgeStrict`],message:"Set `minimumReleaseAgeStrict: true` in aube-workspace.yaml to make the release age gate a hard block.",value:!0}},description:`Make the minimum release age gate a hard failure instead of falling back to the next-oldest satisfying version.`,docs:`https://aube.jdx.dev/security.html`,id:`strict-release-age`,severity:`info`,title:`Enforce strict release age gate`}),{aubeWorkspace:Dn}=D,On=A({bindings:{aube:{docs:`https://aube.jdx.dev/settings/`,file:Dn,keyPath:[`strictStoreIntegrity`],message:"Set `strictStoreIntegrity: true` in aube-workspace.yaml to refuse tarballs that lack integrity metadata.",value:!0}},description:`Refuse to import tarballs from the registry when the packument lacks a dist.integrity field, preventing silent integrity bypass.`,docs:`https://aube.jdx.dev/security.html`,id:`strict-store-integrity`,severity:`warn`,title:`Require tarball integrity metadata`}),{aubeWorkspace:kn,pnpmWorkspace:An}=D,jn={"advisory-check":ze,"approved-git-repos":Ve,"audit-suppression":Ge,"block-auto-install":qe,"block-exotic-subdeps":Ze,"bun-security-scanner":$e,"checksum-verification":tt,"commit-lockfile":rt,"dependency-overrides":ct,"disable-lifecycle-scripts":_t,"enforce-strict-ssl":St,"files-field":Tt,"frozen-lockfile":Mt,"frozen-store":Nt,"hardened-mode":Ft,"minimum-release-age":Wt,"named-registries":qt,"paranoid-mode":Yt,"patched-dependencies":Qt,"pin-exact-versions":hn,provenance:vn,"publish-access":bn,"store-server":Sn,"strict-allow-scripts":wn,"strict-release-age":En,"strict-store-integrity":On,"trust-policy":A({bindings:{aube:{docs:`https://aube.jdx.dev/security.html`,file:kn,keyPath:[`trustPolicy`],message:"Set `trustPolicy: no-downgrade` in aube-workspace.yaml to block packages whose trust level has decreased.",value:`no-downgrade`},pnpm:{docs:`https://pnpm.io/settings#trustpolicy`,file:An,keyPath:[`trustPolicy`],message:"Set `trustPolicy: no-downgrade` in pnpm-workspace.yaml to block packages whose trust level has decreased.",value:`no-downgrade`,versionNote:{configAvailableSince:`pnpm 10.21.0`}}},description:`Fail installation when a package trust level has decreased compared to previous releases, catching publisher credential downgrades.`,docs:`https://pnpm.io/settings#trustpolicy`,id:`trust-policy`,severity:`warn`,title:`Enforce trust policy on dependency updates`})},Z=Object.values(jn),Mn=(e,t)=>{let n=new Set,r=new Set;for(let i of e)(t.has(i.id)||r.has(i.id))&&n.add(i.id),r.add(i.id);return[...n]},Nn=(e,t)=>{let n=[];for(let r of Object.keys(e))t.has(r)||n.push(r);return n},Pn=(e,t,n=[])=>{let r=new Set(t.map(e=>e.id)),i=e.customRules??[],a=Mn(i,r),o=new Set(r);for(let e of n)o.add(e);for(let e of i)o.add(e.id);return{duplicates:a,unknown:Nn(e.rules??{},o)}},Fn=e=>{let t=``;throw e.length>1&&(t=`s`),new p(`siro.config: unknown rule id${t} in rules: ${e.map(e=>`'${e}'`).join(`, `)}`)},In=(e,t)=>{if(e===void 0)return;let{unknown:n}=Pn(e,Z,(t??[]).map(e=>e.id));n.length>0&&Fn(n)},[Q]=[],Ln=i.fallback(i.optional(i.string()),Q),Rn=i.looseObject({files:i.fallback(i.optional(i.array(i.string())),Q),name:Ln,packageManager:Ln,private:i.optional(i.fallback(i.boolean(),!0)),publishConfig:i.fallback(i.optional(i.looseObject({access:i.fallback(i.optional(i.union([i.literal(`public`),i.literal(`restricted`)])),Q)})),Q),trustedDependencies:i.fallback(i.optional(i.array(i.string())),Q)}),zn=e=>{let t=i.safeParse(Rn,e);if(t.success)return t.output},$={exists(e){try{return a(e,o.F_OK),!0}catch(e){if(x(e)&&e.code===`ENOENT`)return!1;throw e}},readText(e){try{return s(e,`utf8`)}catch(e){if(x(e)&&e.code===`ENOENT`)return;throw e}}},Bn=(e,t)=>u(c.join(e,t)),Vn=e=>{try{return JSON.parse(e)}catch(e){let t=String(e);throw e instanceof Error&&(t=e.message),new p(`package.json: invalid JSON — ${t}`)}},Hn=e=>zn(Vn(e.trim())),Un=(e,t=$)=>{let n=n=>t.readText(Bn(e,n)),r=n=>t.exists(Bn(e,n)),i=n(d(`package.json`)),a;return i!==void 0&&(a=Hn(i)),{exists:r,packageJson:a,readText:n,root:e}},Wn=[`siro.config.ts`,`siro.config.mjs`,`siro.config.js`],Gn=i.strictObject({customRules:i.optional(i.array(i.custom(()=>!0))),pms:i.optional(i.pipe(i.array(i.picklist(g)),i.minLength(1,`must not be empty (omit the key to auto-detect, or list at least one PM)`))),reporters:i.optional(i.array(i.custom(y,`must be a { name, format } reporter`))),rules:i.optional(i.record(i.string(),i.union([i.picklist(_),i.literal(`off`)])))},`unknown config key (check for a typo)`),Kn=e=>e instanceof Error?e.message:String(e),qn=e=>e.map(e=>{let t=(e.path??[]).map(e=>e.key).filter(e=>typeof e==`string`||typeof e==`number`).join(`.`);return t?`${t}: ${e.message}`:e.message}).join(`; `),Jn=(e,t,n)=>e.import(t).catch(e=>{throw new p(`Failed to load ${n}: ${Kn(e)}`)}),Yn=e=>typeof e==`object`&&e&&`default`in e?e.default:e,Xn=(e,t)=>{if(typeof e!=`object`||!e||Array.isArray(e)){let n=typeof e;throw Array.isArray(e)&&(n=`an array`),new p(`${t} must export a config object (got ${n}).`)}return e},Zn=(e,t)=>{let n=i.safeParse(Gn,e);if(!n.success)throw new p(`${t}: ${qn(n.issues)}`);return n.output},Qn=(e,t)=>{let{duplicates:n}=Pn(e,Z);if(n.length>0){let e=``;throw n.length>1&&(e=`s`),new p(`${t}: duplicate customRule id${e}: ${n.map(e=>`'${e}'`).join(`, `)}. Pick a unique id (or use 'rules' to override an existing rule's severity).`)}},$n=(e,t,n)=>Jn(e,t,n).then(e=>{let t=Zn(Xn(Yn(e),n),n);return Qn(t,n),t}),er=(e,t=$)=>{let n=l(e,{moduleCache:!1}),r=Wn.find(n=>t.exists(u(c.join(e,n))));return r===void 0?Promise.resolve(void 0):$n(n,u(c.join(e,r)),r)},tr=(e,t)=>{let n=new Set,r=new Set;for(let i of e)(t.has(i.id)||n.has(i.id))&&r.add(i.id),n.add(i.id);return r},nr=e=>{let t=[...e],n=`rule id collides`;throw t.length>1&&(n=`rule ids collide`),new p(`customRules: ${n} with builtin, config, or another programmatic rule: ${t.map(e=>`'${e}'`).join(`, `)}. Pick a unique id (or use 'rules' to override an existing rule's severity).`)},rr=(e,t,n)=>{if(t===void 0||t.length===0)return e;let r=tr(t,new Set([...e.map(e=>e.id),...(n??[]).map(e=>e.id)]));return r.size>0&&nr(r),[...e,...t]},ir=(e,t,n)=>{for(let r of e){let e=n.get(r);if(e!==void 0)throw TypeError(`detection signal '${r}' is claimed by both '${e}' and '${t}'`);n.set(r,t)}},ar=(()=>{let e=new Map,t=new Map;for(let n of g){let{lockfiles:r,configs:i}=L[n],a=[...r,...i];ir(a,n,t),e.set(n,a)}return e})(),or=(e,t)=>{let n;if(e.packageJson&&(n=e.packageJson.packageManager),typeof n==`string`){let e=ae(n);e!==void 0&&t.add(e)}},sr=e=>{let t=new Set;or(e,t);for(let n of g){let r=ar.get(n);r&&r.some(t=>e.exists(d(t)))&&t.add(n)}return g.filter(e=>t.has(e))},cr=(e,t,n)=>{if(n){if(t.pmOverride)throw new m(`--pm ${t.pmOverride} is not in siro.config.ts pms (${n.join(`, `)}). Adjust the config or the flag.`);let r=`No package manager detected, and siro.config.ts restricts pms to ${n.join(`, `)}. Adjust the config or remove the restriction.`;throw e.length>0&&(r=`Detected PMs (${e.join(`, `)}) do not match siro.config.ts pms (${n.join(`, `)}). Adjust the config or remove the restriction.`),new m(r)}throw new m(`No package manager detected. Pass --pm <${g.join(`|`)}> to be explicit.`)},lr=(e,t)=>{let n=sr(e);t.pmOverride&&(n=[t.pmOverride]);let{allowed:r}=t,i=n;if(r){let e=new Set(r);i=n.filter(t=>e.has(t))}return{detected:n,pms:i}},ur=(e,t)=>{let{detected:n,pms:r}=lr(e,t);if(r.length>0)return r;throw cr(n,t,t.allowed)},dr=(e,t)=>{let n;e&&(n=e.customRules);let r=rr(Z,t,n);return In(e,t),Ae(r,e)},fr=(e,t)=>{let{cwd:n,fs:r,pm:i,customRules:a}=e,o=Un(n,r),s;return t&&(s=t.pms),{ctx:o,pms:ur(o,{allowed:s,pmOverride:i}),ruleSet:dr(t,a),userConfig:t}},pr=e=>er(e.cwd,e.fs).then(t=>fr(e,t)),mr=e=>{let{ctx:t,pms:n,ruleSet:r,parseConfig:i,onViolation:a}=e;for(let e of r)for(let r of n){let n=e.bindings[r];if(n!==void 0){let{parsed:o}=i(t,n.file),s=n.check(t,o);s.state===`violation`&&a({binding:n,pm:r,rule:e,status:s})}}},hr=Object.freeze({parsed:Object.freeze({})}),gr=(e,t,n)=>{let r=e.readText(t.path)??``,i={parsed:h(t.path,()=>n.codecFor(t.kind).parse(r))};return n.cache.set(n.cacheKey,i),i},_r=e=>{let t=new Map;return(n,r)=>{if(r.kind===`fileGlob`)return hr;let i=`${r.kind}:${r.path}`,a=t.get(i);return a===void 0?gr(n,r,{cache:t,cacheKey:i,codecFor:e}):a}},vr=(e,t,n)=>e.severity??t.severity??n.severity,yr=e=>{if(e!==void 0&&e.length>0)return e},br=(e,t)=>{let{rule:n,pm:r,binding:i,status:a}=e,o=vr(a,i,n),s=yr(a.manualSteps),c;i.file.kind!==`fileGlob`&&(c=i.file.path);let l=[];return s===void 0&&(l=i.fix(t)),{actual:a.actual,docs:i.docs??n.docs,expected:a.expected,file:c,fix:l,fixable:i.fixKind===`auto`&&s===void 0,manualSteps:s,message:a.message,pm:r,ruleId:n.id,severity:o}},xr=e=>{let{ctx:t,pms:n,ruleSet:r,codecFor:i}=e,a=[],o={error:0,info:0,warn:0};return mr({ctx:t,onViolation:e=>{let n=br(e,t);a.push(n),o[n.severity]+=1},parseConfig:_r(i),pms:n,ruleSet:r}),{findings:a,summary:o}},Sr=(e,t)=>{if(e===void 0)return t.get(`pretty`);if(typeof e==`string`)return t.get(e);if(!y(e))throw new m(`The 'reporter' option must be a name or a { name, format } object.`);return e},Cr=e=>{for(let t of e??[])if(!y(t))throw new m(`A reporter in 'reporters' needs a string 'name' and a 'format' function.`)},wr=(e,t)=>{let n=Sr(e,t);if(n===void 0)throw new m(`Unknown reporter: ${String(e)} (available: ${t.list().join(`, `)})`);return n},Tr=e=>{let t=xr({codecFor:Te,ctx:e.ctx,pms:e.pms,ruleSet:e.ruleSet}),n=e.options.severity??`info`,r=e.options.severity??`error`;return e.reporter.format(be(t,n),e.io),S(t,r)},Er=(e,t)=>pr({customRules:e.customRules,cwd:e.cwd,fs:e.fs,pm:e.pm}).then(({userConfig:n,ctx:r,pms:i,ruleSet:a})=>{Cr(e.reporters);let o=[];n&&n.reporters&&(o=n.reporters);let s=ve([...e.reporters??[],...o]);return Tr({ctx:r,io:t,options:e,pms:i,reporter:wr(e.reporter,s),ruleSet:a})}),Dr={stderr:e=>process.stderr.write(`${e}\n`),stdout:e=>process.stdout.write(`${e}\n`)};export{d as A,v as C,p as D,m as E,f as O,_ as S,h as T,ue as _,L as a,y as b,A as c,S as d,be as f,he as g,ve as h,$ as i,u as k,D as l,_e as m,Er as n,O as o,x as p,sr as r,k as s,Dr as t,w as u,le as v,re as w,g as x,ce as y};
4
+ //# sourceMappingURL=node-io-C_ERSDGJ.mjs.map
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@pho9ubenaa/siro",
3
+ "version": "0.1.0-rc",
4
+ "description": "Security best-practices linter for the npm ecosystem (npm, pnpm, deno, bun, yarn, aube).",
5
+ "keywords": [
6
+ "aube",
7
+ "bun",
8
+ "cli",
9
+ "deno",
10
+ "hardening",
11
+ "lint",
12
+ "linter",
13
+ "npm",
14
+ "pnpm",
15
+ "security",
16
+ "supply-chain",
17
+ "yarn"
18
+ ],
19
+ "homepage": "https://github.com/pHo9UBenaA/siro#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/pHo9UBenaA/siro/issues"
22
+ },
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/pHo9UBenaA/siro.git"
27
+ },
28
+ "bin": {
29
+ "siro": "./dist/cli.js"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "!dist/*.map",
34
+ "README.md",
35
+ "LICENSE",
36
+ "CHANGELOG.md"
37
+ ],
38
+ "type": "module",
39
+ "exports": {
40
+ ".": {
41
+ "types": "./dist/index.d.mts",
42
+ "import": "./dist/index.mjs"
43
+ }
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "dependencies": {
49
+ "cac": "7.0.0",
50
+ "ini": "7.0.0",
51
+ "jiti": "2.7.0",
52
+ "picocolors": "1.1.1",
53
+ "smol-toml": "1.6.1",
54
+ "valibot": "1.4.1",
55
+ "yaml": "2.9.0"
56
+ },
57
+ "devDependencies": {
58
+ "@commitlint/cli": "21.0.2",
59
+ "@commitlint/config-conventional": "21.0.2",
60
+ "@types/ini": "4.1.1",
61
+ "@types/node": "25.9.1",
62
+ "@vitest/coverage-v8": "4.1.7",
63
+ "knip": "6.15.0",
64
+ "memfs": "4.57.3",
65
+ "oxfmt": "0.53.0",
66
+ "oxlint": "1.68.0",
67
+ "tinybench": "6.0.2",
68
+ "tsdown": "0.22.1",
69
+ "typedoc": "0.28.19",
70
+ "typedoc-plugin-markdown": "4.11.0",
71
+ "typescript": "6.0.3",
72
+ "unrun": "0.3.1",
73
+ "vitest": "4.1.7"
74
+ },
75
+ "engines": {
76
+ "node": ">=20"
77
+ },
78
+ "scripts": {
79
+ "bench": "node ./scripts/bench/run.mjs",
80
+ "build": "node ./scripts/gen/version.mjs && tsdown && node ./scripts/gen/bin-wrapper.mjs",
81
+ "check": "oxlint --deny-warnings && oxfmt --check",
82
+ "format": "oxfmt --write",
83
+ "gen": "pnpm gen:version && pnpm gen:comparison && pnpm gen:rules && pnpm gen:api",
84
+ "gen:api": "typedoc",
85
+ "gen:comparison": "node ./scripts/gen/comparison.mjs",
86
+ "gen:rule": "node ./scripts/gen/rule.mjs",
87
+ "gen:rules": "node ./scripts/gen/rules.mjs",
88
+ "gen:version": "node ./scripts/gen/version.mjs",
89
+ "knip": "knip",
90
+ "test": "vitest run",
91
+ "test:coverage": "vitest run --coverage",
92
+ "test:watch": "vitest",
93
+ "typecheck": "tsc --noEmit && node ./scripts/check/layers.mjs"
94
+ }
95
+ }