@cejel/cejel 0.4.11 → 0.6.0

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/dist/index.js CHANGED
@@ -9,10 +9,12 @@ import {
9
9
  WitanReportSchema,
10
10
  assertSelectableRubricVersion,
11
11
  buildRelyingPartySummary,
12
+ computeApplicableMeasuredCoverage,
12
13
  createWitanAttestation,
13
14
  formatCertificateMetricAppliedWeightPercent,
14
15
  formatCertificateMetricLabel,
15
16
  formatCertificateMetricValue,
17
+ formatCoverageSummary,
16
18
  formatExternalSourceLine,
17
19
  isProspectiveRubricVersion,
18
20
  renderWitanBadgeEndpoint,
@@ -21,7 +23,7 @@ import {
21
23
  runCejelScan,
22
24
  serializeWitanReport,
23
25
  verifyWitanAttestationBinding
24
- } from "./chunk-UBCIGOIP.js";
26
+ } from "./chunk-2453M7C2.js";
25
27
  import "./chunk-63XQDYQ5.js";
26
28
  import {
27
29
  external_exports
@@ -32,7 +34,7 @@ import {
32
34
 
33
35
  // src/index.ts
34
36
  init_esm_shims();
35
- import { createHash as createHash2 } from "crypto";
37
+ import { createHash as createHash3 } from "crypto";
36
38
  import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
37
39
  import { dirname, join, resolve } from "path";
38
40
  import { fileURLToPath, pathToFileURL } from "url";
@@ -370,7 +372,7 @@ function verifyWitanIssuanceSignature(options) {
370
372
  reason: `key type ${JSON.stringify(parsed.keyType)} is not one this build verifies (supported: ${WITAN_ISSUANCE_SUPPORTED_KEY_TYPES.join(", ")}); verify with OpenSSH instead \u2014 see docs/issuance.md`
371
373
  };
372
374
  }
373
- const fingerprint = sshPublicKeyFingerprint(parsed.publicKeyBlob);
375
+ const fingerprint2 = sshPublicKeyFingerprint(parsed.publicKeyBlob);
374
376
  let signatureVerified;
375
377
  try {
376
378
  const publicKey = ed25519PublicKeyFromBlob(parsed.publicKeyBlob);
@@ -405,16 +407,16 @@ function verifyWitanIssuanceSignature(options) {
405
407
  return { status: "invalid", reason: error instanceof Error ? error.message : String(error) };
406
408
  }
407
409
  if (signers.length === 0) {
408
- return { status: "unlisted_key", keyType: parsed.keyType, fingerprint };
410
+ return { status: "unlisted_key", keyType: parsed.keyType, fingerprint: fingerprint2 };
409
411
  }
410
412
  const signedKeyBase64 = parsed.publicKeyBlob.toString("base64");
411
413
  const listed = signers.some(
412
414
  (entry) => entry.uninterpretedOptions === void 0 && entry.keyType === parsed.keyType && normalizeBase64(entry.keyBase64) === signedKeyBase64 && entry.principals.includes(expectedPrincipal) && (entry.namespaces === void 0 || entry.namespaces.includes(expectedNamespace))
413
415
  );
414
416
  if (!listed) {
415
- return { status: "unlisted_key", keyType: parsed.keyType, fingerprint };
417
+ return { status: "unlisted_key", keyType: parsed.keyType, fingerprint: fingerprint2 };
416
418
  }
417
- return { status: "valid", principal: expectedPrincipal, keyType: parsed.keyType, fingerprint };
419
+ return { status: "valid", principal: expectedPrincipal, keyType: parsed.keyType, fingerprint: fingerprint2 };
418
420
  }
419
421
  function normalizeBase64(value) {
420
422
  return Buffer.from(value, "base64").toString("base64");
@@ -468,6 +470,71 @@ function parseIssuerPublicKey(contents) {
468
470
  return { keyType, keyBase64, fingerprint: sshPublicKeyFingerprint(blob) };
469
471
  }
470
472
 
473
+ // src/export/gitlab-codequality.ts
474
+ init_esm_shims();
475
+ import { createHash as createHash2 } from "crypto";
476
+ var GITLAB_CODEQUALITY_SEVERITY = {
477
+ critical: "critical",
478
+ warning: "major",
479
+ info: "info"
480
+ };
481
+ function normalizeRepoRelativePath(raw) {
482
+ let path = raw.replaceAll("\\", "/");
483
+ while (path.startsWith("./")) path = path.slice(2);
484
+ if (path.length === 0 || path.startsWith("/") || /^[A-Za-z]:/.test(path)) return void 0;
485
+ if (path.split("/").some((segment) => segment === ".." || segment === "")) return void 0;
486
+ return path;
487
+ }
488
+ function fingerprint(rule, path, line, identity) {
489
+ return createHash2("sha256").update(JSON.stringify([rule, path, line, identity])).digest("hex");
490
+ }
491
+ function exportGitLabCodeQuality(report) {
492
+ const ingestedCount = (report.consumedSignals ?? []).reduce(
493
+ (total, signal) => total + signal.findings.length,
494
+ 0
495
+ );
496
+ if (report.verdict === "insufficient_source") {
497
+ return {
498
+ entries: [],
499
+ excludedUnlocated: 0,
500
+ excludedIngested: ingestedCount,
501
+ abstainedReason: report.insufficientSourceReason
502
+ };
503
+ }
504
+ const entries = [];
505
+ let excludedUnlocated = 0;
506
+ const seen = /* @__PURE__ */ new Set();
507
+ for (const criterion of report.criteria) {
508
+ for (const finding of criterion.findings) {
509
+ const path = finding.evidence.path === void 0 ? void 0 : normalizeRepoRelativePath(finding.evidence.path);
510
+ const line = finding.evidence.line;
511
+ if (path === void 0 || line == null) {
512
+ excludedUnlocated += 1;
513
+ continue;
514
+ }
515
+ const print = fingerprint(criterion.id, path, line, finding.summary);
516
+ if (seen.has(print)) continue;
517
+ seen.add(print);
518
+ entries.push({
519
+ description: finding.summary,
520
+ check_name: criterion.id,
521
+ fingerprint: print,
522
+ location: { path, lines: { begin: line } },
523
+ severity: GITLAB_CODEQUALITY_SEVERITY[finding.severity]
524
+ });
525
+ }
526
+ }
527
+ return { entries, excludedUnlocated, excludedIngested: ingestedCount };
528
+ }
529
+ function renderGitLabCodeQualityFooter(result) {
530
+ if (result.abstainedReason !== void 0) {
531
+ return `Cejel: abstained report (insufficient_source): exported 0 entries. ${result.abstainedReason}
532
+ `;
533
+ }
534
+ return `Cejel: exported ${result.entries.length} located finding(s); ${result.excludedUnlocated} without a file and line excluded; ${result.excludedIngested} ingested third-party finding(s) not exported.
535
+ `;
536
+ }
537
+
471
538
  // src/terminal.ts
472
539
  init_esm_shims();
473
540
  function renderMinScoreAbstentionFailure(summary, minScore) {
@@ -484,6 +551,13 @@ function renderMinScoreLimitationFailure(summary, minScore) {
484
551
  return `Cejel: cannot evaluate the required minimum ${minScore.toFixed(1)}/4.0 because the scan has limited evidence (${summary.scanLimitations.length} limitation${summary.scanLimitations.length === 1 ? "" : "s"}). Resolve the reported limitations and re-scan.
485
552
  `;
486
553
  }
554
+ function renderMinScoreCoverageFailure(coverage, minScore) {
555
+ if (!coverage.lowConfidence) {
556
+ throw new Error("A minimum-score coverage message requires low measurement coverage.");
557
+ }
558
+ return `Cejel: cannot evaluate the required minimum ${minScore.toFixed(1)}/4.0 because fewer than half of the applicable dimensions were measured in at least one score (${formatCoverageSummary(coverage)}). Resolve the reported evidence gaps and re-scan.
559
+ `;
560
+ }
487
561
  function renderTerminalCertificate(summary, report) {
488
562
  const abstained = summary.verdict === "Insufficient source" || summary.verdict === "Insufficient evidence";
489
563
  if (!abstained && (summary.overallScore === null || summary.codeTrustScore === null || summary.processTrustScore === null)) {
@@ -636,7 +710,7 @@ var CLI_FLAG_SPECS = [
636
710
  {
637
711
  tokens: ["--min-score"],
638
712
  value: "<0-4>",
639
- description: "exit nonzero below this overall score",
713
+ description: "exit nonzero below this overall score or with insufficient measurement coverage",
640
714
  kind: "minScore"
641
715
  },
642
716
  {
@@ -730,12 +804,14 @@ Usage:
730
804
  npx ${NPX_PACKAGE_NAME} [path] [options]
731
805
  npx ${NPX_PACKAGE_NAME} scan [path] [options]
732
806
  npx ${NPX_PACKAGE_NAME} verify <report.json> <attestation.json> [issuance.json issuance.json.sig]
807
+ npx ${NPX_PACKAGE_NAME} export gitlab-codequality <report.json> [-o gl-code-quality-report.json]
733
808
  npx ${NPX_PACKAGE_NAME} issue [path] --report <report.json> --attestation <attestation.json> --key <key.pub> --engagement-ref <ref>
734
809
 
735
810
  Commands:
736
811
  scan score a repository (default when the command is omitted)
737
812
  verify verify the report/attestation binding; with an issuance pair, also check the issuer
738
813
  signature against docs/security/${ISSUER_SIGNERS_FILE} and docs/security/${ISSUER_REVOCATIONS_FILE}
814
+ export write report.json's located findings as a GitLab Code Quality report (offline)
739
815
  issue re-run this version at the certificate's revision and, only if report.json reproduces
740
816
  byte for byte, write an unsigned issuance.json for the issuer to sign through ssh-agent
741
817
 
@@ -780,6 +856,9 @@ async function runWitanCli(args, rubricVersion) {
780
856
  if (invocation.command === "issue") {
781
857
  return runIssue(invocation.options);
782
858
  }
859
+ if (invocation.command === "export") {
860
+ return runExport(invocation);
861
+ }
783
862
  const options = invocation.options;
784
863
  if (options.showVersion) {
785
864
  process.stdout.write(`${cliVersion()}
@@ -851,6 +930,11 @@ Wrote:
851
930
  process.stderr.write(renderMinScoreAbstentionFailure(summary, options.minScore));
852
931
  return 1;
853
932
  }
933
+ const coverage = computeApplicableMeasuredCoverage(report);
934
+ if (coverage.lowConfidence) {
935
+ process.stderr.write(renderMinScoreCoverageFailure(coverage, options.minScore));
936
+ return 1;
937
+ }
854
938
  if (report.overallScore < options.minScore) {
855
939
  process.stderr.write(
856
940
  `Cejel: overall score ${report.overallScore.toFixed(1)}/4.0 is below the required minimum ${options.minScore.toFixed(1)}/4.0
@@ -876,6 +960,13 @@ function parseCliInvocation(args) {
876
960
  }
877
961
  return parseVerifyArgs(verifyArgs);
878
962
  }
963
+ if (command === "export") {
964
+ const exportArgs = args.slice(1);
965
+ if (exportArgs.some((arg) => arg === "-h" || arg === "--help")) {
966
+ return { command: "scan", options: parseArgs(["--help"]) };
967
+ }
968
+ return parseExportArgs(exportArgs);
969
+ }
879
970
  if (command === "issue") {
880
971
  const issueArgs = args.slice(1);
881
972
  if (issueArgs.some((arg) => arg === "-h" || arg === "--help")) {
@@ -888,6 +979,54 @@ function parseCliInvocation(args) {
888
979
  }
889
980
  return { command: "scan", options: parseArgs(args) };
890
981
  }
982
+ var EXPORT_USAGE = () => `Usage: npx ${NPX_PACKAGE_NAME} export gitlab-codequality <report.json> [-o gl-code-quality-report.json]`;
983
+ function parseExportArgs(args) {
984
+ const [format, ...rest] = args;
985
+ if (format !== "gitlab-codequality") throw new Error(EXPORT_USAGE());
986
+ const positionals = [];
987
+ let outPath = "gl-code-quality-report.json";
988
+ for (let index = 0; index < rest.length; index += 1) {
989
+ const arg = rest[index];
990
+ if (arg === void 0) continue;
991
+ if (arg === "-o" || arg === "--out") {
992
+ const value = rest[index + 1];
993
+ if (!value) throw new Error(`Missing value for ${arg}`);
994
+ outPath = value;
995
+ index += 1;
996
+ continue;
997
+ }
998
+ if (arg.startsWith("-")) throw new Error(`Unknown Cejel export flag: ${arg}`);
999
+ positionals.push(arg);
1000
+ }
1001
+ const [reportPath] = positionals;
1002
+ if (positionals.length !== 1 || !reportPath) throw new Error(EXPORT_USAGE());
1003
+ return {
1004
+ command: "export",
1005
+ format,
1006
+ reportPath: resolve(reportPath),
1007
+ outPath: resolve(outPath)
1008
+ };
1009
+ }
1010
+ function runExport(invocation) {
1011
+ const reportArtifact = readJsonArtifact(invocation.reportPath, "report");
1012
+ const reportResult = WitanReportSchema.safeParse(reportArtifact.value);
1013
+ if (!reportResult.success) {
1014
+ const members = reportResult.error.issues.map(
1015
+ (issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`
1016
+ );
1017
+ process.stderr.write(
1018
+ `Cejel: report validation failed:
1019
+ ${members.map((member) => ` - ${member}`).join("\n")}
1020
+ `
1021
+ );
1022
+ return 1;
1023
+ }
1024
+ const result = exportGitLabCodeQuality(reportResult.data);
1025
+ writeFileSync(invocation.outPath, `${JSON.stringify(result.entries, null, 2)}
1026
+ `, "utf8");
1027
+ process.stderr.write(renderGitLabCodeQualityFooter(result));
1028
+ return 0;
1029
+ }
891
1030
  var VERIFY_USAGE = () => `Usage: npx ${NPX_PACKAGE_NAME} verify <report.json> <attestation.json> [issuance.json issuance.json.sig] [--signers <file>] [--revocations <file>]`;
892
1031
  function parseVerifyArgs(args) {
893
1032
  const positionals = [];
@@ -1029,7 +1168,7 @@ ${members.map((member) => ` - ${member}`).join("\n")}
1029
1168
  return 1;
1030
1169
  }
1031
1170
  const statement = readJsonArtifact(invocation.attestationPath, "attestation");
1032
- const reportSha256 = createHash2("sha256").update(reportArtifact.contents).digest("hex");
1171
+ const reportSha256 = createHash3("sha256").update(reportArtifact.contents).digest("hex");
1033
1172
  const result = verifyWitanAttestationBinding(statement.value, reportResult.data, {
1034
1173
  reportSha256
1035
1174
  });
@@ -1046,7 +1185,7 @@ ${result.errors.map((error) => ` - ${error}`).join("\n")}
1046
1185
  process.stdout.write("Cejel: signature and signer identity were not verified.\n");
1047
1186
  return 0;
1048
1187
  }
1049
- const attestationSha256 = createHash2("sha256").update(statement.contents).digest("hex");
1188
+ const attestationSha256 = createHash3("sha256").update(statement.contents).digest("hex");
1050
1189
  return runVerifyIssuance(invocation, {
1051
1190
  reportSha256,
1052
1191
  attestationSha256,
@@ -1149,8 +1288,8 @@ ${members.map((member) => ` - ${member}`).join("\n")}
1149
1288
  }
1150
1289
  const suppliedReport = reportResult.data;
1151
1290
  const attestationArtifact = readJsonArtifact(options.attestationPath, "attestation");
1152
- const reportSha256 = createHash2("sha256").update(reportArtifact.contents).digest("hex");
1153
- const attestationSha256 = createHash2("sha256").update(attestationArtifact.contents).digest("hex");
1291
+ const reportSha256 = createHash3("sha256").update(reportArtifact.contents).digest("hex");
1292
+ const attestationSha256 = createHash3("sha256").update(attestationArtifact.contents).digest("hex");
1154
1293
  const attestationBinding = verifyWitanAttestationBinding(
1155
1294
  attestationArtifact.value,
1156
1295
  suppliedReport,
@@ -1182,7 +1321,7 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1182
1321
  toolVersion: cliVersion()
1183
1322
  });
1184
1323
  const reproducedBytes = serializeWitanReport(reproduction.report);
1185
- const reproducedSha256 = createHash2("sha256").update(reproducedBytes, "utf8").digest("hex");
1324
+ const reproducedSha256 = createHash3("sha256").update(reproducedBytes, "utf8").digest("hex");
1186
1325
  if (reproducedSha256 !== reportSha256) {
1187
1326
  process.stderr.write(
1188
1327
  [
@@ -1403,6 +1542,7 @@ export {
1403
1542
  WitanReportSchema,
1404
1543
  parseArgs,
1405
1544
  parseCliInvocation,
1545
+ parseExportArgs,
1406
1546
  parseIssueArgs,
1407
1547
  parseVerifyArgs,
1408
1548
  runWitanFreeCli,
package/dist/mcp/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  renderWitanBadgeSvg,
6
6
  renderWitanHtmlReport,
7
7
  runCejelScan
8
- } from "../chunk-UBCIGOIP.js";
8
+ } from "../chunk-2453M7C2.js";
9
9
  import "../chunk-63XQDYQ5.js";
10
10
  import {
11
11
  ZodFirstPartyTypeKind,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cejel/cejel",
3
- "version": "0.4.11",
3
+ "version": "0.6.0",
4
4
  "mcpName": "io.github.BargLabs/cejel",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -63,6 +63,7 @@
63
63
  "gate:d5": "pnpm run build && node scripts/d5-precision-gate.mjs",
64
64
  "gate:d6": "pnpm run build && node scripts/d6-precision-gate.mjs",
65
65
  "validate:distribution": "node scripts/validate-distribution-metadata.mjs",
66
+ "test:release": "bash scripts/release_tests.sh",
66
67
  "pnpm:devPreinstall": "node scripts/assert-dev-environment.mjs --phase=preinstall",
67
68
  "prepublishOnly": "pnpm run build",
68
69
  "prepack": "pnpm run build",