@cejel/cejel 0.2.1 → 0.3.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 +100 -14
- package/dist/chunk-LZVKQV3R.js +360 -0
- package/dist/{chunk-BR2DO4QJ.js → chunk-XB65L6WE.js} +791 -338
- package/dist/index.js +46 -4
- package/dist/mcp/index.js +218 -5
- package/dist/packs/d-series/index.js +211708 -0
- package/package.json +14 -1
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
|
|
@@ -49,6 +53,29 @@ chmod +x cejel
|
|
|
49
53
|
./cejel .
|
|
50
54
|
```
|
|
51
55
|
|
|
56
|
+
**Windows x86_64 (PowerShell).**
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
$asset = "cejel-Windows-x86_64.exe"
|
|
60
|
+
Invoke-WebRequest "https://github.com/BargLabs/cejel/releases/latest/download/$asset" -OutFile $asset
|
|
61
|
+
Invoke-WebRequest "https://github.com/BargLabs/cejel/releases/latest/download/SHA256SUMS" -OutFile SHA256SUMS
|
|
62
|
+
$expected = ((Select-String -Path SHA256SUMS -Pattern " $([regex]::Escape($asset))$").Line -split "\s+")[0]
|
|
63
|
+
$actual = (Get-FileHash -Algorithm SHA256 $asset).Hash.ToLowerInvariant()
|
|
64
|
+
if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
|
|
65
|
+
throw "SHA-256 verification failed for $asset"
|
|
66
|
+
}
|
|
67
|
+
.\cejel-Windows-x86_64.exe .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
|
|
71
|
+
> 0.3.1 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
|
|
72
|
+
> signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
|
|
73
|
+
> it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
|
|
74
|
+
> GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM and an
|
|
75
|
+
> own-platform verification receipt covering `--version`, `--help`, a real scan,
|
|
76
|
+
> source/binary parity, and a network-denied scan. If your policy requires Authenticode,
|
|
77
|
+
> use the npm package or the OCI image until a human-approved signing path is available.
|
|
78
|
+
|
|
52
79
|
**Don't take the offline claim on trust — check it.** Turn your network off, then run the
|
|
53
80
|
binary. It will score your repository and write you a certificate anyway. That is the whole
|
|
54
81
|
product, and you can falsify it in ten seconds:
|
|
@@ -61,7 +88,17 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
|
|
|
61
88
|
**npm.**
|
|
62
89
|
|
|
63
90
|
```bash
|
|
64
|
-
npx @cejel/cejel .
|
|
91
|
+
npx @cejel/cejel@latest .
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **Distribution note:** Cejel `0.3.1` is the coordinated release version for npm,
|
|
95
|
+
> standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry.
|
|
96
|
+
|
|
97
|
+
`npx` can reuse a stale cached package. Force the current npm release with the `@latest`
|
|
98
|
+
specifier above, and check the version that will run before comparing certificates:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx @cejel/cejel@latest --version
|
|
65
102
|
```
|
|
66
103
|
|
|
67
104
|
If a root `package.json` still carries a template name, override only the certificate display
|
|
@@ -89,30 +126,30 @@ pnpm install && pnpm build
|
|
|
89
126
|
node dist/index.js .
|
|
90
127
|
```
|
|
91
128
|
|
|
92
|
-
Released binaries: `cejel-Darwin-arm64`, `cejel-Darwin-x86_64`, `cejel-Linux-aarch64`,
|
|
93
|
-
`cejel-Linux-x86_64`. The release also carries
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
129
|
+
Released binaries: `cejel-Darwin-arm64`, `cejel-Darwin-x86_64`, `cejel-Linux-aarch64`,
|
|
130
|
+
`cejel-Linux-x86_64`, and `cejel-Windows-x86_64.exe`. The release also carries
|
|
131
|
+
`SHA256SUMS`, a per-binary SPDX SBOM, and an own-platform verification receipt. Each binary
|
|
132
|
+
is executed against the source build and with networking denied before attachment. Releases
|
|
133
|
+
from v0.1.6 also carry a Sigstore bundle containing GitHub's signed build-provenance
|
|
134
|
+
attestation for the release set. Verify a downloaded binary with:
|
|
97
135
|
|
|
98
136
|
```bash
|
|
99
|
-
gh attestation verify ./cejel-
|
|
137
|
+
gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
|
|
100
138
|
```
|
|
101
139
|
|
|
102
140
|
This is cryptographically signed provenance. It is distinct from Apple Developer ID or
|
|
103
141
|
Microsoft Authenticode code-signing.
|
|
104
142
|
|
|
105
|
-
**Docker / OCI.** The current container release is `0.
|
|
106
|
-
versioned independently from npm-only patches):
|
|
143
|
+
**Docker / OCI.** The current container release is `0.3.1`:
|
|
107
144
|
|
|
108
145
|
```bash
|
|
109
|
-
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.
|
|
146
|
+
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.3.1
|
|
110
147
|
```
|
|
111
148
|
|
|
112
149
|
The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
|
|
113
150
|
|
|
114
151
|
```bash
|
|
115
|
-
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.
|
|
152
|
+
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.3.1 .
|
|
116
153
|
```
|
|
117
154
|
|
|
118
155
|
The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
|
|
@@ -360,12 +397,61 @@ The server exposes one tool and two resources:
|
|
|
360
397
|
Like the CLI, scoring over MCP is fully offline: no network calls, no telemetry, no signup,
|
|
361
398
|
and the server writes no files.
|
|
362
399
|
|
|
400
|
+
## Install on OpenClaw
|
|
401
|
+
|
|
402
|
+
OpenClaw stores outbound MCP servers under `mcp.servers`. Add Cejel with the npm package's
|
|
403
|
+
shipped `cejel-mcp` bin:
|
|
404
|
+
|
|
405
|
+
```json
|
|
406
|
+
{
|
|
407
|
+
"mcp": {
|
|
408
|
+
"servers": {
|
|
409
|
+
"cejel": {
|
|
410
|
+
"command": "npx",
|
|
411
|
+
"args": ["-y", "--package=@cejel/cejel", "cejel-mcp"]
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
The equivalent OpenClaw command is:
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
openclaw mcp add cejel --command npx --arg -y --arg --package=@cejel/cejel --arg cejel-mcp
|
|
422
|
+
openclaw mcp doctor cejel --probe
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The OCI image is an alternative when Docker is the preferred execution boundary. Replace
|
|
426
|
+
the host path with the repository OpenClaw should allow Cejel to read:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
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.1"]}'
|
|
430
|
+
openclaw mcp doctor cejel-oci --probe
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Cejel is active in the Official MCP Registry as `io.github.BargLabs/cejel`. The explicit
|
|
434
|
+
OpenClaw configuration above works independently of catalog indexing.
|
|
435
|
+
|
|
436
|
+
Cejel scans code you point it at. This free adoption surface does **not** watch, intercept,
|
|
437
|
+
or govern an agent's runtime actions. Runtime-action governance is a separate boundary for
|
|
438
|
+
the future paid Agent Pack.
|
|
439
|
+
|
|
363
440
|
## What "offline" means here
|
|
364
441
|
|
|
365
442
|
Scoring a repo — `cejel scan .` itself, and the Action's scoring step — makes zero network
|
|
366
|
-
calls: no telemetry, no signup, no model call.
|
|
367
|
-
|
|
368
|
-
|
|
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.
|
|
369
455
|
|
|
370
456
|
## Help validate Cejel
|
|
371
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
|
+
};
|