@cejel/cejel 0.6.7 → 0.7.0-rc.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 +73 -14
- package/dist/calibration/llm-detector.js +5 -5
- package/dist/{chunk-RH6FKREX.js → chunk-6LVNNJTG.js} +1 -1
- package/dist/{chunk-FSMZV3Y6.js → chunk-AMVP7L26.js} +1 -1
- package/dist/{chunk-O3MHSK25.js → chunk-AWLKQUI4.js} +56 -6
- package/dist/{chunk-VGBFZBZQ.js → chunk-D7MWEPPX.js} +76 -19
- package/dist/{chunk-TOQPQPDF.js → chunk-LBJFDK4R.js} +3 -3
- package/dist/{chunk-ZZHQZW26.js → chunk-SNRQAKKE.js} +13987 -9623
- package/dist/{chunk-5PFYOIOS.js → chunk-UMGZWGOI.js} +2 -2
- package/dist/index.js +102 -13
- package/dist/mcp/index.js +5 -5
- package/dist/packs/d-series/index.js +4 -4
- package/dist/packs/decision-contracts/index.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
[](https://github.com/BargLabs/cejel/releases/latest)
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
|
|
6
|
+
<p align="left"><img src="brand/cejel-icon.png" alt="Cejel logo" width="96"></p>
|
|
7
|
+
|
|
8
|
+
**Evidence about a repository that anyone can rerun.** Cejel reads a repository's own evidence
|
|
9
|
+
(tests that really assert something, committed secrets, CI gating) and writes a certificate
|
|
10
|
+
pinned to one commit. Whoever receives it, a customer, a buyer or an acquirer, can run the same
|
|
11
|
+
Cejel version on the same commit themselves, offline, and get the same result. No LLM, no signup.
|
|
12
|
+
|
|
13
|
+
Add it to a workflow; the result is posted to the job summary:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
on: [pull_request]
|
|
17
|
+
jobs:
|
|
18
|
+
cejel:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0 # full history: A2 and B2 read commit history
|
|
24
|
+
- uses: BargLabs/cejel/action@v1
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or run it once from a shell: `npx @cejel/cejel@latest .` More on the Action under
|
|
28
|
+
[GitHub Action](#github-action).
|
|
29
|
+
|
|
6
30
|
Cejel (*"SEH-jel"*) — a trust certificate for your codebase.
|
|
7
31
|
|
|
8
32
|
Free, offline, no-signup CLI that scores the engineering signals that tell you whether to
|
|
@@ -92,7 +116,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
|
|
|
92
116
|
```
|
|
93
117
|
|
|
94
118
|
> **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
|
|
95
|
-
> 0.6.
|
|
119
|
+
> 0.6.8 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
|
|
96
120
|
> signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
|
|
97
121
|
> it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
|
|
98
122
|
> GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM (the packages
|
|
@@ -116,7 +140,7 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
|
|
|
116
140
|
npx @cejel/cejel@latest .
|
|
117
141
|
```
|
|
118
142
|
|
|
119
|
-
> **Distribution note:** Cejel `0.6.
|
|
143
|
+
> **Distribution note:** Cejel `0.6.8` is the coordinated release version for npm,
|
|
120
144
|
> standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry. A GitLab CI
|
|
121
145
|
> template and Code Quality export live in [`docs/gitlab-ci.md`](./docs/gitlab-ci.md).
|
|
122
146
|
|
|
@@ -157,8 +181,24 @@ The npm package is scoped as `@cejel/cejel`; its executable remains the short co
|
|
|
157
181
|
min-score: "2.5" # optional: fail the build below this
|
|
158
182
|
```
|
|
159
183
|
|
|
160
|
-
`actions/checkout` fetches one commit by default
|
|
161
|
-
commit
|
|
184
|
+
`actions/checkout` fetches one commit by default, so clone in full with `fetch-depth: 0`. On a
|
|
185
|
+
shallow clone the criteria that read commit history see only the commits it holds: A2's scans of
|
|
186
|
+
earlier commits for `.env` files and credentials read only those, and B2 withholds its
|
|
187
|
+
pull-request merge ratio, or abstains where nothing else is left to score it on, saying why in its
|
|
188
|
+
note. The certificate says so under "History limitations".
|
|
189
|
+
Under the opt-in prospective v27 rubric (`--rubric-pin witan-rubric-v27-prospective-2026-10-19`;
|
|
190
|
+
v17 remains the default), A2 abstains on a shallow clone unless a confirmed critical secret finding
|
|
191
|
+
stands, including where it would otherwise be marked not applicable, since that decision also rests
|
|
192
|
+
on earlier commits. That abstention is left out of the composite, but the verdict is capped at the
|
|
193
|
+
band the composite reaches with it counted as 0.
|
|
194
|
+
A2 also abstains under v27 on any clone where one of its reads of earlier commits failed:
|
|
195
|
+
for example on a partial clone made with `--filter`, which `fetch-depth: 0` and no `filter` avoid,
|
|
196
|
+
or where a read stops at one of Cejel's own limits: an output limit or the history secret scan's
|
|
197
|
+
cap of 5,000 historical files, which happen on some large complete histories and which a re-scan
|
|
198
|
+
of the same repository does not lift (#567), or the 30-second timeout, which a re-scan may lift.
|
|
199
|
+
It also abstains when it leaves a credential-shaped path in history unread because the path's name
|
|
200
|
+
contains control characters, and when git itself cannot be run on the scanning host. Each of these
|
|
201
|
+
abstentions counts as 0 in the composite, so they can lower the v27 score.
|
|
162
202
|
|
|
163
203
|
For an executed public example that binds a release artifact to the same commit named by its
|
|
164
204
|
Cejel certificate, see [Certified releases with GitHub build provenance](./docs/certified-release-example.md).
|
|
@@ -187,16 +227,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
|
|
|
187
227
|
This is cryptographically signed provenance. It is distinct from Apple Developer ID or
|
|
188
228
|
Microsoft Authenticode code-signing.
|
|
189
229
|
|
|
190
|
-
**Docker / OCI.** The current container release is `0.6.
|
|
230
|
+
**Docker / OCI.** The current container release is `0.6.8`:
|
|
191
231
|
|
|
192
232
|
```bash
|
|
193
|
-
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.
|
|
233
|
+
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.8
|
|
194
234
|
```
|
|
195
235
|
|
|
196
236
|
The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
|
|
197
237
|
|
|
198
238
|
```bash
|
|
199
|
-
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.
|
|
239
|
+
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.8 .
|
|
200
240
|
```
|
|
201
241
|
|
|
202
242
|
The OCI image carries an SBOM of its base-image packages, maximum-mode build provenance, and a
|
|
@@ -209,7 +249,7 @@ The [Cejel OSS trust leaderboard](https://cejel.dev/leaderboard/) is hosted on c
|
|
|
209
249
|
which is the single current board. This repository no longer distributes a second copy of
|
|
210
250
|
its scores or certificates. The board records its producing package version, rubric,
|
|
211
251
|
run date, pinned public revisions, and reproduction instructions for that particular run.
|
|
212
|
-
The private
|
|
252
|
+
The board's row for Barg Labs' own private codebase is a transparency snapshot and is not independently reproducible.
|
|
213
253
|
|
|
214
254
|
The site's History section preserves the **2026-08-18 withdrawal**: the earlier
|
|
215
255
|
sealed-public-scorer reproducibility claim was false. The stale prospective-rubric copy
|
|
@@ -296,6 +336,12 @@ prints that boundary on every successful verification.
|
|
|
296
336
|
- `--ingest <file|glob>` — fold another scanner's output into the score (repeatable). Accepts
|
|
297
337
|
SARIF, OpenSSF Scorecard JSON, or the generic Cejel external-signal shape — format is
|
|
298
338
|
auto-detected. See "Aggregate your scanners" below.
|
|
339
|
+
- `--subject-repository <repo>` — name the scanned repository (`github.com/owner/repo` or a clone
|
|
340
|
+
URL) so an ingested input that declares another repository is recorded as mismatched. Cejel
|
|
341
|
+
never infers it from clone state.
|
|
342
|
+
- `--strict-ingest` — refuse, before scoring, an ingested input that declares another repository or
|
|
343
|
+
commit, was observed before the scanned commit existed, or duplicates another input. Without it,
|
|
344
|
+
the binding status is recorded and scoring is unchanged. See [input binding](docs/input-binding.md).
|
|
299
345
|
- `--rubric-pin <version>` — Explicit opt-in only: pin an alternate rubric instead of the
|
|
300
346
|
calibrated public default. It
|
|
301
347
|
accepts the calibrated version or any published prospective rubric
|
|
@@ -382,7 +428,7 @@ Three ways a scanner's output gets ingested:
|
|
|
382
428
|
`--ingest scorecard.json`; auto-detected by its `checks` array.
|
|
383
429
|
3. **Generic JSON** — for a tool that emits neither, map its output into the minimal shape
|
|
384
430
|
below (or write a small adapter mirroring
|
|
385
|
-
[`scorecard-adapter.ts`](
|
|
431
|
+
[`scorecard-adapter.ts`](./src/witan/scorecard-adapter.ts) if the format needs real
|
|
386
432
|
parsing):
|
|
387
433
|
|
|
388
434
|
```json
|
|
@@ -406,7 +452,7 @@ Three ways a scanner's output gets ingested:
|
|
|
406
452
|
}
|
|
407
453
|
```
|
|
408
454
|
|
|
409
|
-
`dimension` is one of the
|
|
455
|
+
`dimension` is one of the rubric criterion ids (`A1`-`A5`, `B1`-`B6`); `weight`
|
|
410
456
|
(0–1, default `0.5`) bounds how much this signal can move that dimension; `severity` is
|
|
411
457
|
`critical` | `warning` | `info`; `location` is optional.
|
|
412
458
|
|
|
@@ -414,7 +460,17 @@ Three ways a scanner's output gets ingested:
|
|
|
414
460
|
versions rather than guessing. See the [generic ingest contract](docs/ingest-contract.md), its
|
|
415
461
|
[published JSON Schema](docs/schemas/cejel-ingest-v1.schema.json), and the
|
|
416
462
|
[synthetic CI heal-log mapping](docs/heal-log-mapping.md). Output-field stability and version
|
|
417
|
-
routing are documented in [format stability](docs/format-stability.md).
|
|
463
|
+
routing are documented in [format stability](docs/format-stability.md). Contract `1.1` adds
|
|
464
|
+
optional `subject`, `producer` and `observedAt` fields so the input can be bound to a revision.
|
|
465
|
+
|
|
466
|
+
Every ingested document is also recorded in `report.json` under `inputBinding`: the sha256 of its
|
|
467
|
+
bytes, the producer and subject its own format declares (Scorecard `repo.name`/`repo.commit`/`date`,
|
|
468
|
+
SARIF `versionControlProvenance` and `invocations`, generic `subject`/`observedAt`), and whether that
|
|
469
|
+
subject is the scanned HEAD (`matched`, `mismatched-repository`, `mismatched-commit`, `stale`,
|
|
470
|
+
`unbound`, `unverifiable`). By default this is a record, not a gate: it changes no score.
|
|
471
|
+
`--strict-ingest` refuses mismatched, stale and duplicate inputs instead. `cejel verify ... --input
|
|
472
|
+
<file>` checks a set of input files against the recorded digests, so a swapped input is detected.
|
|
473
|
+
See [input binding](docs/input-binding.md), which carries the conformance table the tests run.
|
|
418
474
|
|
|
419
475
|
## Displaying the trust badge
|
|
420
476
|
|
|
@@ -448,7 +504,10 @@ secrets.
|
|
|
448
504
|
```
|
|
449
505
|
|
|
450
506
|
Check out with `fetch-depth: 0`: the default depth of 1 truncates the history A2 and B2 read, and
|
|
451
|
-
the certificate then lists a history limitation.
|
|
507
|
+
the certificate then lists a history limitation. On a shallow clone A2's scans of earlier commits
|
|
508
|
+
read only the commits present and B2 withholds its pull-request merge ratio, each with its reason;
|
|
509
|
+
under the opt-in `witan-rubric-v27-prospective-2026-10-19` rubric, A2 abstains and the verdict is
|
|
510
|
+
capped where A2 could not be read.
|
|
452
511
|
|
|
453
512
|
## Use with Claude
|
|
454
513
|
|
|
@@ -571,7 +630,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
|
|
|
571
630
|
the host path with the repository OpenClaw should allow Cejel to read:
|
|
572
631
|
|
|
573
632
|
```bash
|
|
574
|
-
npx -y openclaw@latest mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.6.
|
|
633
|
+
npx -y openclaw@latest mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.6.8"]}'
|
|
575
634
|
npx -y openclaw@latest mcp doctor cejel-oci --probe
|
|
576
635
|
```
|
|
577
636
|
|
|
@@ -734,7 +793,7 @@ scoring tool that has not been checked:
|
|
|
734
793
|
the invariant it claimed to be. Rubric v3 removes that second path: every corpus row now
|
|
735
794
|
passes through the same sealed public scorer, every external source is pinned to a commit,
|
|
736
795
|
and a required guard re-scores every row and compares score, verdict, coverage, and
|
|
737
|
-
evidence. The honest correction moved
|
|
796
|
+
evidence. The honest correction moved our own private codebase's Code score from 2.5 to 2.4 and A5 from 2.9
|
|
738
797
|
to 2.4. It is less flattering and more trustworthy.
|
|
739
798
|
- **A tracked symlink could make evidence depend on files outside the checkout.** Repository
|
|
740
799
|
discovery used a regular-file check that followed symlinks, so a tracked link could escape
|
|
@@ -5,20 +5,20 @@ import {
|
|
|
5
5
|
deriveProductIdentity,
|
|
6
6
|
runCejelScan,
|
|
7
7
|
serializeWitanReport
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-SNRQAKKE.js";
|
|
9
9
|
import {
|
|
10
10
|
listCejelLlmPackFiles
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
11
|
+
} from "../chunk-UMGZWGOI.js";
|
|
12
|
+
import "../chunk-D7MWEPPX.js";
|
|
13
13
|
import {
|
|
14
14
|
external_exports
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-AMVP7L26.js";
|
|
16
16
|
import {
|
|
17
17
|
init_esm_shims,
|
|
18
18
|
readRepoText,
|
|
19
19
|
recordContentSkip,
|
|
20
20
|
recordFilesystemSkip
|
|
21
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-AWLKQUI4.js";
|
|
22
22
|
|
|
23
23
|
// src/calibration/llm-detector.ts
|
|
24
24
|
init_esm_shims();
|
|
@@ -62,7 +62,11 @@ var MAX_REPOSITORY_CONTENT_BYTES = 512e3;
|
|
|
62
62
|
init_esm_shims();
|
|
63
63
|
import { closeSync, lstatSync, openSync, readFileSync, readSync } from "fs";
|
|
64
64
|
import { resolve } from "path";
|
|
65
|
-
var READ_FAILURE_REASONS = /* @__PURE__ */ new Set([
|
|
65
|
+
var READ_FAILURE_REASONS = /* @__PURE__ */ new Set([
|
|
66
|
+
"unreadable",
|
|
67
|
+
"denied_path",
|
|
68
|
+
"lfs_pointer"
|
|
69
|
+
]);
|
|
66
70
|
function isReadFailureReason(reason) {
|
|
67
71
|
return READ_FAILURE_REASONS.has(reason);
|
|
68
72
|
}
|
|
@@ -131,7 +135,10 @@ function summaryFor(session) {
|
|
|
131
135
|
tooLarge: session.counts.too_large,
|
|
132
136
|
excludedByExtension: session.counts.excluded_by_extension,
|
|
133
137
|
deniedPath: session.counts.denied_path,
|
|
134
|
-
nonRegularFile: session.counts.non_regular_file
|
|
138
|
+
nonRegularFile: session.counts.non_regular_file,
|
|
139
|
+
// Present only when non-zero, so every report without an LFS pointer is byte-identical to one
|
|
140
|
+
// produced before this reason existed (report format 1.6).
|
|
141
|
+
...session.counts.lfs_pointer > 0 ? { lfsPointer: session.counts.lfs_pointer } : {}
|
|
135
142
|
};
|
|
136
143
|
const sortedErrnos = Object.fromEntries(
|
|
137
144
|
[...session.unreadableByErrno.entries()].sort(([left], [right]) => left.localeCompare(right))
|
|
@@ -143,7 +150,7 @@ function summaryFor(session) {
|
|
|
143
150
|
affectedCriteria: [...session.affectedCriteria].sort()
|
|
144
151
|
};
|
|
145
152
|
}
|
|
146
|
-
function trackContentReads(_repoPath, collect) {
|
|
153
|
+
function trackContentReads(_repoPath, collect, options = {}) {
|
|
147
154
|
const existing = activeSession;
|
|
148
155
|
if (existing) {
|
|
149
156
|
return {
|
|
@@ -162,8 +169,10 @@ function trackContentReads(_repoPath, collect) {
|
|
|
162
169
|
too_large: 0,
|
|
163
170
|
excluded_by_extension: 0,
|
|
164
171
|
denied_path: 0,
|
|
165
|
-
non_regular_file: 0
|
|
172
|
+
non_regular_file: 0,
|
|
173
|
+
lfs_pointer: 0
|
|
166
174
|
},
|
|
175
|
+
lfsPointerAbstention: options.lfsPointerAbstention === true,
|
|
167
176
|
unreadableByErrno: /* @__PURE__ */ new Map(),
|
|
168
177
|
deduplicatedSkips: /* @__PURE__ */ new Set(),
|
|
169
178
|
affectedCriteria: /* @__PURE__ */ new Set(),
|
|
@@ -218,6 +227,20 @@ function withContentReadSignals(criterionId, signalIds, collect) {
|
|
|
218
227
|
session.signals = previous;
|
|
219
228
|
}
|
|
220
229
|
}
|
|
230
|
+
function withoutContentReadAttribution(collect) {
|
|
231
|
+
const session = activeSession;
|
|
232
|
+
if (!session) return collect();
|
|
233
|
+
const previousCriterion = session.criterion;
|
|
234
|
+
const previousSignals = session.signals;
|
|
235
|
+
session.criterion = void 0;
|
|
236
|
+
session.signals = void 0;
|
|
237
|
+
try {
|
|
238
|
+
return collect();
|
|
239
|
+
} finally {
|
|
240
|
+
session.criterion = previousCriterion;
|
|
241
|
+
session.signals = previousSignals;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
221
244
|
function recordContentSkip(path2, reason, deduplicate = false) {
|
|
222
245
|
recordSkip(path2, reason, void 0, false, deduplicate);
|
|
223
246
|
}
|
|
@@ -287,12 +310,33 @@ function readRepoText(path2, encoding = "utf8") {
|
|
|
287
310
|
recordFilesystemSkip(path2, error);
|
|
288
311
|
return "";
|
|
289
312
|
}
|
|
313
|
+
let contents;
|
|
290
314
|
try {
|
|
291
|
-
|
|
315
|
+
contents = readFileSync(path2, encoding);
|
|
292
316
|
} catch (error) {
|
|
293
317
|
recordFilesystemSkip(path2, error);
|
|
294
318
|
return "";
|
|
295
319
|
}
|
|
320
|
+
if (isGitLfsPointerText(contents)) {
|
|
321
|
+
recordSkip(path2, "lfs_pointer", void 0, activeSession?.lfsPointerAbstention === true, true);
|
|
322
|
+
}
|
|
323
|
+
return contents;
|
|
324
|
+
}
|
|
325
|
+
var GIT_LFS_POINTER_MAX_BYTES = 1024;
|
|
326
|
+
var GIT_LFS_POINTER_VERSION_LINE = /^version https:\/\/(?:git-lfs\.github\.com\/spec\/v1|hawser\.github\.com\/spec\/v1)\n/;
|
|
327
|
+
var GIT_LFS_POINTER_OID_LINE = /^oid sha256:[0-9a-f]{64}$/m;
|
|
328
|
+
var GIT_LFS_POINTER_SIZE_LINE = /^size [0-9]+$/m;
|
|
329
|
+
var GIT_LFS_POINTER_KEY_LINE = /^[a-z0-9.-]+ \S[^\n]*$/;
|
|
330
|
+
function isGitLfsPointerText(contents) {
|
|
331
|
+
if (isLfGitLfsPointerText(contents)) return true;
|
|
332
|
+
return contents.length < 2 * GIT_LFS_POINTER_MAX_BYTES && contents.includes("\r\n") && !/\r(?!\n)/.test(contents) && !/(?:^|[^\r])\n/.test(contents) && isLfGitLfsPointerText(contents.replaceAll("\r\n", "\n"));
|
|
333
|
+
}
|
|
334
|
+
function isLfGitLfsPointerText(contents) {
|
|
335
|
+
if (contents.length >= GIT_LFS_POINTER_MAX_BYTES || !contents.startsWith("version ")) return false;
|
|
336
|
+
if (!GIT_LFS_POINTER_VERSION_LINE.test(contents)) return false;
|
|
337
|
+
const lines = contents.replace(/\n$/, "").split("\n");
|
|
338
|
+
if (!lines.slice(1).every((line) => GIT_LFS_POINTER_KEY_LINE.test(line))) return false;
|
|
339
|
+
return GIT_LFS_POINTER_OID_LINE.test(contents) && GIT_LFS_POINTER_SIZE_LINE.test(contents);
|
|
296
340
|
}
|
|
297
341
|
function readRepoTextPrefix(path2, byteLimit) {
|
|
298
342
|
try {
|
|
@@ -310,7 +354,11 @@ function readRepoTextPrefix(path2, byteLimit) {
|
|
|
310
354
|
descriptor = openSync(path2, "r");
|
|
311
355
|
const buffer = Buffer.allocUnsafe(byteLimit);
|
|
312
356
|
const bytesRead = readSync(descriptor, buffer, 0, byteLimit, 0);
|
|
313
|
-
|
|
357
|
+
const contents = buffer.subarray(0, bytesRead).toString("utf8");
|
|
358
|
+
if (bytesRead < byteLimit && isGitLfsPointerText(contents)) {
|
|
359
|
+
recordSkip(path2, "lfs_pointer", void 0, activeSession?.lfsPointerAbstention === true, true);
|
|
360
|
+
}
|
|
361
|
+
return contents;
|
|
314
362
|
} catch (error) {
|
|
315
363
|
recordFilesystemSkip(path2, error);
|
|
316
364
|
return "";
|
|
@@ -340,11 +388,13 @@ export {
|
|
|
340
388
|
withContentReadCriterion,
|
|
341
389
|
withContentReadSignal,
|
|
342
390
|
withContentReadSignals,
|
|
391
|
+
withoutContentReadAttribution,
|
|
343
392
|
recordContentSkip,
|
|
344
393
|
registerWithheldRepoPath,
|
|
345
394
|
withheldRepoPaths,
|
|
346
395
|
abstainSignalOnWithheldPaths,
|
|
347
396
|
recordFilesystemSkip,
|
|
348
397
|
readRepoText,
|
|
398
|
+
isGitLfsPointerText,
|
|
349
399
|
readRepoTextPrefix
|
|
350
400
|
};
|
|
@@ -3,13 +3,13 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
|
|
|
3
3
|
const require = __cejelCreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
init_esm_shims
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-AWLKQUI4.js";
|
|
7
7
|
|
|
8
8
|
// src/witan/git-exec.ts
|
|
9
9
|
init_esm_shims();
|
|
10
10
|
import { execFileSync } from "child_process";
|
|
11
|
-
import { realpathSync } from "fs";
|
|
12
|
-
import { dirname, resolve } from "path";
|
|
11
|
+
import { closeSync, lstatSync, openSync, readSync, realpathSync } from "fs";
|
|
12
|
+
import { dirname, join, resolve } from "path";
|
|
13
13
|
var GIT_EXEC_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
14
14
|
var GIT_EXEC_TIMEOUT_MS = 3e4;
|
|
15
15
|
var HARDENED_GIT_ARGUMENTS = [
|
|
@@ -54,6 +54,55 @@ var HARDENED_GIT_ARGUMENTS = [
|
|
|
54
54
|
"protocol.ssh.allow=never"
|
|
55
55
|
];
|
|
56
56
|
var discoveryCeiling;
|
|
57
|
+
var GIT_FILE_PREFIX_BYTES = 4096;
|
|
58
|
+
function fsErrorCode(error) {
|
|
59
|
+
return typeof error === "object" && error !== null ? error.code : void 0;
|
|
60
|
+
}
|
|
61
|
+
var isDefinitelyAbsent = (error) => fsErrorCode(error) === "ENOENT" || fsErrorCode(error) === "ENOTDIR";
|
|
62
|
+
function isGitMetadataEntry(path) {
|
|
63
|
+
let link;
|
|
64
|
+
try {
|
|
65
|
+
link = lstatSync(path);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return !isDefinitelyAbsent(error);
|
|
68
|
+
}
|
|
69
|
+
if (link.isSymbolicLink()) return true;
|
|
70
|
+
if (link.isDirectory()) {
|
|
71
|
+
try {
|
|
72
|
+
lstatSync(join(path, "HEAD"));
|
|
73
|
+
return true;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return !isDefinitelyAbsent(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!link.isFile()) return false;
|
|
79
|
+
let descriptor;
|
|
80
|
+
try {
|
|
81
|
+
descriptor = openSync(path, "r");
|
|
82
|
+
const buffer = Buffer.alloc(GIT_FILE_PREFIX_BYTES);
|
|
83
|
+
const length = readSync(descriptor, buffer, 0, GIT_FILE_PREFIX_BYTES, 0);
|
|
84
|
+
return /^gitdir:/m.test(buffer.subarray(0, length).toString("utf8"));
|
|
85
|
+
} catch {
|
|
86
|
+
return true;
|
|
87
|
+
} finally {
|
|
88
|
+
if (descriptor !== void 0) closeSync(descriptor);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function hasVisibleGitMetadata(repoPath) {
|
|
92
|
+
let current;
|
|
93
|
+
try {
|
|
94
|
+
current = realpathSync(repoPath);
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
for (; ; ) {
|
|
99
|
+
if (discoveryCeiling !== void 0 && current === discoveryCeiling) return false;
|
|
100
|
+
if (isGitMetadataEntry(join(current, ".git"))) return true;
|
|
101
|
+
const parent = dirname(current);
|
|
102
|
+
if (parent === current) return false;
|
|
103
|
+
current = parent;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
57
106
|
function hardenedGitEnvironment(cwd) {
|
|
58
107
|
const environment = {};
|
|
59
108
|
for (const key of ["PATH", "HOME", "TZ"]) {
|
|
@@ -88,27 +137,32 @@ function errorField(error, key) {
|
|
|
88
137
|
if (typeof error !== "object" || error === null) return void 0;
|
|
89
138
|
return error[key];
|
|
90
139
|
}
|
|
91
|
-
function
|
|
140
|
+
function stderrText(error) {
|
|
92
141
|
const stderr = errorField(error, "stderr");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
142
|
+
return typeof stderr === "string" ? stderr : Buffer.isBuffer(stderr) ? stderr.toString("utf8") : "";
|
|
143
|
+
}
|
|
144
|
+
var GIT_NO_REPOSITORY_DIAGNOSTICS = [
|
|
145
|
+
/^fatal: not a git repository\b/i,
|
|
146
|
+
/^fatal: gitfile does not point to a valid repository: /,
|
|
147
|
+
/^fatal: not in a git directory$/,
|
|
148
|
+
/^fatal: this operation must be run in a work tree$/,
|
|
149
|
+
/^fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree\.$/,
|
|
150
|
+
/^fatal: needed a single revision$/i
|
|
151
|
+
];
|
|
152
|
+
var GIT_UNBORN_BRANCH_DIAGNOSTIC = /^fatal: your current branch '' does not have any commits yet$/;
|
|
153
|
+
function isNoRepositoryDiagnostic(stderr) {
|
|
154
|
+
const line = stderr.split("\n").find((candidate) => candidate.startsWith("fatal: "))?.trimEnd();
|
|
155
|
+
if (line === void 0) return false;
|
|
156
|
+
if (GIT_NO_REPOSITORY_DIAGNOSTICS.some((pattern) => pattern.test(line))) return true;
|
|
157
|
+
const branchEmptied = line.replace(/^(fatal: your current branch )'[^'\n]*'/, "$1''");
|
|
158
|
+
return GIT_UNBORN_BRANCH_DIAGNOSTIC.test(branchEmptied);
|
|
97
159
|
}
|
|
98
160
|
function classifyGitFailure(error) {
|
|
99
161
|
const code = errorField(error, "code");
|
|
100
|
-
const signal = errorField(error, "signal");
|
|
101
|
-
const text = errorText(error);
|
|
102
162
|
if (code === "ENOENT") return "git_absent";
|
|
103
|
-
if (code === "ENOBUFS" ||
|
|
104
|
-
if (code === "ETIMEDOUT"
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
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(
|
|
108
|
-
text
|
|
109
|
-
)) {
|
|
110
|
-
return "not_a_repo";
|
|
111
|
-
}
|
|
163
|
+
if (code === "ENOBUFS" || code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return "buffer_exceeded";
|
|
164
|
+
if (code === "ETIMEDOUT") return "timeout";
|
|
165
|
+
if (isNoRepositoryDiagnostic(stderrText(error))) return "not_a_repo";
|
|
112
166
|
return "exec_failed";
|
|
113
167
|
}
|
|
114
168
|
function execGit(argv, options) {
|
|
@@ -154,6 +208,9 @@ function describeGitFailure(reason, operation, maxBufferBytes = GIT_EXEC_MAX_BUF
|
|
|
154
208
|
}
|
|
155
209
|
|
|
156
210
|
export {
|
|
211
|
+
GIT_EXEC_MAX_BUFFER_BYTES,
|
|
212
|
+
GIT_EXEC_TIMEOUT_MS,
|
|
213
|
+
hasVisibleGitMetadata,
|
|
157
214
|
execGit,
|
|
158
215
|
isExpectedGitAbsence,
|
|
159
216
|
describeGitFailure
|
|
@@ -5,19 +5,19 @@ import {
|
|
|
5
5
|
renderWitanBadgeSvg,
|
|
6
6
|
renderWitanHtmlReport,
|
|
7
7
|
runCejelScan
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SNRQAKKE.js";
|
|
9
9
|
import {
|
|
10
10
|
ZodFirstPartyTypeKind,
|
|
11
11
|
ZodOptional,
|
|
12
12
|
external_exports,
|
|
13
13
|
objectType
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-AMVP7L26.js";
|
|
15
15
|
import {
|
|
16
16
|
__commonJS,
|
|
17
17
|
__export,
|
|
18
18
|
__toESM,
|
|
19
19
|
init_esm_shims
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-AWLKQUI4.js";
|
|
21
21
|
|
|
22
22
|
// node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
23
23
|
var require_code = __commonJS({
|