pmsec 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -16
- package/package.json +1 -1
- package/src/cli.mjs +39 -34
package/README.md
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
# pmsec (Node)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
land in your machine within hours of upload.
|
|
3
|
+
Zero-config install-time supply-chain hardening for npm, pnpm, yarn, bun,
|
|
4
|
+
cargo, mise, and uv. One command flips on every safe-by-default knob each
|
|
5
|
+
package manager exposes — install cooldown, signature trust policy, lockfile
|
|
6
|
+
re-verification, build-script attestation, and more.
|
|
8
7
|
|
|
9
8
|
## Install
|
|
10
9
|
|
|
11
10
|
```bash
|
|
12
|
-
npx pmsec
|
|
13
|
-
npx pmsec
|
|
14
|
-
npx pmsec
|
|
11
|
+
npx pmsec enable
|
|
12
|
+
npx pmsec check
|
|
13
|
+
npx pmsec disable
|
|
15
14
|
```
|
|
16
15
|
|
|
17
16
|
```bash
|
|
18
|
-
uvx pmsec
|
|
19
|
-
uvx pmsec
|
|
20
|
-
uvx pmsec
|
|
17
|
+
uvx pmsec enable
|
|
18
|
+
uvx pmsec check
|
|
19
|
+
uvx pmsec disable
|
|
21
20
|
```
|
|
22
21
|
|
|
23
22
|
If your environment already enforces cooldown (or routes through a proxy
|
|
@@ -28,18 +27,18 @@ npx --registry=https://registry.npmjs.org/ --min-release-age=0 pmsec check
|
|
|
28
27
|
uvx --index https://pypi.org/simple --exclude-newer-package pmsec=2099-01-01 pmsec check
|
|
29
28
|
```
|
|
30
29
|
|
|
31
|
-
Zero
|
|
30
|
+
Zero runtime dependencies, ESM, requires Node 22+.
|
|
32
31
|
|
|
33
32
|
## Commands
|
|
34
33
|
|
|
35
34
|
| Command | Description |
|
|
36
35
|
| --- | --- |
|
|
37
|
-
| `pmsec
|
|
38
|
-
| `pmsec
|
|
39
|
-
| `pmsec
|
|
36
|
+
| `pmsec enable` | Write the hardening bundle (3-day cooldown + per-tool extras) to every selected tool's user config. |
|
|
37
|
+
| `pmsec check` | Read each tool's config; exit 1 if any row is missing or below the bundled value. |
|
|
38
|
+
| `pmsec disable` | Remove every key the bundle set; other keys in the file are preserved. |
|
|
40
39
|
| `pmsec --version` | Print the installed pmsec version. |
|
|
41
40
|
|
|
42
|
-
Options: `--tool npm,pnpm,yarn,bun,cargo,mise,uv`, `--json`.
|
|
41
|
+
Options: `--tool npm,pnpm,yarn,bun,cargo,mise,uv`, `--days N` (override the 3-day default), `--json`.
|
|
43
42
|
|
|
44
43
|
When the target file is owned by another user (typical: `~/.npmrc` left
|
|
45
44
|
root-owned by an old `sudo npm config set`), the write fails with `EACCES`
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -9,7 +9,9 @@ import * as mise from "./tools/mise.mjs";
|
|
|
9
9
|
import * as uv from "./tools/uv.mjs";
|
|
10
10
|
|
|
11
11
|
const TOOLS = [npm, pnpm, yarn, bun, cargo, mise, uv];
|
|
12
|
-
|
|
12
|
+
// Default cooldown for the hardening bundle. Override per-invocation with
|
|
13
|
+
// `--days N`; the default tracks the safest value we'd recommend.
|
|
14
|
+
const BUNDLE_DAYS = 3;
|
|
13
15
|
export const VERSION = JSON.parse(
|
|
14
16
|
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
15
17
|
).version;
|
|
@@ -17,45 +19,52 @@ export const VERSION = JSON.parse(
|
|
|
17
19
|
const USAGE = `pmsec <command> [options]
|
|
18
20
|
|
|
19
21
|
Zero-config install-time supply-chain hardening across npm, pnpm, yarn,
|
|
20
|
-
bun, cargo, mise, uv.
|
|
21
|
-
|
|
22
|
-
attestation re-verification, ...).
|
|
22
|
+
bun, cargo, mise, uv. \`set\` flips on every safe-by-default key each
|
|
23
|
+
tool exposes (cooldown, audit-level, trust-policy, hardened mode,
|
|
24
|
+
attestation re-verification, ...). No knobs.
|
|
23
25
|
|
|
24
26
|
Commands:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
enable Apply the hardening bundle to all selected tools
|
|
28
|
+
disable Remove the hardening bundle from selected tools
|
|
29
|
+
check Verify the bundle is in place (exit 1 if anything missing)
|
|
28
30
|
|
|
29
31
|
Options:
|
|
30
32
|
--tool TOOL[,TOOL] Restrict to specific tools (npm,pnpm,yarn,bun,cargo,mise,uv)
|
|
31
|
-
--
|
|
33
|
+
--days N Override cooldown days (default 3)
|
|
32
34
|
--json Emit JSON output
|
|
33
35
|
-V, --version Show version
|
|
34
36
|
-h, --help Show this help
|
|
35
37
|
|
|
36
38
|
Examples:
|
|
37
|
-
npx pmsec
|
|
38
|
-
npx pmsec
|
|
39
|
-
npx pmsec
|
|
39
|
+
npx pmsec enable
|
|
40
|
+
npx pmsec enable --days 7
|
|
41
|
+
npx pmsec check
|
|
42
|
+
npx pmsec disable --tool npm
|
|
40
43
|
`;
|
|
41
44
|
|
|
45
|
+
function parseDays(raw) {
|
|
46
|
+
const n = Number(raw);
|
|
47
|
+
if (!Number.isInteger(n) || n < 1) throw new Error(`--days must be a positive integer (got "${raw}")`);
|
|
48
|
+
return n;
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
function parse(argv) {
|
|
43
|
-
const opts = { command: null,
|
|
52
|
+
const opts = { command: null, json: false, only: null, days: BUNDLE_DAYS, help: false, version: false };
|
|
44
53
|
const positional = [];
|
|
45
54
|
for (let i = 0; i < argv.length; i++) {
|
|
46
55
|
const a = argv[i];
|
|
47
56
|
if (a === "-h" || a === "--help") opts.help = true;
|
|
48
57
|
else if (a === "-V" || a === "--version") opts.version = true;
|
|
49
58
|
else if (a === "--json") opts.json = true;
|
|
50
|
-
else if (a === "--min") opts.min = Number(argv[++i]);
|
|
51
|
-
else if (a.startsWith("--min=")) opts.min = Number(a.slice(6));
|
|
52
59
|
else if (a === "--tool") opts.only = argv[++i].split(",");
|
|
53
60
|
else if (a.startsWith("--tool=")) opts.only = a.slice(7).split(",");
|
|
61
|
+
else if (a === "--days") opts.days = parseDays(argv[++i]);
|
|
62
|
+
else if (a.startsWith("--days=")) opts.days = parseDays(a.slice(7));
|
|
54
63
|
else if (a.startsWith("-")) throw new Error(`unknown flag: ${a}`);
|
|
55
64
|
else positional.push(a);
|
|
56
65
|
}
|
|
57
66
|
opts.command = positional[0] ?? null;
|
|
58
|
-
if (
|
|
67
|
+
if (positional.length > 1) throw new Error(`unexpected argument: ${positional[1]}`);
|
|
59
68
|
return opts;
|
|
60
69
|
}
|
|
61
70
|
|
|
@@ -101,15 +110,15 @@ function renderHuman(rows, min) {
|
|
|
101
110
|
return lines.join("\n") + "\n";
|
|
102
111
|
}
|
|
103
112
|
|
|
104
|
-
async function runCheck(targets, {
|
|
113
|
+
async function runCheck(targets, { json, days }, env, home, platform, out, err) {
|
|
105
114
|
const rows = await gatherStatus(targets, env, home, platform);
|
|
106
|
-
const failingPrimary = rows.filter(r => r.days === null || r.days <
|
|
115
|
+
const failingPrimary = rows.filter(r => r.days === null || r.days < days);
|
|
107
116
|
const failingExtras = rows.flatMap(r => r.extras.filter(e => !e.ok));
|
|
108
117
|
const ok = failingPrimary.length === 0 && failingExtras.length === 0;
|
|
109
|
-
if (json) out.write(JSON.stringify({
|
|
110
|
-
else out.write(renderHuman(rows,
|
|
111
|
-
if (failingPrimary.length) err.write(`pmsec: ${failingPrimary.length} tool(s) below ${
|
|
112
|
-
if (failingExtras.length) err.write(`pmsec: ${failingExtras.length} hardening setting(s) not at safe value
|
|
118
|
+
if (json) out.write(JSON.stringify({ bundleDays: days, rows, ok }, null, 2) + "\n");
|
|
119
|
+
else out.write(renderHuman(rows, days));
|
|
120
|
+
if (failingPrimary.length) err.write(`pmsec: ${failingPrimary.length} tool(s) below ${days} days — run \`pmsec enable\`\n`);
|
|
121
|
+
if (failingExtras.length) err.write(`pmsec: ${failingExtras.length} hardening setting(s) not at safe value — run \`pmsec enable\`\n`);
|
|
113
122
|
return ok ? 0 : 1;
|
|
114
123
|
}
|
|
115
124
|
|
|
@@ -125,11 +134,7 @@ function explainFsError(e, tool) {
|
|
|
125
134
|
return `${tool}: ${e?.message ?? e}`;
|
|
126
135
|
}
|
|
127
136
|
|
|
128
|
-
async function
|
|
129
|
-
if (!Number.isInteger(days) || days <= 0) {
|
|
130
|
-
err.write(`pmsec: set requires integer DAYS > 0\n`);
|
|
131
|
-
return 2;
|
|
132
|
-
}
|
|
137
|
+
async function runEnable(targets, json, days, env, home, platform, out, err) {
|
|
133
138
|
const results = [];
|
|
134
139
|
const failures = [];
|
|
135
140
|
const warnings = [];
|
|
@@ -144,13 +149,13 @@ async function runSet(targets, days, json, env, home, platform, out, err) {
|
|
|
144
149
|
results.push({ tool: t.name, path: e?.path ?? null, days, ok: false, error: explainFsError(e, t.name), warn });
|
|
145
150
|
}
|
|
146
151
|
}
|
|
147
|
-
if (json) out.write(JSON.stringify({
|
|
152
|
+
if (json) out.write(JSON.stringify({ enabled: true, bundleDays: days, results, warnings, ok: failures.length === 0 }, null, 2) + "\n");
|
|
148
153
|
else for (const r of results) {
|
|
149
154
|
if (r.ok) {
|
|
150
155
|
const tail = r.warn ? `\n ⚠ ${r.warn}` : "";
|
|
151
|
-
out.write(`
|
|
156
|
+
out.write(`enable ${r.tool.padEnd(4)} [${r.path}]${tail}\n`);
|
|
152
157
|
} else {
|
|
153
|
-
out.write(`FAIL
|
|
158
|
+
out.write(`FAIL ${r.tool.padEnd(4)} ${r.error}\n`);
|
|
154
159
|
}
|
|
155
160
|
}
|
|
156
161
|
for (const f of failures) err.write(`pmsec: ${f.error}\n`);
|
|
@@ -158,7 +163,7 @@ async function runSet(targets, days, json, env, home, platform, out, err) {
|
|
|
158
163
|
return failures.length ? 1 : 0;
|
|
159
164
|
}
|
|
160
165
|
|
|
161
|
-
async function
|
|
166
|
+
async function runDisable(targets, json, env, home, platform, out, err) {
|
|
162
167
|
const results = [];
|
|
163
168
|
const failures = [];
|
|
164
169
|
for (const t of targets) {
|
|
@@ -172,8 +177,8 @@ async function runUnset(targets, json, env, home, platform, out, err) {
|
|
|
172
177
|
}
|
|
173
178
|
if (json) out.write(JSON.stringify({ results, ok: failures.length === 0 }, null, 2) + "\n");
|
|
174
179
|
else for (const r of results) {
|
|
175
|
-
if (!r.ok) out.write(`FAIL
|
|
176
|
-
else out.write(`${r.removed ? "
|
|
180
|
+
if (!r.ok) out.write(`FAIL ${r.tool.padEnd(4)} ${r.error}\n`);
|
|
181
|
+
else out.write(`${r.removed ? "disable " : "skip "} ${r.tool.padEnd(4)} [${r.path}]\n`);
|
|
177
182
|
}
|
|
178
183
|
for (const f of failures) err.write(`pmsec: ${f.error}\n`);
|
|
179
184
|
return failures.length ? 1 : 0;
|
|
@@ -196,8 +201,8 @@ export async function run(argv, {
|
|
|
196
201
|
catch (e) { err.write(`pmsec: ${e.message}\n`); return 2; }
|
|
197
202
|
try {
|
|
198
203
|
if (opts.command === "check") return await runCheck(targets, opts, env, home, platform, out, err);
|
|
199
|
-
if (opts.command === "
|
|
200
|
-
if (opts.command === "
|
|
204
|
+
if (opts.command === "enable") return await runEnable(targets, opts.json, opts.days, env, home, platform, out, err);
|
|
205
|
+
if (opts.command === "disable") return await runDisable(targets, opts.json, env, home, platform, out, err);
|
|
201
206
|
err.write(`pmsec: unknown command "${opts.command}"\n`);
|
|
202
207
|
return 2;
|
|
203
208
|
} catch (e) {
|