@vola-trebla/croak 0.1.1 → 0.1.3

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/dist/cli.js CHANGED
@@ -18,7 +18,7 @@ import { anthropicTriage, DEFAULT_MODEL } from "./llm/client.js";
18
18
  import { renderScan, scanToJson } from "./repo-report.js";
19
19
  import { ScanTargetError, scanRepoAt } from "./scan.js";
20
20
  import { aggregate } from "./verdict.js";
21
- export const VERSION = "0.1.1";
21
+ export const VERSION = "0.1.3";
22
22
  export const EXIT = { ok: 0, block: 1, usage: 2, incomplete: 3 };
23
23
  export const EXIT_USAGE = EXIT.usage;
24
24
  const DEFAULT_DEPS = {
@@ -33,29 +33,36 @@ function sourceProblem(resolved, name, version) {
33
33
  return `is downloaded over ${url.protocol.replace(":", "")}, not https`;
34
34
  return `comes from ${url.host}${url.username === "" ? "" : " with credentials in the URL"}, not ${REGISTRY}`;
35
35
  }
36
- export const source = (changes) => changes.flatMap((c) => {
37
- if (c.entry.link) {
38
- return [
39
- {
40
- ruleId: "install/source",
41
- severity: "info",
42
- file: label(c),
43
- message: "is a workspace link, not a download",
44
- },
45
- ];
46
- }
47
- const problem = sourceProblem(c.entry.resolved, c.entry.name, c.entry.version);
48
- return problem === undefined
49
- ? []
50
- : [
51
- {
52
- ruleId: "install/source",
53
- severity: "warn",
54
- file: label(c),
55
- message: `${what(c)} and ${problem}`,
56
- },
57
- ];
58
- });
36
+ export const source = (changes) => {
37
+ const folders = new Set(changes.map((c) => c.entry.path));
38
+ return changes.flatMap((c) => {
39
+ if (c.entry.link &&
40
+ c.entry.resolved !== undefined &&
41
+ folders.has(c.entry.resolved))
42
+ return [];
43
+ if (c.entry.link) {
44
+ return [
45
+ {
46
+ ruleId: "install/source",
47
+ severity: "info",
48
+ file: label(c),
49
+ message: "is a workspace link, not a download",
50
+ },
51
+ ];
52
+ }
53
+ const problem = sourceProblem(c.entry.resolved, c.entry.name, c.entry.version);
54
+ return problem === undefined
55
+ ? []
56
+ : [
57
+ {
58
+ ruleId: "install/source",
59
+ severity: "warn",
60
+ file: label(c),
61
+ message: `${what(c)} and ${problem}`,
62
+ },
63
+ ];
64
+ });
65
+ };
59
66
  export const integrity = (changes) => changes
60
67
  .filter((c) => !c.entry.link &&
61
68
  c.entry.resolved !== undefined &&
@@ -79,7 +79,10 @@ export function metadataFindings(change, facts, now = new Date()) {
79
79
  ? "; with an install script, this is the pattern of a hijacked maintainer account"
80
80
  : "";
81
81
  if (recentRelease) {
82
- findings.push(finding("install/recent-release", "warn", `was published ${facts.ageDays} day(s) ago; new versions of popular packages are where hijacks land first`));
82
+ const when = facts.ageDays !== undefined && facts.ageDays < 0
83
+ ? "has a publish time after this machine's clock"
84
+ : `was published ${facts.ageDays} day(s) ago`;
85
+ findings.push(finding("install/recent-release", "warn", `${when}; new versions of popular packages are where hijacks land first`));
83
86
  }
84
87
  if (provenanceDropped) {
85
88
  findings.push(finding("install/provenance", hijack ? "block" : "warn", `has no provenance, although earlier versions were published with it${pattern}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vola-trebla/croak",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Croak before you install: a pre-execution gate for package installs, built for AI agents",
5
5
  "keywords": [
6
6
  "security",
@@ -15,8 +15,10 @@ here=$(dirname "$self")
15
15
  cli="$here/../dist/cli.js"
16
16
  install="$here/croak-install.sh"
17
17
  [ -f "$cli" ] || deny "$cli not found (run pnpm build in the croak repo)"
18
- # Below Node 24.2 the CLI's entry point does not run and exits 0 with no
19
- # output: every step would "succeed" silently, and every install would pass.
18
+ # croak needs Node 24.2 or later (package.json engines). On a Node whose
19
+ # entry point check does not run, the CLI exits 0 with no output, and every
20
+ # step would "succeed" silently; on others, behaviour croak was never tested
21
+ # with would decide. Either way: deny instead.
20
22
  node -e 'const [a, b] = process.versions.node.split(".").map(Number); process.exit(a > 24 || (a === 24 && b >= 2) ? 0 : 1)' ||
21
23
  deny "croak needs Node 24.2 or later (found $(node --version 2>/dev/null || echo none))"
22
24
 
@@ -229,12 +229,20 @@ if ! isolated "$@" >"$work/plan.log" 2>&1; then
229
229
  tail -n 15 "$work/plan.log" >&2
230
230
  exit 3
231
231
  fi
232
- if [ ! -f "$work/plan/$lockname" ] && [ -n "$member" ] && [ -f "$work/plan/$member/$lockname" ]; then
233
- # The package manager planned the folder as its own project: the workspace
234
- # does not list it, and the real install runs there too. That plan lacked
235
- # the folder's own lockfile and sanitized manifest, so plan the folder again
236
- # as a project of its own, exactly as outside any workspace (D48). croak
237
- # plan still refuses a --member the lockfile does not list (M6 review).
232
+ # The copy holds package.json files only, so a lockfile in the member's folder
233
+ # means the package manager planned that folder apart from the workspace, as
234
+ # the real install will. Decide by that, never by the root lockfile, which the
235
+ # package manager then left as it was and the repository may have forged (D52).
236
+ if [ -n "$member" ] && [ -f "$work/plan/$member/$lockname" ]; then
237
+ if [ "$manager" != npm ]; then
238
+ # pnpm writes one for a real member too (sharedWorkspaceLockfile: false);
239
+ # planning it alone would leave the rest of the workspace out.
240
+ echo "croak-install: $manager planned $member apart from its workspace, so this install was not checked" >&2
241
+ exit 3
242
+ fi
243
+ # npm installs a folder the workspace does not list as a project of its own.
244
+ # That plan lacked the folder's own lockfile and sanitized manifest, so plan
245
+ # the folder again, exactly as outside any workspace (D48).
238
246
  cd "$start"
239
247
  if [ "$manager" = npm ] && [ -f npm-shrinkwrap.json ]; then
240
248
  echo "croak-install: npm-shrinkwrap.json is not supported yet, so this install was not checked" >&2