@cejel/cejel 0.1.5 → 0.1.7
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 +87 -19
- package/dist/{chunk-A7NZKJ5H.js → chunk-OMIRVZSF.js} +64 -13
- package/dist/index.js +116 -9
- package/dist/mcp/index.js +1 -1
- package/package.json +3 -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.6
|
|
68
74
|
with:
|
|
69
75
|
min-score: "2.5" # optional: fail the build below this
|
|
70
76
|
```
|
|
@@ -79,27 +85,62 @@ node dist/index.js .
|
|
|
79
85
|
|
|
80
86
|
Released binaries: `cejel-Darwin-arm64`, `cejel-Darwin-x86_64`, `cejel-Linux-aarch64`, and
|
|
81
87
|
`cejel-Linux-x86_64`. The release also carries `SHA256SUMS`; each binary is executed against
|
|
82
|
-
the source build and with networking denied before attachment.
|
|
88
|
+
the source build and with networking denied before attachment. Releases from v0.1.6 also
|
|
89
|
+
carry a Sigstore bundle containing GitHub's signed build-provenance attestation for all four
|
|
90
|
+
binaries. Verify a downloaded binary with:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
gh attestation verify ./cejel-Darwin-arm64 -R BargLabs/cejel
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This is cryptographically signed provenance. It is distinct from Apple Developer ID or
|
|
97
|
+
Microsoft Authenticode code-signing.
|
|
98
|
+
|
|
99
|
+
**Docker / OCI.** The same MCP server is published as a non-root, multi-platform image:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.1.6
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.1.6 .
|
|
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.7 --version
|
|
116
|
+
docker run --rm ghcr.io/barglabs/cejel:0.1.7 --help
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
|
|
120
|
+
attestation. It is also the package referenced by [`server.json`](./server.json) for the
|
|
121
|
+
Official MCP Registry.
|
|
83
122
|
|
|
84
123
|
## Leaderboard
|
|
85
124
|
|
|
86
|
-
This repo ships the [Cejel OSS trust leaderboard](./leaderboard/leaderboard.md):
|
|
87
|
-
|
|
88
|
-
studio monorepo Cejel was built
|
|
89
|
-
row under [`leaderboard/reports/`](./leaderboard/reports/). The
|
|
90
|
-
[cejel.dev](https://cejel.dev). Every score
|
|
91
|
-
public scorer used by `npx @cejel/cejel
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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.
|
|
95
136
|
|
|
96
137
|
### Redaction policy
|
|
97
138
|
|
|
98
139
|
A path is published exactly when the reader can check it. Every public repository on the
|
|
99
140
|
board cites full evidence paths and line numbers, everywhere, in every artifact — a
|
|
100
|
-
certificate whose evidence you cannot open is not evidence. The
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
103
144
|
dimension, status, score, and content hash always survive; only the location is withheld,
|
|
104
145
|
marked uniformly as "path withheld — private repository". Redaction removes a location, never
|
|
105
146
|
a fact: a private repository failing its own check still shows up on its certificate, by
|
|
@@ -127,10 +168,11 @@ better-evidenced rows.
|
|
|
127
168
|
## Usage
|
|
128
169
|
|
|
129
170
|
```bash
|
|
130
|
-
./cejel .
|
|
171
|
+
./cejel scan .
|
|
131
172
|
```
|
|
132
173
|
|
|
133
|
-
|
|
174
|
+
`scan` scores the current directory with sensible defaults: no signup, fully offline. The
|
|
175
|
+
original `./cejel .` form remains supported as a compatibility shorthand.
|
|
134
176
|
Prints a concise terminal certificate and writes to `.cejel/`:
|
|
135
177
|
|
|
136
178
|
- `report.json` — the full structured report; scored runs carry numeric headline scores and a
|
|
@@ -147,6 +189,16 @@ statement, but it does not pretend to be an independent signer. Until a customer
|
|
|
147
189
|
or provenance system signs it, `assurance.status` is `unsigned` and `issuer` is
|
|
148
190
|
`self-generated`. An abstained scan carries only the refusal reason, never a numeric score.
|
|
149
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
|
+
|
|
150
202
|
### Flags
|
|
151
203
|
|
|
152
204
|
- `<path>` — repo to score (default: current directory)
|
|
@@ -272,7 +324,7 @@ a configurable `min-score` threshold. The scoring step makes no network calls an
|
|
|
272
324
|
secrets.
|
|
273
325
|
|
|
274
326
|
```yaml
|
|
275
|
-
- uses: BargLabs/cejel/action@
|
|
327
|
+
- uses: BargLabs/cejel/action@v0.1.6
|
|
276
328
|
with:
|
|
277
329
|
min-score: '2.5' # optional; omit to never fail the check
|
|
278
330
|
```
|
|
@@ -284,6 +336,10 @@ server over stdio, so any MCP client (Claude Code, Cowork, Cursor, Codex) can re
|
|
|
284
336
|
trust certificate as a tool call. It wraps the exact same scan the CLI runs — same scores,
|
|
285
337
|
same verdict — and is listed on Smithery via the repo's `smithery.yaml`.
|
|
286
338
|
|
|
339
|
+
The canonical discovery manifest is [`server.json`](./server.json), published under
|
|
340
|
+
`io.github.BargLabs/cejel` in the Official MCP Registry. Its npm ownership metadata,
|
|
341
|
+
OCI ownership label, image version, and registry version are checked together before release.
|
|
342
|
+
|
|
287
343
|
Add it to an MCP client config:
|
|
288
344
|
|
|
289
345
|
```json
|
|
@@ -311,7 +367,7 @@ and the server writes no files.
|
|
|
311
367
|
|
|
312
368
|
## What "offline" means here
|
|
313
369
|
|
|
314
|
-
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
|
|
315
371
|
calls: no telemetry, no signup, no model call. Fetching the `@cejel/cejel` package the
|
|
316
372
|
first time (like any npm-distributed CLI, including this Action's own dependency install)
|
|
317
373
|
does need network; that's a one-time install cost, not part of the scoring guarantee.
|
|
@@ -347,7 +403,7 @@ enforced structurally — the public artifacts are built from filtered data rath
|
|
|
347
403
|
rendered and then scrubbed — and a build that would emit a private path fails rather than
|
|
348
404
|
publishes.
|
|
349
405
|
|
|
350
|
-
**What we exclude from ranking.** The
|
|
406
|
+
**What we exclude from ranking.** The v5 repository scanner does not evaluate B1 (dispatch
|
|
351
407
|
trace completeness) or B5 (verified learning trace) for repository inputs, including ours:
|
|
352
408
|
both are always *not applicable* in a repository certificate. They remain defined in the
|
|
353
409
|
rubric for structured substrate evidence, but that evidence is not accepted by this scanner.
|
|
@@ -392,6 +448,18 @@ scoring tool that has not been checked:
|
|
|
392
448
|
and a required guard re-scores every row and compares score, verdict, coverage, and
|
|
393
449
|
evidence. The honest correction moved Alfred's Code score from 2.5 to 2.4 and A5 from 2.9
|
|
394
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.
|
|
395
463
|
- **Version 0.1.1 rejected `--help` and `--version` as unknown flags, and read `-h` as a
|
|
396
464
|
directory path.** Version 0.1.2 handles both aliases before positional-path parsing and
|
|
397
465
|
derives its printed version from the package manifest.
|
|
@@ -4074,7 +4074,9 @@ 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 = WITAN_RUBRIC_VERSION_V5;
|
|
4078
4080
|
var WITAN_TRADING_RUBRIC_VERSION_V0 = "witan-trading-rubric-v0-2026-07-01";
|
|
4079
4081
|
var WITAN_CRITERION_IDS = [
|
|
4080
4082
|
"A1",
|
|
@@ -4509,12 +4511,13 @@ function criteriaByCategory(report, category) {
|
|
|
4509
4511
|
function renderCriterionColumn(title, criteria) {
|
|
4510
4512
|
const applicable = criteria.filter((criterion) => criterion.status !== "not_applicable");
|
|
4511
4513
|
const notApplicable = criteria.filter((criterion) => criterion.status === "not_applicable");
|
|
4514
|
+
const notApplicableGroup = notApplicable.length > 0 ? `
|
|
4515
|
+
${renderNotApplicableGroup(notApplicable)}` : "";
|
|
4512
4516
|
return `<section class="criteria-column">
|
|
4513
4517
|
<h2>${escapeHtml(title)}</h2>
|
|
4514
4518
|
<div class="criteria-list">
|
|
4515
4519
|
${applicable.map(renderCriterionCard).join("")}
|
|
4516
|
-
</div
|
|
4517
|
-
${notApplicable.length > 0 ? renderNotApplicableGroup(notApplicable) : ""}
|
|
4520
|
+
</div>${notApplicableGroup}
|
|
4518
4521
|
</section>`;
|
|
4519
4522
|
}
|
|
4520
4523
|
function renderNotApplicableGroup(criteria) {
|
|
@@ -4863,11 +4866,55 @@ function createWitanAttestation(report, options) {
|
|
|
4863
4866
|
}
|
|
4864
4867
|
});
|
|
4865
4868
|
}
|
|
4869
|
+
function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
4870
|
+
const parsed = WitanAttestationStatementSchema.safeParse(statement);
|
|
4871
|
+
if (!parsed.success) {
|
|
4872
|
+
return {
|
|
4873
|
+
valid: false,
|
|
4874
|
+
errors: parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
4875
|
+
};
|
|
4876
|
+
}
|
|
4877
|
+
const errors = [];
|
|
4878
|
+
const expectedReportHash = options.reportSha256 ?? hashWitanReport(report);
|
|
4879
|
+
const subject = parsed.data.subject[0];
|
|
4880
|
+
const subjectHash = subject?.digest.sha256;
|
|
4881
|
+
if (subjectHash !== expectedReportHash) errors.push("subject digest does not match report.json");
|
|
4882
|
+
if (subject?.name !== `${report.productSlug}/report.json`) {
|
|
4883
|
+
errors.push("subject name does not match report repository identity");
|
|
4884
|
+
}
|
|
4885
|
+
if (parsed.data.predicate.report.sha256 !== expectedReportHash) {
|
|
4886
|
+
errors.push("predicate report digest does not match report.json");
|
|
4887
|
+
}
|
|
4888
|
+
if (parsed.data.predicate.rubricVersion !== report.rubricVersion) {
|
|
4889
|
+
errors.push("rubric version does not match report.json");
|
|
4890
|
+
}
|
|
4891
|
+
if (parsed.data.predicate.generatedAt !== report.generatedAt) {
|
|
4892
|
+
errors.push("generated timestamp does not match report.json");
|
|
4893
|
+
}
|
|
4894
|
+
if (parsed.data.predicate.repository.headSha !== report.repo.headSha) {
|
|
4895
|
+
errors.push("repository revision does not match report.json");
|
|
4896
|
+
}
|
|
4897
|
+
if (parsed.data.predicate.repository.productSlug !== report.productSlug) {
|
|
4898
|
+
errors.push("repository product slug does not match report.json");
|
|
4899
|
+
}
|
|
4900
|
+
if (parsed.data.predicate.repository.url !== report.repo.url) {
|
|
4901
|
+
errors.push("repository URL does not match report.json");
|
|
4902
|
+
}
|
|
4903
|
+
const outcome = parsed.data.predicate.outcome;
|
|
4904
|
+
if (report.verdict === "insufficient_source") {
|
|
4905
|
+
if (outcome.status !== "abstained" || outcome.reason !== report.insufficientSourceReason) {
|
|
4906
|
+
errors.push("attestation does not preserve the report abstention");
|
|
4907
|
+
}
|
|
4908
|
+
} else if (outcome.status !== "scored" || outcome.overallScore !== report.overallScore || outcome.codeTrustScore !== report.codeTrustScore || outcome.processTrustScore !== report.processTrustScore) {
|
|
4909
|
+
errors.push("attested scores do not match report.json");
|
|
4910
|
+
}
|
|
4911
|
+
return { valid: errors.length === 0, errors };
|
|
4912
|
+
}
|
|
4866
4913
|
|
|
4867
4914
|
// src/witan/repo-signals.ts
|
|
4868
4915
|
import { execFileSync } from "child_process";
|
|
4869
4916
|
import { createHash as createHash2 } from "crypto";
|
|
4870
|
-
import { readFileSync, readdirSync, realpathSync, statSync } from "fs";
|
|
4917
|
+
import { lstatSync, readFileSync, readdirSync, realpathSync, statSync } from "fs";
|
|
4871
4918
|
import { basename, join, relative, resolve, sep } from "path";
|
|
4872
4919
|
|
|
4873
4920
|
// src/witan/json-safe.ts
|
|
@@ -6442,7 +6489,9 @@ function listGitTrackedFiles(repoPath) {
|
|
|
6442
6489
|
stdio: ["ignore", "pipe", "ignore"]
|
|
6443
6490
|
}).trim();
|
|
6444
6491
|
if (!output) return [];
|
|
6445
|
-
return output.split("\n").filter(
|
|
6492
|
+
return output.split("\n").filter(
|
|
6493
|
+
(file) => file.length > 0 && !isHardExcludedPath(file) && isRegularFile(join(repoPath, file))
|
|
6494
|
+
).sort();
|
|
6446
6495
|
} catch {
|
|
6447
6496
|
return null;
|
|
6448
6497
|
}
|
|
@@ -6960,7 +7009,7 @@ function findRootPackageJson(repoFiles) {
|
|
|
6960
7009
|
}
|
|
6961
7010
|
function isRegularFile(path) {
|
|
6962
7011
|
try {
|
|
6963
|
-
return
|
|
7012
|
+
return lstatSync(path).isFile();
|
|
6964
7013
|
} catch {
|
|
6965
7014
|
return false;
|
|
6966
7015
|
}
|
|
@@ -7030,7 +7079,7 @@ function readCredentialHistoryEntries(repoPath) {
|
|
|
7030
7079
|
entries.push(entry);
|
|
7031
7080
|
};
|
|
7032
7081
|
try {
|
|
7033
|
-
const commits = execFileSync("git", ["rev-list", "
|
|
7082
|
+
const commits = execFileSync("git", ["rev-list", "HEAD"], {
|
|
7034
7083
|
cwd: repoPath,
|
|
7035
7084
|
encoding: "utf8",
|
|
7036
7085
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -7089,7 +7138,7 @@ function readDeletedCredentialHistoryPaths(repoPath) {
|
|
|
7089
7138
|
try {
|
|
7090
7139
|
const output = execFileSync(
|
|
7091
7140
|
"git",
|
|
7092
|
-
["log", "
|
|
7141
|
+
["log", "HEAD", "--diff-filter=D", "--name-status", "--format=commit:%H"],
|
|
7093
7142
|
{
|
|
7094
7143
|
cwd: repoPath,
|
|
7095
7144
|
encoding: "utf8",
|
|
@@ -7115,7 +7164,7 @@ function readHistoryCommitsForPath(repoPath, path) {
|
|
|
7115
7164
|
try {
|
|
7116
7165
|
const output = execFileSync(
|
|
7117
7166
|
"git",
|
|
7118
|
-
["log", "
|
|
7167
|
+
["log", "HEAD", "--diff-filter=AM", "--format=%H", "--", path],
|
|
7119
7168
|
{
|
|
7120
7169
|
cwd: repoPath,
|
|
7121
7170
|
encoding: "utf8",
|
|
@@ -7273,7 +7322,7 @@ function hasEnvPathInGitHistory(repoPath) {
|
|
|
7273
7322
|
try {
|
|
7274
7323
|
const output = execFileSync(
|
|
7275
7324
|
"git",
|
|
7276
|
-
["log", "
|
|
7325
|
+
["log", "HEAD", "--diff-filter=AM", "--name-only", "--format="],
|
|
7277
7326
|
{
|
|
7278
7327
|
cwd: repoPath,
|
|
7279
7328
|
encoding: "utf8",
|
|
@@ -7615,7 +7664,7 @@ function rawToolName(raw) {
|
|
|
7615
7664
|
if (typeof obj.tool === "string" && obj.tool.length > 0) return obj.tool;
|
|
7616
7665
|
return "scorecard";
|
|
7617
7666
|
}
|
|
7618
|
-
function parseIngestFile(filePath) {
|
|
7667
|
+
function parseIngestFile(filePath, options = {}) {
|
|
7619
7668
|
let raw;
|
|
7620
7669
|
try {
|
|
7621
7670
|
raw = JSON.parse(stripBom(readFileSync5(filePath, "utf8")));
|
|
@@ -7624,7 +7673,7 @@ function parseIngestFile(filePath) {
|
|
|
7624
7673
|
throw new Error(`Cejel: could not parse ingest file as JSON: ${filePath} (${message})`);
|
|
7625
7674
|
}
|
|
7626
7675
|
let signals;
|
|
7627
|
-
if (looksLikeSarif(raw)) signals = parseSarifJson(raw);
|
|
7676
|
+
if (looksLikeSarif(raw)) signals = parseSarifJson(raw, options.extraSarifDimensionRules);
|
|
7628
7677
|
else if (looksLikeScorecard(raw)) signals = parseScorecardJson(raw);
|
|
7629
7678
|
else if (isGenericSignalDocument(raw)) signals = parseGenericJson(raw);
|
|
7630
7679
|
if (!signals) {
|
|
@@ -7888,7 +7937,7 @@ function mergeSignalsByCriterion(signals) {
|
|
|
7888
7937
|
return merged;
|
|
7889
7938
|
}
|
|
7890
7939
|
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;
|
|
7940
|
+
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_TRADING_RUBRIC_VERSION_V0;
|
|
7892
7941
|
}
|
|
7893
7942
|
function unmeasuredStatus(rubricVersion) {
|
|
7894
7943
|
return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
|
|
@@ -8186,11 +8235,13 @@ export {
|
|
|
8186
8235
|
ZodFirstPartyTypeKind,
|
|
8187
8236
|
objectType,
|
|
8188
8237
|
external_exports,
|
|
8238
|
+
WitanReportSchema,
|
|
8189
8239
|
formatExternalSourceLine,
|
|
8190
8240
|
renderWitanHtmlReport,
|
|
8191
8241
|
renderWitanBadgeEndpoint,
|
|
8192
8242
|
renderWitanBadgeSvg,
|
|
8193
8243
|
serializeWitanReport,
|
|
8194
8244
|
createWitanAttestation,
|
|
8245
|
+
verifyWitanAttestationBinding,
|
|
8195
8246
|
runCejelScan
|
|
8196
8247
|
};
|
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-OMIRVZSF.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-OMIRVZSF.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({
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cejel/cejel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"mcpName": "io.github.BargLabs/cejel",
|
|
4
5
|
"private": false,
|
|
5
6
|
"publishConfig": {
|
|
6
7
|
"access": "public"
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
"scripts": {
|
|
47
48
|
"build": "tsup",
|
|
48
49
|
"build:sea-js": "tsup --config tsup.public-sea.config.ts",
|
|
50
|
+
"validate:distribution": "node scripts/validate-distribution-metadata.mjs",
|
|
49
51
|
"prepublishOnly": "pnpm run build",
|
|
50
52
|
"prepack": "pnpm run build",
|
|
51
53
|
"dev": "tsx src/index.ts",
|