@cejel/cejel 0.1.6 → 0.1.8
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 +64 -20
- package/dist/{chunk-A7NZKJ5H.js → chunk-SFEPABAX.js} +69 -15
- package/dist/index.js +116 -9
- package/dist/mcp/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,9 +31,15 @@ No account, no key, no signup.
|
|
|
31
31
|
**Single-file binary.** One file. No Node, no npm, no `node_modules`, nothing installed.
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
+
set -eu
|
|
34
35
|
asset="cejel-$(uname -s)-$(uname -m)"
|
|
35
36
|
curl -fsSL "https://github.com/BargLabs/cejel/releases/latest/download/$asset" -o "$asset"
|
|
36
37
|
curl -fsSL https://github.com/BargLabs/cejel/releases/latest/download/SHA256SUMS -o SHA256SUMS
|
|
38
|
+
checksum_entry_count="$(awk -v asset="$asset" '$2 == asset { count++ } END { print count + 0 }' SHA256SUMS)"
|
|
39
|
+
if [ "$checksum_entry_count" -ne 1 ]; then
|
|
40
|
+
echo "Expected exactly one SHA256SUMS entry for $asset; found $checksum_entry_count" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
37
43
|
if command -v sha256sum >/dev/null; then
|
|
38
44
|
grep " $asset$" SHA256SUMS | sha256sum -c -
|
|
39
45
|
else
|
|
@@ -64,7 +70,7 @@ The npm package is scoped as `@cejel/cejel`; its executable remains the short co
|
|
|
64
70
|
**GitHub Action** — score every PR and publish the badge:
|
|
65
71
|
|
|
66
72
|
```yaml
|
|
67
|
-
- uses: BargLabs/cejel/action@
|
|
73
|
+
- uses: BargLabs/cejel/action@v0.1.8
|
|
68
74
|
with:
|
|
69
75
|
min-score: "2.5" # optional: fail the build below this
|
|
70
76
|
```
|
|
@@ -93,13 +99,21 @@ Microsoft Authenticode code-signing.
|
|
|
93
99
|
**Docker / OCI.** The same MCP server is published as a non-root, multi-platform image:
|
|
94
100
|
|
|
95
101
|
```bash
|
|
96
|
-
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.1.
|
|
102
|
+
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.1.8
|
|
97
103
|
```
|
|
98
104
|
|
|
99
105
|
The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
|
|
100
106
|
|
|
101
107
|
```bash
|
|
102
|
-
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.1.
|
|
108
|
+
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.1.8 .
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Beginning with v0.1.7, the standard CLI identity flags are also available directly, while an
|
|
112
|
+
invocation without arguments continues to start the MCP server:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
docker run --rm ghcr.io/barglabs/cejel:0.1.8 --version
|
|
116
|
+
docker run --rm ghcr.io/barglabs/cejel:0.1.8 --help
|
|
103
117
|
```
|
|
104
118
|
|
|
105
119
|
The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
|
|
@@ -108,23 +122,25 @@ Official MCP Registry.
|
|
|
108
122
|
|
|
109
123
|
## Leaderboard
|
|
110
124
|
|
|
111
|
-
This repo ships the [Cejel OSS trust leaderboard](./leaderboard/leaderboard.md):
|
|
112
|
-
|
|
113
|
-
studio monorepo Cejel was built
|
|
114
|
-
row under [`leaderboard/reports/`](./leaderboard/reports/). The
|
|
115
|
-
[cejel.dev](https://cejel.dev). Every score
|
|
116
|
-
public scorer used by `npx @cejel/cejel
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
This repo ships the [Cejel OSS trust leaderboard](./leaderboard/leaderboard.md): elite OSS
|
|
126
|
+
projects, the public Cejel repository itself, and one explicitly labeled transparency
|
|
127
|
+
snapshot from the private studio monorepo where Cejel was built, with a per-repository
|
|
128
|
+
evidence report for every row under [`leaderboard/reports/`](./leaderboard/reports/). The
|
|
129
|
+
board is also hosted at [cejel.dev](https://cejel.dev). Every score is produced by the same
|
|
130
|
+
sealed public scorer used by `npx @cejel/cejel .`; no private domain collector contributes.
|
|
131
|
+
For each public-repository row, check out the immutable source commit printed in its report
|
|
132
|
+
and run the public scorer to reproduce the score, verdict, measured coverage, and evidence.
|
|
133
|
+
The private Alfred snapshot discloses its limitation instead: its source commit and withheld
|
|
134
|
+
locations are not publicly available, so that row is not independently reproducible and is
|
|
135
|
+
not presented as a public-repository self-score.
|
|
120
136
|
|
|
121
137
|
### Redaction policy
|
|
122
138
|
|
|
123
139
|
A path is published exactly when the reader can check it. Every public repository on the
|
|
124
140
|
board cites full evidence paths and line numbers, everywhere, in every artifact — a
|
|
125
|
-
certificate whose evidence you cannot open is not evidence. The
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
certificate whose evidence you cannot open is not evidence. The private-repository entry
|
|
142
|
+
(the studio monorepo's transparency snapshot) never cites a source path, anywhere, in any
|
|
143
|
+
field or format — but the finding itself, its
|
|
128
144
|
dimension, status, score, and content hash always survive; only the location is withheld,
|
|
129
145
|
marked uniformly as "path withheld — private repository". Redaction removes a location, never
|
|
130
146
|
a fact: a private repository failing its own check still shows up on its certificate, by
|
|
@@ -152,10 +168,11 @@ better-evidenced rows.
|
|
|
152
168
|
## Usage
|
|
153
169
|
|
|
154
170
|
```bash
|
|
155
|
-
./cejel .
|
|
171
|
+
./cejel scan .
|
|
156
172
|
```
|
|
157
173
|
|
|
158
|
-
|
|
174
|
+
`scan` scores the current directory with sensible defaults: no signup, fully offline. The
|
|
175
|
+
original `./cejel .` form remains supported as a compatibility shorthand.
|
|
159
176
|
Prints a concise terminal certificate and writes to `.cejel/`:
|
|
160
177
|
|
|
161
178
|
- `report.json` — the full structured report; scored runs carry numeric headline scores and a
|
|
@@ -172,6 +189,16 @@ statement, but it does not pretend to be an independent signer. Until a customer
|
|
|
172
189
|
or provenance system signs it, `assurance.status` is `unsigned` and `issuer` is
|
|
173
190
|
`self-generated`. An abstained scan carries only the refusal reason, never a numeric score.
|
|
174
191
|
|
|
192
|
+
Verify that an emitted report still matches its attestation:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
./cejel verify .cejel/report.json .cejel/attestation.json
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
This verifies the report schema and the report-to-attestation digest, repository, rubric,
|
|
199
|
+
timestamp, and outcome binding. It does not verify a signature or signer identity; the command
|
|
200
|
+
prints that boundary on every successful verification.
|
|
201
|
+
|
|
175
202
|
### Flags
|
|
176
203
|
|
|
177
204
|
- `<path>` — repo to score (default: current directory)
|
|
@@ -297,7 +324,7 @@ a configurable `min-score` threshold. The scoring step makes no network calls an
|
|
|
297
324
|
secrets.
|
|
298
325
|
|
|
299
326
|
```yaml
|
|
300
|
-
- uses: BargLabs/cejel/action@
|
|
327
|
+
- uses: BargLabs/cejel/action@v0.1.8
|
|
301
328
|
with:
|
|
302
329
|
min-score: '2.5' # optional; omit to never fail the check
|
|
303
330
|
```
|
|
@@ -340,7 +367,7 @@ and the server writes no files.
|
|
|
340
367
|
|
|
341
368
|
## What "offline" means here
|
|
342
369
|
|
|
343
|
-
Scoring a repo — `cejel .` itself, and the Action's scoring step — makes zero network
|
|
370
|
+
Scoring a repo — `cejel scan .` itself, and the Action's scoring step — makes zero network
|
|
344
371
|
calls: no telemetry, no signup, no model call. Fetching the `@cejel/cejel` package the
|
|
345
372
|
first time (like any npm-distributed CLI, including this Action's own dependency install)
|
|
346
373
|
does need network; that's a one-time install cost, not part of the scoring guarantee.
|
|
@@ -376,7 +403,7 @@ enforced structurally — the public artifacts are built from filtered data rath
|
|
|
376
403
|
rendered and then scrubbed — and a build that would emit a private path fails rather than
|
|
377
404
|
publishes.
|
|
378
405
|
|
|
379
|
-
**What we exclude from ranking.** The
|
|
406
|
+
**What we exclude from ranking.** The v6 repository scanner does not evaluate B1 (dispatch
|
|
380
407
|
trace completeness) or B5 (verified learning trace) for repository inputs, including ours:
|
|
381
408
|
both are always *not applicable* in a repository certificate. They remain defined in the
|
|
382
409
|
rubric for structured substrate evidence, but that evidence is not accepted by this scanner.
|
|
@@ -421,6 +448,18 @@ scoring tool that has not been checked:
|
|
|
421
448
|
and a required guard re-scores every row and compares score, verdict, coverage, and
|
|
422
449
|
evidence. The honest correction moved Alfred's Code score from 2.5 to 2.4 and A5 from 2.9
|
|
423
450
|
to 2.4. It is less flattering and more trustworthy.
|
|
451
|
+
- **A tracked symlink could make evidence depend on files outside the checkout.** Repository
|
|
452
|
+
discovery used a regular-file check that followed symlinks, so a tracked link could escape
|
|
453
|
+
the immutable source snapshot. Rubric v4 accepts only tracked regular files. On the pinned
|
|
454
|
+
corpus this removes a duplicate claim source from Zod: A5 moves from 2.2 to 2.0 while its
|
|
455
|
+
rounded Code score, Overall, verdict, and rank stay unchanged. The full 24-row delta is in
|
|
456
|
+
the rubric changelog.
|
|
457
|
+
- **A pinned commit could score differently depending on unrelated Git refs in the clone.**
|
|
458
|
+
History evidence traversed every local and remote-tracking ref, including branches that were
|
|
459
|
+
not ancestors of the revision being certified. The required clean-clone reproduction job
|
|
460
|
+
caught the mismatch. Rubric v5 scans only `HEAD` ancestry, so ambient branch state can no
|
|
461
|
+
longer add evidence or change coverage. The complete v4-to-v5 delta is published in the
|
|
462
|
+
rubric changelog.
|
|
424
463
|
- **Version 0.1.1 rejected `--help` and `--version` as unknown flags, and read `-h` as a
|
|
425
464
|
directory path.** Version 0.1.2 handles both aliases before positional-path parsing and
|
|
426
465
|
derives its printed version from the package manifest.
|
|
@@ -441,6 +480,11 @@ scoring tool that has not been checked:
|
|
|
441
480
|
"Unverified". Every real legacy repository has a deploy script, so the fix that passed every
|
|
442
481
|
test was unreachable for essentially all of them. Recognised source must now be *dominant*,
|
|
443
482
|
not merely present. **A fixture cleaner than reality proves nothing.**
|
|
483
|
+
- **Version 0.1.7 missed AVA's root-level `test.js` convention.** A smoke test against
|
|
484
|
+
`sindresorhus/slugify` reported that a runner was configured but no concrete test files were
|
|
485
|
+
detected even though `test.js` was present at the repository root. That was a detection gap
|
|
486
|
+
in Cejel, not evidence about the repository. Rubric v6 recognizes AVA's `test.js` and
|
|
487
|
+
`test-*.js` conventions; a regression fixture preserves the correction.
|
|
444
488
|
|
|
445
489
|
Every one was a trust failure produced by *us* — false alarms about other people's code,
|
|
446
490
|
silent omissions, inconsistent presentation, or a home-only scoring path — and we would
|
|
@@ -4074,7 +4074,10 @@ var NEVER = INVALID;
|
|
|
4074
4074
|
var WITAN_RUBRIC_VERSION_V1 = "witan-rubric-v1-2026-06-24";
|
|
4075
4075
|
var WITAN_RUBRIC_VERSION_V2 = "witan-rubric-v2-2026-07-12";
|
|
4076
4076
|
var WITAN_RUBRIC_VERSION_V3 = "witan-rubric-v3-2026-07-13";
|
|
4077
|
-
var
|
|
4077
|
+
var WITAN_RUBRIC_VERSION_V4 = "witan-rubric-v4-2026-07-18";
|
|
4078
|
+
var WITAN_RUBRIC_VERSION_V5 = "witan-rubric-v5-2026-07-18";
|
|
4079
|
+
var WITAN_RUBRIC_VERSION_V6 = "witan-rubric-v6-2026-07-21";
|
|
4080
|
+
var WITAN_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V6;
|
|
4078
4081
|
var WITAN_TRADING_RUBRIC_VERSION_V0 = "witan-trading-rubric-v0-2026-07-01";
|
|
4079
4082
|
var WITAN_CRITERION_IDS = [
|
|
4080
4083
|
"A1",
|
|
@@ -4509,12 +4512,13 @@ function criteriaByCategory(report, category) {
|
|
|
4509
4512
|
function renderCriterionColumn(title, criteria) {
|
|
4510
4513
|
const applicable = criteria.filter((criterion) => criterion.status !== "not_applicable");
|
|
4511
4514
|
const notApplicable = criteria.filter((criterion) => criterion.status === "not_applicable");
|
|
4515
|
+
const notApplicableGroup = notApplicable.length > 0 ? `
|
|
4516
|
+
${renderNotApplicableGroup(notApplicable)}` : "";
|
|
4512
4517
|
return `<section class="criteria-column">
|
|
4513
4518
|
<h2>${escapeHtml(title)}</h2>
|
|
4514
4519
|
<div class="criteria-list">
|
|
4515
4520
|
${applicable.map(renderCriterionCard).join("")}
|
|
4516
|
-
</div
|
|
4517
|
-
${notApplicable.length > 0 ? renderNotApplicableGroup(notApplicable) : ""}
|
|
4521
|
+
</div>${notApplicableGroup}
|
|
4518
4522
|
</section>`;
|
|
4519
4523
|
}
|
|
4520
4524
|
function renderNotApplicableGroup(criteria) {
|
|
@@ -4863,11 +4867,55 @@ function createWitanAttestation(report, options) {
|
|
|
4863
4867
|
}
|
|
4864
4868
|
});
|
|
4865
4869
|
}
|
|
4870
|
+
function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
4871
|
+
const parsed = WitanAttestationStatementSchema.safeParse(statement);
|
|
4872
|
+
if (!parsed.success) {
|
|
4873
|
+
return {
|
|
4874
|
+
valid: false,
|
|
4875
|
+
errors: parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
4876
|
+
};
|
|
4877
|
+
}
|
|
4878
|
+
const errors = [];
|
|
4879
|
+
const expectedReportHash = options.reportSha256 ?? hashWitanReport(report);
|
|
4880
|
+
const subject = parsed.data.subject[0];
|
|
4881
|
+
const subjectHash = subject?.digest.sha256;
|
|
4882
|
+
if (subjectHash !== expectedReportHash) errors.push("subject digest does not match report.json");
|
|
4883
|
+
if (subject?.name !== `${report.productSlug}/report.json`) {
|
|
4884
|
+
errors.push("subject name does not match report repository identity");
|
|
4885
|
+
}
|
|
4886
|
+
if (parsed.data.predicate.report.sha256 !== expectedReportHash) {
|
|
4887
|
+
errors.push("predicate report digest does not match report.json");
|
|
4888
|
+
}
|
|
4889
|
+
if (parsed.data.predicate.rubricVersion !== report.rubricVersion) {
|
|
4890
|
+
errors.push("rubric version does not match report.json");
|
|
4891
|
+
}
|
|
4892
|
+
if (parsed.data.predicate.generatedAt !== report.generatedAt) {
|
|
4893
|
+
errors.push("generated timestamp does not match report.json");
|
|
4894
|
+
}
|
|
4895
|
+
if (parsed.data.predicate.repository.headSha !== report.repo.headSha) {
|
|
4896
|
+
errors.push("repository revision does not match report.json");
|
|
4897
|
+
}
|
|
4898
|
+
if (parsed.data.predicate.repository.productSlug !== report.productSlug) {
|
|
4899
|
+
errors.push("repository product slug does not match report.json");
|
|
4900
|
+
}
|
|
4901
|
+
if (parsed.data.predicate.repository.url !== report.repo.url) {
|
|
4902
|
+
errors.push("repository URL does not match report.json");
|
|
4903
|
+
}
|
|
4904
|
+
const outcome = parsed.data.predicate.outcome;
|
|
4905
|
+
if (report.verdict === "insufficient_source") {
|
|
4906
|
+
if (outcome.status !== "abstained" || outcome.reason !== report.insufficientSourceReason) {
|
|
4907
|
+
errors.push("attestation does not preserve the report abstention");
|
|
4908
|
+
}
|
|
4909
|
+
} else if (outcome.status !== "scored" || outcome.overallScore !== report.overallScore || outcome.codeTrustScore !== report.codeTrustScore || outcome.processTrustScore !== report.processTrustScore) {
|
|
4910
|
+
errors.push("attested scores do not match report.json");
|
|
4911
|
+
}
|
|
4912
|
+
return { valid: errors.length === 0, errors };
|
|
4913
|
+
}
|
|
4866
4914
|
|
|
4867
4915
|
// src/witan/repo-signals.ts
|
|
4868
4916
|
import { execFileSync } from "child_process";
|
|
4869
4917
|
import { createHash as createHash2 } from "crypto";
|
|
4870
|
-
import { readFileSync, readdirSync, realpathSync, statSync } from "fs";
|
|
4918
|
+
import { lstatSync, readFileSync, readdirSync, realpathSync, statSync } from "fs";
|
|
4871
4919
|
import { basename, join, relative, resolve, sep } from "path";
|
|
4872
4920
|
|
|
4873
4921
|
// src/witan/json-safe.ts
|
|
@@ -6282,9 +6330,11 @@ function isProductionSourcePath(path) {
|
|
|
6282
6330
|
function isTestFile(file) {
|
|
6283
6331
|
return /(^|\/)(__tests__|test|tests|spec)\/.*\.(?:[cm]?[jt]sx?|py|go|rs|java|rb|cpp|cc|cxx)$/.test(
|
|
6284
6332
|
file
|
|
6285
|
-
) ||
|
|
6333
|
+
) || // AVA's default discovery includes test.js and test-*.js at the scanned package root.
|
|
6334
|
+
// Keep this root-anchored: nested files need an existing test-directory/suffix convention.
|
|
6335
|
+
/^test(?:-[^/]+)?\.[cm]?[jt]sx?$/.test(file) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(file) || /(^|\/)test_.*\.py$/.test(file) || /_test\.go$/.test(file) || /(^|\/)test_.*\.(cpp|cc|cxx)$/.test(file) || /_(test|tests)\.(cpp|cc|cxx)$/.test(file) || /Tests?\.(java|kt)$/.test(file) || /(_test|_spec)\.(rs|rb|php|swift|kt)$/.test(file);
|
|
6286
6336
|
}
|
|
6287
|
-
var NAME_SHAPED_TEST_FILE_PATTERN =
|
|
6337
|
+
var NAME_SHAPED_TEST_FILE_PATTERN = /^test(?:-[^/]+)?\.[cm]?[jt]sx?$|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)__tests__\/|(^|\/)test_[^/]*\.py$|(^|\/)tests?\.py$|_test\.go$|(^|\/)test_[^/]*\.(cpp|cc|cxx)$|_(test|tests)\.(cpp|cc|cxx)$|Tests?\.(java|kt)$|(_test|_spec)\.(rs|rb|php|swift|kt)$/;
|
|
6288
6338
|
var TEST_RUNNER_PATTERN = /\b(vitest|jest|mocha|ava|tap|pytest|go test|cargo test|rspec|phpunit|gradle test|mvn test)\b/i;
|
|
6289
6339
|
var CI_TEST_COMMAND_PATTERN = /\b(pytest|go\s+test|cargo\s+test|mvn\s+test|gradle\s+test|ctest|make\s+test|jest|vitest|mocha|rspec|phpunit|pnpm\s+test|npm\s+test|yarn\s+test|node\s+--test|node:test)\b/i;
|
|
6290
6340
|
var CI_LINT_COMMAND_PATTERN = /\b(eslint|pylint|flake8|black|ruff)\b/i;
|
|
@@ -6442,7 +6492,9 @@ function listGitTrackedFiles(repoPath) {
|
|
|
6442
6492
|
stdio: ["ignore", "pipe", "ignore"]
|
|
6443
6493
|
}).trim();
|
|
6444
6494
|
if (!output) return [];
|
|
6445
|
-
return output.split("\n").filter(
|
|
6495
|
+
return output.split("\n").filter(
|
|
6496
|
+
(file) => file.length > 0 && !isHardExcludedPath(file) && isRegularFile(join(repoPath, file))
|
|
6497
|
+
).sort();
|
|
6446
6498
|
} catch {
|
|
6447
6499
|
return null;
|
|
6448
6500
|
}
|
|
@@ -6960,7 +7012,7 @@ function findRootPackageJson(repoFiles) {
|
|
|
6960
7012
|
}
|
|
6961
7013
|
function isRegularFile(path) {
|
|
6962
7014
|
try {
|
|
6963
|
-
return
|
|
7015
|
+
return lstatSync(path).isFile();
|
|
6964
7016
|
} catch {
|
|
6965
7017
|
return false;
|
|
6966
7018
|
}
|
|
@@ -7030,7 +7082,7 @@ function readCredentialHistoryEntries(repoPath) {
|
|
|
7030
7082
|
entries.push(entry);
|
|
7031
7083
|
};
|
|
7032
7084
|
try {
|
|
7033
|
-
const commits = execFileSync("git", ["rev-list", "
|
|
7085
|
+
const commits = execFileSync("git", ["rev-list", "HEAD"], {
|
|
7034
7086
|
cwd: repoPath,
|
|
7035
7087
|
encoding: "utf8",
|
|
7036
7088
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -7089,7 +7141,7 @@ function readDeletedCredentialHistoryPaths(repoPath) {
|
|
|
7089
7141
|
try {
|
|
7090
7142
|
const output = execFileSync(
|
|
7091
7143
|
"git",
|
|
7092
|
-
["log", "
|
|
7144
|
+
["log", "HEAD", "--diff-filter=D", "--name-status", "--format=commit:%H"],
|
|
7093
7145
|
{
|
|
7094
7146
|
cwd: repoPath,
|
|
7095
7147
|
encoding: "utf8",
|
|
@@ -7115,7 +7167,7 @@ function readHistoryCommitsForPath(repoPath, path) {
|
|
|
7115
7167
|
try {
|
|
7116
7168
|
const output = execFileSync(
|
|
7117
7169
|
"git",
|
|
7118
|
-
["log", "
|
|
7170
|
+
["log", "HEAD", "--diff-filter=AM", "--format=%H", "--", path],
|
|
7119
7171
|
{
|
|
7120
7172
|
cwd: repoPath,
|
|
7121
7173
|
encoding: "utf8",
|
|
@@ -7273,7 +7325,7 @@ function hasEnvPathInGitHistory(repoPath) {
|
|
|
7273
7325
|
try {
|
|
7274
7326
|
const output = execFileSync(
|
|
7275
7327
|
"git",
|
|
7276
|
-
["log", "
|
|
7328
|
+
["log", "HEAD", "--diff-filter=AM", "--name-only", "--format="],
|
|
7277
7329
|
{
|
|
7278
7330
|
cwd: repoPath,
|
|
7279
7331
|
encoding: "utf8",
|
|
@@ -7615,7 +7667,7 @@ function rawToolName(raw) {
|
|
|
7615
7667
|
if (typeof obj.tool === "string" && obj.tool.length > 0) return obj.tool;
|
|
7616
7668
|
return "scorecard";
|
|
7617
7669
|
}
|
|
7618
|
-
function parseIngestFile(filePath) {
|
|
7670
|
+
function parseIngestFile(filePath, options = {}) {
|
|
7619
7671
|
let raw;
|
|
7620
7672
|
try {
|
|
7621
7673
|
raw = JSON.parse(stripBom(readFileSync5(filePath, "utf8")));
|
|
@@ -7624,7 +7676,7 @@ function parseIngestFile(filePath) {
|
|
|
7624
7676
|
throw new Error(`Cejel: could not parse ingest file as JSON: ${filePath} (${message})`);
|
|
7625
7677
|
}
|
|
7626
7678
|
let signals;
|
|
7627
|
-
if (looksLikeSarif(raw)) signals = parseSarifJson(raw);
|
|
7679
|
+
if (looksLikeSarif(raw)) signals = parseSarifJson(raw, options.extraSarifDimensionRules);
|
|
7628
7680
|
else if (looksLikeScorecard(raw)) signals = parseScorecardJson(raw);
|
|
7629
7681
|
else if (isGenericSignalDocument(raw)) signals = parseGenericJson(raw);
|
|
7630
7682
|
if (!signals) {
|
|
@@ -7888,7 +7940,7 @@ function mergeSignalsByCriterion(signals) {
|
|
|
7888
7940
|
return merged;
|
|
7889
7941
|
}
|
|
7890
7942
|
function usesMetricScoring(rubricVersion) {
|
|
7891
|
-
return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
|
|
7943
|
+
return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
|
|
7892
7944
|
}
|
|
7893
7945
|
function unmeasuredStatus(rubricVersion) {
|
|
7894
7946
|
return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
|
|
@@ -8186,11 +8238,13 @@ export {
|
|
|
8186
8238
|
ZodFirstPartyTypeKind,
|
|
8187
8239
|
objectType,
|
|
8188
8240
|
external_exports,
|
|
8241
|
+
WitanReportSchema,
|
|
8189
8242
|
formatExternalSourceLine,
|
|
8190
8243
|
renderWitanHtmlReport,
|
|
8191
8244
|
renderWitanBadgeEndpoint,
|
|
8192
8245
|
renderWitanBadgeSvg,
|
|
8193
8246
|
serializeWitanReport,
|
|
8194
8247
|
createWitanAttestation,
|
|
8248
|
+
verifyWitanAttestationBinding,
|
|
8195
8249
|
runCejelScan
|
|
8196
8250
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
WitanReportSchema,
|
|
3
4
|
createWitanAttestation,
|
|
4
5
|
formatExternalSourceLine,
|
|
5
6
|
renderWitanBadgeEndpoint,
|
|
6
7
|
renderWitanBadgeSvg,
|
|
7
8
|
renderWitanHtmlReport,
|
|
8
9
|
runCejelScan,
|
|
9
|
-
serializeWitanReport
|
|
10
|
-
|
|
10
|
+
serializeWitanReport,
|
|
11
|
+
verifyWitanAttestationBinding
|
|
12
|
+
} from "./chunk-SFEPABAX.js";
|
|
11
13
|
|
|
12
14
|
// src/index.ts
|
|
15
|
+
import { createHash } from "crypto";
|
|
13
16
|
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
|
|
14
17
|
import { join, resolve } from "path";
|
|
15
18
|
import { pathToFileURL } from "url";
|
|
@@ -121,18 +124,31 @@ var CLI_FLAG_TOKENS = CLI_FLAG_SPECS.flatMap(
|
|
|
121
124
|
var CLI_FLAG_KIND_BY_TOKEN = new Map(
|
|
122
125
|
CLI_FLAG_SPECS.flatMap((spec) => spec.tokens.map((token) => [token, spec.kind]))
|
|
123
126
|
);
|
|
127
|
+
function readCliManifest() {
|
|
128
|
+
return JSON.parse(
|
|
129
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
130
|
+
);
|
|
131
|
+
}
|
|
124
132
|
function cliVersion() {
|
|
125
133
|
if (typeof __CEJEL_SEA_VERSION__ === "string" && __CEJEL_SEA_VERSION__.length > 0) {
|
|
126
134
|
return __CEJEL_SEA_VERSION__;
|
|
127
135
|
}
|
|
128
|
-
const manifest =
|
|
129
|
-
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
130
|
-
);
|
|
136
|
+
const manifest = readCliManifest();
|
|
131
137
|
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
|
|
132
138
|
throw new Error("Cejel package manifest has no version.");
|
|
133
139
|
}
|
|
134
140
|
return manifest.version;
|
|
135
141
|
}
|
|
142
|
+
function cliPackageName() {
|
|
143
|
+
if (typeof __CEJEL_SEA_PACKAGE_NAME__ === "string" && __CEJEL_SEA_PACKAGE_NAME__.length > 0) {
|
|
144
|
+
return __CEJEL_SEA_PACKAGE_NAME__;
|
|
145
|
+
}
|
|
146
|
+
const manifest = readCliManifest();
|
|
147
|
+
if (typeof manifest.name !== "string" || manifest.name.length === 0) {
|
|
148
|
+
throw new Error("Cejel package manifest has no name.");
|
|
149
|
+
}
|
|
150
|
+
return manifest.name;
|
|
151
|
+
}
|
|
136
152
|
var usageOptionLabels = CLI_FLAG_SPECS.map(
|
|
137
153
|
(spec) => `${spec.tokens.join(", ")}${"value" in spec && spec.value ? ` ${spec.value}` : ""}`
|
|
138
154
|
);
|
|
@@ -140,11 +156,19 @@ var usageOptionLabelWidth = Math.max(...usageOptionLabels.map((label) => label.l
|
|
|
140
156
|
var usageOptions = CLI_FLAG_SPECS.map(
|
|
141
157
|
(spec, index) => ` ${usageOptionLabels[index]?.padEnd(usageOptionLabelWidth)} ${spec.description}`
|
|
142
158
|
).join("\n");
|
|
159
|
+
var NPX_PACKAGE_NAME = cliPackageName();
|
|
143
160
|
var USAGE = `cejel \u2014 a trust certificate for your codebase
|
|
144
161
|
|
|
145
|
-
Usage:
|
|
162
|
+
Usage:
|
|
163
|
+
npx ${NPX_PACKAGE_NAME} [path] [options]
|
|
164
|
+
npx ${NPX_PACKAGE_NAME} scan [path] [options]
|
|
165
|
+
npx ${NPX_PACKAGE_NAME} verify <report.json> <attestation.json>
|
|
166
|
+
|
|
167
|
+
Commands:
|
|
168
|
+
scan score a repository (default when the command is omitted)
|
|
169
|
+
verify verify report/attestation binding only; no signature or signer identity check
|
|
146
170
|
|
|
147
|
-
|
|
171
|
+
Scan options:
|
|
148
172
|
${usageOptions}
|
|
149
173
|
|
|
150
174
|
Runs entirely offline. No code leaves your machine.
|
|
@@ -155,7 +179,11 @@ async function main() {
|
|
|
155
179
|
process.exitCode = exitCode;
|
|
156
180
|
}
|
|
157
181
|
async function runWitanFreeCli(args) {
|
|
158
|
-
const
|
|
182
|
+
const invocation = parseCliInvocation(args);
|
|
183
|
+
if (invocation.command === "verify") {
|
|
184
|
+
return runVerifyBinding(invocation.reportPath, invocation.attestationPath);
|
|
185
|
+
}
|
|
186
|
+
const options = invocation.options;
|
|
159
187
|
if (options.showVersion) {
|
|
160
188
|
process.stdout.write(`${cliVersion()}
|
|
161
189
|
`);
|
|
@@ -217,6 +245,80 @@ Wrote:
|
|
|
217
245
|
}
|
|
218
246
|
return 0;
|
|
219
247
|
}
|
|
248
|
+
function parseCliInvocation(args) {
|
|
249
|
+
const command = args[0];
|
|
250
|
+
if (command === "scan") {
|
|
251
|
+
return { command: "scan", options: parseArgs(args.slice(1)) };
|
|
252
|
+
}
|
|
253
|
+
if (command === "verify") {
|
|
254
|
+
const verifyArgs = args.slice(1);
|
|
255
|
+
if (verifyArgs.some((arg) => arg === "-h" || arg === "--help")) {
|
|
256
|
+
return { command: "scan", options: parseArgs(["--help"]) };
|
|
257
|
+
}
|
|
258
|
+
if (verifyArgs.some((arg) => arg === "-v" || arg === "--version")) {
|
|
259
|
+
return { command: "scan", options: parseArgs(["--version"]) };
|
|
260
|
+
}
|
|
261
|
+
if (verifyArgs.length !== 2) {
|
|
262
|
+
throw new Error(`Usage: npx ${NPX_PACKAGE_NAME} verify <report.json> <attestation.json>`);
|
|
263
|
+
}
|
|
264
|
+
const [reportPath, attestationPath] = verifyArgs;
|
|
265
|
+
if (!reportPath || !attestationPath) {
|
|
266
|
+
throw new Error(`Usage: npx ${NPX_PACKAGE_NAME} verify <report.json> <attestation.json>`);
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
command: "verify",
|
|
270
|
+
reportPath: resolve(reportPath),
|
|
271
|
+
attestationPath: resolve(attestationPath)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return { command: "scan", options: parseArgs(args) };
|
|
275
|
+
}
|
|
276
|
+
function runVerifyBinding(reportPath, attestationPath) {
|
|
277
|
+
const reportArtifact = readJsonArtifact(reportPath, "report");
|
|
278
|
+
const reportResult = WitanReportSchema.safeParse(reportArtifact.value);
|
|
279
|
+
if (!reportResult.success) {
|
|
280
|
+
const members = reportResult.error.issues.map(
|
|
281
|
+
(issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`
|
|
282
|
+
);
|
|
283
|
+
process.stderr.write(
|
|
284
|
+
`Cejel: report validation failed:
|
|
285
|
+
${members.map((member) => ` - ${member}`).join("\n")}
|
|
286
|
+
`
|
|
287
|
+
);
|
|
288
|
+
return 1;
|
|
289
|
+
}
|
|
290
|
+
const statement = readJsonArtifact(attestationPath, "attestation");
|
|
291
|
+
const reportSha256 = createHash("sha256").update(reportArtifact.contents).digest("hex");
|
|
292
|
+
const result = verifyWitanAttestationBinding(statement.value, reportResult.data, {
|
|
293
|
+
reportSha256
|
|
294
|
+
});
|
|
295
|
+
if (!result.valid) {
|
|
296
|
+
process.stderr.write(
|
|
297
|
+
`Cejel: report/attestation binding verification failed:
|
|
298
|
+
${result.errors.map((error) => ` - ${error}`).join("\n")}
|
|
299
|
+
`
|
|
300
|
+
);
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
process.stdout.write("Cejel: report/attestation binding verified.\n");
|
|
304
|
+
process.stdout.write("Cejel: signature and signer identity were not verified.\n");
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
307
|
+
function readJsonArtifact(path, label) {
|
|
308
|
+
let contents;
|
|
309
|
+
try {
|
|
310
|
+
contents = readFileSync(path);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
313
|
+
throw new Error(`Cejel: could not read ${label} file ${path}: ${message}`);
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
return { contents, value: JSON.parse(contents.toString("utf8")) };
|
|
317
|
+
} catch (error) {
|
|
318
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
319
|
+
throw new Error(`Cejel: invalid JSON in ${label} file ${path}: ${message}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
220
322
|
function parseArgs(args) {
|
|
221
323
|
let repoPath;
|
|
222
324
|
let outDir = DEFAULT_OUT_DIR;
|
|
@@ -271,7 +373,11 @@ function parseArgs(args) {
|
|
|
271
373
|
}
|
|
272
374
|
continue;
|
|
273
375
|
}
|
|
274
|
-
if (repoPath === void 0)
|
|
376
|
+
if (repoPath === void 0) {
|
|
377
|
+
repoPath = arg;
|
|
378
|
+
} else {
|
|
379
|
+
throw new Error(`Unexpected positional argument: ${arg}`);
|
|
380
|
+
}
|
|
275
381
|
}
|
|
276
382
|
return {
|
|
277
383
|
repoPath: resolve(repoPath ?? "."),
|
|
@@ -307,5 +413,6 @@ export {
|
|
|
307
413
|
CLI_FLAG_TOKENS,
|
|
308
414
|
USAGE,
|
|
309
415
|
parseArgs,
|
|
416
|
+
parseCliInvocation,
|
|
310
417
|
runWitanFreeCli
|
|
311
418
|
};
|
package/dist/mcp/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
renderWitanBadgeSvg,
|
|
11
11
|
renderWitanHtmlReport,
|
|
12
12
|
runCejelScan
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-SFEPABAX.js";
|
|
14
14
|
|
|
15
15
|
// node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
16
16
|
var require_code = __commonJS({
|