pmsec 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/package.json +8 -3
- package/src/cli.mjs +38 -10
- package/src/tools/bun.mjs +2 -1
- package/src/tools/cargo.mjs +2 -1
- package/src/tools/mise.mjs +14 -4
- package/src/tools/npm.mjs +15 -4
- package/src/tools/pnpm.mjs +16 -4
- package/src/tools/uv.mjs +2 -1
- package/src/tools/yarn.mjs +14 -4
- package/src/util/extras.mjs +32 -0
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ npx --registry=https://registry.npmjs.org/ --min-release-age=0 pmsec check
|
|
|
28
28
|
uvx --index https://pypi.org/simple --exclude-newer-package pmsec=2099-01-01 pmsec check
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Zero runtime dependencies, ESM, requires Node
|
|
31
|
+
Zero-config install-time supply-chain hardening (cooldown + audit-level + trust-policy + …) across npm, pnpm, yarn, bun, cargo, mise, uv. Zero runtime dependencies, ESM, requires Node 22+.
|
|
32
32
|
|
|
33
33
|
## Commands
|
|
34
34
|
|
|
@@ -37,13 +37,14 @@ Zero runtime dependencies, ESM, requires Node 20+.
|
|
|
37
37
|
| `pmsec check [--min N]` | Read each tool's config; exit 1 if any tool is below `N` days or unset. |
|
|
38
38
|
| `pmsec set <DAYS>` | Write `DAYS`-day cooldown to every selected tool. Always proceeds; if the runtime is too old to honor the key, prints a `⚠` line under the success line. |
|
|
39
39
|
| `pmsec unset` | Remove only the cooldown key from each config (other keys preserved). |
|
|
40
|
+
| `pmsec --version` | Print the installed pmsec version. |
|
|
40
41
|
|
|
41
42
|
Options: `--tool npm,pnpm,yarn,bun,cargo,mise,uv`, `--json`.
|
|
42
43
|
|
|
43
44
|
When the target file is owned by another user (typical: `~/.npmrc` left
|
|
44
|
-
root-owned by an old `sudo npm config set`),
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
root-owned by an old `sudo npm config set`), the write fails with `EACCES`
|
|
46
|
+
and pmsec prints the exact `chown` command needed to restore ownership —
|
|
47
|
+
re-run after applying it. pmsec never escalates privileges itself.
|
|
47
48
|
|
|
48
49
|
See the [project README](https://github.com/HikaruEgashira/pmsec) for the
|
|
49
50
|
full table of keys, units, paths, and environment overrides.
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmsec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"type": "module",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": ">=
|
|
20
|
+
"node": ">=22"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
23
23
|
"pmsec": "./bin/cli.mjs"
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"min-release-age",
|
|
37
37
|
"exclude-newer",
|
|
38
38
|
"npm",
|
|
39
|
+
"pnpm",
|
|
40
|
+
"yarn",
|
|
41
|
+
"bun",
|
|
42
|
+
"cargo",
|
|
43
|
+
"mise",
|
|
39
44
|
"uv",
|
|
40
45
|
"pmsec"
|
|
41
46
|
]
|
package/src/cli.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import * as npm from "./tools/npm.mjs";
|
|
3
4
|
import * as pnpm from "./tools/pnpm.mjs";
|
|
4
5
|
import * as yarn from "./tools/yarn.mjs";
|
|
@@ -9,18 +10,27 @@ import * as uv from "./tools/uv.mjs";
|
|
|
9
10
|
|
|
10
11
|
const TOOLS = [npm, pnpm, yarn, bun, cargo, mise, uv];
|
|
11
12
|
const DEFAULT_MIN = 7;
|
|
13
|
+
export const VERSION = JSON.parse(
|
|
14
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
15
|
+
).version;
|
|
12
16
|
|
|
13
17
|
const USAGE = `pmsec <command> [options]
|
|
14
18
|
|
|
19
|
+
Zero-config install-time supply-chain hardening across npm, pnpm, yarn,
|
|
20
|
+
bun, cargo, mise, uv. Each \`set\` writes the cooldown plus every safe-by-
|
|
21
|
+
default key the tool exposes (audit-level, trust-policy, hardened mode,
|
|
22
|
+
attestation re-verification, ...).
|
|
23
|
+
|
|
15
24
|
Commands:
|
|
16
|
-
check Inspect
|
|
17
|
-
set <DAYS> Apply DAYS-day cooldown to all selected tools
|
|
18
|
-
unset Remove cooldown
|
|
25
|
+
check Inspect hardening state (exit 1 if cooldown < --min or any extra unset)
|
|
26
|
+
set <DAYS> Apply DAYS-day cooldown + the hardening bundle to all selected tools
|
|
27
|
+
unset Remove the cooldown + hardening bundle from selected tools
|
|
19
28
|
|
|
20
29
|
Options:
|
|
21
30
|
--tool TOOL[,TOOL] Restrict to specific tools (npm,pnpm,yarn,bun,cargo,mise,uv)
|
|
22
31
|
--min DAYS Minimum acceptable days for check (default ${DEFAULT_MIN})
|
|
23
32
|
--json Emit JSON output
|
|
33
|
+
-V, --version Show version
|
|
24
34
|
-h, --help Show this help
|
|
25
35
|
|
|
26
36
|
Examples:
|
|
@@ -30,11 +40,12 @@ Examples:
|
|
|
30
40
|
`;
|
|
31
41
|
|
|
32
42
|
function parse(argv) {
|
|
33
|
-
const opts = { command: null, days: null, min: DEFAULT_MIN, json: false, only: null, help: false };
|
|
43
|
+
const opts = { command: null, days: null, min: DEFAULT_MIN, json: false, only: null, help: false, version: false };
|
|
34
44
|
const positional = [];
|
|
35
45
|
for (let i = 0; i < argv.length; i++) {
|
|
36
46
|
const a = argv[i];
|
|
37
47
|
if (a === "-h" || a === "--help") opts.help = true;
|
|
48
|
+
else if (a === "-V" || a === "--version") opts.version = true;
|
|
38
49
|
else if (a === "--json") opts.json = true;
|
|
39
50
|
else if (a === "--min") opts.min = Number(argv[++i]);
|
|
40
51
|
else if (a.startsWith("--min=")) opts.min = Number(a.slice(6));
|
|
@@ -65,7 +76,12 @@ function preflightWarn(t) {
|
|
|
65
76
|
async function gatherStatus(targets, env, home, platform) {
|
|
66
77
|
return Promise.all(targets.map(async t => {
|
|
67
78
|
const r = await t.read(env, home, platform);
|
|
68
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
tool: t.name, key: t.key, path: r.path,
|
|
81
|
+
configured: r.configured, days: r.days,
|
|
82
|
+
extras: r.extras ?? [],
|
|
83
|
+
warn: preflightWarn(t)
|
|
84
|
+
};
|
|
69
85
|
}));
|
|
70
86
|
}
|
|
71
87
|
|
|
@@ -76,17 +92,25 @@ function renderHuman(rows, min) {
|
|
|
76
92
|
const value = r.configured ?? "(unset)";
|
|
77
93
|
const tail = r.warn ? `\n ⚠ ${r.warn}` : "";
|
|
78
94
|
lines.push(`${status} ${r.tool.padEnd(4)} ${r.key} = ${value} [${r.path}]${tail}`);
|
|
95
|
+
for (const e of r.extras) {
|
|
96
|
+
const exStatus = e.configured === null ? "MISSING" : e.ok ? "OK " : "STALE ";
|
|
97
|
+
const exValue = e.configured ?? "(unset)";
|
|
98
|
+
lines.push(`${exStatus} ${r.tool.padEnd(4)} ${e.key} = ${exValue} [${r.path}]`);
|
|
99
|
+
}
|
|
79
100
|
}
|
|
80
101
|
return lines.join("\n") + "\n";
|
|
81
102
|
}
|
|
82
103
|
|
|
83
104
|
async function runCheck(targets, { min, json }, env, home, platform, out, err) {
|
|
84
105
|
const rows = await gatherStatus(targets, env, home, platform);
|
|
85
|
-
const
|
|
86
|
-
|
|
106
|
+
const failingPrimary = rows.filter(r => r.days === null || r.days < min);
|
|
107
|
+
const failingExtras = rows.flatMap(r => r.extras.filter(e => !e.ok));
|
|
108
|
+
const ok = failingPrimary.length === 0 && failingExtras.length === 0;
|
|
109
|
+
if (json) out.write(JSON.stringify({ min, rows, ok }, null, 2) + "\n");
|
|
87
110
|
else out.write(renderHuman(rows, min));
|
|
88
|
-
if (
|
|
89
|
-
|
|
111
|
+
if (failingPrimary.length) err.write(`pmsec: ${failingPrimary.length} tool(s) below ${min} days\n`);
|
|
112
|
+
if (failingExtras.length) err.write(`pmsec: ${failingExtras.length} hardening setting(s) not at safe value\n`);
|
|
113
|
+
return ok ? 0 : 1;
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
function shQuote(s) { return s ? `'${String(s).replace(/'/g, `'\\''`)}'` : ""; }
|
|
@@ -102,7 +126,10 @@ function explainFsError(e, tool) {
|
|
|
102
126
|
}
|
|
103
127
|
|
|
104
128
|
async function runSet(targets, days, json, env, home, platform, out, err) {
|
|
105
|
-
if (!Number.isInteger(days) || days <= 0)
|
|
129
|
+
if (!Number.isInteger(days) || days <= 0) {
|
|
130
|
+
err.write(`pmsec: set requires integer DAYS > 0\n`);
|
|
131
|
+
return 2;
|
|
132
|
+
}
|
|
106
133
|
const results = [];
|
|
107
134
|
const failures = [];
|
|
108
135
|
const warnings = [];
|
|
@@ -162,6 +189,7 @@ export async function run(argv, {
|
|
|
162
189
|
let opts;
|
|
163
190
|
try { opts = parse(argv); }
|
|
164
191
|
catch (e) { err.write(`pmsec: ${e.message}\n`); return 2; }
|
|
192
|
+
if (opts.version) { out.write(`pmsec ${VERSION}\n`); return 0; }
|
|
165
193
|
if (opts.help || !opts.command) { out.write(USAGE); return opts.help ? 0 : 2; }
|
|
166
194
|
let targets;
|
|
167
195
|
try { targets = selectTools(opts.only); }
|
package/src/tools/bun.mjs
CHANGED
|
@@ -8,6 +8,7 @@ export const key = "minimumReleaseAge";
|
|
|
8
8
|
export const section = "install";
|
|
9
9
|
export const docs = "https://bun.com/docs/runtime/bunfig#install";
|
|
10
10
|
export const minBin = [1, 3, 0];
|
|
11
|
+
export const extras = [];
|
|
11
12
|
|
|
12
13
|
export function path(env, home) { return bunConfigPath(env, home); }
|
|
13
14
|
|
|
@@ -23,7 +24,7 @@ export async function read(env, home) {
|
|
|
23
24
|
const raw = await readSafe(p);
|
|
24
25
|
const value = readKey(raw, key, { section });
|
|
25
26
|
const seconds = value === null ? null : Number(value);
|
|
26
|
-
return { path: p, configured: value, days: seconds === null || Number.isNaN(seconds) ? null : Math.floor(seconds / 86400) };
|
|
27
|
+
return { path: p, configured: value, days: seconds === null || Number.isNaN(seconds) ? null : Math.floor(seconds / 86400), extras: [] };
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export async function write(days, env, home) {
|
package/src/tools/cargo.mjs
CHANGED
|
@@ -6,6 +6,7 @@ export const name = "cargo";
|
|
|
6
6
|
export const key = "minimum-release-age";
|
|
7
7
|
export const section = "install";
|
|
8
8
|
export const docs = "https://rust-lang.github.io/rfcs/3801-package-cooldown.html";
|
|
9
|
+
export const extras = [];
|
|
9
10
|
|
|
10
11
|
export function path(env, home) { return cargoConfigPath(env, home); }
|
|
11
12
|
|
|
@@ -21,7 +22,7 @@ export async function read(env, home) {
|
|
|
21
22
|
const p = path(env, home);
|
|
22
23
|
const raw = await readSafe(p);
|
|
23
24
|
const value = readKey(raw, key, { section });
|
|
24
|
-
return { path: p, configured: value, days: parseDays(value) };
|
|
25
|
+
return { path: p, configured: value, days: parseDays(value), extras: [] };
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export async function write(days, env, home) {
|
package/src/tools/mise.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { miseConfigPath } from "../util/paths.mjs";
|
|
2
2
|
import { readSafe, writeAtomic } from "../util/io.mjs";
|
|
3
3
|
import { readKey, setKey, removeKey } from "../util/lines.mjs";
|
|
4
|
+
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
4
5
|
import { detectVersion, gte } from "../util/version.mjs";
|
|
5
6
|
export const name = "mise";
|
|
6
7
|
export const key = "minimum_release_age";
|
|
7
8
|
export const section = "settings";
|
|
8
9
|
export const docs = "https://mise.jdx.dev/configuration/settings.html#minimum_release_age";
|
|
9
10
|
export const minBin = [2026, 4, 22];
|
|
11
|
+
export const extras = [
|
|
12
|
+
{ key: "paranoid", expected: "true", line: "paranoid = true", section }
|
|
13
|
+
];
|
|
10
14
|
|
|
11
15
|
export function path(env, home, platform) { return miseConfigPath(env, home, platform); }
|
|
12
16
|
|
|
@@ -33,13 +37,17 @@ export async function read(env, home, platform) {
|
|
|
33
37
|
const p = path(env, home, platform);
|
|
34
38
|
const raw = await readSafe(p);
|
|
35
39
|
const value = readKey(raw, key, { section });
|
|
36
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
path: p, configured: value, days: parseDays(value),
|
|
42
|
+
extras: readExtras(raw, extras)
|
|
43
|
+
};
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
export async function write(days, env, home, platform) {
|
|
40
47
|
const p = path(env, home, platform);
|
|
41
48
|
const before = await readSafe(p);
|
|
42
|
-
|
|
49
|
+
let after = setKey(before, key, `${key} = "${days}d"`, { section });
|
|
50
|
+
after = applyExtras(after, extras);
|
|
43
51
|
await writeAtomic(p, after);
|
|
44
52
|
return { path: p, before, after };
|
|
45
53
|
}
|
|
@@ -47,7 +55,9 @@ export async function write(days, env, home, platform) {
|
|
|
47
55
|
export async function unset(env, home, platform) {
|
|
48
56
|
const p = path(env, home, platform);
|
|
49
57
|
const before = await readSafe(p);
|
|
50
|
-
const
|
|
51
|
-
|
|
58
|
+
const cooldown = removeKey(before, key, { section });
|
|
59
|
+
const ex = removeExtras(cooldown.text, extras);
|
|
60
|
+
const removed = cooldown.removed || ex.removed;
|
|
61
|
+
if (removed) await writeAtomic(p, ex.text);
|
|
52
62
|
return { path: p, removed };
|
|
53
63
|
}
|
package/src/tools/npm.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { npmrcPath } from "../util/paths.mjs";
|
|
2
2
|
import { readSafe, writeAtomic } from "../util/io.mjs";
|
|
3
3
|
import { readKey, setKey, removeKey } from "../util/lines.mjs";
|
|
4
|
+
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
4
5
|
import { detectVersion, gte } from "../util/version.mjs";
|
|
5
6
|
|
|
6
7
|
export const name = "npm";
|
|
7
8
|
export const key = "min-release-age";
|
|
8
9
|
export const docs = "https://docs.npmjs.com/cli/v11/using-npm/config#min-release-age";
|
|
9
10
|
export const minBin = [11, 10, 0];
|
|
11
|
+
export const extras = [
|
|
12
|
+
{ key: "audit-level", expected: "high", line: "audit-level=high" }
|
|
13
|
+
];
|
|
10
14
|
|
|
11
15
|
export function path(env, home) { return npmrcPath(env, home); }
|
|
12
16
|
|
|
@@ -21,13 +25,18 @@ export async function read(env, home) {
|
|
|
21
25
|
const p = path(env, home);
|
|
22
26
|
const raw = await readSafe(p);
|
|
23
27
|
const value = readKey(raw, key);
|
|
24
|
-
return {
|
|
28
|
+
return {
|
|
29
|
+
path: p, configured: value,
|
|
30
|
+
days: value === null ? null : Number(value),
|
|
31
|
+
extras: readExtras(raw, extras)
|
|
32
|
+
};
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
export async function write(days, env, home) {
|
|
28
36
|
const p = path(env, home);
|
|
29
37
|
const before = await readSafe(p);
|
|
30
|
-
|
|
38
|
+
let after = setKey(before, key, `${key}=${days}`);
|
|
39
|
+
after = applyExtras(after, extras);
|
|
31
40
|
await writeAtomic(p, after);
|
|
32
41
|
return { path: p, before, after };
|
|
33
42
|
}
|
|
@@ -35,7 +44,9 @@ export async function write(days, env, home) {
|
|
|
35
44
|
export async function unset(env, home) {
|
|
36
45
|
const p = path(env, home);
|
|
37
46
|
const before = await readSafe(p);
|
|
38
|
-
const
|
|
39
|
-
|
|
47
|
+
const cooldown = removeKey(before, key);
|
|
48
|
+
const ex = removeExtras(cooldown.text, extras);
|
|
49
|
+
const removed = cooldown.removed || ex.removed;
|
|
50
|
+
if (removed) await writeAtomic(p, ex.text);
|
|
40
51
|
return { path: p, removed };
|
|
41
52
|
}
|
package/src/tools/pnpm.mjs
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { npmrcPath } from "../util/paths.mjs";
|
|
2
2
|
import { readSafe, writeAtomic } from "../util/io.mjs";
|
|
3
3
|
import { readKey, setKey, removeKey } from "../util/lines.mjs";
|
|
4
|
+
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
4
5
|
import { detectVersion, gte } from "../util/version.mjs";
|
|
5
6
|
|
|
6
7
|
export const name = "pnpm";
|
|
7
8
|
export const key = "minimum-release-age";
|
|
8
9
|
export const docs = "https://pnpm.io/settings#minimumreleaseage";
|
|
9
10
|
export const minBin = [10, 6, 0];
|
|
11
|
+
export const extras = [
|
|
12
|
+
{ key: "trust-policy", expected: "no-downgrade", line: "trust-policy=no-downgrade" },
|
|
13
|
+
{ key: "block-exotic-subdeps", expected: "true", line: "block-exotic-subdeps=true" }
|
|
14
|
+
];
|
|
10
15
|
|
|
11
16
|
export function path(env, home) { return npmrcPath(env, home); }
|
|
12
17
|
|
|
@@ -22,13 +27,18 @@ export async function read(env, home) {
|
|
|
22
27
|
const raw = await readSafe(p);
|
|
23
28
|
const value = readKey(raw, key);
|
|
24
29
|
const minutes = value === null ? null : Number(value);
|
|
25
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
path: p, configured: value,
|
|
32
|
+
days: minutes === null || Number.isNaN(minutes) ? null : Math.floor(minutes / (60 * 24)),
|
|
33
|
+
extras: readExtras(raw, extras)
|
|
34
|
+
};
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
export async function write(days, env, home) {
|
|
29
38
|
const p = path(env, home);
|
|
30
39
|
const before = await readSafe(p);
|
|
31
|
-
|
|
40
|
+
let after = setKey(before, key, `${key}=${days * 24 * 60}`);
|
|
41
|
+
after = applyExtras(after, extras);
|
|
32
42
|
await writeAtomic(p, after);
|
|
33
43
|
return { path: p, before, after };
|
|
34
44
|
}
|
|
@@ -36,7 +46,9 @@ export async function write(days, env, home) {
|
|
|
36
46
|
export async function unset(env, home) {
|
|
37
47
|
const p = path(env, home);
|
|
38
48
|
const before = await readSafe(p);
|
|
39
|
-
const
|
|
40
|
-
|
|
49
|
+
const cooldown = removeKey(before, key);
|
|
50
|
+
const ex = removeExtras(cooldown.text, extras);
|
|
51
|
+
const removed = cooldown.removed || ex.removed;
|
|
52
|
+
if (removed) await writeAtomic(p, ex.text);
|
|
41
53
|
return { path: p, removed };
|
|
42
54
|
}
|
package/src/tools/uv.mjs
CHANGED
|
@@ -7,6 +7,7 @@ export const name = "uv";
|
|
|
7
7
|
export const key = "exclude-newer";
|
|
8
8
|
export const docs = "https://docs.astral.sh/uv/reference/settings/#exclude-newer";
|
|
9
9
|
export const minBin = [0, 9, 17];
|
|
10
|
+
export const extras = [];
|
|
10
11
|
|
|
11
12
|
export function path(env, home, platform) { return uvConfigPath(env, home, platform); }
|
|
12
13
|
|
|
@@ -29,7 +30,7 @@ export async function read(env, home, platform) {
|
|
|
29
30
|
const p = path(env, home, platform);
|
|
30
31
|
const raw = await readSafe(p);
|
|
31
32
|
const value = readKey(raw, key);
|
|
32
|
-
return { path: p, configured: value, days: parseDays(value) };
|
|
33
|
+
return { path: p, configured: value, days: parseDays(value), extras: [] };
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export async function write(days, env, home, platform) {
|
package/src/tools/yarn.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { yarnrcPath } from "../util/paths.mjs";
|
|
2
2
|
import { readSafe, writeAtomic } from "../util/io.mjs";
|
|
3
3
|
import { readKey, setKey, removeKey } from "../util/lines.mjs";
|
|
4
|
+
import { readExtras, applyExtras, removeExtras } from "../util/extras.mjs";
|
|
4
5
|
import { detectVersion, gte } from "../util/version.mjs";
|
|
5
6
|
|
|
6
7
|
export const name = "yarn";
|
|
@@ -8,6 +9,9 @@ export const key = "npmMinimalAgeGate";
|
|
|
8
9
|
export const docs = "https://yarnpkg.com/configuration/yarnrc#npmMinimalAgeGate";
|
|
9
10
|
export const minBin = [4, 10, 0];
|
|
10
11
|
const SEP = ":";
|
|
12
|
+
export const extras = [
|
|
13
|
+
{ key: "enableHardenedMode", expected: "true", line: "enableHardenedMode: true", sep: SEP }
|
|
14
|
+
];
|
|
11
15
|
|
|
12
16
|
export function path(env, home) { return yarnrcPath(env, home); }
|
|
13
17
|
|
|
@@ -30,13 +34,17 @@ export async function read(env, home) {
|
|
|
30
34
|
const p = path(env, home);
|
|
31
35
|
const raw = await readSafe(p);
|
|
32
36
|
const value = readKey(raw, key, { sep: SEP });
|
|
33
|
-
return {
|
|
37
|
+
return {
|
|
38
|
+
path: p, configured: value, days: parseDays(value),
|
|
39
|
+
extras: readExtras(raw, extras)
|
|
40
|
+
};
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export async function write(days, env, home) {
|
|
37
44
|
const p = path(env, home);
|
|
38
45
|
const before = await readSafe(p);
|
|
39
|
-
|
|
46
|
+
let after = setKey(before, key, `${key}: "${days}d"`, { sep: SEP });
|
|
47
|
+
after = applyExtras(after, extras);
|
|
40
48
|
await writeAtomic(p, after);
|
|
41
49
|
return { path: p, before, after };
|
|
42
50
|
}
|
|
@@ -44,7 +52,9 @@ export async function write(days, env, home) {
|
|
|
44
52
|
export async function unset(env, home) {
|
|
45
53
|
const p = path(env, home);
|
|
46
54
|
const before = await readSafe(p);
|
|
47
|
-
const
|
|
48
|
-
|
|
55
|
+
const cooldown = removeKey(before, key, { sep: SEP });
|
|
56
|
+
const ex = removeExtras(cooldown.text, extras);
|
|
57
|
+
const removed = cooldown.removed || ex.removed;
|
|
58
|
+
if (removed) await writeAtomic(p, ex.text);
|
|
49
59
|
return { path: p, removed };
|
|
50
60
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Per-tool "extras" are fixed-value hardening keys written alongside the main
|
|
2
|
+
// cooldown key on `set`, removed on `unset`, and validated on `check`.
|
|
3
|
+
// Each entry: { key, expected, line, sep?, section? } — `line` is the verbatim
|
|
4
|
+
// text to write (handles per-format quoting/typing); `expected` is the bare
|
|
5
|
+
// value compared against readKey output.
|
|
6
|
+
import { readKey, setKey, removeKey } from "./lines.mjs";
|
|
7
|
+
|
|
8
|
+
export function readExtras(raw, extras) {
|
|
9
|
+
return extras.map(e => {
|
|
10
|
+
const cur = readKey(raw, e.key, { sep: e.sep, section: e.section });
|
|
11
|
+
return { key: e.key, configured: cur, expected: e.expected, ok: cur === e.expected };
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function applyExtras(text, extras) {
|
|
16
|
+
let out = text;
|
|
17
|
+
for (const e of extras) {
|
|
18
|
+
out = setKey(out, e.key, e.line, { sep: e.sep, section: e.section });
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function removeExtras(text, extras) {
|
|
24
|
+
let out = text;
|
|
25
|
+
let removed = false;
|
|
26
|
+
for (const e of extras) {
|
|
27
|
+
const r = removeKey(out, e.key, { sep: e.sep, section: e.section });
|
|
28
|
+
if (r.removed) removed = true;
|
|
29
|
+
out = r.text;
|
|
30
|
+
}
|
|
31
|
+
return { text: out, removed };
|
|
32
|
+
}
|