pmsec 0.4.0 → 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 +14 -15
- package/package.json +2 -2
- package/src/cli.mjs +41 -31
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
|
|
@@ -34,12 +33,12 @@ Zero runtime dependencies, ESM, requires Node 22+.
|
|
|
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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmsec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "Zero-config install-time supply-chain hardening for npm, pnpm, yarn, bun, cargo, mise, and uv.",
|
|
8
8
|
"homepage": "https://github.com/HikaruEgashira/pmsec#readme",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
package/src/cli.mjs
CHANGED
|
@@ -9,48 +9,62 @@ 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;
|
|
16
18
|
|
|
17
19
|
const USAGE = `pmsec <command> [options]
|
|
18
20
|
|
|
21
|
+
Zero-config install-time supply-chain hardening across npm, pnpm, yarn,
|
|
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.
|
|
25
|
+
|
|
19
26
|
Commands:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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)
|
|
23
30
|
|
|
24
31
|
Options:
|
|
25
32
|
--tool TOOL[,TOOL] Restrict to specific tools (npm,pnpm,yarn,bun,cargo,mise,uv)
|
|
26
|
-
--
|
|
33
|
+
--days N Override cooldown days (default 3)
|
|
27
34
|
--json Emit JSON output
|
|
28
35
|
-V, --version Show version
|
|
29
36
|
-h, --help Show this help
|
|
30
37
|
|
|
31
38
|
Examples:
|
|
32
|
-
npx pmsec
|
|
33
|
-
npx pmsec
|
|
34
|
-
npx pmsec
|
|
39
|
+
npx pmsec enable
|
|
40
|
+
npx pmsec enable --days 7
|
|
41
|
+
npx pmsec check
|
|
42
|
+
npx pmsec disable --tool npm
|
|
35
43
|
`;
|
|
36
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
|
+
|
|
37
51
|
function parse(argv) {
|
|
38
|
-
const opts = { command: null,
|
|
52
|
+
const opts = { command: null, json: false, only: null, days: BUNDLE_DAYS, help: false, version: false };
|
|
39
53
|
const positional = [];
|
|
40
54
|
for (let i = 0; i < argv.length; i++) {
|
|
41
55
|
const a = argv[i];
|
|
42
56
|
if (a === "-h" || a === "--help") opts.help = true;
|
|
43
57
|
else if (a === "-V" || a === "--version") opts.version = true;
|
|
44
58
|
else if (a === "--json") opts.json = true;
|
|
45
|
-
else if (a === "--min") opts.min = Number(argv[++i]);
|
|
46
|
-
else if (a.startsWith("--min=")) opts.min = Number(a.slice(6));
|
|
47
59
|
else if (a === "--tool") opts.only = argv[++i].split(",");
|
|
48
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));
|
|
49
63
|
else if (a.startsWith("-")) throw new Error(`unknown flag: ${a}`);
|
|
50
64
|
else positional.push(a);
|
|
51
65
|
}
|
|
52
66
|
opts.command = positional[0] ?? null;
|
|
53
|
-
if (
|
|
67
|
+
if (positional.length > 1) throw new Error(`unexpected argument: ${positional[1]}`);
|
|
54
68
|
return opts;
|
|
55
69
|
}
|
|
56
70
|
|
|
@@ -96,15 +110,15 @@ function renderHuman(rows, min) {
|
|
|
96
110
|
return lines.join("\n") + "\n";
|
|
97
111
|
}
|
|
98
112
|
|
|
99
|
-
async function runCheck(targets, {
|
|
113
|
+
async function runCheck(targets, { json, days }, env, home, platform, out, err) {
|
|
100
114
|
const rows = await gatherStatus(targets, env, home, platform);
|
|
101
|
-
const failingPrimary = rows.filter(r => r.days === null || r.days <
|
|
115
|
+
const failingPrimary = rows.filter(r => r.days === null || r.days < days);
|
|
102
116
|
const failingExtras = rows.flatMap(r => r.extras.filter(e => !e.ok));
|
|
103
117
|
const ok = failingPrimary.length === 0 && failingExtras.length === 0;
|
|
104
|
-
if (json) out.write(JSON.stringify({
|
|
105
|
-
else out.write(renderHuman(rows,
|
|
106
|
-
if (failingPrimary.length) err.write(`pmsec: ${failingPrimary.length} tool(s) below ${
|
|
107
|
-
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`);
|
|
108
122
|
return ok ? 0 : 1;
|
|
109
123
|
}
|
|
110
124
|
|
|
@@ -120,11 +134,7 @@ function explainFsError(e, tool) {
|
|
|
120
134
|
return `${tool}: ${e?.message ?? e}`;
|
|
121
135
|
}
|
|
122
136
|
|
|
123
|
-
async function
|
|
124
|
-
if (!Number.isInteger(days) || days <= 0) {
|
|
125
|
-
err.write(`pmsec: set requires integer DAYS > 0\n`);
|
|
126
|
-
return 2;
|
|
127
|
-
}
|
|
137
|
+
async function runEnable(targets, json, days, env, home, platform, out, err) {
|
|
128
138
|
const results = [];
|
|
129
139
|
const failures = [];
|
|
130
140
|
const warnings = [];
|
|
@@ -139,13 +149,13 @@ async function runSet(targets, days, json, env, home, platform, out, err) {
|
|
|
139
149
|
results.push({ tool: t.name, path: e?.path ?? null, days, ok: false, error: explainFsError(e, t.name), warn });
|
|
140
150
|
}
|
|
141
151
|
}
|
|
142
|
-
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");
|
|
143
153
|
else for (const r of results) {
|
|
144
154
|
if (r.ok) {
|
|
145
155
|
const tail = r.warn ? `\n ⚠ ${r.warn}` : "";
|
|
146
|
-
out.write(`
|
|
156
|
+
out.write(`enable ${r.tool.padEnd(4)} [${r.path}]${tail}\n`);
|
|
147
157
|
} else {
|
|
148
|
-
out.write(`FAIL
|
|
158
|
+
out.write(`FAIL ${r.tool.padEnd(4)} ${r.error}\n`);
|
|
149
159
|
}
|
|
150
160
|
}
|
|
151
161
|
for (const f of failures) err.write(`pmsec: ${f.error}\n`);
|
|
@@ -153,7 +163,7 @@ async function runSet(targets, days, json, env, home, platform, out, err) {
|
|
|
153
163
|
return failures.length ? 1 : 0;
|
|
154
164
|
}
|
|
155
165
|
|
|
156
|
-
async function
|
|
166
|
+
async function runDisable(targets, json, env, home, platform, out, err) {
|
|
157
167
|
const results = [];
|
|
158
168
|
const failures = [];
|
|
159
169
|
for (const t of targets) {
|
|
@@ -167,8 +177,8 @@ async function runUnset(targets, json, env, home, platform, out, err) {
|
|
|
167
177
|
}
|
|
168
178
|
if (json) out.write(JSON.stringify({ results, ok: failures.length === 0 }, null, 2) + "\n");
|
|
169
179
|
else for (const r of results) {
|
|
170
|
-
if (!r.ok) out.write(`FAIL
|
|
171
|
-
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`);
|
|
172
182
|
}
|
|
173
183
|
for (const f of failures) err.write(`pmsec: ${f.error}\n`);
|
|
174
184
|
return failures.length ? 1 : 0;
|
|
@@ -191,8 +201,8 @@ export async function run(argv, {
|
|
|
191
201
|
catch (e) { err.write(`pmsec: ${e.message}\n`); return 2; }
|
|
192
202
|
try {
|
|
193
203
|
if (opts.command === "check") return await runCheck(targets, opts, env, home, platform, out, err);
|
|
194
|
-
if (opts.command === "
|
|
195
|
-
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);
|
|
196
206
|
err.write(`pmsec: unknown command "${opts.command}"\n`);
|
|
197
207
|
return 2;
|
|
198
208
|
} catch (e) {
|