@cejel/cejel 0.6.6 → 0.6.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.
@@ -5,19 +5,19 @@ import {
5
5
  renderWitanBadgeSvg,
6
6
  renderWitanHtmlReport,
7
7
  runCejelScan
8
- } from "./chunk-JNTTFAHQ.js";
8
+ } from "./chunk-KJVFCZZ3.js";
9
9
  import {
10
10
  ZodFirstPartyTypeKind,
11
11
  ZodOptional,
12
12
  external_exports,
13
13
  objectType
14
- } from "./chunk-FSMZV3Y6.js";
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-O3MHSK25.js";
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({
@@ -3,7 +3,7 @@ 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-O3MHSK25.js";
6
+ } from "./chunk-AWLKQUI4.js";
7
7
 
8
8
  // src/witan/git-exec.ts
9
9
  init_esm_shims();
@@ -5,10 +5,10 @@ import {
5
5
  describeGitFailure,
6
6
  execGit,
7
7
  isExpectedGitAbsence
8
- } from "./chunk-VGBFZBZQ.js";
8
+ } from "./chunk-NIWSYJ6N.js";
9
9
  import {
10
10
  init_esm_shims
11
- } from "./chunk-O3MHSK25.js";
11
+ } from "./chunk-AWLKQUI4.js";
12
12
 
13
13
  // src/packs/llm/files.ts
14
14
  init_esm_shims();
package/dist/index.js CHANGED
@@ -3,10 +3,11 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  serveCejelMcpOverStdio
6
- } from "./chunk-BG4ZRBQ5.js";
6
+ } from "./chunk-KNTBED5T.js";
7
7
  import {
8
8
  CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
9
9
  CERTIFICATE_GLOSSARY,
10
+ EXTERNAL_FINDINGS_DISPLAY_LIMIT,
10
11
  PROSPECTIVE_RUBRIC_NOTICE,
11
12
  WITAN_RUBRIC_VERSION_V22,
12
13
  WitanReportSchema,
@@ -15,6 +16,8 @@ import {
15
16
  buildVerdictExplanation,
16
17
  computeApplicableMeasuredCoverage,
17
18
  createWitanAttestation,
19
+ describeInputBindingRecord,
20
+ describeScannedSubject,
18
21
  formatCertificateMetricAppliedWeightPercent,
19
22
  formatCertificateMetricLabel,
20
23
  formatCertificateMetricValue,
@@ -22,27 +25,33 @@ import {
22
25
  formatDimensionsMeasuredSentence,
23
26
  formatExternalSourceLine,
24
27
  formatLowConfidenceSentence,
28
+ importedAdjustmentSentence,
29
+ inheritedFindingNote,
30
+ inputBindingHeadline,
25
31
  isProspectiveRubricVersion,
26
32
  renderWitanBadgeEndpoint,
27
33
  renderWitanBadgeSvg,
28
34
  renderWitanHtmlReport,
35
+ resolveIngestFilePath,
29
36
  runCejelScan,
30
37
  serializeWitanReport,
38
+ stripTerminalControls,
39
+ verifyRecordedInputDigests,
31
40
  verifyWitanAttestationBinding
32
- } from "./chunk-JNTTFAHQ.js";
33
- import "./chunk-VGBFZBZQ.js";
41
+ } from "./chunk-KJVFCZZ3.js";
42
+ import "./chunk-NIWSYJ6N.js";
34
43
  import {
35
44
  external_exports
36
- } from "./chunk-FSMZV3Y6.js";
45
+ } from "./chunk-AMVP7L26.js";
37
46
  import {
38
47
  init_esm_shims
39
- } from "./chunk-O3MHSK25.js";
48
+ } from "./chunk-AWLKQUI4.js";
40
49
 
41
50
  // src/index.ts
42
51
  init_esm_shims();
43
52
  import { createHash as createHash3 } from "crypto";
44
- import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
45
- import { dirname, join, resolve as resolve2 } from "path";
53
+ import { existsSync, readFileSync, realpathSync } from "fs";
54
+ import { dirname, join as join2, resolve as resolve3 } from "path";
46
55
  import { fileURLToPath, pathToFileURL } from "url";
47
56
 
48
57
  // src/issuance/index.ts
@@ -578,6 +587,68 @@ function resolvedToLocalTree(env, cwd) {
578
587
  return first !== void 0 && first === resolve(cwd, "node_modules", ".bin");
579
588
  }
580
589
 
590
+ // src/output-files.ts
591
+ init_esm_shims();
592
+ import { closeSync, constants, lstatSync, mkdirSync, openSync, writeFileSync } from "fs";
593
+ import { join, resolve as resolve2 } from "path";
594
+ var O_NOFOLLOW = constants.O_NOFOLLOW ?? 0;
595
+ function isSymbolicLink(path) {
596
+ try {
597
+ return lstatSync(path).isSymbolicLink();
598
+ } catch (error) {
599
+ if (error.code === "ENOENT") return false;
600
+ throw error;
601
+ }
602
+ }
603
+ function refusal(what, subject) {
604
+ return new Error(
605
+ `Cejel: refusing to write ${what}: ${subject} is a symbolic link. Nothing was written; remove the link or choose another output path.`
606
+ );
607
+ }
608
+ function refuseSymlinkedDirectory(dir, what) {
609
+ if (isSymbolicLink(resolve2(dir))) throw refusal(what, `output directory ${dir}`);
610
+ }
611
+ function refuseSymlinkedFile(path, what) {
612
+ if (isSymbolicLink(resolve2(path))) throw refusal(what, path);
613
+ }
614
+ function refuseSymlinkedOutputs(dir, names, what) {
615
+ refuseSymlinkedDirectory(dir, what);
616
+ for (const name of names) refuseSymlinkedFile(join(dir, name), what);
617
+ }
618
+ function writeOutputFileNoFollow(path, contents, what) {
619
+ let fd;
620
+ try {
621
+ fd = openSync(
622
+ path,
623
+ constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | O_NOFOLLOW,
624
+ 438
625
+ );
626
+ } catch (error) {
627
+ if (error.code === "ELOOP") {
628
+ throw new Error(
629
+ `Cejel: refusing to write ${what}: ${path} is a symbolic link (the open was refused without following it).`
630
+ );
631
+ }
632
+ throw error;
633
+ }
634
+ try {
635
+ writeFileSync(fd, contents, "utf8");
636
+ } finally {
637
+ closeSync(fd);
638
+ }
639
+ }
640
+ function writeOutputFile(path, contents, what) {
641
+ refuseSymlinkedFile(path, what);
642
+ writeOutputFileNoFollow(path, contents, what);
643
+ }
644
+ function writeOutputFiles(dir, files, what) {
645
+ const names = files.map((file) => file.name);
646
+ refuseSymlinkedOutputs(dir, names, what);
647
+ mkdirSync(dir, { recursive: true });
648
+ refuseSymlinkedDirectory(dir, what);
649
+ for (const file of files) writeOutputFileNoFollow(join(dir, file.name), file.contents, what);
650
+ }
651
+
581
652
  // src/terminal.ts
582
653
  init_esm_shims();
583
654
  function renderMinScoreAbstentionFailure(summary, minScore) {
@@ -601,9 +672,6 @@ function renderMinScoreCoverageFailure(coverage, minScore) {
601
672
  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.
602
673
  `;
603
674
  }
604
- function stripTerminalControls(text) {
605
- return text.replace(/[\t\n]/g, " ").replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
606
- }
607
675
  function renderTerminalCertificate(summary, report) {
608
676
  const abstained = summary.verdict === "Insufficient source" || summary.verdict === "Insufficient evidence";
609
677
  if (!abstained && (summary.overallScore === null || summary.codeTrustScore === null || summary.processTrustScore === null)) {
@@ -634,7 +702,7 @@ function renderTerminalCertificate(summary, report) {
634
702
  `Scope: ${relyingPartySummary.scope}`,
635
703
  `What was examined: ${stripTerminalControls(relyingPartySummary.examined)}`,
636
704
  `What was established: ${relyingPartySummary.established}`,
637
- `What was not established: ${relyingPartySummary.notEstablished}`,
705
+ `What was not established: ${stripTerminalControls(relyingPartySummary.notEstablished)}`,
638
706
  `What to do next: ${relyingPartySummary.next}`,
639
707
  ""
640
708
  );
@@ -657,7 +725,7 @@ function renderTerminalCertificate(summary, report) {
657
725
  const reads = summary.contentReadSummary;
658
726
  const errnoCounts = Object.entries(reads.unreadableByErrno).map(([errno, count]) => `${errno}: ${count}`).join(", ");
659
727
  lines.push(
660
- `Skipped content entries: ${reads.skipped} (unreadable: ${reads.byReason.unreadable}${errnoCounts ? ` [${errnoCounts}]` : ""}; too large: ${reads.byReason.tooLarge}; excluded by extension: ${reads.byReason.excludedByExtension}; denied path: ${reads.byReason.deniedPath}; non-regular file: ${reads.byReason.nonRegularFile}).`
728
+ `Skipped content entries: ${reads.skipped} (unreadable: ${reads.byReason.unreadable}${errnoCounts ? ` [${errnoCounts}]` : ""}; too large: ${reads.byReason.tooLarge}; excluded by extension: ${reads.byReason.excludedByExtension}; denied path: ${reads.byReason.deniedPath}; non-regular file: ${reads.byReason.nonRegularFile}${reads.byReason.lfsPointer ? `; Git LFS pointer: ${reads.byReason.lfsPointer}` : ""}).`
661
729
  );
662
730
  if (reads.affectedCriteria.length > 0) {
663
731
  lines.push(
@@ -675,6 +743,20 @@ function renderTerminalCertificate(summary, report) {
675
743
  }
676
744
  lines.push("");
677
745
  }
746
+ if (report?.inputBinding) {
747
+ lines.push(stripTerminalControls(inputBindingHeadline(report.inputBinding)));
748
+ lines.push(` ${stripTerminalControls(describeScannedSubject(report.inputBinding))}`);
749
+ for (const input of report.inputBinding.inputs.slice(0, EXTERNAL_FINDINGS_DISPLAY_LIMIT)) {
750
+ lines.push(` ${stripTerminalControls(describeInputBindingRecord(input))}`);
751
+ }
752
+ const remaining = report.inputBinding.inputs.length - EXTERNAL_FINDINGS_DISPLAY_LIMIT;
753
+ if (remaining > 0) lines.push(` ...and ${remaining} more \u2014 see report.json inputBinding.`);
754
+ for (const criterion of report.criteria) {
755
+ const sentence = importedAdjustmentSentence(criterion);
756
+ if (sentence) lines.push(` ${criterion.id}: ${sentence}`);
757
+ }
758
+ lines.push("");
759
+ }
678
760
  const measurements = report?.criteria.flatMap(
679
761
  (criterion) => criterion.metrics.map((metric) => ({
680
762
  criterionId: criterion.id,
@@ -687,7 +769,7 @@ function renderTerminalCertificate(summary, report) {
687
769
  lines.push("Measurements:");
688
770
  for (const measurement of measurements ?? []) {
689
771
  lines.push(
690
- ` ${measurement.criterionId} \u2014 ${measurement.label}: ${measurement.value} (weight ${measurement.weightPercent}% of ${measurement.criterionId})`
772
+ ` ${measurement.criterionId} \u2014 ${measurement.label}: ${stripTerminalControls(measurement.value)} (weight ${measurement.weightPercent}% of ${measurement.criterionId})`
691
773
  );
692
774
  }
693
775
  lines.push("");
@@ -721,6 +803,8 @@ function renderTerminalCertificate(summary, report) {
721
803
  lines.push(
722
804
  ` [${finding.severity}] ${stripTerminalControls(finding.label)} \u2192 ${finding.dimension}: ${stripTerminalControls(finding.ruleId)} \u2014 ${stripTerminalControls(finding.message)}${finding.location ? ` (${stripTerminalControls(finding.location)})` : ""}`
723
805
  );
806
+ const note = inheritedFindingNote(finding.ruleId);
807
+ if (note) lines.push(` Note: ${note}`);
724
808
  }
725
809
  const remaining = summary.externalFindingCount - summary.topExternalFindings.length;
726
810
  if (remaining > 0) {
@@ -744,6 +828,15 @@ function formatScore(score) {
744
828
 
745
829
  // src/index.ts
746
830
  var DEFAULT_OUT_DIR = ".cejel";
831
+ var SCAN_OUTPUT_FILES = [
832
+ "report.json",
833
+ "attestation.json",
834
+ "certificate.html",
835
+ "badge.json",
836
+ "badge.svg",
837
+ "summary.json"
838
+ ];
839
+ var ISSUANCE_OUTPUT_FILE = "issuance.json";
747
840
  var ISSUER_SIGNERS_FILE = "issuer-signers";
748
841
  var ISSUER_REVOCATIONS_FILE = "issuer-revocations";
749
842
  var SECURITY_FILE_CANDIDATES = ["../docs/security/", "../../docs/security/"];
@@ -790,6 +883,17 @@ var CLI_FLAG_SPECS = [
790
883
  description: "fold in a SARIF/JSON scanner report (repeatable)",
791
884
  kind: "ingest"
792
885
  },
886
+ {
887
+ tokens: ["--subject-repository"],
888
+ value: "<repo>",
889
+ description: "name this repository (e.g. github.com/owner/repo) so ingested inputs declaring another repository are recorded as mismatched",
890
+ kind: "subjectRepository"
891
+ },
892
+ {
893
+ tokens: ["--strict-ingest"],
894
+ description: "refuse ingested inputs for another repository or commit, observed before this commit, or duplicated (default: record only)",
895
+ kind: "strictIngest"
896
+ },
793
897
  {
794
898
  tokens: ["--rubric-pin"],
795
899
  value: "<version>",
@@ -882,6 +986,8 @@ ${usageOptions}
882
986
  Verify options:
883
987
  --signers <file> use this allowed-signers file instead of the published one
884
988
  --revocations <file> use this revocations file instead of the published one
989
+ --input <file> an ingested input (repeatable); fails unless the supplied set of files
990
+ has exactly the sha256 digests report.json records in inputBinding
885
991
 
886
992
  Issue options:
887
993
  --report <file> the report.json being countersigned (required)
@@ -893,6 +999,7 @@ Issue options:
893
999
  --name <display> pass exactly what the original scan used, or the bytes will not reproduce
894
1000
  --rubric-pin <version> pass exactly what the original scan used, or the bytes will not reproduce
895
1001
  --ingest <file> pass exactly what the original scan used, or the bytes will not reproduce
1002
+ (--strict-ingest and --subject-repository are read from report.json's inputBinding)
896
1003
 
897
1004
  cejel never signs and never reads private key material.
898
1005
 
@@ -956,6 +1063,7 @@ async function runWitanCli(args, rubricVersion) {
956
1063
  return 0;
957
1064
  }
958
1065
  const effectiveRubricVersion = options.rubricPin ?? rubricVersion;
1066
+ refuseSymlinkedOutputs(options.outDir, SCAN_OUTPUT_FILES, "scan output");
959
1067
  const { report, summary, generatedAt } = runCejelScan({
960
1068
  repoPath: options.repoPath,
961
1069
  ...options.productName ? { productName: options.productName } : {},
@@ -963,6 +1071,8 @@ async function runWitanCli(args, rubricVersion) {
963
1071
  ...effectiveRubricVersion ? { rubricVersion: effectiveRubricVersion } : {},
964
1072
  ingestPatterns: options.ingestPatterns,
965
1073
  warnOnEmptyIngestMatch: !options.quiet,
1074
+ ...options.strictIngest ? { strictIngest: true } : {},
1075
+ ...options.subjectRepository ? { subjectRepository: options.subjectRepository } : {},
966
1076
  toolVersion: cliVersion()
967
1077
  });
968
1078
  const attestation = createWitanAttestation(report, {
@@ -970,29 +1080,25 @@ async function runWitanCli(args, rubricVersion) {
970
1080
  generatedAt,
971
1081
  ...options.runAttempt ? { githubRunAttempt: options.runAttempt } : {}
972
1082
  });
973
- mkdirSync(options.outDir, { recursive: true });
974
- writeFileSync(join(options.outDir, "report.json"), serializeWitanReport(report), "utf8");
975
- writeFileSync(
976
- join(options.outDir, "attestation.json"),
977
- JSON.stringify(attestation, null, 2),
978
- "utf8"
979
- );
980
- writeFileSync(
981
- join(options.outDir, "certificate.html"),
982
- renderWitanHtmlReport(report, {
983
- cliVersion: cliVersion(),
984
- generatedAt,
985
- ...options.runAttempt ? { runAttempt: options.runAttempt } : {}
986
- }),
987
- "utf8"
988
- );
989
- writeFileSync(
990
- join(options.outDir, "badge.json"),
991
- JSON.stringify(renderWitanBadgeEndpoint(report), null, 2),
992
- "utf8"
1083
+ writeOutputFiles(
1084
+ options.outDir,
1085
+ [
1086
+ { name: "report.json", contents: serializeWitanReport(report) },
1087
+ { name: "attestation.json", contents: JSON.stringify(attestation, null, 2) },
1088
+ {
1089
+ name: "certificate.html",
1090
+ contents: renderWitanHtmlReport(report, {
1091
+ cliVersion: cliVersion(),
1092
+ generatedAt,
1093
+ ...options.runAttempt ? { runAttempt: options.runAttempt } : {}
1094
+ })
1095
+ },
1096
+ { name: "badge.json", contents: JSON.stringify(renderWitanBadgeEndpoint(report), null, 2) },
1097
+ { name: "badge.svg", contents: renderWitanBadgeSvg(report) },
1098
+ { name: "summary.json", contents: JSON.stringify(summary, null, 2) }
1099
+ ],
1100
+ "scan output"
993
1101
  );
994
- writeFileSync(join(options.outDir, "badge.svg"), renderWitanBadgeSvg(report), "utf8");
995
- writeFileSync(join(options.outDir, "summary.json"), JSON.stringify(summary, null, 2), "utf8");
996
1102
  if (!options.quiet) {
997
1103
  process.stdout.write(renderTerminalCertificate(summary, report));
998
1104
  process.stdout.write(
@@ -1102,8 +1208,8 @@ function parseExportArgs(args) {
1102
1208
  return {
1103
1209
  command: "export",
1104
1210
  format,
1105
- reportPath: resolve2(reportPath),
1106
- outPath: resolve2(outPath)
1211
+ reportPath: resolve3(reportPath),
1212
+ outPath: resolve3(outPath)
1107
1213
  };
1108
1214
  }
1109
1215
  function runExport(invocation) {
@@ -1121,24 +1227,36 @@ ${members.map((member) => ` - ${member}`).join("\n")}
1121
1227
  return 1;
1122
1228
  }
1123
1229
  const result = exportGitLabCodeQuality(reportResult.data);
1124
- writeFileSync(invocation.outPath, `${JSON.stringify(result.entries, null, 2)}
1125
- `, "utf8");
1230
+ writeOutputFile(
1231
+ invocation.outPath,
1232
+ `${JSON.stringify(result.entries, null, 2)}
1233
+ `,
1234
+ "export output"
1235
+ );
1126
1236
  process.stderr.write(renderGitLabCodeQualityFooter(result));
1127
1237
  return 0;
1128
1238
  }
1129
- var VERIFY_USAGE = () => `Usage: npx ${NPX_PACKAGE_NAME} verify <report.json> <attestation.json> [issuance.json issuance.json.sig] [--signers <file>] [--revocations <file>]`;
1239
+ var VERIFY_USAGE = () => `Usage: npx ${NPX_PACKAGE_NAME} verify <report.json> <attestation.json> [issuance.json issuance.json.sig] [--signers <file>] [--revocations <file>] [--input <file>...]`;
1130
1240
  function parseVerifyArgs(args) {
1131
1241
  const positionals = [];
1132
1242
  let signersPath;
1133
1243
  let revocationsPath;
1244
+ const inputPaths = [];
1134
1245
  for (let index = 0; index < args.length; index += 1) {
1135
1246
  const arg = args[index];
1136
1247
  if (arg === void 0) continue;
1248
+ if (arg === "--input") {
1249
+ const value = args[index + 1];
1250
+ if (!value) throw new Error(`Missing value for ${arg}`);
1251
+ inputPaths.push(resolve3(value));
1252
+ index += 1;
1253
+ continue;
1254
+ }
1137
1255
  if (arg === "--signers" || arg === "--revocations") {
1138
1256
  const value = args[index + 1];
1139
1257
  if (!value) throw new Error(`Missing value for ${arg}`);
1140
- if (arg === "--signers") signersPath = resolve2(value);
1141
- else revocationsPath = resolve2(value);
1258
+ if (arg === "--signers") signersPath = resolve3(value);
1259
+ else revocationsPath = resolve3(value);
1142
1260
  index += 1;
1143
1261
  continue;
1144
1262
  }
@@ -1150,12 +1268,13 @@ function parseVerifyArgs(args) {
1150
1268
  if (!reportPath || !attestationPath) throw new Error(VERIFY_USAGE());
1151
1269
  return {
1152
1270
  command: "verify",
1153
- reportPath: resolve2(reportPath),
1154
- attestationPath: resolve2(attestationPath),
1155
- ...issuancePath ? { issuancePath: resolve2(issuancePath) } : {},
1156
- ...issuanceSignaturePath ? { issuanceSignaturePath: resolve2(issuanceSignaturePath) } : {},
1271
+ reportPath: resolve3(reportPath),
1272
+ attestationPath: resolve3(attestationPath),
1273
+ ...issuancePath ? { issuancePath: resolve3(issuancePath) } : {},
1274
+ ...issuanceSignaturePath ? { issuanceSignaturePath: resolve3(issuanceSignaturePath) } : {},
1157
1275
  ...signersPath ? { signersPath } : {},
1158
- ...revocationsPath ? { revocationsPath } : {}
1276
+ ...revocationsPath ? { revocationsPath } : {},
1277
+ ...inputPaths.length > 0 ? { inputPaths } : {}
1159
1278
  };
1160
1279
  }
1161
1280
  function parseIssueArgs(args) {
@@ -1179,15 +1298,15 @@ function parseIssueArgs(args) {
1179
1298
  if (arg === void 0) continue;
1180
1299
  switch (arg) {
1181
1300
  case "--report":
1182
- reportPath = resolve2(takeValue(arg, index + 1));
1301
+ reportPath = resolve3(takeValue(arg, index + 1));
1183
1302
  index += 1;
1184
1303
  continue;
1185
1304
  case "--attestation":
1186
- attestationPath = resolve2(takeValue(arg, index + 1));
1305
+ attestationPath = resolve3(takeValue(arg, index + 1));
1187
1306
  index += 1;
1188
1307
  continue;
1189
1308
  case "--key":
1190
- keyPath = resolve2(takeValue(arg, index + 1));
1309
+ keyPath = resolve3(takeValue(arg, index + 1));
1191
1310
  index += 1;
1192
1311
  continue;
1193
1312
  case "--engagement-ref":
@@ -1196,7 +1315,7 @@ function parseIssueArgs(args) {
1196
1315
  continue;
1197
1316
  case "--out":
1198
1317
  case "--out-dir":
1199
- outDir = resolve2(takeValue(arg, index + 1));
1318
+ outDir = resolve3(takeValue(arg, index + 1));
1200
1319
  index += 1;
1201
1320
  continue;
1202
1321
  case "--product-name":
@@ -1240,7 +1359,7 @@ function parseIssueArgs(args) {
1240
1359
  throw new Error("--product-name and --name cannot be used together");
1241
1360
  }
1242
1361
  return {
1243
- repoPath: resolve2(repoPath ?? "."),
1362
+ repoPath: resolve3(repoPath ?? "."),
1244
1363
  reportPath,
1245
1364
  attestationPath,
1246
1365
  keyPath,
@@ -1280,6 +1399,26 @@ ${result.errors.map((error) => ` - ${error}`).join("\n")}
1280
1399
  return 1;
1281
1400
  }
1282
1401
  process.stdout.write("Cejel: report/attestation binding verified.\n");
1402
+ if (invocation.inputPaths) {
1403
+ let inputCheck;
1404
+ try {
1405
+ inputCheck = verifyRecordedInputDigests(
1406
+ reportResult.data,
1407
+ invocation.inputPaths,
1408
+ (path) => readFileSync(resolveIngestFilePath(path))
1409
+ );
1410
+ } catch (error) {
1411
+ const message = error instanceof Error ? error.message : String(error);
1412
+ process.stderr.write(
1413
+ `Cejel: input digests not verified \u2014 ${stripTerminalControls(message.replace(/^Cejel: /, ""))}
1414
+ `
1415
+ );
1416
+ return 1;
1417
+ }
1418
+ for (const line of inputCheck.lines) process.stdout.write(`${stripTerminalControls(line)}
1419
+ `);
1420
+ if (!inputCheck.valid) return 1;
1421
+ }
1283
1422
  if (!invocation.issuancePath || !invocation.issuanceSignaturePath) {
1284
1423
  process.stdout.write("Cejel: signature and signer identity were not verified.\n");
1285
1424
  return 0;
@@ -1410,6 +1549,7 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1410
1549
  throw new Error(`Cejel: could not read --key file ${options.keyPath}: ${message}`);
1411
1550
  }
1412
1551
  const issuerKey = parseIssuerPublicKey(keyContents);
1552
+ refuseSymlinkedOutputs(options.outDir, [ISSUANCE_OUTPUT_FILE], "issuance output");
1413
1553
  const reproduction = runCejelScan({
1414
1554
  repoPath: options.repoPath,
1415
1555
  ...options.productName ? { productName: options.productName } : {},
@@ -1417,6 +1557,12 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1417
1557
  ...options.rubricPin ? { rubricVersion: options.rubricPin } : {},
1418
1558
  ingestPatterns: options.ingestPatterns,
1419
1559
  warnOnEmptyIngestMatch: true,
1560
+ // The binding policy and the named subject repository are part of the invocation the report
1561
+ // records, so the reproduction reads them from the report it is reproducing rather than from
1562
+ // flags an issuer could forget. Anything else a report states differently fails the byte
1563
+ // comparison below, as it always has.
1564
+ ...suppliedReport.inputBinding?.policy === "strict" ? { strictIngest: true } : {},
1565
+ ...suppliedReport.inputBinding?.scannedSubject.repository ? { subjectRepository: suppliedReport.inputBinding.scannedSubject.repository } : {},
1420
1566
  toolVersion: cliVersion()
1421
1567
  });
1422
1568
  const reproducedBytes = serializeWitanReport(reproduction.report);
@@ -1444,9 +1590,12 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1444
1590
  issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1445
1591
  engagementRef: options.engagementRef
1446
1592
  });
1447
- mkdirSync(options.outDir, { recursive: true });
1448
- const issuancePath = join(options.outDir, "issuance.json");
1449
- writeFileSync(issuancePath, serializeWitanIssuance(statement), "utf8");
1593
+ const issuancePath = join2(options.outDir, ISSUANCE_OUTPUT_FILE);
1594
+ writeOutputFiles(
1595
+ options.outDir,
1596
+ [{ name: ISSUANCE_OUTPUT_FILE, contents: serializeWitanIssuance(statement) }],
1597
+ "issuance output"
1598
+ );
1450
1599
  process.stdout.write(
1451
1600
  [
1452
1601
  `Cejel: reproduced report.json byte for byte (sha256:${reportSha256}).`,
@@ -1492,6 +1641,8 @@ function parseArgs(args) {
1492
1641
  let showVersion = false;
1493
1642
  let rubricPin;
1494
1643
  let runAttempt;
1644
+ let strictIngest = false;
1645
+ let subjectRepository;
1495
1646
  const ingestPatterns = [];
1496
1647
  for (let index = 0; index < args.length; index += 1) {
1497
1648
  const arg = args[index];
@@ -1570,6 +1721,16 @@ function parseArgs(args) {
1570
1721
  index += 1;
1571
1722
  break;
1572
1723
  }
1724
+ case "strictIngest":
1725
+ strictIngest = true;
1726
+ break;
1727
+ case "subjectRepository": {
1728
+ const value = args[index + 1];
1729
+ if (!value) throw new Error("Missing value for --subject-repository");
1730
+ subjectRepository = value;
1731
+ index += 1;
1732
+ break;
1733
+ }
1573
1734
  case "runAttempt": {
1574
1735
  const value = args[index + 1];
1575
1736
  if (!value) throw new Error("Missing value for --run-attempt");
@@ -1593,7 +1754,7 @@ function parseArgs(args) {
1593
1754
  throw new Error("--product-name and --name cannot be used together");
1594
1755
  }
1595
1756
  return {
1596
- repoPath: resolve2(repoPath ?? "."),
1757
+ repoPath: resolve3(repoPath ?? "."),
1597
1758
  outDir,
1598
1759
  ...minScore != null ? { minScore } : {},
1599
1760
  ...productName ? { productName } : {},
@@ -1602,6 +1763,8 @@ function parseArgs(args) {
1602
1763
  showHelp,
1603
1764
  showVersion,
1604
1765
  ingestPatterns,
1766
+ ...strictIngest ? { strictIngest } : {},
1767
+ ...subjectRepository ? { subjectRepository } : {},
1605
1768
  ...rubricPin ? { rubricPin } : {},
1606
1769
  ...runAttempt ? { runAttempt } : {}
1607
1770
  };
package/dist/mcp/index.js CHANGED
@@ -3,13 +3,13 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  serveCejelMcpOverStdio
6
- } from "../chunk-BG4ZRBQ5.js";
7
- import "../chunk-JNTTFAHQ.js";
8
- import "../chunk-VGBFZBZQ.js";
9
- import "../chunk-FSMZV3Y6.js";
6
+ } from "../chunk-KNTBED5T.js";
7
+ import "../chunk-KJVFCZZ3.js";
8
+ import "../chunk-NIWSYJ6N.js";
9
+ import "../chunk-AMVP7L26.js";
10
10
  import {
11
11
  init_esm_shims
12
- } from "../chunk-O3MHSK25.js";
12
+ } from "../chunk-AWLKQUI4.js";
13
13
 
14
14
  // src/mcp/index.ts
15
15
  init_esm_shims();
@@ -3,18 +3,18 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  listCejelLlmPackFiles
6
- } from "../../chunk-5PFYOIOS.js";
6
+ } from "../../chunk-WXPGEQBE.js";
7
7
  import {
8
8
  execGit
9
- } from "../../chunk-VGBFZBZQ.js";
9
+ } from "../../chunk-NIWSYJ6N.js";
10
10
  import {
11
11
  require_typescript
12
- } from "../../chunk-RH6FKREX.js";
12
+ } from "../../chunk-6LVNNJTG.js";
13
13
  import {
14
14
  __toESM,
15
15
  init_esm_shims,
16
16
  readRepoText
17
- } from "../../chunk-O3MHSK25.js";
17
+ } from "../../chunk-AWLKQUI4.js";
18
18
 
19
19
  // src/packs/d-series/index.ts
20
20
  init_esm_shims();
@@ -3,15 +3,15 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  external_exports
6
- } from "../../chunk-FSMZV3Y6.js";
6
+ } from "../../chunk-AMVP7L26.js";
7
7
  import {
8
8
  require_typescript
9
- } from "../../chunk-RH6FKREX.js";
9
+ } from "../../chunk-6LVNNJTG.js";
10
10
  import {
11
11
  __toESM,
12
12
  init_esm_shims,
13
13
  readRepoText
14
- } from "../../chunk-O3MHSK25.js";
14
+ } from "../../chunk-AWLKQUI4.js";
15
15
 
16
16
  // src/packs/decision-contracts/index.ts
17
17
  init_esm_shims();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cejel/cejel",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "mcpName": "io.github.BargLabs/cejel",
5
5
  "private": false,
6
6
  "publishConfig": {