@cejel/cejel 0.2.2 → 0.3.1
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 +22 -9
- package/dist/chunk-LZVKQV3R.js +360 -0
- package/dist/{chunk-SNDRU52Z.js → chunk-XB65L6WE.js} +759 -332
- package/dist/index.js +42 -4
- package/dist/mcp/index.js +214 -5
- package/dist/packs/d-series/index.js +211708 -0
- package/package.json +14 -1
|
@@ -1,33 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
-
mod
|
|
30
|
-
));
|
|
2
|
+
import { createRequire as __cejelCreateRequire } from 'node:module';
|
|
3
|
+
const require = __cejelCreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
__export,
|
|
6
|
+
describeGitFailure,
|
|
7
|
+
execGit,
|
|
8
|
+
init_esm_shims,
|
|
9
|
+
isExpectedGitAbsence,
|
|
10
|
+
readRepoText,
|
|
11
|
+
readRepoTextPrefix,
|
|
12
|
+
recordContentSkip,
|
|
13
|
+
recordFilesystemSkip,
|
|
14
|
+
trackContentReads,
|
|
15
|
+
withContentReadCriterion
|
|
16
|
+
} from "./chunk-LZVKQV3R.js";
|
|
17
|
+
|
|
18
|
+
// src/witan/schemas.ts
|
|
19
|
+
init_esm_shims();
|
|
20
|
+
|
|
21
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.js
|
|
22
|
+
init_esm_shims();
|
|
31
23
|
|
|
32
24
|
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
33
25
|
var external_exports = {};
|
|
@@ -140,8 +132,19 @@ __export(external_exports, {
|
|
|
140
132
|
util: () => util,
|
|
141
133
|
void: () => voidType
|
|
142
134
|
});
|
|
135
|
+
init_esm_shims();
|
|
136
|
+
|
|
137
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
138
|
+
init_esm_shims();
|
|
139
|
+
|
|
140
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
141
|
+
init_esm_shims();
|
|
142
|
+
|
|
143
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
144
|
+
init_esm_shims();
|
|
143
145
|
|
|
144
146
|
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
147
|
+
init_esm_shims();
|
|
145
148
|
var util;
|
|
146
149
|
(function(util2) {
|
|
147
150
|
util2.assertEqual = (_) => {
|
|
@@ -506,6 +509,7 @@ function getErrorMap() {
|
|
|
506
509
|
}
|
|
507
510
|
|
|
508
511
|
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
512
|
+
init_esm_shims();
|
|
509
513
|
var makeIssue = (params) => {
|
|
510
514
|
const { data, path, errorMaps, issueData } = params;
|
|
511
515
|
const fullPath = [...path, ...issueData.path || []];
|
|
@@ -615,7 +619,11 @@ var isDirty = (x) => x.status === "dirty";
|
|
|
615
619
|
var isValid = (x) => x.status === "valid";
|
|
616
620
|
var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
|
|
617
621
|
|
|
622
|
+
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
623
|
+
init_esm_shims();
|
|
624
|
+
|
|
618
625
|
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
626
|
+
init_esm_shims();
|
|
619
627
|
var errorUtil;
|
|
620
628
|
(function(errorUtil2) {
|
|
621
629
|
errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
|
|
@@ -4188,8 +4196,45 @@ var WitanCriterionSignalSchema = external_exports.object({
|
|
|
4188
4196
|
findings: external_exports.array(WitanFindingSchema).default([]),
|
|
4189
4197
|
metrics: external_exports.array(WitanCriterionMetricSchema).default([]),
|
|
4190
4198
|
notes: external_exports.string().min(1).max(1e3).optional(),
|
|
4191
|
-
notApplicable: external_exports.literal(true).optional()
|
|
4192
|
-
|
|
4199
|
+
notApplicable: external_exports.literal(true).optional(),
|
|
4200
|
+
// A collector reached relevant files but could not read their contents. This is a
|
|
4201
|
+
// measurement gap, never negative evidence: scoreCriterion maps it to insufficient_data.
|
|
4202
|
+
insufficientData: external_exports.literal(true).optional()
|
|
4203
|
+
}).strict().refine((signal) => !(signal.notApplicable && signal.insufficientData), {
|
|
4204
|
+
message: "A criterion cannot be both not applicable and insufficient data."
|
|
4205
|
+
});
|
|
4206
|
+
var WitanContentReadSummarySchema = external_exports.object({
|
|
4207
|
+
skipped: external_exports.number().int().nonnegative(),
|
|
4208
|
+
byReason: external_exports.object({
|
|
4209
|
+
unreadable: external_exports.number().int().nonnegative(),
|
|
4210
|
+
tooLarge: external_exports.number().int().nonnegative(),
|
|
4211
|
+
excludedByExtension: external_exports.number().int().nonnegative(),
|
|
4212
|
+
deniedPath: external_exports.number().int().nonnegative(),
|
|
4213
|
+
nonRegularFile: external_exports.number().int().nonnegative()
|
|
4214
|
+
}).strict(),
|
|
4215
|
+
unreadableByErrno: external_exports.record(external_exports.string().min(1).max(32), external_exports.number().int().positive()),
|
|
4216
|
+
affectedCriteria: external_exports.array(WitanCriterionIdSchema)
|
|
4217
|
+
}).strict().superRefine((summary, context) => {
|
|
4218
|
+
const reasonTotal = Object.values(summary.byReason).reduce((sum, count) => sum + count, 0);
|
|
4219
|
+
if (reasonTotal !== summary.skipped) {
|
|
4220
|
+
context.addIssue({
|
|
4221
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4222
|
+
message: "Content-read reason counts must sum to skipped.",
|
|
4223
|
+
path: ["byReason"]
|
|
4224
|
+
});
|
|
4225
|
+
}
|
|
4226
|
+
const errnoTotal = Object.values(summary.unreadableByErrno).reduce(
|
|
4227
|
+
(sum, count) => sum + count,
|
|
4228
|
+
0
|
|
4229
|
+
);
|
|
4230
|
+
if (errnoTotal !== summary.byReason.unreadable) {
|
|
4231
|
+
context.addIssue({
|
|
4232
|
+
code: external_exports.ZodIssueCode.custom,
|
|
4233
|
+
message: "Unreadable errno counts must sum to the unreadable count.",
|
|
4234
|
+
path: ["unreadableByErrno"]
|
|
4235
|
+
});
|
|
4236
|
+
}
|
|
4237
|
+
});
|
|
4193
4238
|
var WitanRepoRefSchema = external_exports.object({
|
|
4194
4239
|
path: external_exports.string().min(1).max(700).optional(),
|
|
4195
4240
|
url: external_exports.string().url().optional(),
|
|
@@ -4205,7 +4250,9 @@ var WitanReportInputSchema = external_exports.object({
|
|
|
4205
4250
|
rubricVersion: external_exports.string().min(1).max(120).default(WITAN_RUBRIC_VERSION),
|
|
4206
4251
|
signals: external_exports.array(WitanCriterionSignalSchema).default([]),
|
|
4207
4252
|
archetype: WitanRepoArchetypeSchema.optional(),
|
|
4208
|
-
insufficientSourceReason: external_exports.string().min(1).max(2e3).optional()
|
|
4253
|
+
insufficientSourceReason: external_exports.string().min(1).max(2e3).optional(),
|
|
4254
|
+
scanLimitations: external_exports.array(external_exports.string().min(1).max(1e3)).max(16).default([]),
|
|
4255
|
+
contentReadSummary: WitanContentReadSummarySchema.optional()
|
|
4209
4256
|
}).strict();
|
|
4210
4257
|
var WitanCriterionScoreSchema = external_exports.object({
|
|
4211
4258
|
id: WitanCriterionIdSchema,
|
|
@@ -4225,14 +4272,21 @@ var WitanInputSignalFindingSchema = external_exports.object({
|
|
|
4225
4272
|
message: external_exports.string().min(1).max(500),
|
|
4226
4273
|
location: external_exports.string().max(700).optional()
|
|
4227
4274
|
}).strict();
|
|
4275
|
+
var WitanIngestProvenanceSchema = external_exports.enum(["operator_supplied", "auto_discovered"]);
|
|
4228
4276
|
var WitanInputSignalSchema = external_exports.object({
|
|
4229
4277
|
source: external_exports.string().min(1).max(120),
|
|
4278
|
+
// Adapters can still be used directly by internal callers; absent provenance retains the
|
|
4279
|
+
// historical operator-supplied meaning. The ingest funnel always sets this explicitly.
|
|
4280
|
+
provenance: WitanIngestProvenanceSchema.optional(),
|
|
4230
4281
|
dimension: WitanCriterionIdSchema,
|
|
4231
4282
|
findings: external_exports.array(WitanInputSignalFindingSchema),
|
|
4232
4283
|
weight: external_exports.number().min(0).max(1)
|
|
4233
4284
|
}).strict();
|
|
4234
4285
|
var WitanConsumedSignalSummarySchema = external_exports.object({
|
|
4235
4286
|
source: external_exports.string().min(1).max(120),
|
|
4287
|
+
// Optional on read for compatibility with already-published reports. Newly generated
|
|
4288
|
+
// reports always carry the explicit value copied from WitanInputSignal.
|
|
4289
|
+
provenance: WitanIngestProvenanceSchema.optional(),
|
|
4236
4290
|
dimension: WitanCriterionIdSchema,
|
|
4237
4291
|
findingCount: external_exports.number().int().min(0),
|
|
4238
4292
|
severityBreakdown: external_exports.object({
|
|
@@ -4269,12 +4323,13 @@ var WitanReportCommonSchema = external_exports.object({
|
|
|
4269
4323
|
productSlug: external_exports.string().regex(/^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/),
|
|
4270
4324
|
productDisplayName: external_exports.string().min(1).max(120),
|
|
4271
4325
|
repo: WitanRepoRefSchema,
|
|
4272
|
-
generatedAt: external_exports.string().datetime({ offset: true }),
|
|
4273
4326
|
rubricVersion: external_exports.string().min(1).max(120),
|
|
4274
4327
|
// Rubric-driven: length matches whatever rubric produced this report, not a fixed enum.
|
|
4275
4328
|
criteria: external_exports.array(WitanCriterionScoreSchema).min(1),
|
|
4276
4329
|
consumedSignals: external_exports.array(WitanConsumedSignalSummarySchema).optional(),
|
|
4277
|
-
archetype: WitanRepoArchetypeSchema.optional()
|
|
4330
|
+
archetype: WitanRepoArchetypeSchema.optional(),
|
|
4331
|
+
scanLimitations: external_exports.array(external_exports.string().min(1).max(1e3)).max(16).optional(),
|
|
4332
|
+
contentReadSummary: WitanContentReadSummarySchema.optional()
|
|
4278
4333
|
});
|
|
4279
4334
|
var WitanScoredReportSchema = WitanReportCommonSchema.extend({
|
|
4280
4335
|
verdict: WitanScoredVerdictSchema,
|
|
@@ -4354,6 +4409,12 @@ var WitanAttestationStatementSchema = external_exports.object({
|
|
|
4354
4409
|
artifact: external_exports.literal("report.json"),
|
|
4355
4410
|
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
|
|
4356
4411
|
}).strict(),
|
|
4412
|
+
externalSignalProvenance: external_exports.array(
|
|
4413
|
+
external_exports.object({
|
|
4414
|
+
source: external_exports.string().min(1).max(120),
|
|
4415
|
+
provenance: WitanIngestProvenanceSchema
|
|
4416
|
+
}).strict()
|
|
4417
|
+
).default([]),
|
|
4357
4418
|
outcome: WitanAttestationOutcomeSchema,
|
|
4358
4419
|
assurance: external_exports.object({
|
|
4359
4420
|
status: external_exports.literal("unsigned"),
|
|
@@ -4364,7 +4425,11 @@ var WitanAttestationStatementSchema = external_exports.object({
|
|
|
4364
4425
|
}).strict()
|
|
4365
4426
|
}).strict();
|
|
4366
4427
|
|
|
4428
|
+
// src/witan/html.ts
|
|
4429
|
+
init_esm_shims();
|
|
4430
|
+
|
|
4367
4431
|
// src/witan/abstention.ts
|
|
4432
|
+
init_esm_shims();
|
|
4368
4433
|
var WITAN_NO_MEASUREMENT_REASON = "No free-core rubric criterion produced a measurable signal. Cejel abstains rather than publish a numeric zero for an entirely unmeasured repository.";
|
|
4369
4434
|
var WITAN_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.";
|
|
4370
4435
|
function isWitanNoMeasurementAbstention(report) {
|
|
@@ -4377,6 +4442,7 @@ function renderWitanAbstentionLabel(report) {
|
|
|
4377
4442
|
}
|
|
4378
4443
|
|
|
4379
4444
|
// src/witan/coverage.ts
|
|
4445
|
+
init_esm_shims();
|
|
4380
4446
|
var LOW_CONFIDENCE_COVERAGE_THRESHOLD = 0.5;
|
|
4381
4447
|
function isMeasuredCriterionStatus(status) {
|
|
4382
4448
|
return status !== "not_applicable" && status !== "insufficient_data";
|
|
@@ -4410,6 +4476,7 @@ function formatCoverageCounts(coverage) {
|
|
|
4410
4476
|
}
|
|
4411
4477
|
|
|
4412
4478
|
// src/witan/external-findings.ts
|
|
4479
|
+
init_esm_shims();
|
|
4413
4480
|
var EXTERNAL_FINDINGS_DISPLAY_LIMIT = 10;
|
|
4414
4481
|
var SEVERITY_RANK = {
|
|
4415
4482
|
critical: 0,
|
|
@@ -4463,6 +4530,7 @@ function collectExternalFindings(consumedSignals) {
|
|
|
4463
4530
|
}
|
|
4464
4531
|
|
|
4465
4532
|
// src/witan/finding-presentation.ts
|
|
4533
|
+
init_esm_shims();
|
|
4466
4534
|
var METRIC_DERIVED_FINDING_PATTERN = /^[A-Z]\d+ metric-derived score is (\d+\.\d)\/4\.0, in the (critical|warning) band — no single finding drove this; it reflects the combined metric weighting below\.$/;
|
|
4467
4535
|
var REMEDIATION_BY_METRIC = {
|
|
4468
4536
|
test_to_source_ratio: "add concrete regression tests for the implementation surface",
|
|
@@ -4551,8 +4619,8 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
4551
4619
|
(criterion) => criterion.status === "verified" ? criterion.evidence.map((evidence) => renderEvidenceListItem(criterion, evidence)) : []
|
|
4552
4620
|
);
|
|
4553
4621
|
const openItems = report.criteria.flatMap(renderOpenItems);
|
|
4554
|
-
const
|
|
4555
|
-
const
|
|
4622
|
+
const externalSourceSummaries = summarizeSourcesByProvenance(report.consumedSignals ?? []);
|
|
4623
|
+
const contributingSources = externalSourceSummaries.map(renderSourceProvenanceLabel);
|
|
4556
4624
|
const externalFindings = collectExternalFindings(report.consumedSignals ?? []);
|
|
4557
4625
|
const coverage = computeMeasuredCoverage(report);
|
|
4558
4626
|
const gitHistoryUnavailable = !report.repo.headSha;
|
|
@@ -4576,11 +4644,11 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
4576
4644
|
<p class="eyebrow">Trust Certificate</p>
|
|
4577
4645
|
<h1>${escapeHtml(report.productDisplayName)}</h1>
|
|
4578
4646
|
<dl class="meta">
|
|
4579
|
-
|
|
4647
|
+
${options.generatedAt ? `<div><dt>Date</dt><dd>${escapeHtml(formatDate(options.generatedAt))}</dd></div>` : ""}
|
|
4580
4648
|
<div><dt>Run</dt><dd>${escapeHtml(renderRepo(report))}</dd></div>
|
|
4581
4649
|
<div><dt>CLI</dt><dd>${escapeHtml(options.cliVersion ? `Cejel ${options.cliVersion}` : "Not recorded")}</dd></div>
|
|
4582
4650
|
<div><dt>Rubric</dt><dd>${escapeHtml(report.rubricVersion)}</dd></div>
|
|
4583
|
-
${contributingSources.length > 0 ? `<div><dt>Sources</dt><dd>Incorporates findings from: ${escapeHtml(contributingSources.join(", "))}<ul class="source-counts">${externalSourceSummaries.map((s) => `<li>${escapeHtml(
|
|
4651
|
+
${contributingSources.length > 0 ? `<div><dt>Sources</dt><dd>Incorporates findings from: ${escapeHtml(contributingSources.join(", "))}<ul class="source-counts">${externalSourceSummaries.map((s) => `<li>${escapeHtml(formatSourceProvenanceLine(s))}</li>`).join("")}</ul></dd></div>` : ""}
|
|
4584
4652
|
</dl>
|
|
4585
4653
|
</section>
|
|
4586
4654
|
<aside class="score-panel" aria-label="Summary trust score">
|
|
@@ -4598,6 +4666,15 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
4598
4666
|
</div>
|
|
4599
4667
|
</header>
|
|
4600
4668
|
|
|
4669
|
+
${(report.scanLimitations?.length ?? 0) > 0 ? `<section class="scan-limitations-section evidence-section" aria-label="Scan limitations">
|
|
4670
|
+
<h2>Scan limitations</h2>
|
|
4671
|
+
<ul>${(report.scanLimitations ?? []).map(
|
|
4672
|
+
(limitation) => `<li class="scan-warning"><strong>Scan limitation</strong><span>${escapeHtml(limitation)}</span></li>`
|
|
4673
|
+
).join("")}</ul>
|
|
4674
|
+
</section>` : ""}
|
|
4675
|
+
|
|
4676
|
+
${renderContentReadSummary(report)}
|
|
4677
|
+
|
|
4601
4678
|
<section class="trust-grid" aria-label="Rubric criteria">
|
|
4602
4679
|
${renderCriterionColumn("Code trust", codeCriteria, gitHistoryUnavailable)}
|
|
4603
4680
|
${renderCriterionColumn("Process trust", processCriteria, gitHistoryUnavailable)}
|
|
@@ -4626,8 +4703,50 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
4626
4703
|
</html>
|
|
4627
4704
|
`;
|
|
4628
4705
|
}
|
|
4629
|
-
function
|
|
4630
|
-
|
|
4706
|
+
function renderContentReadSummary(report) {
|
|
4707
|
+
const summary = report.contentReadSummary;
|
|
4708
|
+
if (!summary || summary.skipped === 0) return "";
|
|
4709
|
+
const errnoCounts = Object.entries(summary.unreadableByErrno).map(([errno, count]) => `${errno}: ${count}`).join(", ");
|
|
4710
|
+
const affected = summary.affectedCriteria.length > 0 ? `<p>Affected criteria abstained as <code>insufficient_data</code>: ${escapeHtml(summary.affectedCriteria.join(", "))}.</p>` : "";
|
|
4711
|
+
return `<section class="scan-limitations-section evidence-section" aria-label="Skipped content reads">
|
|
4712
|
+
<h2>Skipped content reads</h2>
|
|
4713
|
+
<p>${summary.skipped} content entr${summary.skipped === 1 ? "y was" : "ies were"} skipped. Counts only; repository paths are intentionally omitted.</p>
|
|
4714
|
+
<ul>
|
|
4715
|
+
<li>Unreadable: ${summary.byReason.unreadable}${errnoCounts ? ` (${escapeHtml(errnoCounts)})` : ""}</li>
|
|
4716
|
+
<li>Too large: ${summary.byReason.tooLarge}</li>
|
|
4717
|
+
<li>Excluded by extension: ${summary.byReason.excludedByExtension}</li>
|
|
4718
|
+
<li>Denied path: ${summary.byReason.deniedPath}</li>
|
|
4719
|
+
<li>Non-regular file: ${summary.byReason.nonRegularFile}</li>
|
|
4720
|
+
</ul>
|
|
4721
|
+
${affected}
|
|
4722
|
+
</section>`;
|
|
4723
|
+
}
|
|
4724
|
+
function summarizeSourcesByProvenance(signals) {
|
|
4725
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4726
|
+
for (const signal of signals) {
|
|
4727
|
+
const provenance = signal.provenance ?? "operator_supplied";
|
|
4728
|
+
const key = `${signal.source}\0${provenance}`;
|
|
4729
|
+
const summary = grouped.get(key) ?? {
|
|
4730
|
+
source: signal.source,
|
|
4731
|
+
provenance,
|
|
4732
|
+
findingCount: 0,
|
|
4733
|
+
dimensions: /* @__PURE__ */ new Set()
|
|
4734
|
+
};
|
|
4735
|
+
summary.findingCount += signal.findingCount;
|
|
4736
|
+
summary.dimensions.add(signal.dimension);
|
|
4737
|
+
grouped.set(key, summary);
|
|
4738
|
+
}
|
|
4739
|
+
return Array.from(grouped.values()).map((summary) => ({ ...summary, dimensions: Array.from(summary.dimensions).sort() })).sort(
|
|
4740
|
+
(a, b) => a.source.localeCompare(b.source) || a.provenance.localeCompare(b.provenance)
|
|
4741
|
+
);
|
|
4742
|
+
}
|
|
4743
|
+
function renderSourceProvenanceLabel(summary) {
|
|
4744
|
+
const label = formatExternalSourceLabel(summary.source);
|
|
4745
|
+
return summary.provenance === "auto_discovered" ? `"${label}" (self-declared by the scanned repository \u2014 not verified)` : `${label} (operator-supplied)`;
|
|
4746
|
+
}
|
|
4747
|
+
function formatSourceProvenanceLine(summary) {
|
|
4748
|
+
const noun = summary.findingCount === 1 ? "finding" : "findings";
|
|
4749
|
+
return `${renderSourceProvenanceLabel(summary)}: ${summary.findingCount} ${noun} ingested (folded into ${summary.dimensions.join(", ")})`;
|
|
4631
4750
|
}
|
|
4632
4751
|
function renderExternalFindingItems(findings) {
|
|
4633
4752
|
const shown = findings.slice(0, EXTERNAL_FINDINGS_DISPLAY_LIMIT);
|
|
@@ -4864,6 +4983,8 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
|
|
|
4864
4983
|
.coverage-note { margin-top: 8px; color: var(--faint); font-family: var(--mono); font-size: 11px; }
|
|
4865
4984
|
.source-counts { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
|
4866
4985
|
.trust-grid, .evidence-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 28px; }
|
|
4986
|
+
.scan-limitations-section { margin-top: 28px; border-color: rgba(231, 191, 114, .44); }
|
|
4987
|
+
.scan-limitations-section .scan-warning { list-style: none; margin-left: -18px; }
|
|
4867
4988
|
.external-findings-section { margin-top: 18px; }
|
|
4868
4989
|
.external-note { margin: 0 0 14px; color: var(--muted); font-size: 13px; }
|
|
4869
4990
|
.criteria-column, .evidence-section { border: 1px solid var(--line); border-radius: 8px; background: var(--surface); padding: 22px; }
|
|
@@ -4913,6 +5034,7 @@ a:hover { text-decoration: underline; text-underline-offset: 2px; }
|
|
|
4913
5034
|
`;
|
|
4914
5035
|
|
|
4915
5036
|
// src/witan/badge.ts
|
|
5037
|
+
init_esm_shims();
|
|
4916
5038
|
var VERDICT_COLOR = {
|
|
4917
5039
|
Verified: "brightgreen",
|
|
4918
5040
|
Conditional: "yellow",
|
|
@@ -4930,24 +5052,26 @@ var VERDICT_SVG_FILL = {
|
|
|
4930
5052
|
"Insufficient evidence": "#8c8c8c"
|
|
4931
5053
|
};
|
|
4932
5054
|
function badgeMessage(report, verdict) {
|
|
5055
|
+
if ((report.scanLimitations?.length ?? 0) > 0) return "unrated: limited evidence";
|
|
4933
5056
|
if (isWitanNoMeasurementAbstention(report)) return "unrated: no measurable evidence";
|
|
4934
5057
|
if (report.verdict === "insufficient_source") return "unrated: no source";
|
|
4935
5058
|
return `${formatScore2(report.overallScore)}/4.0 ${verdict.toLowerCase()}`;
|
|
4936
5059
|
}
|
|
4937
5060
|
function renderWitanBadgeEndpoint(report) {
|
|
4938
5061
|
const verdict = renderReportVerdict(report);
|
|
5062
|
+
const limited = (report.scanLimitations?.length ?? 0) > 0;
|
|
4939
5063
|
return {
|
|
4940
5064
|
schemaVersion: 1,
|
|
4941
5065
|
label: "cejel trust",
|
|
4942
5066
|
message: badgeMessage(report, verdict),
|
|
4943
|
-
color: VERDICT_COLOR[verdict] ?? "lightgrey"
|
|
5067
|
+
color: limited ? "lightgrey" : VERDICT_COLOR[verdict] ?? "lightgrey"
|
|
4944
5068
|
};
|
|
4945
5069
|
}
|
|
4946
5070
|
function renderWitanBadgeSvg(report) {
|
|
4947
5071
|
const verdict = renderReportVerdict(report);
|
|
4948
5072
|
const label = "cejel trust";
|
|
4949
5073
|
const message = badgeMessage(report, verdict);
|
|
4950
|
-
const fill = VERDICT_SVG_FILL[verdict] ?? "#8c8c8c";
|
|
5074
|
+
const fill = (report.scanLimitations?.length ?? 0) > 0 ? "#8c8c8c" : VERDICT_SVG_FILL[verdict] ?? "#8c8c8c";
|
|
4951
5075
|
const charWidth = 6.5;
|
|
4952
5076
|
const padding = 10;
|
|
4953
5077
|
const labelWidth = Math.round(label.length * charWidth + padding);
|
|
@@ -4983,6 +5107,7 @@ function escapeXml(value) {
|
|
|
4983
5107
|
}
|
|
4984
5108
|
|
|
4985
5109
|
// src/witan/attestation.ts
|
|
5110
|
+
init_esm_shims();
|
|
4986
5111
|
import { createHash } from "crypto";
|
|
4987
5112
|
var ATTESTATION_LIMITATIONS = [
|
|
4988
5113
|
"Cejel measures observable repository signals; it is not a security or compliance guarantee.",
|
|
@@ -4996,6 +5121,19 @@ function hashWitanReport(report) {
|
|
|
4996
5121
|
}
|
|
4997
5122
|
function createWitanAttestation(report, options) {
|
|
4998
5123
|
const reportSha256 = hashWitanReport(report);
|
|
5124
|
+
const externalSignalProvenance = Array.from(
|
|
5125
|
+
new Map(
|
|
5126
|
+
(report.consumedSignals ?? []).map((signal) => {
|
|
5127
|
+
const provenance = signal.provenance ?? "operator_supplied";
|
|
5128
|
+
return [
|
|
5129
|
+
`${signal.source}\0${provenance}`,
|
|
5130
|
+
{ source: signal.source, provenance }
|
|
5131
|
+
];
|
|
5132
|
+
})
|
|
5133
|
+
).values()
|
|
5134
|
+
).sort(
|
|
5135
|
+
(a, b) => a.source.localeCompare(b.source) || a.provenance.localeCompare(b.provenance)
|
|
5136
|
+
);
|
|
4999
5137
|
const outcome = report.verdict === "insufficient_source" ? {
|
|
5000
5138
|
status: "abstained",
|
|
5001
5139
|
reason: report.insufficientSourceReason
|
|
@@ -5016,7 +5154,7 @@ function createWitanAttestation(report, options) {
|
|
|
5016
5154
|
predicateType: WITAN_ATTESTATION_PREDICATE_TYPE,
|
|
5017
5155
|
predicate: {
|
|
5018
5156
|
tool: { name: "cejel", version: options.toolVersion },
|
|
5019
|
-
generatedAt:
|
|
5157
|
+
generatedAt: options.generatedAt,
|
|
5020
5158
|
rubricVersion: report.rubricVersion,
|
|
5021
5159
|
repository: {
|
|
5022
5160
|
productSlug: report.productSlug,
|
|
@@ -5027,6 +5165,7 @@ function createWitanAttestation(report, options) {
|
|
|
5027
5165
|
artifact: "report.json",
|
|
5028
5166
|
sha256: reportSha256
|
|
5029
5167
|
},
|
|
5168
|
+
externalSignalProvenance,
|
|
5030
5169
|
outcome,
|
|
5031
5170
|
assurance: {
|
|
5032
5171
|
status: "unsigned",
|
|
@@ -5059,9 +5198,6 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
5059
5198
|
if (parsed.data.predicate.rubricVersion !== report.rubricVersion) {
|
|
5060
5199
|
errors.push("rubric version does not match report.json");
|
|
5061
5200
|
}
|
|
5062
|
-
if (parsed.data.predicate.generatedAt !== report.generatedAt) {
|
|
5063
|
-
errors.push("generated timestamp does not match report.json");
|
|
5064
|
-
}
|
|
5065
5201
|
if (parsed.data.predicate.repository.headSha !== report.repo.headSha) {
|
|
5066
5202
|
errors.push("repository revision does not match report.json");
|
|
5067
5203
|
}
|
|
@@ -5071,6 +5207,22 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
5071
5207
|
if (parsed.data.predicate.repository.url !== report.repo.url) {
|
|
5072
5208
|
errors.push("repository URL does not match report.json");
|
|
5073
5209
|
}
|
|
5210
|
+
const expectedExternalSignalProvenance = Array.from(
|
|
5211
|
+
new Map(
|
|
5212
|
+
(report.consumedSignals ?? []).map((signal) => {
|
|
5213
|
+
const provenance = signal.provenance ?? "operator_supplied";
|
|
5214
|
+
return [
|
|
5215
|
+
`${signal.source}\0${provenance}`,
|
|
5216
|
+
{ source: signal.source, provenance }
|
|
5217
|
+
];
|
|
5218
|
+
})
|
|
5219
|
+
).values()
|
|
5220
|
+
).sort(
|
|
5221
|
+
(a, b) => a.source.localeCompare(b.source) || a.provenance.localeCompare(b.provenance)
|
|
5222
|
+
);
|
|
5223
|
+
if (JSON.stringify(parsed.data.predicate.externalSignalProvenance) !== JSON.stringify(expectedExternalSignalProvenance)) {
|
|
5224
|
+
errors.push("external signal provenance does not match report.json");
|
|
5225
|
+
}
|
|
5074
5226
|
const outcome = parsed.data.predicate.outcome;
|
|
5075
5227
|
if (report.verdict === "insufficient_source") {
|
|
5076
5228
|
if (outcome.status !== "abstained" || outcome.reason !== report.insufficientSourceReason) {
|
|
@@ -5082,15 +5234,17 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
5082
5234
|
return { valid: errors.length === 0, errors };
|
|
5083
5235
|
}
|
|
5084
5236
|
|
|
5237
|
+
// src/witan/index.ts
|
|
5238
|
+
init_esm_shims();
|
|
5239
|
+
|
|
5240
|
+
// src/witan/markdown.ts
|
|
5241
|
+
init_esm_shims();
|
|
5242
|
+
|
|
5085
5243
|
// src/witan/repo-signals.ts
|
|
5086
|
-
|
|
5244
|
+
init_esm_shims();
|
|
5087
5245
|
import { createHash as createHash2 } from "crypto";
|
|
5088
5246
|
import {
|
|
5089
|
-
closeSync,
|
|
5090
5247
|
lstatSync,
|
|
5091
|
-
openSync,
|
|
5092
|
-
readFileSync,
|
|
5093
|
-
readSync,
|
|
5094
5248
|
readdirSync,
|
|
5095
5249
|
realpathSync,
|
|
5096
5250
|
statSync
|
|
@@ -5098,11 +5252,13 @@ import {
|
|
|
5098
5252
|
import { basename, dirname, extname, join, relative, resolve, sep } from "path";
|
|
5099
5253
|
|
|
5100
5254
|
// src/witan/json-safe.ts
|
|
5255
|
+
init_esm_shims();
|
|
5101
5256
|
function stripBom(text) {
|
|
5102
5257
|
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
5103
5258
|
}
|
|
5104
5259
|
|
|
5105
5260
|
// src/witan/rubric-version.ts
|
|
5261
|
+
init_esm_shims();
|
|
5106
5262
|
var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
|
|
5107
5263
|
var WITAN_RUBRIC_VERSION_V10 = "witan-rubric-v10-2026-07-23";
|
|
5108
5264
|
var WITAN_RUBRIC_VERSION_V11 = "witan-rubric-v11-2026-07-23";
|
|
@@ -5119,12 +5275,41 @@ function usesV17DetectorClosure(rubricVersion) {
|
|
|
5119
5275
|
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18;
|
|
5120
5276
|
}
|
|
5121
5277
|
function buildWitanInputFromRepo(options) {
|
|
5278
|
+
const tracked = trackContentReads(options.repoPath, () => buildWitanInputFromRepoUntracked(options));
|
|
5279
|
+
const affectedCriteria = tracked.affectedCriteria;
|
|
5280
|
+
const signals = (tracked.value.signals ?? []).map((signal) => {
|
|
5281
|
+
if (!affectedCriteria.has(signal.criterionId)) return signal;
|
|
5282
|
+
return {
|
|
5283
|
+
criterionId: signal.criterionId,
|
|
5284
|
+
positiveEvidence: [],
|
|
5285
|
+
findings: [],
|
|
5286
|
+
metrics: [],
|
|
5287
|
+
insufficientData: true,
|
|
5288
|
+
notes: "Cejel abstained on this criterion because one or more relevant repository files could not be read. Resolve the content-read limitations and re-scan."
|
|
5289
|
+
};
|
|
5290
|
+
});
|
|
5291
|
+
const affectedCount = affectedCriteria.size;
|
|
5292
|
+
const scanLimitations = [...tracked.value.scanLimitations ?? []];
|
|
5293
|
+
if (affectedCount > 0 && scanLimitations.length < 16) {
|
|
5294
|
+
scanLimitations.push(
|
|
5295
|
+
`${affectedCount} ${affectedCount === 1 ? "criterion" : "criteria"} abstained because relevant repository content could not be read. Counts and errno classes are recorded in contentReadSummary; file paths are intentionally omitted.`
|
|
5296
|
+
);
|
|
5297
|
+
}
|
|
5298
|
+
return {
|
|
5299
|
+
...tracked.value,
|
|
5300
|
+
signals,
|
|
5301
|
+
...scanLimitations.length > 0 ? { scanLimitations } : {},
|
|
5302
|
+
contentReadSummary: tracked.summary
|
|
5303
|
+
};
|
|
5304
|
+
}
|
|
5305
|
+
function buildWitanInputFromRepoUntracked(options) {
|
|
5122
5306
|
assertRepoPathExists(options.repoPath);
|
|
5123
5307
|
const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5124
5308
|
const rubricVersion = options.rubricVersion ?? WITAN_RUBRIC_VERSION;
|
|
5309
|
+
const fileInventory = collectRepoFileInventory(options.repoPath);
|
|
5125
5310
|
const headSha = readGitHead(options.repoPath);
|
|
5126
|
-
const repoFiles =
|
|
5127
|
-
const inventoryFiles =
|
|
5311
|
+
const repoFiles = fileInventory.repoFiles;
|
|
5312
|
+
const inventoryFiles = fileInventory.inventoryFiles;
|
|
5128
5313
|
const ignoredTargetReason = repoFiles.length === 0 ? explainIgnoredScanTarget(options.repoPath) : void 0;
|
|
5129
5314
|
const usesV17Detectors = usesV17DetectorClosure(rubricVersion);
|
|
5130
5315
|
const usesV18NativeRls = rubricVersion === WITAN_RUBRIC_VERSION_V18;
|
|
@@ -5148,6 +5333,7 @@ function buildWitanInputFromRepo(options) {
|
|
|
5148
5333
|
const usesV39Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion);
|
|
5149
5334
|
const usesV47Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion);
|
|
5150
5335
|
const reviewableSourceProof = usesV17Detectors ? buildReviewableSourceProof(options.repoPath, inventoryFiles, headSha, rubricVersion) : void 0;
|
|
5336
|
+
const scanLimitations = new Set(fileInventory.scanLimitations);
|
|
5151
5337
|
const coreSignals = collectRepoSignals(
|
|
5152
5338
|
options.repoPath,
|
|
5153
5339
|
generatedAt,
|
|
@@ -5158,7 +5344,8 @@ function buildWitanInputFromRepo(options) {
|
|
|
5158
5344
|
usesV39Detectors,
|
|
5159
5345
|
usesV47Detectors,
|
|
5160
5346
|
usesV18NativeRls,
|
|
5161
|
-
reviewableSourceProof
|
|
5347
|
+
reviewableSourceProof,
|
|
5348
|
+
scanLimitations
|
|
5162
5349
|
);
|
|
5163
5350
|
const archetype = usesV17Detectors && reviewableSourceProof?.passes === true && isV17RescuableAbstention(gatedArchetype.abstentionKind) && coreSignals.some(isMeasuredCriterionSignal) ? {
|
|
5164
5351
|
archetype: isLikelyMonorepo(inventoryFiles) ? "monorepo" : "source",
|
|
@@ -5176,6 +5363,7 @@ function buildWitanInputFromRepo(options) {
|
|
|
5176
5363
|
...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {},
|
|
5177
5364
|
archetype: archetype.archetype,
|
|
5178
5365
|
...ignoredTargetReason ?? archetype.insufficientSourceReason ? { insufficientSourceReason: ignoredTargetReason ?? archetype.insufficientSourceReason } : {},
|
|
5366
|
+
...scanLimitations.size > 0 ? { scanLimitations: [...scanLimitations] } : {},
|
|
5179
5367
|
signals: [
|
|
5180
5368
|
...coreSignals,
|
|
5181
5369
|
...(options.domainCollectors ?? []).map((collect) => collect(options.repoPath, repoFiles)),
|
|
@@ -5648,26 +5836,21 @@ function isRubyPackageRecipeMetadataSource(repoPath, file) {
|
|
|
5648
5836
|
function readSemanticMetadataPrefix(repoPath, file) {
|
|
5649
5837
|
const absolutePath = join(repoPath, file);
|
|
5650
5838
|
if (!isRegularFile(absolutePath)) return null;
|
|
5651
|
-
|
|
5652
|
-
try {
|
|
5653
|
-
descriptor = openSync(absolutePath, "r");
|
|
5654
|
-
const buffer = Buffer.allocUnsafe(SEMANTIC_METADATA_PREFIX_BYTES_V14);
|
|
5655
|
-
const bytesRead = readSync(descriptor, buffer, 0, SEMANTIC_METADATA_PREFIX_BYTES_V14, 0);
|
|
5656
|
-
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
5657
|
-
} catch {
|
|
5658
|
-
return null;
|
|
5659
|
-
} finally {
|
|
5660
|
-
if (descriptor !== null) closeSync(descriptor);
|
|
5661
|
-
}
|
|
5839
|
+
return readRepoTextPrefix(absolutePath, SEMANTIC_METADATA_PREFIX_BYTES_V14);
|
|
5662
5840
|
}
|
|
5663
5841
|
function readRepresentativeSourceText(repoPath, file) {
|
|
5664
5842
|
const absolutePath = join(repoPath, file);
|
|
5665
5843
|
try {
|
|
5666
5844
|
if (!isRegularFile(absolutePath)) return null;
|
|
5667
5845
|
const size = statSync(absolutePath).size;
|
|
5668
|
-
if (size === 0
|
|
5669
|
-
|
|
5670
|
-
|
|
5846
|
+
if (size === 0) return null;
|
|
5847
|
+
if (size > READABLE_SOURCE_REPRESENTATION_FILE_BYTES_V13) {
|
|
5848
|
+
recordContentSkip(absolutePath, "too_large", true);
|
|
5849
|
+
return null;
|
|
5850
|
+
}
|
|
5851
|
+
return readRepoText(absolutePath, "utf8");
|
|
5852
|
+
} catch (error) {
|
|
5853
|
+
recordFilesystemSkip(absolutePath, error, false, true);
|
|
5671
5854
|
return null;
|
|
5672
5855
|
}
|
|
5673
5856
|
}
|
|
@@ -5737,28 +5920,25 @@ function assertRepoPathExists(repoPath) {
|
|
|
5737
5920
|
}
|
|
5738
5921
|
}
|
|
5739
5922
|
function explainIgnoredScanTarget(repoPath) {
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
encoding: "utf8",
|
|
5745
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5746
|
-
}).trim();
|
|
5747
|
-
} catch {
|
|
5748
|
-
return void 0;
|
|
5923
|
+
const topResult = execGit(["rev-parse", "--show-toplevel"], { cwd: repoPath });
|
|
5924
|
+
if (!topResult.ok) {
|
|
5925
|
+
if (isExpectedGitAbsence(topResult)) return void 0;
|
|
5926
|
+
throw new Error(describeGitFailure(topResult.reason, "repository-root discovery"));
|
|
5749
5927
|
}
|
|
5928
|
+
const top = topResult.stdout.trim();
|
|
5750
5929
|
if (!top) return void 0;
|
|
5751
5930
|
const topResolved = realpathSync(top);
|
|
5752
5931
|
const targetResolved = realpathSync(repoPath);
|
|
5753
5932
|
const target = relative(topResolved, targetResolved).split(sep).join("/");
|
|
5754
5933
|
if (!target || target.startsWith("../")) return void 0;
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5934
|
+
const ignored = execGit(["check-ignore", "--quiet", "--", `${target}/`], {
|
|
5935
|
+
cwd: topResolved
|
|
5936
|
+
});
|
|
5937
|
+
if (!ignored.ok) {
|
|
5938
|
+
if (isExpectedGitAbsence(ignored) || ignored.reason === "exec_failed" && ignored.exitCode === 1) {
|
|
5939
|
+
return void 0;
|
|
5940
|
+
}
|
|
5941
|
+
throw new Error(describeGitFailure(ignored.reason, "ignore-rule check"));
|
|
5762
5942
|
}
|
|
5763
5943
|
return `The requested scan path (${target}) is excluded by this repository's Git ignore rules, so Cejel found no tracked source to evaluate. Scan a tracked project or intentionally add the path to version control; Cejel abstains rather than score an ignored or vendored working tree.`;
|
|
5764
5944
|
}
|
|
@@ -5772,48 +5952,74 @@ function buildNotApplicableSignal(criterionId, reason) {
|
|
|
5772
5952
|
notApplicable: true
|
|
5773
5953
|
};
|
|
5774
5954
|
}
|
|
5775
|
-
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, reviewableSourceProof) {
|
|
5955
|
+
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, reviewableSourceProof, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
5776
5956
|
const signals = [];
|
|
5777
|
-
const mono = resolveMonorepoContext(repoPath);
|
|
5778
|
-
const
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5957
|
+
const mono = resolveMonorepoContext(repoPath, scanLimitations);
|
|
5958
|
+
const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
|
|
5959
|
+
const a1Signal = collectCriterion(
|
|
5960
|
+
"A1",
|
|
5961
|
+
() => collectA1TestIntegrityEvidence(
|
|
5962
|
+
repoPath,
|
|
5963
|
+
repoFiles,
|
|
5964
|
+
useV27Detectors,
|
|
5965
|
+
useV33Detectors,
|
|
5966
|
+
useV36Detectors,
|
|
5967
|
+
reviewableSourceProof
|
|
5968
|
+
)
|
|
5785
5969
|
);
|
|
5786
|
-
const a2Signal =
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5970
|
+
const a2Signal = collectCriterion(
|
|
5971
|
+
"A2",
|
|
5972
|
+
() => collectA2IsolationEvidence(
|
|
5973
|
+
repoPath,
|
|
5974
|
+
repoFiles,
|
|
5975
|
+
useV27Detectors,
|
|
5976
|
+
useV33Detectors,
|
|
5977
|
+
useV36Detectors,
|
|
5978
|
+
useV39Detectors,
|
|
5979
|
+
useV47Detectors,
|
|
5980
|
+
useV18NativeRls,
|
|
5981
|
+
scanLimitations
|
|
5982
|
+
)
|
|
5795
5983
|
);
|
|
5796
|
-
const a3Signal =
|
|
5797
|
-
|
|
5798
|
-
repoPath,
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5984
|
+
const a3Signal = collectCriterion(
|
|
5985
|
+
"A3",
|
|
5986
|
+
() => collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors)
|
|
5987
|
+
);
|
|
5988
|
+
const a4Signal = collectCriterion(
|
|
5989
|
+
"A4",
|
|
5990
|
+
() => collectA4DependencyEvidence(
|
|
5991
|
+
repoPath,
|
|
5992
|
+
repoFiles,
|
|
5993
|
+
mono,
|
|
5994
|
+
useV27Detectors,
|
|
5995
|
+
useV47Detectors
|
|
5996
|
+
)
|
|
5997
|
+
);
|
|
5998
|
+
const a5Signal = collectCriterion(
|
|
5999
|
+
"A5",
|
|
6000
|
+
() => collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors)
|
|
5803
6001
|
);
|
|
5804
|
-
const a5Signal = collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors);
|
|
5805
6002
|
const b1Signal = buildNotApplicableSignal(
|
|
5806
6003
|
"B1",
|
|
5807
6004
|
"Repository scans do not evaluate the dispatch-trace process dimension."
|
|
5808
6005
|
);
|
|
5809
|
-
const b2Signal = collectB2PrTraceEvidence(repoPath, repoFiles);
|
|
5810
|
-
const b3Signal =
|
|
5811
|
-
|
|
6006
|
+
const b2Signal = collectCriterion("B2", () => collectB2PrTraceEvidence(repoPath, repoFiles));
|
|
6007
|
+
const b3Signal = collectCriterion(
|
|
6008
|
+
"B3",
|
|
6009
|
+
() => collectB3CiDisciplineEvidence(repoPath, repoFiles, mono)
|
|
6010
|
+
);
|
|
6011
|
+
const b4Signal = collectCriterion(
|
|
6012
|
+
"B4",
|
|
6013
|
+
() => collectB4AuditEvidence(repoPath, repoFiles, generatedAt)
|
|
6014
|
+
);
|
|
5812
6015
|
const b5Signal = buildNotApplicableSignal(
|
|
5813
6016
|
"B5",
|
|
5814
6017
|
"Repository scans do not evaluate the learning-trace process dimension."
|
|
5815
6018
|
);
|
|
5816
|
-
const b6Signal =
|
|
6019
|
+
const b6Signal = collectCriterion(
|
|
6020
|
+
"B6",
|
|
6021
|
+
() => collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detectors)
|
|
6022
|
+
);
|
|
5817
6023
|
for (const signal of [
|
|
5818
6024
|
a1Signal,
|
|
5819
6025
|
a2Signal,
|
|
@@ -6083,7 +6289,7 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
|
|
|
6083
6289
|
notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files.`
|
|
6084
6290
|
};
|
|
6085
6291
|
}
|
|
6086
|
-
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls) {
|
|
6292
|
+
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, scanLimitations) {
|
|
6087
6293
|
const evidence = [];
|
|
6088
6294
|
const findings = [];
|
|
6089
6295
|
const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
|
|
@@ -6129,6 +6335,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
6129
6335
|
repoPath,
|
|
6130
6336
|
useV27Detectors,
|
|
6131
6337
|
currentSecretFingerprintsByPath,
|
|
6338
|
+
scanLimitations,
|
|
6132
6339
|
useV33Detectors,
|
|
6133
6340
|
useV36Detectors,
|
|
6134
6341
|
useV39Detectors,
|
|
@@ -6145,7 +6352,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
6145
6352
|
const committedEnvFilePath = repoFiles.find(
|
|
6146
6353
|
(f) => /(?:^|\/)\.env(?:\.|$)/i.test(f) && !isEnvTemplatePath(f, useV47Detectors) && (!useV47Detectors || isV47AuthoredProductionPath(f)) && (!useV39Detectors || !hasTemplateOnlyEnvContent(repoPath, f))
|
|
6147
6354
|
);
|
|
6148
|
-
const hasEnvInHistory = committedEnvFilePath === void 0 && hasEnvPathInGitHistory(repoPath);
|
|
6355
|
+
const hasEnvInHistory = committedEnvFilePath === void 0 && hasEnvPathInGitHistory(repoPath, scanLimitations);
|
|
6149
6356
|
const dbLayerImportFile = migrationFiles.length === 0 ? implFiles.slice(0, 30).find((f) => fileContains(repoPath, f, DB_CLIENT_PATTERN)) : void 0;
|
|
6150
6357
|
const hasDataLayer = migrationFiles.length > 0 || dbLayerImportFile !== void 0;
|
|
6151
6358
|
const hasSecretsSurface = envExamples.length > 0 || committedEnvFilePath !== void 0 || gitignoreHasEnvRule || hasEnvInHistory;
|
|
@@ -7305,14 +7512,14 @@ function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
|
|
|
7305
7512
|
function findInsecureSecretCompareLine(repoPath, file, useV39Detectors = false) {
|
|
7306
7513
|
const fullPath = join(repoPath, file);
|
|
7307
7514
|
if (!isRegularFile(fullPath)) return null;
|
|
7308
|
-
const lines =
|
|
7515
|
+
const lines = readRepoText(fullPath, "utf8").split("\n");
|
|
7309
7516
|
const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line, useV39Detectors));
|
|
7310
7517
|
return index === -1 ? null : index + 1;
|
|
7311
7518
|
}
|
|
7312
7519
|
function findFirstMatchingLine(repoPath, file, pattern) {
|
|
7313
7520
|
const fullPath = join(repoPath, file);
|
|
7314
7521
|
if (!isRegularFile(fullPath)) return null;
|
|
7315
|
-
const lines =
|
|
7522
|
+
const lines = readRepoText(fullPath, "utf8").split("\n");
|
|
7316
7523
|
const index = lines.findIndex((line) => pattern.test(line));
|
|
7317
7524
|
return index === -1 ? null : index + 1;
|
|
7318
7525
|
}
|
|
@@ -7320,7 +7527,7 @@ function lineAt(repoPath, file, line) {
|
|
|
7320
7527
|
if (line < 1) return null;
|
|
7321
7528
|
const fullPath = join(repoPath, file);
|
|
7322
7529
|
if (!isRegularFile(fullPath)) return null;
|
|
7323
|
-
return
|
|
7530
|
+
return readRepoText(fullPath, "utf8").split("\n")[line - 1] ?? null;
|
|
7324
7531
|
}
|
|
7325
7532
|
var UNSORTED_JSON_SIGN_PATTERN = /\.update\s*\(\s*JSON\.stringify\s*\(|\.sign\s*\([^)]*JSON\.stringify\s*\(/;
|
|
7326
7533
|
var UNSORTED_JSON_DIRECT_SIGN_PATTERN = /\.sign\s*\([^)]*JSON\.stringify\s*\(/;
|
|
@@ -7329,7 +7536,7 @@ var JSON_STRINGIFY_ASSIGNMENT_PATTERN = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*
|
|
|
7329
7536
|
function findUnsortedJsonHmacLine(repoPath, file) {
|
|
7330
7537
|
const fullPath = join(repoPath, file);
|
|
7331
7538
|
if (!isRegularFile(fullPath)) return null;
|
|
7332
|
-
const contents =
|
|
7539
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7333
7540
|
const directSign = findFirstMatchingLine(repoPath, file, UNSORTED_JSON_DIRECT_SIGN_PATTERN);
|
|
7334
7541
|
if (directSign !== null) return directSign;
|
|
7335
7542
|
const directConstructorUpdate = findFirstMatchingLine(
|
|
@@ -7410,27 +7617,92 @@ function isEnvTemplatePath(path, useV47Detectors = false) {
|
|
|
7410
7617
|
}
|
|
7411
7618
|
var HARD_EXCLUDED_PATH_PATTERN = /(^|\/)(?:\.git|\.venv|venv|site-packages|node_modules|dist|build|\.next|__pycache__|vendor|\.terraform|coverage)(?:\/|$)/;
|
|
7412
7619
|
var COVERAGE_PERCENT_PATTERN = /(?:statements|branches|lines|functions|fail_under)\s*[:=]\s*["']?(\d+(?:\.\d+)?)/gi;
|
|
7413
|
-
function
|
|
7414
|
-
|
|
7415
|
-
if (
|
|
7620
|
+
function gitFailureDetail(reason) {
|
|
7621
|
+
if (reason === "buffer_exceeded") return "output exceeded the explicit 8 MiB limit";
|
|
7622
|
+
if (reason === "timeout") return "execution exceeded the explicit 30 second timeout";
|
|
7623
|
+
return "the local Git command failed";
|
|
7624
|
+
}
|
|
7625
|
+
function readGitTrackedFileOutput(repoPath) {
|
|
7626
|
+
const workTree = execGit(["rev-parse", "--is-inside-work-tree"], { cwd: repoPath });
|
|
7627
|
+
if (!workTree.ok) return workTree;
|
|
7628
|
+
if (workTree.stdout.trim() !== "true") return { ok: false, reason: "not_a_repo" };
|
|
7629
|
+
return execGit(["ls-files", "--cached"], { cwd: repoPath });
|
|
7630
|
+
}
|
|
7631
|
+
function parseGitTrackedFiles(repoPath, output, includeHardExcluded) {
|
|
7416
7632
|
const files = [];
|
|
7417
|
-
|
|
7633
|
+
for (const file of output.trim().split("\n")) {
|
|
7634
|
+
if (file.length === 0) continue;
|
|
7635
|
+
const fullPath = join(repoPath, file);
|
|
7636
|
+
if (!includeHardExcluded && isHardExcludedPath(file)) {
|
|
7637
|
+
recordContentSkip(fullPath, "denied_path", true);
|
|
7638
|
+
continue;
|
|
7639
|
+
}
|
|
7640
|
+
try {
|
|
7641
|
+
if (!lstatSync(fullPath).isFile()) {
|
|
7642
|
+
recordContentSkip(fullPath, "non_regular_file", true);
|
|
7643
|
+
continue;
|
|
7644
|
+
}
|
|
7645
|
+
} catch (error) {
|
|
7646
|
+
recordFilesystemSkip(fullPath, error, false, true);
|
|
7647
|
+
continue;
|
|
7648
|
+
}
|
|
7649
|
+
if (!isPotentialContentPath(file)) {
|
|
7650
|
+
recordContentSkip(fullPath, "excluded_by_extension", true);
|
|
7651
|
+
}
|
|
7652
|
+
files.push(file);
|
|
7653
|
+
}
|
|
7654
|
+
return files.sort();
|
|
7655
|
+
}
|
|
7656
|
+
function directoryInventory(repoPath) {
|
|
7657
|
+
const files = [];
|
|
7658
|
+
const rootDevice = statSync(repoPath).dev;
|
|
7659
|
+
visitRepoDir(repoPath, repoPath, files, rootDevice);
|
|
7418
7660
|
return files.filter((file) => !isHardExcludedPath(file)).sort();
|
|
7419
7661
|
}
|
|
7420
|
-
function
|
|
7421
|
-
|
|
7662
|
+
function collectRepoFileInventory(repoPath) {
|
|
7663
|
+
const tracked = readGitTrackedFileOutput(repoPath);
|
|
7664
|
+
if (tracked.ok) {
|
|
7665
|
+
return {
|
|
7666
|
+
repoFiles: parseGitTrackedFiles(repoPath, tracked.stdout, false),
|
|
7667
|
+
inventoryFiles: parseGitTrackedFiles(repoPath, tracked.stdout, true),
|
|
7668
|
+
scanLimitations: []
|
|
7669
|
+
};
|
|
7670
|
+
}
|
|
7671
|
+
const fallbackFiles = directoryInventory(repoPath);
|
|
7672
|
+
if (isExpectedGitAbsence(tracked)) {
|
|
7673
|
+
return {
|
|
7674
|
+
repoFiles: fallbackFiles,
|
|
7675
|
+
inventoryFiles: fallbackFiles,
|
|
7676
|
+
scanLimitations: []
|
|
7677
|
+
};
|
|
7678
|
+
}
|
|
7679
|
+
return {
|
|
7680
|
+
repoFiles: fallbackFiles,
|
|
7681
|
+
inventoryFiles: fallbackFiles,
|
|
7682
|
+
scanLimitations: [
|
|
7683
|
+
`Tracked-file inventory was unavailable because ${gitFailureDetail(tracked.reason)}. Cejel used a bounded directory walk with different exclusion semantics; re-scan after the local Git failure is resolved to compare the certificate.`
|
|
7684
|
+
]
|
|
7685
|
+
};
|
|
7422
7686
|
}
|
|
7423
|
-
function
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7687
|
+
function listRepoFiles(repoPath) {
|
|
7688
|
+
return [...collectRepoFileInventory(repoPath).repoFiles];
|
|
7689
|
+
}
|
|
7690
|
+
function listRepoInventory(repoPath, fallbackFiles) {
|
|
7691
|
+
const tracked = readGitTrackedFileOutput(repoPath);
|
|
7692
|
+
if (tracked.ok) return parseGitTrackedFiles(repoPath, tracked.stdout, true);
|
|
7693
|
+
if (isExpectedGitAbsence(tracked)) return [...fallbackFiles];
|
|
7694
|
+
throw new Error(describeGitFailure(tracked.reason, "tracked-file inventory"));
|
|
7695
|
+
}
|
|
7696
|
+
function resolveMonorepoContext(repoPath, scanLimitations) {
|
|
7697
|
+
const topResult = execGit(["rev-parse", "--show-toplevel"], { cwd: repoPath });
|
|
7698
|
+
if (!topResult.ok) {
|
|
7699
|
+
if (isExpectedGitAbsence(topResult)) return null;
|
|
7700
|
+
scanLimitations.add(
|
|
7701
|
+
`${describeGitFailure(topResult.reason, "repository-root discovery")} Cejel continued without monorepo root-level shared configuration.`
|
|
7702
|
+
);
|
|
7432
7703
|
return null;
|
|
7433
7704
|
}
|
|
7705
|
+
const top = topResult.stdout.trim();
|
|
7434
7706
|
if (!top) return null;
|
|
7435
7707
|
const rootResolved = resolve(top);
|
|
7436
7708
|
const repoResolved = (() => {
|
|
@@ -7442,7 +7714,7 @@ function resolveMonorepoContext(repoPath) {
|
|
|
7442
7714
|
})();
|
|
7443
7715
|
if (rootResolved === repoResolved) return null;
|
|
7444
7716
|
if (!repoResolved.startsWith(rootResolved + sep)) return null;
|
|
7445
|
-
const rootFiles = listGitTrackedFiles(rootResolved);
|
|
7717
|
+
const rootFiles = listGitTrackedFiles(rootResolved, scanLimitations);
|
|
7446
7718
|
if (!rootFiles || rootFiles.length === 0) return null;
|
|
7447
7719
|
const subRel = repoResolved.slice(rootResolved.length + 1).split(sep).join("/");
|
|
7448
7720
|
const subPrefix = subRel ? `${subRel}/` : "";
|
|
@@ -7452,50 +7724,119 @@ function resolveMonorepoContext(repoPath) {
|
|
|
7452
7724
|
if (sharedFiles.length === 0) return null;
|
|
7453
7725
|
return { root: rootResolved, sharedFiles };
|
|
7454
7726
|
}
|
|
7455
|
-
function listGitTrackedFiles(repoPath, includeHardExcluded = false) {
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
cwd: repoPath,
|
|
7464
|
-
encoding: "utf8",
|
|
7465
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
7466
|
-
}).trim();
|
|
7467
|
-
if (!output) return [];
|
|
7468
|
-
return output.split("\n").filter(
|
|
7469
|
-
(file) => file.length > 0 && (includeHardExcluded || !isHardExcludedPath(file)) && isRegularFile(join(repoPath, file))
|
|
7470
|
-
).sort();
|
|
7471
|
-
} catch {
|
|
7472
|
-
return null;
|
|
7473
|
-
}
|
|
7727
|
+
function listGitTrackedFiles(repoPath, scanLimitations, includeHardExcluded = false) {
|
|
7728
|
+
const tracked = readGitTrackedFileOutput(repoPath);
|
|
7729
|
+
if (tracked.ok) return parseGitTrackedFiles(repoPath, tracked.stdout, includeHardExcluded);
|
|
7730
|
+
if (isExpectedGitAbsence(tracked)) return null;
|
|
7731
|
+
scanLimitations.add(
|
|
7732
|
+
`${describeGitFailure(tracked.reason, "monorepo shared-file inventory")} Cejel continued without monorepo root-level shared configuration.`
|
|
7733
|
+
);
|
|
7734
|
+
return null;
|
|
7474
7735
|
}
|
|
7475
|
-
function visitRepoDir(repoPath, dirPath, files) {
|
|
7736
|
+
function visitRepoDir(repoPath, dirPath, files, rootDevice) {
|
|
7476
7737
|
let entries;
|
|
7477
7738
|
try {
|
|
7478
7739
|
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
7479
|
-
} catch {
|
|
7740
|
+
} catch (error) {
|
|
7741
|
+
recordFilesystemSkip(dirPath, error, true);
|
|
7480
7742
|
return;
|
|
7481
7743
|
}
|
|
7482
7744
|
for (const entry of entries) {
|
|
7483
|
-
if (shouldSkipDir(entry.name)) continue;
|
|
7484
7745
|
const fullPath = join(dirPath, entry.name);
|
|
7746
|
+
if (shouldSkipDir(entry.name)) {
|
|
7747
|
+
recordContentSkip(fullPath, "denied_path");
|
|
7748
|
+
continue;
|
|
7749
|
+
}
|
|
7485
7750
|
if (entry.isDirectory()) {
|
|
7486
|
-
|
|
7751
|
+
try {
|
|
7752
|
+
if (lstatSync(fullPath, { bigint: true }).dev !== BigInt(rootDevice)) {
|
|
7753
|
+
recordContentSkip(fullPath, "denied_path");
|
|
7754
|
+
continue;
|
|
7755
|
+
}
|
|
7756
|
+
} catch (error) {
|
|
7757
|
+
recordFilesystemSkip(fullPath, error);
|
|
7758
|
+
continue;
|
|
7759
|
+
}
|
|
7760
|
+
visitRepoDir(repoPath, fullPath, files, rootDevice);
|
|
7761
|
+
continue;
|
|
7762
|
+
}
|
|
7763
|
+
if (!entry.isFile()) {
|
|
7764
|
+
recordContentSkip(fullPath, "non_regular_file");
|
|
7487
7765
|
continue;
|
|
7488
7766
|
}
|
|
7489
|
-
if (!entry.isFile()) continue;
|
|
7490
7767
|
let size;
|
|
7491
7768
|
try {
|
|
7492
7769
|
size = statSync(fullPath).size;
|
|
7493
|
-
} catch {
|
|
7770
|
+
} catch (error) {
|
|
7771
|
+
recordFilesystemSkip(fullPath, error);
|
|
7494
7772
|
continue;
|
|
7495
7773
|
}
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7774
|
+
const repoRelativePath = relative(repoPath, fullPath);
|
|
7775
|
+
if (size > 512e3) {
|
|
7776
|
+
recordContentSkip(fullPath, "too_large");
|
|
7777
|
+
continue;
|
|
7778
|
+
}
|
|
7779
|
+
if (!isPotentialContentPath(repoRelativePath)) {
|
|
7780
|
+
recordContentSkip(fullPath, "excluded_by_extension");
|
|
7781
|
+
}
|
|
7782
|
+
files.push(repoRelativePath);
|
|
7783
|
+
}
|
|
7784
|
+
}
|
|
7785
|
+
var CONTENT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
7786
|
+
".c",
|
|
7787
|
+
".cc",
|
|
7788
|
+
".cfg",
|
|
7789
|
+
".cjs",
|
|
7790
|
+
".cmake",
|
|
7791
|
+
".conf",
|
|
7792
|
+
".cpp",
|
|
7793
|
+
".cs",
|
|
7794
|
+
".css",
|
|
7795
|
+
".cts",
|
|
7796
|
+
".cxx",
|
|
7797
|
+
".env",
|
|
7798
|
+
".go",
|
|
7799
|
+
".gradle",
|
|
7800
|
+
".graphql",
|
|
7801
|
+
".h",
|
|
7802
|
+
".hpp",
|
|
7803
|
+
".html",
|
|
7804
|
+
".ini",
|
|
7805
|
+
".java",
|
|
7806
|
+
".js",
|
|
7807
|
+
".json",
|
|
7808
|
+
".json5",
|
|
7809
|
+
".jsx",
|
|
7810
|
+
".kt",
|
|
7811
|
+
".kts",
|
|
7812
|
+
".lua",
|
|
7813
|
+
".md",
|
|
7814
|
+
".mdx",
|
|
7815
|
+
".mjs",
|
|
7816
|
+
".mts",
|
|
7817
|
+
".php",
|
|
7818
|
+
".properties",
|
|
7819
|
+
".proto",
|
|
7820
|
+
".py",
|
|
7821
|
+
".rb",
|
|
7822
|
+
".rs",
|
|
7823
|
+
".rst",
|
|
7824
|
+
".scss",
|
|
7825
|
+
".sh",
|
|
7826
|
+
".sql",
|
|
7827
|
+
".swift",
|
|
7828
|
+
".toml",
|
|
7829
|
+
".ts",
|
|
7830
|
+
".tsx",
|
|
7831
|
+
".txt",
|
|
7832
|
+
".xml",
|
|
7833
|
+
".yaml",
|
|
7834
|
+
".yml"
|
|
7835
|
+
]);
|
|
7836
|
+
function isPotentialContentPath(path) {
|
|
7837
|
+
const name = basename(path);
|
|
7838
|
+
if (CONTENT_FILE_EXTENSIONS.has(extname(name).toLowerCase())) return true;
|
|
7839
|
+
return /^(?:Dockerfile|Makefile|Rakefile|Gemfile|Pipfile|Procfile|CMakeLists\.txt)$/i.test(name) || /^\.(?:env|gitignore|npmrc|nvmrc|prettierrc|eslintrc)(?:\.|$)/i.test(name);
|
|
7499
7840
|
}
|
|
7500
7841
|
function shouldSkipDir(name) {
|
|
7501
7842
|
return [
|
|
@@ -7522,7 +7863,7 @@ function collectContentBasedCppTestFiles(repoPath, repoFiles) {
|
|
|
7522
7863
|
return candidates.filter((file) => {
|
|
7523
7864
|
const fullPath = join(repoPath, file);
|
|
7524
7865
|
if (!isRegularFile(fullPath)) return false;
|
|
7525
|
-
const contents =
|
|
7866
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7526
7867
|
return CPP_TEST_FRAMEWORK_PATTERN.test(contents) || CPP_TEST_MACRO_PATTERN.test(contents);
|
|
7527
7868
|
});
|
|
7528
7869
|
}
|
|
@@ -7542,7 +7883,7 @@ function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
|
|
|
7542
7883
|
for (const file of files) {
|
|
7543
7884
|
const fullPath = join(repoPath, file);
|
|
7544
7885
|
if (!isRegularFile(fullPath)) continue;
|
|
7545
|
-
const contents =
|
|
7886
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7546
7887
|
const nonHollow = (JS_TEST_ASSERTION_PATTERN.test(contents) || CPP_TEST_ASSERTION_PATTERN.test(contents) || MULTI_ECOSYSTEM_TEST_ASSERTION_PATTERN.test(contents)) && !SUITE_LEVEL_SKIP_PATTERN.test(contents);
|
|
7547
7888
|
if (nonHollow) {
|
|
7548
7889
|
nonHollowCount += 1;
|
|
@@ -7563,7 +7904,7 @@ function countPatternMatches(repoPath, files, pattern) {
|
|
|
7563
7904
|
for (const file of files) {
|
|
7564
7905
|
const fullPath = join(repoPath, file);
|
|
7565
7906
|
if (!isRegularFile(fullPath)) continue;
|
|
7566
|
-
count += [...
|
|
7907
|
+
count += [...readRepoText(fullPath, "utf8").matchAll(matcher)].length;
|
|
7567
7908
|
}
|
|
7568
7909
|
return count;
|
|
7569
7910
|
}
|
|
@@ -7571,7 +7912,7 @@ function readCoveragePercent(repoPath, files) {
|
|
|
7571
7912
|
for (const file of files) {
|
|
7572
7913
|
const fullPath = join(repoPath, file);
|
|
7573
7914
|
if (!isRegularFile(fullPath)) continue;
|
|
7574
|
-
const contents =
|
|
7915
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7575
7916
|
const summaryPercent = parseCoverageSummaryPercent(contents);
|
|
7576
7917
|
if (summaryPercent !== null) return summaryPercent;
|
|
7577
7918
|
const thresholdPercent = parseCoverageThresholdPercent(contents);
|
|
@@ -7661,7 +8002,7 @@ function readDependencySpecs(repoPath, manifests) {
|
|
|
7661
8002
|
}
|
|
7662
8003
|
function readGoModDependencySpecs(path) {
|
|
7663
8004
|
const specs = [];
|
|
7664
|
-
const contents =
|
|
8005
|
+
const contents = readRepoText(path, "utf8");
|
|
7665
8006
|
const blockPattern = /require\s*\(([^)]+)\)/gs;
|
|
7666
8007
|
const singlePattern = /^require\s+(\S+)\s+(\S+)/gm;
|
|
7667
8008
|
for (const blockMatch of contents.matchAll(blockPattern)) {
|
|
@@ -7682,7 +8023,7 @@ function readGoModDependencySpecs(path) {
|
|
|
7682
8023
|
}
|
|
7683
8024
|
function readCargoTomlDependencySpecs(path) {
|
|
7684
8025
|
const specs = [];
|
|
7685
|
-
const contents =
|
|
8026
|
+
const contents = readRepoText(path, "utf8");
|
|
7686
8027
|
const depSectionPattern = /^\[(?:dependencies|dev-dependencies|build-dependencies)\]([\s\S]*?)(?=^\[|(?![\s\S]))/gm;
|
|
7687
8028
|
for (const sectionMatch of contents.matchAll(depSectionPattern)) {
|
|
7688
8029
|
const section = sectionMatch[1] ?? "";
|
|
@@ -7704,7 +8045,7 @@ function readCargoTomlDependencySpecs(path) {
|
|
|
7704
8045
|
}
|
|
7705
8046
|
function readPyprojectDependencySpecs(path) {
|
|
7706
8047
|
const specs = [];
|
|
7707
|
-
const contents =
|
|
8048
|
+
const contents = readRepoText(path, "utf8");
|
|
7708
8049
|
const poetrySectionPattern = /^\[tool\.poetry\.dependencies\]([\s\S]*?)(?=^\[|(?![\s\S]))/gm;
|
|
7709
8050
|
for (const sectionMatch of contents.matchAll(poetrySectionPattern)) {
|
|
7710
8051
|
const section = sectionMatch[1] ?? "";
|
|
@@ -7738,7 +8079,7 @@ function readPyprojectDependencySpecs(path) {
|
|
|
7738
8079
|
}
|
|
7739
8080
|
function readPipfileDependencySpecs(path) {
|
|
7740
8081
|
const specs = [];
|
|
7741
|
-
const contents =
|
|
8082
|
+
const contents = readRepoText(path, "utf8");
|
|
7742
8083
|
const sectionPattern = /^\[(?:packages|dev-packages)\]([\s\S]*?)(?=^\[|$(?![\s\S]))/gm;
|
|
7743
8084
|
for (const sectionMatch of contents.matchAll(sectionPattern)) {
|
|
7744
8085
|
for (const line of (sectionMatch[1] ?? "").split("\n")) {
|
|
@@ -7751,7 +8092,7 @@ function readPipfileDependencySpecs(path) {
|
|
|
7751
8092
|
return specs;
|
|
7752
8093
|
}
|
|
7753
8094
|
function readVcpkgJsonDependencySpecs(path) {
|
|
7754
|
-
const parsed = parseJsonObject(
|
|
8095
|
+
const parsed = parseJsonObject(readRepoText(path, "utf8"));
|
|
7755
8096
|
if (!parsed) return [];
|
|
7756
8097
|
const rawDeps = Array.isArray(parsed.dependencies) ? parsed.dependencies : [];
|
|
7757
8098
|
const specs = [];
|
|
@@ -7767,7 +8108,7 @@ function readVcpkgJsonDependencySpecs(path) {
|
|
|
7767
8108
|
}
|
|
7768
8109
|
function readConanfileTxtDependencySpecs(path) {
|
|
7769
8110
|
const specs = [];
|
|
7770
|
-
const contents =
|
|
8111
|
+
const contents = readRepoText(path, "utf8");
|
|
7771
8112
|
const requiresMatch = /^\[requires\]([\s\S]*?)(?=^\[|$(?![\s\S]))/m.exec(contents);
|
|
7772
8113
|
if (!requiresMatch) return specs;
|
|
7773
8114
|
for (const line of (requiresMatch[1] ?? "").split("\n")) {
|
|
@@ -7780,7 +8121,7 @@ function readConanfileTxtDependencySpecs(path) {
|
|
|
7780
8121
|
}
|
|
7781
8122
|
function readConanfilePyDependencySpecs(path) {
|
|
7782
8123
|
const specs = [];
|
|
7783
|
-
const contents =
|
|
8124
|
+
const contents = readRepoText(path, "utf8");
|
|
7784
8125
|
for (const match of contents.matchAll(/self\.requires\s*\(\s*["']([\w.\-]+)\/([\w.\-]+)["']/g)) {
|
|
7785
8126
|
specs.push({ name: match[1] ?? "", version: match[2] ?? "" });
|
|
7786
8127
|
}
|
|
@@ -7794,7 +8135,7 @@ function readConanfilePyDependencySpecs(path) {
|
|
|
7794
8135
|
}
|
|
7795
8136
|
function readCMakeDependencySpecs(path) {
|
|
7796
8137
|
const specs = [];
|
|
7797
|
-
const contents =
|
|
8138
|
+
const contents = readRepoText(path, "utf8");
|
|
7798
8139
|
for (const match of contents.matchAll(/find_package\s*\(\s*([\w]+)(?:\s+([\d.]+))?/gi)) {
|
|
7799
8140
|
const name = match[1];
|
|
7800
8141
|
if (!name) continue;
|
|
@@ -7809,7 +8150,7 @@ function readCMakeDependencySpecs(path) {
|
|
|
7809
8150
|
return specs;
|
|
7810
8151
|
}
|
|
7811
8152
|
function readPackageJsonDependencySpecs(packageJsonPath) {
|
|
7812
|
-
const parsed = parseJsonObject(
|
|
8153
|
+
const parsed = parseJsonObject(readRepoText(packageJsonPath, "utf8"));
|
|
7813
8154
|
if (!parsed) return [];
|
|
7814
8155
|
const optionalPeerNames = /* @__PURE__ */ new Set();
|
|
7815
8156
|
const peerDependenciesMeta = parsed.peerDependenciesMeta;
|
|
@@ -7836,7 +8177,7 @@ function readPackageJsonDependencySpecs(packageJsonPath) {
|
|
|
7836
8177
|
return specs;
|
|
7837
8178
|
}
|
|
7838
8179
|
function readRequirementsDependencySpecs(path) {
|
|
7839
|
-
return
|
|
8180
|
+
return readRepoText(path, "utf8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((line) => {
|
|
7840
8181
|
const [name = line, version = ""] = line.split(/[<>=~!]=?/, 2);
|
|
7841
8182
|
return { name: name.trim(), version: line.includes("==") ? version.trim() : line };
|
|
7842
8183
|
});
|
|
@@ -7854,7 +8195,7 @@ function isPinnedDependencyVersion(version) {
|
|
|
7854
8195
|
function workflowTargetsDefaultBranch(repoPath, file) {
|
|
7855
8196
|
const fullPath = join(repoPath, file);
|
|
7856
8197
|
if (!isRegularFile(fullPath)) return false;
|
|
7857
|
-
const contents =
|
|
8198
|
+
const contents = readRepoText(fullPath, "utf8").toLowerCase();
|
|
7858
8199
|
return /\bpull_request\b/.test(contents) || /\bbranches:\s*\[[^\]]*\b(main|master)\b[^\]]*\]/.test(contents) || /\bbranches:\s*\n\s*-\s*(main|master)\b/.test(contents);
|
|
7859
8200
|
}
|
|
7860
8201
|
function isPrMergeCommit(commit) {
|
|
@@ -8008,7 +8349,7 @@ function deriveRlsPolicyScopeInventory(repoPath, repoFiles) {
|
|
|
8008
8349
|
for (const file of candidatePolicyFiles) {
|
|
8009
8350
|
let content;
|
|
8010
8351
|
try {
|
|
8011
|
-
content =
|
|
8352
|
+
content = readRepoText(join(repoPath, file), "utf8");
|
|
8012
8353
|
} catch {
|
|
8013
8354
|
continue;
|
|
8014
8355
|
}
|
|
@@ -8112,7 +8453,7 @@ function findClaimRealityReconciliationArtifacts(repoPath, repoFiles) {
|
|
|
8112
8453
|
function isAuthenticatedClaimRealityArtifact(repoPath, file) {
|
|
8113
8454
|
const fullPath = join(repoPath, file);
|
|
8114
8455
|
if (!isRegularFile(fullPath)) return false;
|
|
8115
|
-
const contents =
|
|
8456
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
8116
8457
|
if (/\.json$/i.test(file)) {
|
|
8117
8458
|
return parseJsonObject(contents)?.artifactKind === "claim_reality_reconciliation";
|
|
8118
8459
|
}
|
|
@@ -8151,7 +8492,7 @@ function isCoverageConfig(repoPath, file) {
|
|
|
8151
8492
|
function packageJsonHasCoverageTooling(repoPath, file) {
|
|
8152
8493
|
const fullPath = join(repoPath, file);
|
|
8153
8494
|
if (!isRegularFile(fullPath)) return false;
|
|
8154
|
-
const parsed = parseJsonObject(
|
|
8495
|
+
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
8155
8496
|
if (!parsed) return false;
|
|
8156
8497
|
const scripts = parsed.scripts;
|
|
8157
8498
|
if (typeof scripts === "object" && scripts !== null && !Array.isArray(scripts)) {
|
|
@@ -8625,7 +8966,7 @@ function findRuntimeContainerEntrypointFile(repoPath, repoFiles) {
|
|
|
8625
8966
|
return repoFiles.filter((file) => /(^|\/)Dockerfile$/i.test(file) && isAuthoredProductionPath(file)).find((file) => {
|
|
8626
8967
|
const fullPath = join(repoPath, file);
|
|
8627
8968
|
if (!isRegularFile(fullPath)) return false;
|
|
8628
|
-
const lines =
|
|
8969
|
+
const lines = readRepoText(fullPath, "utf8").split(/\r?\n/);
|
|
8629
8970
|
const finalStageStart = lines.reduce(
|
|
8630
8971
|
(last, line, index) => /^\s*FROM\b/i.test(line) ? index : last,
|
|
8631
8972
|
0
|
|
@@ -8674,7 +9015,7 @@ function packageJsonHasAuditScript(repoPath, manifest) {
|
|
|
8674
9015
|
function packageJsonHasHeavyTestDependency(repoPath, manifest) {
|
|
8675
9016
|
const fullPath = join(repoPath, manifest);
|
|
8676
9017
|
if (!isRegularFile(fullPath)) return false;
|
|
8677
|
-
const parsed = parseJsonObject(
|
|
9018
|
+
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
8678
9019
|
if (!parsed) return false;
|
|
8679
9020
|
const names = [
|
|
8680
9021
|
...Object.keys(isRecord(parsed.dependencies) ? parsed.dependencies : {}),
|
|
@@ -8685,7 +9026,7 @@ function packageJsonHasHeavyTestDependency(repoPath, manifest) {
|
|
|
8685
9026
|
function packageJsonDescribesPackagedApplication(repoPath, manifest, repoFiles) {
|
|
8686
9027
|
const fullPath = join(repoPath, manifest);
|
|
8687
9028
|
if (!isRegularFile(fullPath)) return false;
|
|
8688
|
-
const parsed = parseJsonObject(
|
|
9029
|
+
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
8689
9030
|
if (!parsed) return false;
|
|
8690
9031
|
const scripts = isRecord(parsed.scripts) ? Object.values(parsed.scripts) : [];
|
|
8691
9032
|
const dependencies = {
|
|
@@ -8710,7 +9051,7 @@ function findTauriApplicationMetadata(repoPath, manifest, repoFiles) {
|
|
|
8710
9051
|
if (!repoFiles.includes(file)) return false;
|
|
8711
9052
|
const fullPath = join(repoPath, file);
|
|
8712
9053
|
if (!isRegularFile(fullPath)) return false;
|
|
8713
|
-
const config = parseJsonObject(
|
|
9054
|
+
const config = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
8714
9055
|
const packageMetadata = isRecord(config?.package) ? config.package : null;
|
|
8715
9056
|
return isNonEmptyString(config?.productName) || isNonEmptyString(config?.identifier) || packageMetadata != null && isNonEmptyString(packageMetadata.productName);
|
|
8716
9057
|
});
|
|
@@ -8757,12 +9098,12 @@ function isRegularFile(path) {
|
|
|
8757
9098
|
function fileContains(repoPath, file, pattern) {
|
|
8758
9099
|
const fullPath = join(repoPath, file);
|
|
8759
9100
|
if (!isRegularFile(fullPath)) return false;
|
|
8760
|
-
return pattern.test(
|
|
9101
|
+
return pattern.test(readRepoText(fullPath, "utf8"));
|
|
8761
9102
|
}
|
|
8762
9103
|
function findCommittedSecretInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
8763
9104
|
const fullPath = join(repoPath, file);
|
|
8764
9105
|
if (!isRegularFile(fullPath)) return null;
|
|
8765
|
-
const contents =
|
|
9106
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
8766
9107
|
const isTestCredentialConfiguration = useV39Detectors && isLikelyTestCredentialConfiguration(file, contents);
|
|
8767
9108
|
const secretScanContents = prepareCredentialScanContents(
|
|
8768
9109
|
contents,
|
|
@@ -8855,7 +9196,7 @@ function isLikelyTestCredentialConfiguration(file, contents) {
|
|
|
8855
9196
|
function hasTemplateOnlyEnvContent(repoPath, file) {
|
|
8856
9197
|
const fullPath = join(repoPath, file);
|
|
8857
9198
|
if (!isRegularFile(fullPath)) return false;
|
|
8858
|
-
const substantiveLines =
|
|
9199
|
+
const substantiveLines = readRepoText(fullPath, "utf8").split(/\r?\n/).filter((line) => line.trim().length > 0 && !/^\s*#/.test(line));
|
|
8859
9200
|
if (substantiveLines.length === 0) return true;
|
|
8860
9201
|
const assignments = substantiveLines.map(
|
|
8861
9202
|
(line) => /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=\s*(.*?)\s*$/.exec(line)?.[1] ?? null
|
|
@@ -8870,7 +9211,7 @@ function hasTemplateOnlyEnvContent(repoPath, file) {
|
|
|
8870
9211
|
function findSecretFingerprintsInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
8871
9212
|
const fullPath = join(repoPath, file);
|
|
8872
9213
|
if (!isRegularFile(fullPath)) return [];
|
|
8873
|
-
const contents =
|
|
9214
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
8874
9215
|
const scanContents = prepareCredentialScanContents(
|
|
8875
9216
|
contents,
|
|
8876
9217
|
file,
|
|
@@ -9021,8 +9362,18 @@ function secretEvidenceLabel(prefix, match) {
|
|
|
9021
9362
|
function isSecretIdentifierCharacter(characterCode) {
|
|
9022
9363
|
return characterCode >= 48 && characterCode <= 57 || characterCode >= 65 && characterCode <= 90 || characterCode === 95 || characterCode >= 97 && characterCode <= 122;
|
|
9023
9364
|
}
|
|
9024
|
-
|
|
9025
|
-
|
|
9365
|
+
var CONTROL_CHARACTER_HISTORY_PATH_LIMITATION = "Git history contained a credential-shaped pathname with control characters. Cejel omitted that path from historical blob inspection and continued with reduced history evidence.";
|
|
9366
|
+
function declareHistoryFailure(scanLimitations, reason, operation, maxBufferBytes) {
|
|
9367
|
+
scanLimitations.add(
|
|
9368
|
+
`${describeGitFailure(reason, operation, maxBufferBytes)} Cejel continued with reduced Git history evidence.`
|
|
9369
|
+
);
|
|
9370
|
+
}
|
|
9371
|
+
function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFingerprintsByPath, scanLimitations, authoredProductionOnly = false, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
9372
|
+
const historyEntries = readCredentialHistoryEntries(
|
|
9373
|
+
repoPath,
|
|
9374
|
+
scanLimitations,
|
|
9375
|
+
useV36Detectors
|
|
9376
|
+
);
|
|
9026
9377
|
const head = excludeHead ? readGitHead(repoPath) : null;
|
|
9027
9378
|
let scannedFiles = 0;
|
|
9028
9379
|
let envPathEvidence = null;
|
|
@@ -9035,7 +9386,7 @@ function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFinger
|
|
|
9035
9386
|
return { evidence: null, envPathEvidence, truncated: true };
|
|
9036
9387
|
}
|
|
9037
9388
|
scannedFiles += 1;
|
|
9038
|
-
const contents = readGitBlob(repoPath, entry.commit, entry.path);
|
|
9389
|
+
const contents = readGitBlob(repoPath, entry.commit, entry.path, scanLimitations);
|
|
9039
9390
|
if (!contents) continue;
|
|
9040
9391
|
const scanContents = prepareCredentialScanContents(
|
|
9041
9392
|
contents,
|
|
@@ -9078,7 +9429,7 @@ function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFinger
|
|
|
9078
9429
|
}
|
|
9079
9430
|
return { evidence: null, envPathEvidence, truncated: false };
|
|
9080
9431
|
}
|
|
9081
|
-
function readCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
9432
|
+
function readCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors = false) {
|
|
9082
9433
|
const entries = [];
|
|
9083
9434
|
const seen = /* @__PURE__ */ new Set();
|
|
9084
9435
|
const appendEntry = (entry) => {
|
|
@@ -9087,14 +9438,12 @@ function readCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
|
9087
9438
|
seen.add(key);
|
|
9088
9439
|
entries.push(entry);
|
|
9089
9440
|
};
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
const output = execFileSync(
|
|
9097
|
-
"git",
|
|
9441
|
+
const commitsResult = execGit(["rev-list", "HEAD"], { cwd: repoPath });
|
|
9442
|
+
if (!commitsResult.ok && !isExpectedGitAbsence(commitsResult)) {
|
|
9443
|
+
declareHistoryFailure(scanLimitations, commitsResult.reason, "history revision enumeration");
|
|
9444
|
+
}
|
|
9445
|
+
if (commitsResult.ok) {
|
|
9446
|
+
const historyResult = execGit(
|
|
9098
9447
|
[
|
|
9099
9448
|
"diff-tree",
|
|
9100
9449
|
"--stdin",
|
|
@@ -9102,39 +9451,53 @@ function readCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
|
9102
9451
|
"--name-only",
|
|
9103
9452
|
"-r",
|
|
9104
9453
|
"--diff-filter=AM",
|
|
9105
|
-
"
|
|
9454
|
+
"-z",
|
|
9455
|
+
"--pretty=format:commit:%H%x00"
|
|
9106
9456
|
],
|
|
9107
|
-
{
|
|
9108
|
-
cwd: repoPath,
|
|
9109
|
-
encoding: "utf8",
|
|
9110
|
-
input: commits,
|
|
9111
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
9112
|
-
}
|
|
9457
|
+
{ cwd: repoPath, input: commitsResult.stdout }
|
|
9113
9458
|
);
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9459
|
+
if (!historyResult.ok) {
|
|
9460
|
+
if (!isExpectedGitAbsence(historyResult)) {
|
|
9461
|
+
declareHistoryFailure(scanLimitations, historyResult.reason, "history tree enumeration");
|
|
9462
|
+
}
|
|
9463
|
+
} else {
|
|
9464
|
+
let currentCommit = null;
|
|
9465
|
+
for (const rawToken of historyResult.stdout.split("\0")) {
|
|
9466
|
+
const token = rawToken.startsWith("\n") ? rawToken.slice(1) : rawToken;
|
|
9467
|
+
if (token.startsWith("commit:")) {
|
|
9468
|
+
currentCommit = token.slice("commit:".length);
|
|
9469
|
+
continue;
|
|
9470
|
+
}
|
|
9471
|
+
const path = token;
|
|
9472
|
+
if (!currentCommit || !path) continue;
|
|
9473
|
+
if (/[\u0000-\u001f\u007f]/.test(path)) {
|
|
9474
|
+
scanLimitations.add(CONTROL_CHARACTER_HISTORY_PATH_LIMITATION);
|
|
9475
|
+
continue;
|
|
9476
|
+
}
|
|
9477
|
+
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
9478
|
+
appendEntry({ commit: currentCommit, path });
|
|
9119
9479
|
}
|
|
9120
|
-
const path = line.trim();
|
|
9121
|
-
if (!currentCommit || !path) continue;
|
|
9122
|
-
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
9123
|
-
appendEntry({ commit: currentCommit, path });
|
|
9124
9480
|
}
|
|
9125
|
-
} catch {
|
|
9126
9481
|
}
|
|
9127
|
-
for (const entry of readDeletedCredentialHistoryEntries(
|
|
9482
|
+
for (const entry of readDeletedCredentialHistoryEntries(
|
|
9483
|
+
repoPath,
|
|
9484
|
+
scanLimitations,
|
|
9485
|
+
useV36Detectors
|
|
9486
|
+
)) {
|
|
9128
9487
|
appendEntry(entry);
|
|
9129
9488
|
}
|
|
9130
9489
|
return entries;
|
|
9131
9490
|
}
|
|
9132
|
-
function readDeletedCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
9133
|
-
const deletedPaths = readDeletedCredentialHistoryPaths(
|
|
9491
|
+
function readDeletedCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors = false) {
|
|
9492
|
+
const deletedPaths = readDeletedCredentialHistoryPaths(
|
|
9493
|
+
repoPath,
|
|
9494
|
+
scanLimitations,
|
|
9495
|
+
useV36Detectors
|
|
9496
|
+
);
|
|
9134
9497
|
const entries = [];
|
|
9135
9498
|
const seen = /* @__PURE__ */ new Set();
|
|
9136
9499
|
for (const path of deletedPaths) {
|
|
9137
|
-
for (const commit of readHistoryCommitsForPath(repoPath, path)) {
|
|
9500
|
+
for (const commit of readHistoryCommitsForPath(repoPath, path, scanLimitations)) {
|
|
9138
9501
|
const key = `${commit}:${path}`;
|
|
9139
9502
|
if (seen.has(key)) continue;
|
|
9140
9503
|
seen.add(key);
|
|
@@ -9143,47 +9506,51 @@ function readDeletedCredentialHistoryEntries(repoPath, useV36Detectors = false)
|
|
|
9143
9506
|
}
|
|
9144
9507
|
return entries;
|
|
9145
9508
|
}
|
|
9146
|
-
function readDeletedCredentialHistoryPaths(repoPath, useV36Detectors = false) {
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
"
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
if (!path || seen.has(path)) continue;
|
|
9163
|
-
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
9164
|
-
seen.add(path);
|
|
9165
|
-
paths.push(path);
|
|
9166
|
-
}
|
|
9167
|
-
return paths;
|
|
9168
|
-
} catch {
|
|
9509
|
+
function readDeletedCredentialHistoryPaths(repoPath, scanLimitations, useV36Detectors = false) {
|
|
9510
|
+
const historyResult = execGit(
|
|
9511
|
+
[
|
|
9512
|
+
"log",
|
|
9513
|
+
"--no-show-signature",
|
|
9514
|
+
"HEAD",
|
|
9515
|
+
"--diff-filter=D",
|
|
9516
|
+
"--name-only",
|
|
9517
|
+
"-z",
|
|
9518
|
+
"--format=commit:%H%x00"
|
|
9519
|
+
],
|
|
9520
|
+
{ cwd: repoPath }
|
|
9521
|
+
);
|
|
9522
|
+
if (!historyResult.ok) {
|
|
9523
|
+
if (isExpectedGitAbsence(historyResult)) return [];
|
|
9524
|
+
declareHistoryFailure(scanLimitations, historyResult.reason, "deleted-path history scan");
|
|
9169
9525
|
return [];
|
|
9170
9526
|
}
|
|
9527
|
+
const paths = [];
|
|
9528
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9529
|
+
for (const rawToken of historyResult.stdout.split("\0")) {
|
|
9530
|
+
const path = rawToken.startsWith("\n") ? rawToken.slice(1) : rawToken;
|
|
9531
|
+
if (path.startsWith("commit:") || !path) continue;
|
|
9532
|
+
if (/[\u0000-\u001f\u007f]/.test(path)) {
|
|
9533
|
+
scanLimitations.add(CONTROL_CHARACTER_HISTORY_PATH_LIMITATION);
|
|
9534
|
+
continue;
|
|
9535
|
+
}
|
|
9536
|
+
if (!path || seen.has(path)) continue;
|
|
9537
|
+
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
9538
|
+
seen.add(path);
|
|
9539
|
+
paths.push(path);
|
|
9540
|
+
}
|
|
9541
|
+
return paths;
|
|
9171
9542
|
}
|
|
9172
|
-
function readHistoryCommitsForPath(repoPath, path) {
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
9181
|
-
}
|
|
9182
|
-
);
|
|
9183
|
-
return output.split("\n").map((line) => line.trim()).filter((line) => /^[0-9a-f]{40}$/i.test(line));
|
|
9184
|
-
} catch {
|
|
9543
|
+
function readHistoryCommitsForPath(repoPath, path, scanLimitations) {
|
|
9544
|
+
const historyResult = execGit(
|
|
9545
|
+
["log", "--no-show-signature", "HEAD", "--diff-filter=AM", "--format=%H", "--", path],
|
|
9546
|
+
{ cwd: repoPath }
|
|
9547
|
+
);
|
|
9548
|
+
if (!historyResult.ok) {
|
|
9549
|
+
if (isExpectedGitAbsence(historyResult)) return [];
|
|
9550
|
+
declareHistoryFailure(scanLimitations, historyResult.reason, "path history scan");
|
|
9185
9551
|
return [];
|
|
9186
9552
|
}
|
|
9553
|
+
return historyResult.stdout.split("\n").map((line) => line.trim()).filter((line) => /^[0-9a-f]{40}$/i.test(line));
|
|
9187
9554
|
}
|
|
9188
9555
|
function isCredentialHistoryPath(path, _useV36Detectors = false) {
|
|
9189
9556
|
if (isHardExcludedPath(path)) return false;
|
|
@@ -9200,17 +9567,23 @@ function isEnvHistoryPath(path, useV36Detectors = false, useV47Detectors = false
|
|
|
9200
9567
|
const basename3 = path.toLowerCase().split("/").at(-1) ?? path.toLowerCase();
|
|
9201
9568
|
return (useV36Detectors ? /^\.env(?:\.|$)/ : /\.env(?:\.|$)/).test(basename3);
|
|
9202
9569
|
}
|
|
9203
|
-
function readGitBlob(repoPath, commit, path) {
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9570
|
+
function readGitBlob(repoPath, commit, path, scanLimitations) {
|
|
9571
|
+
const maxBufferBytes = 512e3;
|
|
9572
|
+
const blobResult = execGit(["show", `${commit}:${path}`], {
|
|
9573
|
+
cwd: repoPath,
|
|
9574
|
+
maxBufferBytes
|
|
9575
|
+
});
|
|
9576
|
+
if (!blobResult.ok) {
|
|
9577
|
+
if (isExpectedGitAbsence(blobResult)) return null;
|
|
9578
|
+
declareHistoryFailure(
|
|
9579
|
+
scanLimitations,
|
|
9580
|
+
blobResult.reason,
|
|
9581
|
+
"historical blob read",
|
|
9582
|
+
maxBufferBytes
|
|
9583
|
+
);
|
|
9212
9584
|
return null;
|
|
9213
9585
|
}
|
|
9586
|
+
return blobResult.stdout;
|
|
9214
9587
|
}
|
|
9215
9588
|
var ALL_CAPS_SNAKE_PLACEHOLDER_PATTERN = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/;
|
|
9216
9589
|
function isPlaceholderSecretValue(value) {
|
|
@@ -9240,7 +9613,7 @@ function hasSuspiciousDependencies(repoPath, manifest, useV47Detectors = false)
|
|
|
9240
9613
|
if (!manifest.endsWith("package.json")) return false;
|
|
9241
9614
|
const fullPath = join(repoPath, manifest);
|
|
9242
9615
|
if (!isRegularFile(fullPath)) return false;
|
|
9243
|
-
const parsed = parseJsonObject(
|
|
9616
|
+
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
9244
9617
|
if (!parsed) return false;
|
|
9245
9618
|
const dependencies = isRecord(parsed.dependencies) ? parsed.dependencies : {};
|
|
9246
9619
|
const devDependencies = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
|
|
@@ -9256,24 +9629,26 @@ function hasSuspiciousDependencies(repoPath, manifest, useV47Detectors = false)
|
|
|
9256
9629
|
return placeholderIdentity && placeholderMetadata;
|
|
9257
9630
|
}
|
|
9258
9631
|
function readRecentCommits(repoPath) {
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
return { sha, subject };
|
|
9269
|
-
}).filter((commit) => commit.sha.length >= 7);
|
|
9270
|
-
} catch {
|
|
9271
|
-
return [];
|
|
9632
|
+
const historyResult = execGit(
|
|
9633
|
+
["log", "--no-show-signature", "--max-count=12", "--format=%H%x00%s"],
|
|
9634
|
+
{
|
|
9635
|
+
cwd: repoPath
|
|
9636
|
+
}
|
|
9637
|
+
);
|
|
9638
|
+
if (!historyResult.ok) {
|
|
9639
|
+
if (isExpectedGitAbsence(historyResult)) return [];
|
|
9640
|
+
throw new Error(describeGitFailure(historyResult.reason, "recent history scan"));
|
|
9272
9641
|
}
|
|
9642
|
+
const output = historyResult.stdout.trim();
|
|
9643
|
+
if (!output) return [];
|
|
9644
|
+
return output.split("\n").map((line) => {
|
|
9645
|
+
const [sha = "", subject = ""] = line.split("\0");
|
|
9646
|
+
return { sha, subject };
|
|
9647
|
+
}).filter((commit) => commit.sha.length >= 7);
|
|
9273
9648
|
}
|
|
9274
9649
|
function readPackageScripts(packageJsonPath) {
|
|
9275
9650
|
if (!isRegularFile(packageJsonPath)) return /* @__PURE__ */ new Map();
|
|
9276
|
-
const parsed = parseJsonObject(
|
|
9651
|
+
const parsed = parseJsonObject(readRepoText(packageJsonPath, "utf8"));
|
|
9277
9652
|
const scripts = parsed?.scripts;
|
|
9278
9653
|
if (!isRecord(scripts)) return /* @__PURE__ */ new Map();
|
|
9279
9654
|
return new Map(
|
|
@@ -9284,7 +9659,7 @@ function readPackageScripts(packageJsonPath) {
|
|
|
9284
9659
|
}
|
|
9285
9660
|
function evidenceForRelative(repoPath, path, kind, label) {
|
|
9286
9661
|
const fullPath = join(repoPath, path);
|
|
9287
|
-
const contents = isRegularFile(fullPath) ?
|
|
9662
|
+
const contents = isRegularFile(fullPath) ? readRepoText(fullPath, "utf8") : "";
|
|
9288
9663
|
return {
|
|
9289
9664
|
kind,
|
|
9290
9665
|
label,
|
|
@@ -9297,7 +9672,7 @@ function evidenceForRelative(repoPath, path, kind, label) {
|
|
|
9297
9672
|
}
|
|
9298
9673
|
function evidenceForRelativeAtLine(repoPath, path, kind, label, line) {
|
|
9299
9674
|
const fullPath = join(repoPath, path);
|
|
9300
|
-
const contents = isRegularFile(fullPath) ?
|
|
9675
|
+
const contents = isRegularFile(fullPath) ? readRepoText(fullPath, "utf8") : "";
|
|
9301
9676
|
return {
|
|
9302
9677
|
kind,
|
|
9303
9678
|
label,
|
|
@@ -9324,43 +9699,46 @@ function firstMeaningfulLine(contents) {
|
|
|
9324
9699
|
return index === -1 ? 1 : index + 1;
|
|
9325
9700
|
}
|
|
9326
9701
|
function readGitHead(repoPath) {
|
|
9327
|
-
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
9332
|
-
}).trim();
|
|
9333
|
-
} catch {
|
|
9334
|
-
return null;
|
|
9702
|
+
const headResult = execGit(["rev-parse", "HEAD"], { cwd: repoPath });
|
|
9703
|
+
if (!headResult.ok) {
|
|
9704
|
+
if (isExpectedGitAbsence(headResult)) return null;
|
|
9705
|
+
throw new Error(describeGitFailure(headResult.reason, "HEAD resolution"));
|
|
9335
9706
|
}
|
|
9707
|
+
return headResult.stdout.trim() || null;
|
|
9336
9708
|
}
|
|
9337
|
-
function hasEnvPathInGitHistory(repoPath) {
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
9346
|
-
maxBuffer: 512e3
|
|
9347
|
-
}
|
|
9348
|
-
).trim();
|
|
9349
|
-
if (!output) return false;
|
|
9350
|
-
return output.split("\n").some((p) => /(?:^|\/)\.env(?:\.|$)/i.test(p.trim()));
|
|
9351
|
-
} catch {
|
|
9709
|
+
function hasEnvPathInGitHistory(repoPath, scanLimitations) {
|
|
9710
|
+
const historyResult = execGit(
|
|
9711
|
+
["log", "--no-show-signature", "HEAD", "--diff-filter=AM", "--name-only", "-z", "--format="],
|
|
9712
|
+
{ cwd: repoPath }
|
|
9713
|
+
);
|
|
9714
|
+
if (!historyResult.ok) {
|
|
9715
|
+
if (isExpectedGitAbsence(historyResult)) return false;
|
|
9716
|
+
declareHistoryFailure(scanLimitations, historyResult.reason, "environment-file history scan");
|
|
9352
9717
|
return false;
|
|
9353
9718
|
}
|
|
9719
|
+
const output = historyResult.stdout;
|
|
9720
|
+
if (!output) return false;
|
|
9721
|
+
return output.split("\0").some((rawPath) => {
|
|
9722
|
+
const path = rawPath.startsWith("\n") ? rawPath.slice(1) : rawPath;
|
|
9723
|
+
if (/[\u0000-\u001f\u007f]/.test(path)) {
|
|
9724
|
+
scanLimitations.add(CONTROL_CHARACTER_HISTORY_PATH_LIMITATION);
|
|
9725
|
+
return false;
|
|
9726
|
+
}
|
|
9727
|
+
return /(?:^|\/)\.env(?:\.|$)/i.test(path);
|
|
9728
|
+
});
|
|
9354
9729
|
}
|
|
9355
9730
|
|
|
9356
9731
|
// src/witan/public-scan.ts
|
|
9732
|
+
init_esm_shims();
|
|
9357
9733
|
import { resolve as resolve2 } from "path";
|
|
9358
9734
|
|
|
9359
9735
|
// src/witan/ingest.ts
|
|
9360
|
-
|
|
9736
|
+
init_esm_shims();
|
|
9737
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
|
|
9361
9738
|
import { join as join2 } from "path";
|
|
9362
9739
|
|
|
9363
9740
|
// src/witan/finding-limits.ts
|
|
9741
|
+
init_esm_shims();
|
|
9364
9742
|
var RULE_ID_MAX = 200;
|
|
9365
9743
|
var MESSAGE_MAX = 500;
|
|
9366
9744
|
var LOCATION_MAX = 700;
|
|
@@ -9379,7 +9757,8 @@ function clampFinding(finding) {
|
|
|
9379
9757
|
}
|
|
9380
9758
|
|
|
9381
9759
|
// src/witan/generic-adapter.ts
|
|
9382
|
-
|
|
9760
|
+
init_esm_shims();
|
|
9761
|
+
import { readFileSync } from "fs";
|
|
9383
9762
|
var VALID_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
9384
9763
|
"A1",
|
|
9385
9764
|
"A2",
|
|
@@ -9426,7 +9805,8 @@ function parseGenericJson(raw) {
|
|
|
9426
9805
|
}
|
|
9427
9806
|
|
|
9428
9807
|
// src/witan/sarif-adapter.ts
|
|
9429
|
-
|
|
9808
|
+
init_esm_shims();
|
|
9809
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
9430
9810
|
var DIMENSION_RULES = [
|
|
9431
9811
|
// A2 — injection / secrets / auth (high weight)
|
|
9432
9812
|
{
|
|
@@ -9571,7 +9951,8 @@ function parseSarifJson(raw, extraDimensionRules) {
|
|
|
9571
9951
|
}
|
|
9572
9952
|
|
|
9573
9953
|
// src/witan/scorecard-adapter.ts
|
|
9574
|
-
|
|
9954
|
+
init_esm_shims();
|
|
9955
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
9575
9956
|
var CHECK_RULES = {
|
|
9576
9957
|
// B2 — PR outcome traceability
|
|
9577
9958
|
"Branch-Protection": { dimension: "B2", weight: 0.8 },
|
|
@@ -9680,10 +10061,18 @@ function rawToolName(raw) {
|
|
|
9680
10061
|
if (typeof obj.tool === "string" && obj.tool.length > 0) return obj.tool;
|
|
9681
10062
|
return "scorecard";
|
|
9682
10063
|
}
|
|
10064
|
+
var MAX_INGEST_SOURCE_LENGTH = 120;
|
|
10065
|
+
function sanitizeIngestSource(source) {
|
|
10066
|
+
const withoutControls = source.replace(/[\p{Cc}\p{Cf}]/gu, "").trim();
|
|
10067
|
+
const safeSource = withoutControls.length > 0 ? withoutControls : "unknown";
|
|
10068
|
+
const codePoints = Array.from(safeSource);
|
|
10069
|
+
if (codePoints.length <= MAX_INGEST_SOURCE_LENGTH) return safeSource;
|
|
10070
|
+
return `${codePoints.slice(0, MAX_INGEST_SOURCE_LENGTH - 3).join("")}...`;
|
|
10071
|
+
}
|
|
9683
10072
|
function parseIngestFile(filePath, options = {}) {
|
|
9684
10073
|
let raw;
|
|
9685
10074
|
try {
|
|
9686
|
-
raw = JSON.parse(stripBom(
|
|
10075
|
+
raw = JSON.parse(stripBom(readFileSync4(filePath, "utf8")));
|
|
9687
10076
|
} catch (error) {
|
|
9688
10077
|
const message = error instanceof Error ? error.message : String(error);
|
|
9689
10078
|
throw new Error(`Cejel: could not parse ingest file as JSON: ${filePath} (${message})`);
|
|
@@ -9707,6 +10096,8 @@ function parseIngestFile(filePath, options = {}) {
|
|
|
9707
10096
|
}
|
|
9708
10097
|
return signals.map((signal) => ({
|
|
9709
10098
|
...signal,
|
|
10099
|
+
source: sanitizeIngestSource(signal.source),
|
|
10100
|
+
provenance: options.provenance ?? "operator_supplied",
|
|
9710
10101
|
findings: signal.findings.map(clampFinding)
|
|
9711
10102
|
}));
|
|
9712
10103
|
}
|
|
@@ -9715,7 +10106,7 @@ function expandIngestPattern(pattern) {
|
|
|
9715
10106
|
const lastSlash = pattern.lastIndexOf("/");
|
|
9716
10107
|
const dir = lastSlash === -1 ? "." : pattern.slice(0, lastSlash);
|
|
9717
10108
|
const filePattern = lastSlash === -1 ? pattern : pattern.slice(lastSlash + 1);
|
|
9718
|
-
const regexSource = filePattern.replace(/[
|
|
10109
|
+
const regexSource = filePattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
9719
10110
|
const regex = new RegExp(`^${regexSource}$`);
|
|
9720
10111
|
let entries;
|
|
9721
10112
|
try {
|
|
@@ -9736,7 +10127,11 @@ function discoverIngestInputs(repoPath) {
|
|
|
9736
10127
|
return entries.filter((name) => /\.(sarif|json)$/i.test(name)).sort().map((name) => join2(inputsDir, name));
|
|
9737
10128
|
}
|
|
9738
10129
|
|
|
10130
|
+
// src/witan/scoring.ts
|
|
10131
|
+
init_esm_shims();
|
|
10132
|
+
|
|
9739
10133
|
// src/witan/rubric.ts
|
|
10134
|
+
init_esm_shims();
|
|
9740
10135
|
var WITAN_RUBRIC = [
|
|
9741
10136
|
{
|
|
9742
10137
|
id: "A1",
|
|
@@ -9803,6 +10198,9 @@ var MAX_SIGNAL_ADJUSTMENT = 0.8;
|
|
|
9803
10198
|
function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
9804
10199
|
const parsedInput = WitanReportInputSchema.parse(input);
|
|
9805
10200
|
const signalsByCriterion = mergeSignalsByCriterion(parsedInput.signals);
|
|
10201
|
+
const readFailureAbstentions = new Set(
|
|
10202
|
+
[...signalsByCriterion.values()].filter((signal) => signal.insufficientData === true).map((signal) => signal.criterionId)
|
|
10203
|
+
);
|
|
9806
10204
|
const hasInputSignals = inputSignals != null && inputSignals.length > 0;
|
|
9807
10205
|
const inputSignalsByDimension = /* @__PURE__ */ new Map();
|
|
9808
10206
|
if (hasInputSignals && inputSignals) {
|
|
@@ -9866,7 +10264,8 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
9866
10264
|
const categoryScoreMap = {};
|
|
9867
10265
|
for (const category of categoryOrder) {
|
|
9868
10266
|
categoryScoreMap[category] = averageScore(
|
|
9869
|
-
criteria.filter((criterion) => criterion.category === category)
|
|
10267
|
+
criteria.filter((criterion) => criterion.category === category),
|
|
10268
|
+
readFailureAbstentions
|
|
9870
10269
|
);
|
|
9871
10270
|
}
|
|
9872
10271
|
const firstCategory = categoryOrder[0];
|
|
@@ -9889,7 +10288,6 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
9889
10288
|
productSlug: parsedInput.productSlug,
|
|
9890
10289
|
productDisplayName: parsedInput.productDisplayName,
|
|
9891
10290
|
repo: parsedInput.repo,
|
|
9892
|
-
generatedAt: parsedInput.generatedAt,
|
|
9893
10291
|
rubricVersion: parsedInput.rubricVersion,
|
|
9894
10292
|
verdict: abstained ? "insufficient_source" : witanVerdictForScore(overallScore),
|
|
9895
10293
|
codeTrustScore: abstained ? null : codeTrustScore,
|
|
@@ -9897,6 +10295,8 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
9897
10295
|
overallScore: abstained ? null : overallScore,
|
|
9898
10296
|
criteria,
|
|
9899
10297
|
...consumedSignals.length > 0 ? { consumedSignals } : {},
|
|
10298
|
+
...parsedInput.scanLimitations.length > 0 ? { scanLimitations: parsedInput.scanLimitations } : {},
|
|
10299
|
+
...parsedInput.contentReadSummary ? { contentReadSummary: parsedInput.contentReadSummary } : {},
|
|
9900
10300
|
// Only surface the full per-category map for rubrics with more than two buckets —
|
|
9901
10301
|
// the default two-category rubric is fully represented by codeTrustScore/processTrustScore.
|
|
9902
10302
|
...!abstained && categoryOrder.length > 2 ? { categoryScores: categoryScoreMap } : {},
|
|
@@ -9922,6 +10322,7 @@ function applySignalAdjustment(nativeScore, dimension, signals) {
|
|
|
9922
10322
|
totalRawAdj += signalAdj;
|
|
9923
10323
|
summaries.push({
|
|
9924
10324
|
source: signal.source,
|
|
10325
|
+
provenance: signal.provenance ?? "operator_supplied",
|
|
9925
10326
|
dimension,
|
|
9926
10327
|
findingCount: signal.findings.length,
|
|
9927
10328
|
severityBreakdown: { critical: criticalCount, warning: warningCount, info: infoCount },
|
|
@@ -9987,8 +10388,9 @@ function mergeSignalsByCriterion(signals) {
|
|
|
9987
10388
|
findings: [...existing.findings, ...signal.findings],
|
|
9988
10389
|
metrics: [...existing.metrics ?? [], ...signal.metrics ?? []],
|
|
9989
10390
|
notes: signal.notes ?? existing.notes,
|
|
9990
|
-
//
|
|
9991
|
-
|
|
10391
|
+
// Read-failure abstention wins on merge: partial sibling evidence cannot turn an
|
|
10392
|
+
// incomplete criterion into a score. Otherwise N/A retains its historical precedence.
|
|
10393
|
+
...existing.insufficientData === true || signal.insufficientData === true ? { insufficientData: true } : existing.notApplicable === true || signal.notApplicable === true ? { notApplicable: true } : {}
|
|
9992
10394
|
});
|
|
9993
10395
|
}
|
|
9994
10396
|
return merged;
|
|
@@ -10009,6 +10411,15 @@ function scoreCriterion(signal, rubricVersion) {
|
|
|
10009
10411
|
metrics: []
|
|
10010
10412
|
};
|
|
10011
10413
|
}
|
|
10414
|
+
if (signal.insufficientData === true) {
|
|
10415
|
+
return {
|
|
10416
|
+
score: 0,
|
|
10417
|
+
status: unmeasuredStatus(rubricVersion),
|
|
10418
|
+
evidence: [],
|
|
10419
|
+
findings: [],
|
|
10420
|
+
metrics: []
|
|
10421
|
+
};
|
|
10422
|
+
}
|
|
10012
10423
|
if (signal.notApplicable === true) {
|
|
10013
10424
|
return {
|
|
10014
10425
|
score: 0,
|
|
@@ -10384,9 +10795,9 @@ function ensureFindingsExplainStatus(criterionId, score, status, findings, evide
|
|
|
10384
10795
|
}
|
|
10385
10796
|
];
|
|
10386
10797
|
}
|
|
10387
|
-
function averageScore(criteria) {
|
|
10798
|
+
function averageScore(criteria, readFailureAbstentions = /* @__PURE__ */ new Set()) {
|
|
10388
10799
|
const applicable = criteria.filter(
|
|
10389
|
-
(criterion) => criterion.status !== "not_applicable" && criterion.status !== "insufficient_data"
|
|
10800
|
+
(criterion) => criterion.status !== "not_applicable" && (criterion.status !== "insufficient_data" || readFailureAbstentions.has(criterion.id))
|
|
10390
10801
|
);
|
|
10391
10802
|
if (applicable.length === 0) return 0;
|
|
10392
10803
|
const total = applicable.reduce((sum, criterion) => sum + criterion.score, 0);
|
|
@@ -10421,7 +10832,7 @@ function scoreRepoWithPublicCejel(options) {
|
|
|
10421
10832
|
}
|
|
10422
10833
|
function resolvePublicIngestSignals(options) {
|
|
10423
10834
|
const seen = /* @__PURE__ */ new Set();
|
|
10424
|
-
const
|
|
10835
|
+
const explicitFiles = [];
|
|
10425
10836
|
for (const pattern of options.ingestPatterns ?? []) {
|
|
10426
10837
|
const matches = expandIngestPattern(pattern);
|
|
10427
10838
|
if (matches.length === 0 && options.warnOnEmptyIngestMatch) {
|
|
@@ -10432,20 +10843,28 @@ function resolvePublicIngestSignals(options) {
|
|
|
10432
10843
|
const resolved = resolve2(match);
|
|
10433
10844
|
if (seen.has(resolved)) continue;
|
|
10434
10845
|
seen.add(resolved);
|
|
10435
|
-
|
|
10846
|
+
explicitFiles.push(match);
|
|
10436
10847
|
}
|
|
10437
10848
|
}
|
|
10849
|
+
const signals = explicitFiles.flatMap(
|
|
10850
|
+
(file) => parseIngestFile(file, { provenance: "operator_supplied" })
|
|
10851
|
+
);
|
|
10852
|
+
if (!options.autoDiscoverIngest) return signals;
|
|
10438
10853
|
for (const discovered of discoverIngestInputs(options.repoPath)) {
|
|
10439
10854
|
const resolved = resolve2(discovered);
|
|
10440
10855
|
if (seen.has(resolved)) continue;
|
|
10441
10856
|
seen.add(resolved);
|
|
10442
|
-
|
|
10857
|
+
signals.push(...parseIngestFile(discovered, { provenance: "auto_discovered" }));
|
|
10443
10858
|
}
|
|
10444
|
-
return
|
|
10859
|
+
return signals;
|
|
10445
10860
|
}
|
|
10446
10861
|
|
|
10862
|
+
// src/scan.ts
|
|
10863
|
+
init_esm_shims();
|
|
10864
|
+
|
|
10447
10865
|
// src/product-identity.ts
|
|
10448
|
-
|
|
10866
|
+
init_esm_shims();
|
|
10867
|
+
import { existsSync, readFileSync as readFileSync5 } from "fs";
|
|
10449
10868
|
import { basename as basename2, join as join3 } from "path";
|
|
10450
10869
|
var SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/;
|
|
10451
10870
|
var FALLBACK_SLUG = "repo";
|
|
@@ -10462,7 +10881,7 @@ function deriveProductIdentity(repoPath) {
|
|
|
10462
10881
|
function readPackageName(packageJsonPath) {
|
|
10463
10882
|
if (!existsSync(packageJsonPath)) return void 0;
|
|
10464
10883
|
try {
|
|
10465
|
-
const parsed = JSON.parse(
|
|
10884
|
+
const parsed = JSON.parse(readFileSync5(packageJsonPath, "utf8"));
|
|
10466
10885
|
if (typeof parsed === "object" && parsed !== null && "name" in parsed) {
|
|
10467
10886
|
const name = parsed.name;
|
|
10468
10887
|
if (typeof name === "string" && name.trim().length > 0) return name;
|
|
@@ -10479,7 +10898,11 @@ function slugify(raw) {
|
|
|
10479
10898
|
return FALLBACK_SLUG;
|
|
10480
10899
|
}
|
|
10481
10900
|
|
|
10901
|
+
// src/summary.ts
|
|
10902
|
+
init_esm_shims();
|
|
10903
|
+
|
|
10482
10904
|
// src/finding-presentation.ts
|
|
10905
|
+
init_esm_shims();
|
|
10483
10906
|
var METRIC_DERIVED_FINDING_PATTERN2 = /^[A-Z]\d+ metric-derived score is (\d+\.\d)\/4\.0, in the (critical|warning) band — no single finding drove this; it reflects the combined metric weighting below\.$/;
|
|
10484
10907
|
var REMEDIATION_BY_METRIC2 = {
|
|
10485
10908
|
test_to_source_ratio: "add concrete regression tests for the implementation surface",
|
|
@@ -10591,13 +11014,14 @@ function buildWitanCliSummary(report) {
|
|
|
10591
11014
|
const common = {
|
|
10592
11015
|
productSlug: report.productSlug,
|
|
10593
11016
|
productDisplayName: report.productDisplayName,
|
|
10594
|
-
generatedAt: report.generatedAt,
|
|
10595
11017
|
findingCount: allFindings.length,
|
|
10596
11018
|
topFindings: sorted.slice(0, TOP_FINDINGS_LIMIT),
|
|
10597
11019
|
contributingSources,
|
|
10598
11020
|
externalSources,
|
|
10599
11021
|
externalFindingCount: allExternalFindings.length,
|
|
10600
|
-
topExternalFindings: allExternalFindings.slice(0, EXTERNAL_FINDINGS_DISPLAY_LIMIT)
|
|
11022
|
+
topExternalFindings: allExternalFindings.slice(0, EXTERNAL_FINDINGS_DISPLAY_LIMIT),
|
|
11023
|
+
scanLimitations: [...report.scanLimitations ?? []],
|
|
11024
|
+
...report.contentReadSummary ? { contentReadSummary: report.contentReadSummary } : {}
|
|
10601
11025
|
};
|
|
10602
11026
|
if (report.verdict === "insufficient_source") {
|
|
10603
11027
|
return {
|
|
@@ -10621,21 +11045,24 @@ function buildWitanCliSummary(report) {
|
|
|
10621
11045
|
// src/scan.ts
|
|
10622
11046
|
function runCejelScan(options) {
|
|
10623
11047
|
const identity = deriveProductIdentity(options.repoPath);
|
|
11048
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10624
11049
|
const report = scoreRepoWithPublicCejel({
|
|
10625
11050
|
productSlug: identity.productSlug,
|
|
10626
11051
|
productDisplayName: options.productDisplayName ?? identity.productDisplayName,
|
|
10627
11052
|
repoPath: options.repoPath,
|
|
11053
|
+
generatedAt,
|
|
10628
11054
|
ingestPatterns: options.ingestPatterns,
|
|
11055
|
+
// Local CLI/MCP scans retain the documented convenience default: their operator controls
|
|
11056
|
+
// the repository. Direct public/leaderboard scans default this off because their targets
|
|
11057
|
+
// are third-party repositories and must opt in explicitly.
|
|
11058
|
+
autoDiscoverIngest: true,
|
|
10629
11059
|
warnOnEmptyIngestMatch: options.warnOnEmptyIngestMatch
|
|
10630
11060
|
});
|
|
10631
11061
|
const summary = buildWitanCliSummary(report);
|
|
10632
|
-
return { report, summary };
|
|
11062
|
+
return { report, summary, generatedAt };
|
|
10633
11063
|
}
|
|
10634
11064
|
|
|
10635
11065
|
export {
|
|
10636
|
-
__commonJS,
|
|
10637
|
-
__export,
|
|
10638
|
-
__toESM,
|
|
10639
11066
|
ZodOptional,
|
|
10640
11067
|
ZodFirstPartyTypeKind,
|
|
10641
11068
|
objectType,
|