@cejel/cejel 0.6.6 → 0.6.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 CHANGED
@@ -92,7 +92,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
92
92
  ```
93
93
 
94
94
  > **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
95
- > 0.6.6 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
95
+ > 0.6.7 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
96
96
  > signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
97
97
  > it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
98
98
  > GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM (the packages
@@ -116,7 +116,7 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
116
116
  npx @cejel/cejel@latest .
117
117
  ```
118
118
 
119
- > **Distribution note:** Cejel `0.6.6` is the coordinated release version for npm,
119
+ > **Distribution note:** Cejel `0.6.7` is the coordinated release version for npm,
120
120
  > standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry. A GitLab CI
121
121
  > template and Code Quality export live in [`docs/gitlab-ci.md`](./docs/gitlab-ci.md).
122
122
 
@@ -187,16 +187,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
187
187
  This is cryptographically signed provenance. It is distinct from Apple Developer ID or
188
188
  Microsoft Authenticode code-signing.
189
189
 
190
- **Docker / OCI.** The current container release is `0.6.6`:
190
+ **Docker / OCI.** The current container release is `0.6.7`:
191
191
 
192
192
  ```bash
193
- docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.6
193
+ docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.6.7
194
194
  ```
195
195
 
196
196
  The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
197
197
 
198
198
  ```bash
199
- docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.6 .
199
+ docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.6.7 .
200
200
  ```
201
201
 
202
202
  The OCI image carries an SBOM of its base-image packages, maximum-mode build provenance, and a
@@ -571,7 +571,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
571
571
  the host path with the repository OpenClaw should allow Cejel to read:
572
572
 
573
573
  ```bash
574
- npx -y openclaw@latest mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.6.6"]}'
574
+ npx -y openclaw@latest mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.6.7"]}'
575
575
  npx -y openclaw@latest mcp doctor cejel-oci --probe
576
576
  ```
577
577
 
@@ -5,7 +5,7 @@ import {
5
5
  deriveProductIdentity,
6
6
  runCejelScan,
7
7
  serializeWitanReport
8
- } from "../chunk-JNTTFAHQ.js";
8
+ } from "../chunk-ZZHQZW26.js";
9
9
  import {
10
10
  listCejelLlmPackFiles
11
11
  } from "../chunk-5PFYOIOS.js";
@@ -5,7 +5,7 @@ import {
5
5
  renderWitanBadgeSvg,
6
6
  renderWitanHtmlReport,
7
7
  runCejelScan
8
- } from "./chunk-JNTTFAHQ.js";
8
+ } from "./chunk-ZZHQZW26.js";
9
9
  import {
10
10
  ZodFirstPartyTypeKind,
11
11
  ZodOptional,
@@ -572,6 +572,7 @@ var WITAN_DIRECTORY_WALK_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source i
572
572
  var WITAN_LEGACY_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the complete tracked inventory contains no concrete test file, configured test runner, CI test command, or coverage configuration.";
573
573
  var WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE = "Whether this repository's claims are reconciled against its implementation cannot be settled from static evidence; Cejel makes no finding on it.";
574
574
  var WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE = "Whether tenant isolation is enforced outside the database cannot be settled from static evidence; Cejel makes no finding on it.";
575
+ var WITAN_A3_BUILD_STEP_UNSETTLED_DISCLOSURE = "Whether this package has a build or type-check step outside the files read cannot be settled from static evidence; Cejel makes no finding on it.";
575
576
  function isWitanNoMeasurementAbstention(report) {
576
577
  return report.verdict === "insufficient_source" && report.insufficientSourceReason.startsWith(WITAN_NO_MEASUREMENT_REASON) && report.criteria.every(
577
578
  ({ status }) => status === "not_applicable" || status === "insufficient_data"
@@ -1973,6 +1974,7 @@ function buildWithheldPathsGapSentence(report) {
1973
1974
  }
1974
1975
  var SETTLING_EVIDENCE_DISCLOSURES = [
1975
1976
  { criterionId: "A2", sentence: WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE },
1977
+ { criterionId: "A3", sentence: WITAN_A3_BUILD_STEP_UNSETTLED_DISCLOSURE },
1976
1978
  { criterionId: "A5", sentence: WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE }
1977
1979
  ];
1978
1980
  function settlingEvidenceDisclosures(report) {
@@ -3205,6 +3207,7 @@ function buildWitanInputFromRepoUntracked(options) {
3205
3207
  const usesV24SecretContentContext = rubricVersion === WITAN_RUBRIC_VERSION_V24;
3206
3208
  const usesV25InlineHealthRoute = rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
3207
3209
  const usesV26SettlingEvidence = rubricVersion === WITAN_RUBRIC_VERSION_V26;
3210
+ const usesV26BuildStepEvidence = rubricVersion === WITAN_RUBRIC_VERSION_V26;
3208
3211
  const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
3209
3212
  const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure2(rubricVersion) ? applyReadableSourceRepresentationGate(
3210
3213
  options.repoPath,
@@ -3246,6 +3249,7 @@ function buildWitanInputFromRepoUntracked(options) {
3246
3249
  usesV24SecretContentContext,
3247
3250
  usesV25InlineHealthRoute,
3248
3251
  usesV26SettlingEvidence,
3252
+ usesV26BuildStepEvidence,
3249
3253
  reviewableSourceProof,
3250
3254
  inventoryAbsenceContext,
3251
3255
  scanLimitations
@@ -3860,7 +3864,7 @@ function buildNotApplicableSignal(criterionId, reason) {
3860
3864
  notApplicable: true
3861
3865
  };
3862
3866
  }
3863
- function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23StrictCoverageAndRecipeRunners, useV23PemPrivateKeyGrammar, useV23WithheldPathAbstention, useV24SecretContentContext, useV25InlineHealthRoute, useV26SettlingEvidence, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
3867
+ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23StrictCoverageAndRecipeRunners, useV23PemPrivateKeyGrammar, useV23WithheldPathAbstention, useV24SecretContentContext, useV25InlineHealthRoute, useV26SettlingEvidence, useV26BuildStepEvidence, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
3864
3868
  const signals = [];
3865
3869
  const mono = resolveMonorepoContext(repoPath, scanLimitations);
3866
3870
  const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
@@ -3905,7 +3909,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
3905
3909
  useV22PackageStartEntrypoint,
3906
3910
  useV23WithheldPathAbstention,
3907
3911
  inventoryAbsenceContext,
3908
- useV25InlineHealthRoute
3912
+ useV25InlineHealthRoute,
3913
+ useV26BuildStepEvidence
3909
3914
  )
3910
3915
  );
3911
3916
  const a4Signal = collectCriterion(
@@ -4828,7 +4833,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = fal
4828
4833
  }
4829
4834
  return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
4830
4835
  }
4831
- function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, useV25InlineHealthRoute = false) {
4836
+ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, useV25InlineHealthRoute = false, useV26BuildStepEvidence = false) {
4832
4837
  const v20DirectHttpEntrypoint = useV20ExplicitGaps ? findV20DirectHttpServerEntrypointFile(repoPath, repoFiles) : null;
4833
4838
  const v22PackageStartHttpEntrypoint = useV22PackageStartEntrypoint ? findV22PackageStartHttpServerEntrypointFile(repoPath, repoFiles) : null;
4834
4839
  const v20RuntimeContainer = useV20ExplicitGaps ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, true) : null;
@@ -5045,13 +5050,22 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
5045
5050
  }
5046
5051
  }
5047
5052
  if (evidence.length === 0) return null;
5053
+ let buildStepSettlement;
5048
5054
  if (useV20ExplicitGaps) {
5049
5055
  const workspaceManifests = packageJson ? findDeclaredWorkspaceManifests(repoPath, repoFiles, packageJson) : NO_DECLARED_WORKSPACE_MANIFESTS;
5050
5056
  const hasBuildOrTypecheck = scripts.has("build") || scripts.has("typecheck") || workspaceManifests.declared > workspaceManifests.read.length || workspaceManifests.read.some((file) => {
5051
5057
  const workspaceScripts = readPackageScripts(join(repoPath, file));
5052
5058
  return workspaceScripts.has("build") || workspaceScripts.has("typecheck");
5053
5059
  });
5054
- if (packageJson && !hasBuildOrTypecheck) {
5060
+ buildStepSettlement = useV26BuildStepEvidence && packageJson ? settleV26BuildStepEvidence(
5061
+ repoPath,
5062
+ repoFiles,
5063
+ packageJson,
5064
+ workspaceManifests,
5065
+ workflows,
5066
+ (file) => !useV27Detectors || isAuthoredProductionPath(file)
5067
+ ) : void 0;
5068
+ if (packageJson && !hasBuildOrTypecheck && (buildStepSettlement === void 0 || buildStepSettlement === "fires")) {
5055
5069
  const workspaceRead = workspaceManifests.read.length;
5056
5070
  findings.push({
5057
5071
  severity: "info",
@@ -5192,7 +5206,8 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
5192
5206
  "Counts static rollback or migration-safety artifacts.",
5193
5207
  "saturating_count"
5194
5208
  )
5195
- ]
5209
+ ],
5210
+ ...buildStepSettlement === "unsettled" ? { notes: WITAN_A3_BUILD_STEP_UNSETTLED_DISCLOSURE } : {}
5196
5211
  };
5197
5212
  }
5198
5213
  function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors, useV47Detectors, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
@@ -9396,31 +9411,38 @@ function findRootPackageJson(repoFiles) {
9396
9411
  var WORKSPACE_PATTERN_LIMIT = 64;
9397
9412
  var WORKSPACE_PACKAGE_MANIFEST_LIMIT = 200;
9398
9413
  var NO_DECLARED_WORKSPACE_MANIFESTS = { read: [], declared: 0 };
9414
+ var UNREADABLE_WORKSPACE_DECLARATION = {
9415
+ read: [],
9416
+ declared: 0,
9417
+ unreadable: true
9418
+ };
9399
9419
  function findDeclaredWorkspaceManifests(repoPath, repoFiles, rootPackageJson) {
9400
9420
  const patterns = [];
9401
9421
  if (rootPackageJson) {
9402
9422
  const declared = readPackageJsonWorkspacePatterns(join(repoPath, rootPackageJson));
9403
- if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
9423
+ if (declared === null) return UNREADABLE_WORKSPACE_DECLARATION;
9404
9424
  patterns.push(...declared);
9405
9425
  }
9406
9426
  if (repoFiles.includes("pnpm-workspace.yaml")) {
9407
9427
  const declared = readPnpmWorkspacePatterns(join(repoPath, "pnpm-workspace.yaml"));
9408
- if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
9428
+ if (declared === null) return UNREADABLE_WORKSPACE_DECLARATION;
9409
9429
  patterns.push(...declared);
9410
9430
  }
9411
- if (patterns.length === 0 || patterns.length > WORKSPACE_PATTERN_LIMIT) {
9412
- return NO_DECLARED_WORKSPACE_MANIFESTS;
9413
- }
9431
+ if (patterns.length === 0) return NO_DECLARED_WORKSPACE_MANIFESTS;
9432
+ if (patterns.length > WORKSPACE_PATTERN_LIMIT) return UNREADABLE_WORKSPACE_DECLARATION;
9414
9433
  const include = [];
9415
9434
  const exclude = [];
9435
+ let unparsedPattern = false;
9416
9436
  for (const pattern of patterns) {
9417
9437
  const negated = pattern.startsWith("!");
9418
9438
  const matcher = workspaceGlobMatcher(negated ? pattern.slice(1) : pattern);
9419
9439
  if (negated) {
9420
- if (matcher === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
9440
+ if (matcher === null) return UNREADABLE_WORKSPACE_DECLARATION;
9421
9441
  exclude.push(matcher);
9422
9442
  } else if (matcher !== null) {
9423
9443
  include.push(matcher);
9444
+ } else {
9445
+ unparsedPattern = true;
9424
9446
  }
9425
9447
  }
9426
9448
  const matched = repoFiles.filter((file) => {
@@ -9429,7 +9451,11 @@ function findDeclaredWorkspaceManifests(repoPath, repoFiles, rootPackageJson) {
9429
9451
  const dir = file.slice(0, -"/package.json".length);
9430
9452
  return include.some((matches) => matches(dir)) && !exclude.some((matches) => matches(dir));
9431
9453
  }).sort();
9432
- return { read: matched.slice(0, WORKSPACE_PACKAGE_MANIFEST_LIMIT), declared: matched.length };
9454
+ return {
9455
+ read: matched.slice(0, WORKSPACE_PACKAGE_MANIFEST_LIMIT),
9456
+ declared: matched.length,
9457
+ ...unparsedPattern ? { unreadable: true } : {}
9458
+ };
9433
9459
  }
9434
9460
  function readPackageJsonWorkspacePatterns(packageJsonPath) {
9435
9461
  if (!isRegularFile(packageJsonPath)) return [];
@@ -9639,6 +9665,263 @@ function unreadCiWorkflowReasons(includes) {
9639
9665
  }
9640
9666
  return unread;
9641
9667
  }
9668
+ var BUILD_STEP_EXECUTABLES = /* @__PURE__ */ new Set([
9669
+ "babel",
9670
+ "esbuild",
9671
+ "ncc",
9672
+ "rollup",
9673
+ "svelte-check",
9674
+ "swc",
9675
+ "tsc",
9676
+ "tsup",
9677
+ "vue-tsc",
9678
+ "webpack",
9679
+ "webpack-cli"
9680
+ ]);
9681
+ var BUILD_STEP_SUBCOMMANDS = /* @__PURE__ */ new Map([
9682
+ ["bun", "build"],
9683
+ ["nest", "build"],
9684
+ ["next", "build"],
9685
+ ["ng", "build"],
9686
+ ["parcel", "build"],
9687
+ ["vite", "build"]
9688
+ ]);
9689
+ var BUILD_STEP_TASK_RUNNERS = /* @__PURE__ */ new Set([
9690
+ "bun",
9691
+ "gmake",
9692
+ "just",
9693
+ "lerna",
9694
+ "make",
9695
+ "npm",
9696
+ "nx",
9697
+ "pnpm",
9698
+ "turbo",
9699
+ "yarn"
9700
+ ]);
9701
+ var BUILD_STEP_TARGET_NAME_PATTERN = /^(?:build|typecheck)$/u;
9702
+ var BUILD_STEP_TARGET_ARGUMENT_PATTERN = /^(?:[^\s:]+:)?(?:build|typecheck)$/u;
9703
+ var TYPESCRIPT_SOURCE_PATTERN = /\.(?:ts|tsx|mts|cts)$/u;
9704
+ var TYPESCRIPT_DECLARATION_PATTERN = /\.d\.(?:ts|mts|cts)$/u;
9705
+ var TSCONFIG_PATTERN = /(^|\/)tsconfig[^/]*\.json$/u;
9706
+ var BUILD_STEP_DOCKERFILE_PATTERN = /(^|\/)(?:dockerfile|containerfile)(?:\.[^/]*)?$|[^/]\.dockerfile$/iu;
9707
+ function settleV26BuildStepEvidence(repoPath, repoFiles, packageJson, workspaceManifests, workflows, includesWorkflow) {
9708
+ const nestedPackageDirs = repoFiles.filter((file) => file !== packageJson && /(^|\/)package\.json$/u.test(file)).map((file) => file.slice(0, -"package.json".length));
9709
+ const hasSomethingToBuild = repoFiles.some(
9710
+ (file) => isAuthoredProductionPath(file) && (TYPESCRIPT_SOURCE_PATTERN.test(file) && !TYPESCRIPT_DECLARATION_PATTERN.test(file) || TSCONFIG_PATTERN.test(file)) && !nestedPackageDirs.some((dir) => file.startsWith(dir))
9711
+ );
9712
+ if (!hasSomethingToBuild) return "nothing-to-build";
9713
+ let unsettled = false;
9714
+ const commandRunsStep = (command) => {
9715
+ const state = classifyBuildStepCommand(command);
9716
+ if (state === "unparseable") unsettled = true;
9717
+ return state === "step";
9718
+ };
9719
+ for (const manifest of [packageJson, ...workspaceManifests.read]) {
9720
+ const scripts = readBuildStepPackageScripts(join(repoPath, manifest));
9721
+ if (scripts === null) {
9722
+ unsettled = true;
9723
+ continue;
9724
+ }
9725
+ for (const [name, command] of scripts) {
9726
+ if (BUILD_STEP_TARGET_NAME_PATTERN.test(name) || commandRunsStep(command)) return "step-found";
9727
+ }
9728
+ }
9729
+ for (const workflow of workflows) {
9730
+ const fullPath = join(repoPath, workflow);
9731
+ if (!/^\.github\/workflows\//u.test(workflow) || !isRegularFile(fullPath)) {
9732
+ unsettled = true;
9733
+ continue;
9734
+ }
9735
+ if (extractCiRunCommands(readRepoText(fullPath, "utf8")).some(commandRunsStep)) {
9736
+ return "step-found";
9737
+ }
9738
+ }
9739
+ for (const file of repoFiles.filter((path) => BUILD_STEP_DOCKERFILE_PATTERN.test(path))) {
9740
+ const fullPath = join(repoPath, file);
9741
+ if (!isRegularFile(fullPath)) {
9742
+ unsettled = true;
9743
+ continue;
9744
+ }
9745
+ const run = dockerfileRunCommands(readRepoText(fullPath, "utf8"));
9746
+ if (run.unparseable) unsettled = true;
9747
+ if (run.commands.some(commandRunsStep)) return "step-found";
9748
+ }
9749
+ for (const file of repoFiles.filter(isRecipeFile)) {
9750
+ const fullPath = join(repoPath, file);
9751
+ if (!isRegularFile(fullPath)) {
9752
+ unsettled = true;
9753
+ continue;
9754
+ }
9755
+ for (const [name, target] of readRecipeTargets(readRepoText(fullPath, "utf8"))) {
9756
+ if (BUILD_STEP_TARGET_NAME_PATTERN.test(name)) return "step-found";
9757
+ if (target.commands.some((command) => commandRunsStep(command.replace(/\\$/u, "")))) {
9758
+ return "step-found";
9759
+ }
9760
+ }
9761
+ }
9762
+ if (workspaceManifests.declared > workspaceManifests.read.length || workspaceManifests.unreadable) {
9763
+ unsettled = true;
9764
+ }
9765
+ if (unreadCiWorkflowReasons(includesWorkflow).size > 0) unsettled = true;
9766
+ const delegated = countDelegatedCiSteps(repoPath, workflows);
9767
+ if (delegated.crossRepositoryReusableWorkflows + delegated.compositeActions > 0) unsettled = true;
9768
+ if (withheldRepoPaths().some(
9769
+ ({ path }) => BUILD_STEP_DOCKERFILE_PATTERN.test(path) || isRecipeFile(path) || /(^|\/)package\.json$/u.test(path) && isAuthoredProductionPath(path)
9770
+ )) {
9771
+ unsettled = true;
9772
+ }
9773
+ return unsettled ? "unsettled" : "fires";
9774
+ }
9775
+ function readBuildStepPackageScripts(packageJsonPath) {
9776
+ if (!isRegularFile(packageJsonPath)) return null;
9777
+ const parsed = parseJsonObject(readRepoText(packageJsonPath, "utf8"));
9778
+ if (parsed === null) return null;
9779
+ if (parsed.scripts === void 0) return /* @__PURE__ */ new Map();
9780
+ if (!isRecord(parsed.scripts)) return null;
9781
+ const entries = Object.entries(parsed.scripts);
9782
+ if (!entries.every((entry) => typeof entry[1] === "string")) return null;
9783
+ return new Map(entries);
9784
+ }
9785
+ function dockerfileRunCommands(contents) {
9786
+ if (/^#\s*escape\s*=/imu.test(contents)) return { commands: [], unparseable: true };
9787
+ const lines = contents.split(/\r?\n/u);
9788
+ const commands = [];
9789
+ let unparseable = false;
9790
+ for (let index = 0; index < lines.length; index += 1) {
9791
+ const match = /^\s*RUN(?:\s+(.*))?$/iu.exec(lines[index] ?? "");
9792
+ if (!match) continue;
9793
+ const chunks = [match[1] ?? ""];
9794
+ while (endsWithLineContinuation(chunks) && index + 1 < lines.length) {
9795
+ index += 1;
9796
+ const next = lines[index] ?? "";
9797
+ if (/^\s*#/u.test(next)) continue;
9798
+ dropLineContinuation(chunks);
9799
+ chunks.push(" ", next.trim());
9800
+ }
9801
+ let instruction = chunks.join("");
9802
+ instruction = instruction.replace(/^(?:--[A-Za-z][\w-]*(?:=\S*)?\s+)*/u, "").trim();
9803
+ if (instruction === "" || /<<-?\s*["']?[A-Za-z_]/u.test(instruction)) {
9804
+ unparseable = true;
9805
+ continue;
9806
+ }
9807
+ if (!instruction.startsWith("[")) {
9808
+ commands.push(instruction);
9809
+ continue;
9810
+ }
9811
+ const execForm = dockerfileExecFormCommand(instruction);
9812
+ if (execForm === null) unparseable = true;
9813
+ else commands.push(execForm);
9814
+ }
9815
+ return { commands, unparseable };
9816
+ }
9817
+ function endsWithLineContinuation(chunks) {
9818
+ for (let c = chunks.length - 1; c >= 0; c -= 1) {
9819
+ const chunk = chunks[c] ?? "";
9820
+ for (let i = chunk.length - 1; i >= 0; i -= 1) {
9821
+ const character = chunk.charAt(i);
9822
+ if (/\s/u.test(character)) continue;
9823
+ return character === "\\";
9824
+ }
9825
+ }
9826
+ return false;
9827
+ }
9828
+ function dropLineContinuation(chunks) {
9829
+ while (chunks.length > 0) {
9830
+ const chunk = chunks[chunks.length - 1] ?? "";
9831
+ let i = chunk.length - 1;
9832
+ while (i >= 0 && /\s/u.test(chunk.charAt(i))) i -= 1;
9833
+ if (i < 0) {
9834
+ chunks.pop();
9835
+ continue;
9836
+ }
9837
+ chunks[chunks.length - 1] = chunk.slice(0, i);
9838
+ return;
9839
+ }
9840
+ }
9841
+ function dockerfileExecFormCommand(instruction) {
9842
+ let parsed;
9843
+ try {
9844
+ parsed = JSON.parse(instruction);
9845
+ } catch {
9846
+ return null;
9847
+ }
9848
+ if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every((part) => typeof part === "string")) {
9849
+ return null;
9850
+ }
9851
+ return parsed.map(
9852
+ (part) => /^[A-Za-z0-9_@%+=:,./-]+$/u.test(part) ? part : `'${part.replaceAll("'", "'\\''")}'`
9853
+ ).join(" ");
9854
+ }
9855
+ function classifyBuildStepCommand(command) {
9856
+ return buildStepCommandState(command, 0);
9857
+ }
9858
+ function buildStepCommandState(command, depth) {
9859
+ if (depth > 4) return "unparseable";
9860
+ const states = splitShellCommandSegments(command).map(
9861
+ (segment) => buildStepSegmentState(segment, depth)
9862
+ );
9863
+ if (states.includes("step")) return "step";
9864
+ return states.includes("unparseable") ? "unparseable" : "none";
9865
+ }
9866
+ function buildStepSegmentState(raw, depth) {
9867
+ let segment = stripUnbalancedShellParentheses(raw.trim());
9868
+ for (; ; ) {
9869
+ const keyword = /^(?:if|then|else|elif|do|while|until|exec|!|\{)(?:\s+|$)/u.exec(segment);
9870
+ if (!keyword) break;
9871
+ segment = segment.slice(keyword[0].length);
9872
+ }
9873
+ if (segment === "" || /^(?:fi|done|esac|\})$/u.test(segment)) return "none";
9874
+ const normalized = normalizeCoverageCommandPrefixes(segment);
9875
+ if (normalized.splitShellOperators && splitShellCommandSegments(normalized.segment).length > 1) {
9876
+ return buildStepCommandState(normalized.segment, depth + 1);
9877
+ }
9878
+ if (/\b(?:npm|pnpm|yarn|bun)\s+run\s+(?:\$|["']\$|`)/u.test(normalized.segment)) {
9879
+ return "unparseable";
9880
+ }
9881
+ const words = strictShellWords(normalized.segment);
9882
+ if (words === null) return "unparseable";
9883
+ return words.length === 0 ? "none" : buildStepWordsState(words);
9884
+ }
9885
+ function buildStepWordsState(words) {
9886
+ const first = words[0] === "$(MAKE)" || words[0] === "${MAKE}" ? "make" : words[0] ?? "";
9887
+ if (isUnresolvedCommandWord(first)) return "unparseable";
9888
+ const runner = buildStepCommandName(first);
9889
+ if (BUILD_STEP_TASK_RUNNERS.has(runner) && words.slice(1).some((word) => BUILD_STEP_TARGET_ARGUMENT_PATTERN.test(word))) {
9890
+ return "step";
9891
+ }
9892
+ const command = runner === "bunx" ? words.slice(1) : runner === "bun" && words[1] === "x" ? words.slice(2) : unwrapPackageManagerExecutable(words);
9893
+ const executable = command[0] ?? "";
9894
+ if (isUnresolvedCommandWord(executable)) return "unparseable";
9895
+ const name = buildStepCommandName(executable);
9896
+ if (BUILD_STEP_EXECUTABLES.has(name)) return "step";
9897
+ const subcommand = BUILD_STEP_SUBCOMMANDS.get(name);
9898
+ return subcommand !== void 0 && command.slice(1).includes(subcommand) ? "step" : "none";
9899
+ }
9900
+ function isUnresolvedCommandWord(word) {
9901
+ return word.startsWith("$") || word.includes("`");
9902
+ }
9903
+ function buildStepCommandName(word) {
9904
+ return basename(word).replace(/\.(?:cmd|exe)$/iu, "").toLowerCase();
9905
+ }
9906
+ function stripUnbalancedShellParentheses(segment) {
9907
+ const opens = segment.split("(").length - 1;
9908
+ const closes = segment.split(")").length - 1;
9909
+ if (opens > closes) return segment.replace(/^\(+\s*/u, "");
9910
+ if (closes > opens) return segment.replace(/\s*\)+$/u, "");
9911
+ return segment;
9912
+ }
9913
+ function strictShellWords(segment) {
9914
+ const words = [];
9915
+ let offset = 0;
9916
+ for (; ; ) {
9917
+ while (offset < segment.length && /\s/u.test(segment[offset] ?? "")) offset += 1;
9918
+ if (offset >= segment.length) return words;
9919
+ const scanned = scanShellWord(segment, offset);
9920
+ if (!scanned) return null;
9921
+ if (scanned.word !== "") words.push(scanned.word);
9922
+ offset = scanned.end;
9923
+ }
9924
+ }
9642
9925
  function rootOnlyPackageScriptsDisclosure(repoFiles, rootPackageJson) {
9643
9926
  const others = repoFiles.filter(
9644
9927
  (file) => file !== rootPackageJson && /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ 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-TOQPQPDF.js";
7
7
  import {
8
8
  CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
9
9
  CERTIFICATE_GLOSSARY,
@@ -29,7 +29,7 @@ import {
29
29
  runCejelScan,
30
30
  serializeWitanReport,
31
31
  verifyWitanAttestationBinding
32
- } from "./chunk-JNTTFAHQ.js";
32
+ } from "./chunk-ZZHQZW26.js";
33
33
  import "./chunk-VGBFZBZQ.js";
34
34
  import {
35
35
  external_exports
@@ -41,8 +41,8 @@ import {
41
41
  // src/index.ts
42
42
  init_esm_shims();
43
43
  import { createHash as createHash3 } from "crypto";
44
- import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
45
- import { dirname, join, resolve as resolve2 } from "path";
44
+ import { existsSync, readFileSync, realpathSync } from "fs";
45
+ import { dirname, join as join2, resolve as resolve3 } from "path";
46
46
  import { fileURLToPath, pathToFileURL } from "url";
47
47
 
48
48
  // src/issuance/index.ts
@@ -578,6 +578,68 @@ function resolvedToLocalTree(env, cwd) {
578
578
  return first !== void 0 && first === resolve(cwd, "node_modules", ".bin");
579
579
  }
580
580
 
581
+ // src/output-files.ts
582
+ init_esm_shims();
583
+ import { closeSync, constants, lstatSync, mkdirSync, openSync, writeFileSync } from "fs";
584
+ import { join, resolve as resolve2 } from "path";
585
+ var O_NOFOLLOW = constants.O_NOFOLLOW ?? 0;
586
+ function isSymbolicLink(path) {
587
+ try {
588
+ return lstatSync(path).isSymbolicLink();
589
+ } catch (error) {
590
+ if (error.code === "ENOENT") return false;
591
+ throw error;
592
+ }
593
+ }
594
+ function refusal(what, subject) {
595
+ return new Error(
596
+ `Cejel: refusing to write ${what}: ${subject} is a symbolic link. Nothing was written; remove the link or choose another output path.`
597
+ );
598
+ }
599
+ function refuseSymlinkedDirectory(dir, what) {
600
+ if (isSymbolicLink(resolve2(dir))) throw refusal(what, `output directory ${dir}`);
601
+ }
602
+ function refuseSymlinkedFile(path, what) {
603
+ if (isSymbolicLink(resolve2(path))) throw refusal(what, path);
604
+ }
605
+ function refuseSymlinkedOutputs(dir, names, what) {
606
+ refuseSymlinkedDirectory(dir, what);
607
+ for (const name of names) refuseSymlinkedFile(join(dir, name), what);
608
+ }
609
+ function writeOutputFileNoFollow(path, contents, what) {
610
+ let fd;
611
+ try {
612
+ fd = openSync(
613
+ path,
614
+ constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | O_NOFOLLOW,
615
+ 438
616
+ );
617
+ } catch (error) {
618
+ if (error.code === "ELOOP") {
619
+ throw new Error(
620
+ `Cejel: refusing to write ${what}: ${path} is a symbolic link (the open was refused without following it).`
621
+ );
622
+ }
623
+ throw error;
624
+ }
625
+ try {
626
+ writeFileSync(fd, contents, "utf8");
627
+ } finally {
628
+ closeSync(fd);
629
+ }
630
+ }
631
+ function writeOutputFile(path, contents, what) {
632
+ refuseSymlinkedFile(path, what);
633
+ writeOutputFileNoFollow(path, contents, what);
634
+ }
635
+ function writeOutputFiles(dir, files, what) {
636
+ const names = files.map((file) => file.name);
637
+ refuseSymlinkedOutputs(dir, names, what);
638
+ mkdirSync(dir, { recursive: true });
639
+ refuseSymlinkedDirectory(dir, what);
640
+ for (const file of files) writeOutputFileNoFollow(join(dir, file.name), file.contents, what);
641
+ }
642
+
581
643
  // src/terminal.ts
582
644
  init_esm_shims();
583
645
  function renderMinScoreAbstentionFailure(summary, minScore) {
@@ -744,6 +806,15 @@ function formatScore(score) {
744
806
 
745
807
  // src/index.ts
746
808
  var DEFAULT_OUT_DIR = ".cejel";
809
+ var SCAN_OUTPUT_FILES = [
810
+ "report.json",
811
+ "attestation.json",
812
+ "certificate.html",
813
+ "badge.json",
814
+ "badge.svg",
815
+ "summary.json"
816
+ ];
817
+ var ISSUANCE_OUTPUT_FILE = "issuance.json";
747
818
  var ISSUER_SIGNERS_FILE = "issuer-signers";
748
819
  var ISSUER_REVOCATIONS_FILE = "issuer-revocations";
749
820
  var SECURITY_FILE_CANDIDATES = ["../docs/security/", "../../docs/security/"];
@@ -956,6 +1027,7 @@ async function runWitanCli(args, rubricVersion) {
956
1027
  return 0;
957
1028
  }
958
1029
  const effectiveRubricVersion = options.rubricPin ?? rubricVersion;
1030
+ refuseSymlinkedOutputs(options.outDir, SCAN_OUTPUT_FILES, "scan output");
959
1031
  const { report, summary, generatedAt } = runCejelScan({
960
1032
  repoPath: options.repoPath,
961
1033
  ...options.productName ? { productName: options.productName } : {},
@@ -970,29 +1042,25 @@ async function runWitanCli(args, rubricVersion) {
970
1042
  generatedAt,
971
1043
  ...options.runAttempt ? { githubRunAttempt: options.runAttempt } : {}
972
1044
  });
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"
1045
+ writeOutputFiles(
1046
+ options.outDir,
1047
+ [
1048
+ { name: "report.json", contents: serializeWitanReport(report) },
1049
+ { name: "attestation.json", contents: JSON.stringify(attestation, null, 2) },
1050
+ {
1051
+ name: "certificate.html",
1052
+ contents: renderWitanHtmlReport(report, {
1053
+ cliVersion: cliVersion(),
1054
+ generatedAt,
1055
+ ...options.runAttempt ? { runAttempt: options.runAttempt } : {}
1056
+ })
1057
+ },
1058
+ { name: "badge.json", contents: JSON.stringify(renderWitanBadgeEndpoint(report), null, 2) },
1059
+ { name: "badge.svg", contents: renderWitanBadgeSvg(report) },
1060
+ { name: "summary.json", contents: JSON.stringify(summary, null, 2) }
1061
+ ],
1062
+ "scan output"
993
1063
  );
994
- writeFileSync(join(options.outDir, "badge.svg"), renderWitanBadgeSvg(report), "utf8");
995
- writeFileSync(join(options.outDir, "summary.json"), JSON.stringify(summary, null, 2), "utf8");
996
1064
  if (!options.quiet) {
997
1065
  process.stdout.write(renderTerminalCertificate(summary, report));
998
1066
  process.stdout.write(
@@ -1102,8 +1170,8 @@ function parseExportArgs(args) {
1102
1170
  return {
1103
1171
  command: "export",
1104
1172
  format,
1105
- reportPath: resolve2(reportPath),
1106
- outPath: resolve2(outPath)
1173
+ reportPath: resolve3(reportPath),
1174
+ outPath: resolve3(outPath)
1107
1175
  };
1108
1176
  }
1109
1177
  function runExport(invocation) {
@@ -1121,8 +1189,12 @@ ${members.map((member) => ` - ${member}`).join("\n")}
1121
1189
  return 1;
1122
1190
  }
1123
1191
  const result = exportGitLabCodeQuality(reportResult.data);
1124
- writeFileSync(invocation.outPath, `${JSON.stringify(result.entries, null, 2)}
1125
- `, "utf8");
1192
+ writeOutputFile(
1193
+ invocation.outPath,
1194
+ `${JSON.stringify(result.entries, null, 2)}
1195
+ `,
1196
+ "export output"
1197
+ );
1126
1198
  process.stderr.write(renderGitLabCodeQualityFooter(result));
1127
1199
  return 0;
1128
1200
  }
@@ -1137,8 +1209,8 @@ function parseVerifyArgs(args) {
1137
1209
  if (arg === "--signers" || arg === "--revocations") {
1138
1210
  const value = args[index + 1];
1139
1211
  if (!value) throw new Error(`Missing value for ${arg}`);
1140
- if (arg === "--signers") signersPath = resolve2(value);
1141
- else revocationsPath = resolve2(value);
1212
+ if (arg === "--signers") signersPath = resolve3(value);
1213
+ else revocationsPath = resolve3(value);
1142
1214
  index += 1;
1143
1215
  continue;
1144
1216
  }
@@ -1150,10 +1222,10 @@ function parseVerifyArgs(args) {
1150
1222
  if (!reportPath || !attestationPath) throw new Error(VERIFY_USAGE());
1151
1223
  return {
1152
1224
  command: "verify",
1153
- reportPath: resolve2(reportPath),
1154
- attestationPath: resolve2(attestationPath),
1155
- ...issuancePath ? { issuancePath: resolve2(issuancePath) } : {},
1156
- ...issuanceSignaturePath ? { issuanceSignaturePath: resolve2(issuanceSignaturePath) } : {},
1225
+ reportPath: resolve3(reportPath),
1226
+ attestationPath: resolve3(attestationPath),
1227
+ ...issuancePath ? { issuancePath: resolve3(issuancePath) } : {},
1228
+ ...issuanceSignaturePath ? { issuanceSignaturePath: resolve3(issuanceSignaturePath) } : {},
1157
1229
  ...signersPath ? { signersPath } : {},
1158
1230
  ...revocationsPath ? { revocationsPath } : {}
1159
1231
  };
@@ -1179,15 +1251,15 @@ function parseIssueArgs(args) {
1179
1251
  if (arg === void 0) continue;
1180
1252
  switch (arg) {
1181
1253
  case "--report":
1182
- reportPath = resolve2(takeValue(arg, index + 1));
1254
+ reportPath = resolve3(takeValue(arg, index + 1));
1183
1255
  index += 1;
1184
1256
  continue;
1185
1257
  case "--attestation":
1186
- attestationPath = resolve2(takeValue(arg, index + 1));
1258
+ attestationPath = resolve3(takeValue(arg, index + 1));
1187
1259
  index += 1;
1188
1260
  continue;
1189
1261
  case "--key":
1190
- keyPath = resolve2(takeValue(arg, index + 1));
1262
+ keyPath = resolve3(takeValue(arg, index + 1));
1191
1263
  index += 1;
1192
1264
  continue;
1193
1265
  case "--engagement-ref":
@@ -1196,7 +1268,7 @@ function parseIssueArgs(args) {
1196
1268
  continue;
1197
1269
  case "--out":
1198
1270
  case "--out-dir":
1199
- outDir = resolve2(takeValue(arg, index + 1));
1271
+ outDir = resolve3(takeValue(arg, index + 1));
1200
1272
  index += 1;
1201
1273
  continue;
1202
1274
  case "--product-name":
@@ -1240,7 +1312,7 @@ function parseIssueArgs(args) {
1240
1312
  throw new Error("--product-name and --name cannot be used together");
1241
1313
  }
1242
1314
  return {
1243
- repoPath: resolve2(repoPath ?? "."),
1315
+ repoPath: resolve3(repoPath ?? "."),
1244
1316
  reportPath,
1245
1317
  attestationPath,
1246
1318
  keyPath,
@@ -1410,6 +1482,7 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1410
1482
  throw new Error(`Cejel: could not read --key file ${options.keyPath}: ${message}`);
1411
1483
  }
1412
1484
  const issuerKey = parseIssuerPublicKey(keyContents);
1485
+ refuseSymlinkedOutputs(options.outDir, [ISSUANCE_OUTPUT_FILE], "issuance output");
1413
1486
  const reproduction = runCejelScan({
1414
1487
  repoPath: options.repoPath,
1415
1488
  ...options.productName ? { productName: options.productName } : {},
@@ -1444,9 +1517,12 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1444
1517
  issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1445
1518
  engagementRef: options.engagementRef
1446
1519
  });
1447
- mkdirSync(options.outDir, { recursive: true });
1448
- const issuancePath = join(options.outDir, "issuance.json");
1449
- writeFileSync(issuancePath, serializeWitanIssuance(statement), "utf8");
1520
+ const issuancePath = join2(options.outDir, ISSUANCE_OUTPUT_FILE);
1521
+ writeOutputFiles(
1522
+ options.outDir,
1523
+ [{ name: ISSUANCE_OUTPUT_FILE, contents: serializeWitanIssuance(statement) }],
1524
+ "issuance output"
1525
+ );
1450
1526
  process.stdout.write(
1451
1527
  [
1452
1528
  `Cejel: reproduced report.json byte for byte (sha256:${reportSha256}).`,
@@ -1593,7 +1669,7 @@ function parseArgs(args) {
1593
1669
  throw new Error("--product-name and --name cannot be used together");
1594
1670
  }
1595
1671
  return {
1596
- repoPath: resolve2(repoPath ?? "."),
1672
+ repoPath: resolve3(repoPath ?? "."),
1597
1673
  outDir,
1598
1674
  ...minScore != null ? { minScore } : {},
1599
1675
  ...productName ? { productName } : {},
package/dist/mcp/index.js CHANGED
@@ -3,8 +3,8 @@ 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";
6
+ } from "../chunk-TOQPQPDF.js";
7
+ import "../chunk-ZZHQZW26.js";
8
8
  import "../chunk-VGBFZBZQ.js";
9
9
  import "../chunk-FSMZV3Y6.js";
10
10
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cejel/cejel",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "mcpName": "io.github.BargLabs/cejel",
5
5
  "private": false,
6
6
  "publishConfig": {