@cejel/cejel 0.2.2 → 0.3.2

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 CHANGED
@@ -11,6 +11,10 @@ a trust certificate + badge. Especially valuable when AI wrote a lot of the code
11
11
  exactly when you can't eyeball trust. Built on a deterministic, no-LLM scoring core — the
12
12
  free path makes zero network calls and requires no account.
13
13
 
14
+ **Claim boundary:** Cejel scores engineering-trust signals; it does not claim to detect software
15
+ defects or prove that code is safe. Defect and vulnerability scanners remain evidence producers
16
+ that Cejel can aggregate into a trust certificate.
17
+
14
18
  Cejel is not another point scanner competing with the one you already run — it's the open,
15
19
  portable, offline trust certificate that aggregates them. Pipe in SARIF-compatible output
16
20
  (MunaTrust, Snyk, Semgrep, CodeQL, Codex) plus OpenSSF Scorecard, and get one shareable
@@ -64,7 +68,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
64
68
  ```
65
69
 
66
70
  > **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
67
- > 0.2.2 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
71
+ > 0.3.2 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
68
72
  > signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
69
73
  > it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
70
74
  > GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM and an
@@ -87,7 +91,7 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
87
91
  npx @cejel/cejel@latest .
88
92
  ```
89
93
 
90
- > **Distribution note:** Cejel `0.2.2` is the coordinated release version for npm,
94
+ > **Distribution note:** Cejel `0.3.2` is the coordinated release version for npm,
91
95
  > standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry.
92
96
 
93
97
  `npx` can reuse a stale cached package. Force the current npm release with the `@latest`
@@ -136,16 +140,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
136
140
  This is cryptographically signed provenance. It is distinct from Apple Developer ID or
137
141
  Microsoft Authenticode code-signing.
138
142
 
139
- **Docker / OCI.** The current container release is `0.2.2`:
143
+ **Docker / OCI.** The current container release is `0.3.2`:
140
144
 
141
145
  ```bash
142
- docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.2.2
146
+ docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.3.2
143
147
  ```
144
148
 
145
149
  The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
146
150
 
147
151
  ```bash
148
- docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.2.2 .
152
+ docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.3.2 .
149
153
  ```
150
154
 
151
155
  The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
@@ -422,7 +426,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
422
426
  the host path with the repository OpenClaw should allow Cejel to read:
423
427
 
424
428
  ```bash
425
- openclaw mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.2.2"]}'
429
+ openclaw mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.3.2"]}'
426
430
  openclaw mcp doctor cejel-oci --probe
427
431
  ```
428
432
 
@@ -436,9 +440,18 @@ the future paid Agent Pack.
436
440
  ## What "offline" means here
437
441
 
438
442
  Scoring a repo — `cejel scan .` itself, and the Action's scoring step — makes zero network
439
- calls: no telemetry, no signup, no model call. Fetching the `@cejel/cejel` package the
440
- first time (like any npm-distributed CLI, including this Action's own dependency install)
441
- does need network; that's a one-time install cost, not part of the scoring guarantee.
443
+ calls: no telemetry, no signup, no model call. The scanner does invoke your local `git`
444
+ binary through one hardened subprocess chokepoint: fixed argv only (never a shell), an
445
+ explicit timeout and output bound, prompts and network transports disabled, proxy variables
446
+ removed, and repository-controlled filesystem monitoring neutralized. A static CI guard
447
+ allows `node:child_process` in that one module only and rejects direct network primitives
448
+ everywhere, while each release binary is also scanned with outbound networking denied.
449
+
450
+ Fetching the `@cejel/cejel` package the first time (like any npm-distributed CLI, including
451
+ this Action's own dependency install) does need network; that's a one-time install cost, not
452
+ part of the scoring guarantee. If the tracked-file inventory fails unexpectedly, the JSON,
453
+ HTML, and markdown certificates declare that the scanner used its bounded directory fallback
454
+ instead of silently presenting the two inventories as equivalent.
442
455
 
443
456
  ## Help validate Cejel
444
457
 
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __cejelCreateRequire } from 'node:module';
3
+ const require = __cejelCreateRequire(import.meta.url);
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
11
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
12
+ }) : x)(function(x) {
13
+ if (typeof require !== "undefined") return require.apply(this, arguments);
14
+ throw Error('Dynamic require of "' + x + '" is not supported');
15
+ });
16
+ var __esm = (fn, res) => function __init() {
17
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
18
+ };
19
+ var __commonJS = (cb, mod) => function __require2() {
20
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
21
+ };
22
+ var __export = (target, all) => {
23
+ for (var name in all)
24
+ __defProp(target, name, { get: all[name], enumerable: true });
25
+ };
26
+ var __copyProps = (to, from, except, desc) => {
27
+ if (from && typeof from === "object" || typeof from === "function") {
28
+ for (let key of __getOwnPropNames(from))
29
+ if (!__hasOwnProp.call(to, key) && key !== except)
30
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
31
+ }
32
+ return to;
33
+ };
34
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
35
+ // If the importer is in node compatibility mode or this is not an ESM
36
+ // file that has been converted to a CommonJS file using a Babel-
37
+ // compatible transform (i.e. "__esModule" has not been set), then set
38
+ // "default" to the CommonJS "module.exports" for node compatibility.
39
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
40
+ mod
41
+ ));
42
+
43
+ // node_modules/.pnpm/tsup@8.5.1_postcss@8.5.19_tsx@4.23.1_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
44
+ import path from "path";
45
+ import { fileURLToPath } from "url";
46
+ var getFilename, getDirname, __dirname, __filename;
47
+ var init_esm_shims = __esm({
48
+ "node_modules/.pnpm/tsup@8.5.1_postcss@8.5.19_tsx@4.23.1_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
49
+ "use strict";
50
+ getFilename = () => fileURLToPath(import.meta.url);
51
+ getDirname = () => path.dirname(getFilename());
52
+ __dirname = /* @__PURE__ */ getDirname();
53
+ __filename = /* @__PURE__ */ getFilename();
54
+ }
55
+ });
56
+
57
+ // src/witan/content-reads.ts
58
+ init_esm_shims();
59
+ import { closeSync, lstatSync, openSync, readFileSync, readSync } from "fs";
60
+ import { resolve } from "path";
61
+ var activeSession;
62
+ function errnoClass(error) {
63
+ if (typeof error !== "object" || error === null || !("code" in error)) return null;
64
+ const code = error.code;
65
+ return typeof code === "string" && /^E[A-Z0-9_]+$/.test(code) ? code : null;
66
+ }
67
+ function recordSkip(path2, reason, errno, affectsCurrentCriterion = false, deduplicate = false) {
68
+ const session = activeSession;
69
+ if (!session) return;
70
+ if (affectsCurrentCriterion && session.criterion) {
71
+ session.affectedCriteria.add(session.criterion);
72
+ }
73
+ if (deduplicate) {
74
+ const key = `${resolve(path2)}\0${reason}\0${errno ?? ""}`;
75
+ if (session.deduplicatedSkips.has(key)) return;
76
+ session.deduplicatedSkips.add(key);
77
+ }
78
+ session.counts[reason] += 1;
79
+ if (reason === "unreadable" && errno) {
80
+ session.unreadableByErrno.set(errno, (session.unreadableByErrno.get(errno) ?? 0) + 1);
81
+ }
82
+ }
83
+ function summaryFor(session) {
84
+ const byReason = {
85
+ unreadable: session.counts.unreadable,
86
+ tooLarge: session.counts.too_large,
87
+ excludedByExtension: session.counts.excluded_by_extension,
88
+ deniedPath: session.counts.denied_path,
89
+ nonRegularFile: session.counts.non_regular_file
90
+ };
91
+ const sortedErrnos = Object.fromEntries(
92
+ [...session.unreadableByErrno.entries()].sort(([left], [right]) => left.localeCompare(right))
93
+ );
94
+ return {
95
+ skipped: Object.values(session.counts).reduce((total, count) => total + count, 0),
96
+ byReason,
97
+ unreadableByErrno: sortedErrnos,
98
+ affectedCriteria: [...session.affectedCriteria].sort()
99
+ };
100
+ }
101
+ function trackContentReads(_repoPath, collect) {
102
+ const existing = activeSession;
103
+ if (existing) {
104
+ return {
105
+ value: collect(),
106
+ summary: summaryFor(existing),
107
+ affectedCriteria: existing.affectedCriteria
108
+ };
109
+ }
110
+ const session = {
111
+ counts: {
112
+ unreadable: 0,
113
+ too_large: 0,
114
+ excluded_by_extension: 0,
115
+ denied_path: 0,
116
+ non_regular_file: 0
117
+ },
118
+ unreadableByErrno: /* @__PURE__ */ new Map(),
119
+ deduplicatedSkips: /* @__PURE__ */ new Set(),
120
+ affectedCriteria: /* @__PURE__ */ new Set()
121
+ };
122
+ activeSession = session;
123
+ try {
124
+ const value = collect();
125
+ return {
126
+ value,
127
+ summary: summaryFor(session),
128
+ affectedCriteria: session.affectedCriteria
129
+ };
130
+ } finally {
131
+ activeSession = void 0;
132
+ }
133
+ }
134
+ function withContentReadCriterion(criterion, collect) {
135
+ const session = activeSession;
136
+ if (!session) return collect();
137
+ const previous = session.criterion;
138
+ session.criterion = criterion;
139
+ try {
140
+ return collect();
141
+ } finally {
142
+ session.criterion = previous;
143
+ }
144
+ }
145
+ function recordContentSkip(path2, reason, deduplicate = false) {
146
+ recordSkip(path2, reason, void 0, false, deduplicate);
147
+ }
148
+ function recordFilesystemSkip(path2, error, deniedContext = false, deduplicate = true) {
149
+ const errno = errnoClass(error);
150
+ if (!errno) throw error;
151
+ if (deniedContext && (errno === "EACCES" || errno === "EPERM")) {
152
+ recordSkip(path2, "denied_path", errno, false, deduplicate);
153
+ return;
154
+ }
155
+ recordSkip(path2, "unreadable", errno, true, deduplicate);
156
+ }
157
+ function readRepoText(path2, encoding = "utf8") {
158
+ try {
159
+ const stat = lstatSync(path2);
160
+ if (!stat.isFile()) {
161
+ recordSkip(path2, "non_regular_file", void 0, true, true);
162
+ return "";
163
+ }
164
+ } catch (error) {
165
+ recordFilesystemSkip(path2, error);
166
+ return "";
167
+ }
168
+ try {
169
+ return readFileSync(path2, encoding);
170
+ } catch (error) {
171
+ recordFilesystemSkip(path2, error);
172
+ return "";
173
+ }
174
+ }
175
+ function readRepoTextPrefix(path2, byteLimit) {
176
+ try {
177
+ const stat = lstatSync(path2);
178
+ if (!stat.isFile()) {
179
+ recordSkip(path2, "non_regular_file", void 0, true, true);
180
+ return "";
181
+ }
182
+ } catch (error) {
183
+ recordFilesystemSkip(path2, error);
184
+ return "";
185
+ }
186
+ let descriptor = null;
187
+ try {
188
+ descriptor = openSync(path2, "r");
189
+ const buffer = Buffer.allocUnsafe(byteLimit);
190
+ const bytesRead = readSync(descriptor, buffer, 0, byteLimit, 0);
191
+ return buffer.subarray(0, bytesRead).toString("utf8");
192
+ } catch (error) {
193
+ recordFilesystemSkip(path2, error);
194
+ return "";
195
+ } finally {
196
+ if (descriptor !== null) {
197
+ try {
198
+ closeSync(descriptor);
199
+ } catch (error) {
200
+ recordFilesystemSkip(path2, error);
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ // src/witan/git-exec.ts
207
+ init_esm_shims();
208
+ import { execFileSync } from "child_process";
209
+ var GIT_EXEC_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
210
+ var GIT_EXEC_TIMEOUT_MS = 3e4;
211
+ var HARDENED_GIT_ARGUMENTS = [
212
+ "--no-pager",
213
+ "-c",
214
+ "core.fsmonitor=false",
215
+ "-c",
216
+ "core.pager=",
217
+ "-c",
218
+ "core.editor=false",
219
+ "-c",
220
+ "core.sshCommand=false",
221
+ "-c",
222
+ "diff.external=false",
223
+ "-c",
224
+ "credential.helper=",
225
+ "-c",
226
+ "log.showSignature=false",
227
+ "-c",
228
+ "gpg.program=false",
229
+ "-c",
230
+ "gpg.openpgp.program=false",
231
+ "-c",
232
+ "gpg.x509.program=false",
233
+ "-c",
234
+ "gpg.ssh.program=false",
235
+ "-c",
236
+ "core.quotePath=false",
237
+ "-c",
238
+ "protocol.allow=never",
239
+ "-c",
240
+ "protocol.ext.allow=never",
241
+ "-c",
242
+ "protocol.file.allow=never",
243
+ "-c",
244
+ "protocol.git.allow=never",
245
+ "-c",
246
+ "protocol.http.allow=never",
247
+ "-c",
248
+ "protocol.https.allow=never",
249
+ "-c",
250
+ "protocol.ssh.allow=never"
251
+ ];
252
+ function hardenedGitEnvironment() {
253
+ const environment = {};
254
+ for (const key of ["PATH", "HOME", "TZ"]) {
255
+ const entry = Object.entries(process.env).find(
256
+ ([candidate, value]) => candidate.toUpperCase() === key && value !== void 0
257
+ );
258
+ if (entry?.[1] !== void 0) environment[key] = entry[1];
259
+ }
260
+ if (environment.HOME === void 0 && process.env.USERPROFILE !== void 0) {
261
+ environment.HOME = process.env.USERPROFILE;
262
+ }
263
+ return {
264
+ ...environment,
265
+ LC_ALL: "C",
266
+ LANG: "C",
267
+ GIT_CONFIG_NOSYSTEM: "1",
268
+ GIT_TERMINAL_PROMPT: "0",
269
+ GIT_ASKPASS: "true",
270
+ GIT_OPTIONAL_LOCKS: "0",
271
+ GIT_PAGER: "cat",
272
+ PAGER: "cat"
273
+ };
274
+ }
275
+ function errorField(error, key) {
276
+ if (typeof error !== "object" || error === null) return void 0;
277
+ return error[key];
278
+ }
279
+ function errorText(error) {
280
+ const stderr = errorField(error, "stderr");
281
+ const stderrText = typeof stderr === "string" ? stderr : Buffer.isBuffer(stderr) ? stderr.toString("utf8") : "";
282
+ const message = error instanceof Error ? error.message : "";
283
+ return `${stderrText}
284
+ ${message}`;
285
+ }
286
+ function classifyGitFailure(error) {
287
+ const code = errorField(error, "code");
288
+ const signal = errorField(error, "signal");
289
+ const text = errorText(error);
290
+ if (code === "ENOENT") return "git_absent";
291
+ if (code === "ENOBUFS" || /maxBuffer|ENOBUFS/i.test(text)) return "buffer_exceeded";
292
+ if (code === "ETIMEDOUT" || signal === "SIGTERM" || /timed?\s*out|ETIMEDOUT/i.test(text)) {
293
+ return "timeout";
294
+ }
295
+ if (/not a git repository|not inside a work tree|not in a git directory|unknown revision.*HEAD|ambiguous argument ['"]?HEAD|does not have any commits yet|needed a single revision/i.test(
296
+ text
297
+ )) {
298
+ return "not_a_repo";
299
+ }
300
+ return "exec_failed";
301
+ }
302
+ function execGit(argv, options) {
303
+ try {
304
+ const stdout = execFileSync("git", [...HARDENED_GIT_ARGUMENTS, ...argv], {
305
+ cwd: options.cwd,
306
+ encoding: "utf8",
307
+ env: hardenedGitEnvironment(),
308
+ input: options.input,
309
+ maxBuffer: options.maxBufferBytes ?? GIT_EXEC_MAX_BUFFER_BYTES,
310
+ stdio: ["pipe", "pipe", "pipe"],
311
+ timeout: GIT_EXEC_TIMEOUT_MS
312
+ });
313
+ return { ok: true, stdout };
314
+ } catch (error) {
315
+ const status = errorField(error, "status");
316
+ return {
317
+ ok: false,
318
+ reason: classifyGitFailure(error),
319
+ ...typeof status === "number" ? { exitCode: status } : {}
320
+ };
321
+ }
322
+ }
323
+ function isExpectedGitAbsence(result) {
324
+ return result.reason === "git_absent" || result.reason === "not_a_repo";
325
+ }
326
+ function describeGitFailure(reason, operation, maxBufferBytes = GIT_EXEC_MAX_BUFFER_BYTES) {
327
+ if (reason === "buffer_exceeded") {
328
+ const outputLimit = maxBufferBytes % (1024 * 1024) === 0 ? `${maxBufferBytes / (1024 * 1024)} MiB` : `${Math.ceil(maxBufferBytes / 1024)} KiB`;
329
+ return `Cejel: local git ${operation} exceeded the ${outputLimit} output limit.`;
330
+ }
331
+ if (reason === "timeout") {
332
+ return `Cejel: local git ${operation} exceeded the 30 second timeout.`;
333
+ }
334
+ if (reason === "git_absent") {
335
+ return `Cejel: local git is unavailable while running ${operation}.`;
336
+ }
337
+ if (reason === "not_a_repo") {
338
+ return `Cejel: ${operation} requires a usable Git work tree.`;
339
+ }
340
+ return `Cejel: local git ${operation} failed.`;
341
+ }
342
+
343
+ export {
344
+ __require,
345
+ __commonJS,
346
+ __export,
347
+ __toESM,
348
+ __dirname,
349
+ __filename,
350
+ init_esm_shims,
351
+ trackContentReads,
352
+ withContentReadCriterion,
353
+ recordContentSkip,
354
+ recordFilesystemSkip,
355
+ readRepoText,
356
+ readRepoTextPrefix,
357
+ execGit,
358
+ isExpectedGitAbsence,
359
+ describeGitFailure
360
+ };