skalpel 4.0.21 → 4.0.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skalpel",
3
- "version": "4.0.21",
3
+ "version": "4.0.23",
4
4
  "description": "Behavioral graph for AI-assisted coding — learns how you work and steers Claude Code + Codex in real time.",
5
5
  "type": "module",
6
6
  "bin": {
package/stats.mjs CHANGED
@@ -63,9 +63,8 @@ for (const ev of Object.keys(counts)) {
63
63
  }
64
64
  const L = lat[ev] || [];
65
65
  if (L.length) {
66
- console.log(
67
- ` latency p50=${pct(L, 50)}ms p95=${pct(L, 95)}ms max=${Math.max(...L)}ms`,
68
- );
66
+ const maxMs = L.reduce((a, b) => (b > a ? b : a), -Infinity);
67
+ console.log(` latency p50=${pct(L, 50)}ms p95=${pct(L, 95)}ms max=${maxMs}ms`);
69
68
  }
70
69
  console.log(
71
70
  ` → inject rate ${((100 * ok) / n).toFixed(1)}% cold-abort rate ${((100 * cold) / n).toFixed(1)}%\n`,
package/verify-shadow.mjs CHANGED
@@ -139,7 +139,14 @@ function pmPositionals(args) {
139
139
  return pos;
140
140
  }
141
141
 
142
+ // SHARP-5: `--node-options` (npm/pnpm/yarn config flag) is passed through to the lifecycle script's
143
+ // node as NODE_OPTIONS, so `--node-options=--require <any>.js` loads attacker JS before the test runs —
144
+ // arbitrary code execution, exactly the SHARP-2 class but via the PM itself. shell:false does NOT help
145
+ // (the PM applies the flag). Refuse the whole invocation if it appears, in any `=`/space form.
146
+ const NODE_OPTIONS_FLAG = /^--node-options(=|$)/;
147
+
142
148
  function validatePM(base, args) {
149
+ if (args.some((x) => NODE_OPTIONS_FLAG.test(x))) return false; // SHARP-5
143
150
  const pos = pmPositionals(args);
144
151
  if (!pos.length) return false;
145
152
  const p0 = pos[0];
@@ -161,6 +168,7 @@ function validateNPX(args) {
161
168
  // plain `npx <allowed-tool> [value-less flags]` is a proof.
162
169
  for (const a of args) {
163
170
  if (/^(-p|--package|-c|--call)(=|$)/.test(a)) return false;
171
+ if (NODE_OPTIONS_FLAG.test(a)) return false; // SHARP-5: npx also forwards --node-options → NODE_OPTIONS
164
172
  }
165
173
  for (let i = 0; i < args.length; i++) {
166
174
  const a = args[i];
@@ -482,7 +490,10 @@ export function rerunProof({ cmd, args, cwd }) {
482
490
  maxBuffer: MAX_OUTPUT_BYTES,
483
491
  windowsHide: true,
484
492
  shell: false, // RED LINE: never a shell
485
- env: process.env,
493
+ // SHARP-5 (defense in depth): neutralize NODE_OPTIONS so neither a `--node-options` flag the
494
+ // allowlist somehow let through, nor an inherited NODE_OPTIONS in the parent env, can inject a
495
+ // `--require <attacker>.js` into the proof tool's node process.
496
+ env: { ...process.env, NODE_OPTIONS: "" },
486
497
  },
487
498
  (err, stdout, stderr) => {
488
499
  const tail = (String(stdout || "") + String(stderr || "")).replace(/\s+$/, "");