@cejel/cejel 0.2.1 → 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 +100 -14
- package/dist/chunk-LZVKQV3R.js +360 -0
- package/dist/{chunk-BR2DO4QJ.js → chunk-XB65L6WE.js} +791 -338
- package/dist/index.js +46 -4
- package/dist/mcp/index.js +218 -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",
|
|
@@ -4544,17 +4612,18 @@ function formatMetricValue(value) {
|
|
|
4544
4612
|
}
|
|
4545
4613
|
|
|
4546
4614
|
// src/witan/html.ts
|
|
4547
|
-
function renderWitanHtmlReport(report) {
|
|
4615
|
+
function renderWitanHtmlReport(report, options = {}) {
|
|
4548
4616
|
const codeCriteria = criteriaByCategory(report, "code_trust");
|
|
4549
4617
|
const processCriteria = criteriaByCategory(report, "process_trust");
|
|
4550
4618
|
const verifiedEvidence = report.criteria.flatMap(
|
|
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);
|
|
4626
|
+
const gitHistoryUnavailable = !report.repo.headSha;
|
|
4558
4627
|
return `<!doctype html>
|
|
4559
4628
|
<html lang="en">
|
|
4560
4629
|
<head>
|
|
@@ -4575,10 +4644,11 @@ function renderWitanHtmlReport(report) {
|
|
|
4575
4644
|
<p class="eyebrow">Trust Certificate</p>
|
|
4576
4645
|
<h1>${escapeHtml(report.productDisplayName)}</h1>
|
|
4577
4646
|
<dl class="meta">
|
|
4578
|
-
|
|
4647
|
+
${options.generatedAt ? `<div><dt>Date</dt><dd>${escapeHtml(formatDate(options.generatedAt))}</dd></div>` : ""}
|
|
4579
4648
|
<div><dt>Run</dt><dd>${escapeHtml(renderRepo(report))}</dd></div>
|
|
4649
|
+
<div><dt>CLI</dt><dd>${escapeHtml(options.cliVersion ? `Cejel ${options.cliVersion}` : "Not recorded")}</dd></div>
|
|
4580
4650
|
<div><dt>Rubric</dt><dd>${escapeHtml(report.rubricVersion)}</dd></div>
|
|
4581
|
-
${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>` : ""}
|
|
4582
4652
|
</dl>
|
|
4583
4653
|
</section>
|
|
4584
4654
|
<aside class="score-panel" aria-label="Summary trust score">
|
|
@@ -4596,9 +4666,18 @@ function renderWitanHtmlReport(report) {
|
|
|
4596
4666
|
</div>
|
|
4597
4667
|
</header>
|
|
4598
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
|
+
|
|
4599
4678
|
<section class="trust-grid" aria-label="Rubric criteria">
|
|
4600
|
-
${renderCriterionColumn("Code trust", codeCriteria)}
|
|
4601
|
-
${renderCriterionColumn("Process trust", processCriteria)}
|
|
4679
|
+
${renderCriterionColumn("Code trust", codeCriteria, gitHistoryUnavailable)}
|
|
4680
|
+
${renderCriterionColumn("Process trust", processCriteria, gitHistoryUnavailable)}
|
|
4602
4681
|
</section>
|
|
4603
4682
|
|
|
4604
4683
|
<section class="evidence-grid" aria-label="Evidence and gaps">
|
|
@@ -4624,8 +4703,50 @@ function renderWitanHtmlReport(report) {
|
|
|
4624
4703
|
</html>
|
|
4625
4704
|
`;
|
|
4626
4705
|
}
|
|
4627
|
-
function
|
|
4628
|
-
|
|
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(", ")})`;
|
|
4629
4750
|
}
|
|
4630
4751
|
function renderExternalFindingItems(findings) {
|
|
4631
4752
|
const shown = findings.slice(0, EXTERNAL_FINDINGS_DISPLAY_LIMIT);
|
|
@@ -4641,7 +4762,7 @@ function renderExternalFindingItems(findings) {
|
|
|
4641
4762
|
function criteriaByCategory(report, category) {
|
|
4642
4763
|
return report.criteria.filter((criterion) => criterion.category === category);
|
|
4643
4764
|
}
|
|
4644
|
-
function renderCriterionColumn(title, criteria) {
|
|
4765
|
+
function renderCriterionColumn(title, criteria, gitHistoryUnavailable) {
|
|
4645
4766
|
const applicable = criteria.filter((criterion) => criterion.status !== "not_applicable");
|
|
4646
4767
|
const notApplicable = criteria.filter((criterion) => criterion.status === "not_applicable");
|
|
4647
4768
|
const notApplicableGroup = notApplicable.length > 0 ? `
|
|
@@ -4649,7 +4770,7 @@ function renderCriterionColumn(title, criteria) {
|
|
|
4649
4770
|
return `<section class="criteria-column">
|
|
4650
4771
|
<h2>${escapeHtml(title)}</h2>
|
|
4651
4772
|
<div class="criteria-list">
|
|
4652
|
-
${applicable.map(renderCriterionCard).join("")}
|
|
4773
|
+
${applicable.map((criterion) => renderCriterionCard(criterion, gitHistoryUnavailable)).join("")}
|
|
4653
4774
|
</div>${notApplicableGroup}
|
|
4654
4775
|
</section>`;
|
|
4655
4776
|
}
|
|
@@ -4665,12 +4786,14 @@ function renderNotApplicableItem(criterion) {
|
|
|
4665
4786
|
const reason = criterion.notes ?? "Not applicable to this repository.";
|
|
4666
4787
|
return `<li><span class="na-id">${escapeHtml(criterion.id)}</span> ${escapeHtml(criterion.title)} \u2014 <span class="na-reason">${escapeHtml(reason)}</span></li>`;
|
|
4667
4788
|
}
|
|
4668
|
-
function renderCriterionCard(criterion) {
|
|
4789
|
+
function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
4669
4790
|
const evidence = [
|
|
4670
4791
|
...criterion.evidence.map(renderEvidencePointer),
|
|
4671
4792
|
...criterion.findings.map((finding) => renderFindingEvidence(criterion, finding))
|
|
4672
4793
|
];
|
|
4673
4794
|
const metrics = criterion.metrics.map(renderMetric);
|
|
4795
|
+
const statusReconciliation = renderStatusReconciliation(criterion);
|
|
4796
|
+
const historyWarning = gitHistoryUnavailable && criterion.metrics.some((metric2) => metric2.name === "pr_merge_ratio") ? `<p class="scan-warning"><strong>Scan limitation:</strong> Git history was unavailable (for example, this may be a source tarball), so the recent PR merge ratio is not a measured repository outcome and its displayed zero may undercount ${escapeHtml(criterion.id)}. Re-scan a full Git clone for this signal.</p>` : "";
|
|
4674
4797
|
return `<article class="criterion">
|
|
4675
4798
|
<div class="criterion-top">
|
|
4676
4799
|
<div>
|
|
@@ -4680,6 +4803,8 @@ function renderCriterionCard(criterion) {
|
|
|
4680
4803
|
${renderStatusChip(criterion.status)}
|
|
4681
4804
|
</div>
|
|
4682
4805
|
<div class="criterion-score">${criterion.status === "not_applicable" ? "N/A" : criterion.status === "insufficient_data" ? "No data" : formatScore(criterion.score)}</div>
|
|
4806
|
+
${statusReconciliation}
|
|
4807
|
+
${historyWarning}
|
|
4683
4808
|
<ul class="criterion-metrics">
|
|
4684
4809
|
${metrics.length > 0 ? metrics.map((item) => `<li>${item}</li>`).join("") : "<li>No measured depth metrics supplied.</li>"}
|
|
4685
4810
|
</ul>
|
|
@@ -4688,6 +4813,20 @@ function renderCriterionCard(criterion) {
|
|
|
4688
4813
|
</ul>
|
|
4689
4814
|
</article>`;
|
|
4690
4815
|
}
|
|
4816
|
+
function renderStatusReconciliation(criterion) {
|
|
4817
|
+
if (criterion.status === "not_applicable" || criterion.status === "insufficient_data" || criterion.status === "unverified") {
|
|
4818
|
+
return "";
|
|
4819
|
+
}
|
|
4820
|
+
const numericBand = scoreBandForPresentation(criterion.score);
|
|
4821
|
+
if (numericBand === criterion.status) return "";
|
|
4822
|
+
return `<p class="status-explanation"><strong>Why the labels differ:</strong> the ${escapeHtml(criterion.status)} dimension band is calibrated from criterion evidence thresholds and findings, independently of the ${formatScore(criterion.score)}/4.0 weighted score (which falls in the ${numericBand} numeric band).</p>`;
|
|
4823
|
+
}
|
|
4824
|
+
function scoreBandForPresentation(score) {
|
|
4825
|
+
if (score >= 3.5) return "verified";
|
|
4826
|
+
if (score >= 2.5) return "info";
|
|
4827
|
+
if (score >= 1.5) return "warning";
|
|
4828
|
+
return "critical";
|
|
4829
|
+
}
|
|
4691
4830
|
function renderMetric(metric2) {
|
|
4692
4831
|
const unit = metric2.unit ? ` ${escapeHtml(metric2.unit)}` : "";
|
|
4693
4832
|
if (metric2.kind === "saturating_count" && metric2.max !== void 0 && metric2.value > metric2.max) {
|
|
@@ -4844,6 +4983,8 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
|
|
|
4844
4983
|
.coverage-note { margin-top: 8px; color: var(--faint); font-family: var(--mono); font-size: 11px; }
|
|
4845
4984
|
.source-counts { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
|
4846
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; }
|
|
4847
4988
|
.external-findings-section { margin-top: 18px; }
|
|
4848
4989
|
.external-note { margin: 0 0 14px; color: var(--muted); font-size: 13px; }
|
|
4849
4990
|
.criteria-column, .evidence-section { border: 1px solid var(--line); border-radius: 8px; background: var(--surface); padding: 22px; }
|
|
@@ -4860,6 +5001,12 @@ h2 { font-family: var(--serif); font-size: 30px; font-weight: 400; letter-spacin
|
|
|
4860
5001
|
.criterion-id { color: var(--periwinkle); font-family: var(--mono); font-size: 12px; margin-bottom: 4px; }
|
|
4861
5002
|
h3 { font-size: 15px; line-height: 1.35; font-weight: 600; margin-bottom: 0; }
|
|
4862
5003
|
.criterion-score { margin-top: 12px; color: var(--text); font-family: var(--mono); font-size: 24px; }
|
|
5004
|
+
.status-explanation, .scan-warning {
|
|
5005
|
+
margin: 10px 0 0; border-left: 3px solid var(--periwinkle); padding: 8px 10px;
|
|
5006
|
+
background: var(--periwinkle-weak); color: var(--muted); font-size: 12px; line-height: 1.5;
|
|
5007
|
+
}
|
|
5008
|
+
.scan-warning { border-left-color: var(--warn); background: var(--warn-weak); }
|
|
5009
|
+
.status-explanation strong, .scan-warning strong { color: var(--text); }
|
|
4863
5010
|
.criterion-metrics { display: grid; gap: 7px; margin-top: 10px; padding: 0; list-style: none; font-size: 12px; }
|
|
4864
5011
|
.criterion-metrics li { display: flex; justify-content: space-between; gap: 12px; border-bottom: 1px solid rgba(238, 244, 247, .08); padding-bottom: 6px; }
|
|
4865
5012
|
.criterion-metrics strong { color: var(--text); font-weight: 650; }
|
|
@@ -4887,6 +5034,7 @@ a:hover { text-decoration: underline; text-underline-offset: 2px; }
|
|
|
4887
5034
|
`;
|
|
4888
5035
|
|
|
4889
5036
|
// src/witan/badge.ts
|
|
5037
|
+
init_esm_shims();
|
|
4890
5038
|
var VERDICT_COLOR = {
|
|
4891
5039
|
Verified: "brightgreen",
|
|
4892
5040
|
Conditional: "yellow",
|
|
@@ -4904,24 +5052,26 @@ var VERDICT_SVG_FILL = {
|
|
|
4904
5052
|
"Insufficient evidence": "#8c8c8c"
|
|
4905
5053
|
};
|
|
4906
5054
|
function badgeMessage(report, verdict) {
|
|
5055
|
+
if ((report.scanLimitations?.length ?? 0) > 0) return "unrated: limited evidence";
|
|
4907
5056
|
if (isWitanNoMeasurementAbstention(report)) return "unrated: no measurable evidence";
|
|
4908
5057
|
if (report.verdict === "insufficient_source") return "unrated: no source";
|
|
4909
5058
|
return `${formatScore2(report.overallScore)}/4.0 ${verdict.toLowerCase()}`;
|
|
4910
5059
|
}
|
|
4911
5060
|
function renderWitanBadgeEndpoint(report) {
|
|
4912
5061
|
const verdict = renderReportVerdict(report);
|
|
5062
|
+
const limited = (report.scanLimitations?.length ?? 0) > 0;
|
|
4913
5063
|
return {
|
|
4914
5064
|
schemaVersion: 1,
|
|
4915
5065
|
label: "cejel trust",
|
|
4916
5066
|
message: badgeMessage(report, verdict),
|
|
4917
|
-
color: VERDICT_COLOR[verdict] ?? "lightgrey"
|
|
5067
|
+
color: limited ? "lightgrey" : VERDICT_COLOR[verdict] ?? "lightgrey"
|
|
4918
5068
|
};
|
|
4919
5069
|
}
|
|
4920
5070
|
function renderWitanBadgeSvg(report) {
|
|
4921
5071
|
const verdict = renderReportVerdict(report);
|
|
4922
5072
|
const label = "cejel trust";
|
|
4923
5073
|
const message = badgeMessage(report, verdict);
|
|
4924
|
-
const fill = VERDICT_SVG_FILL[verdict] ?? "#8c8c8c";
|
|
5074
|
+
const fill = (report.scanLimitations?.length ?? 0) > 0 ? "#8c8c8c" : VERDICT_SVG_FILL[verdict] ?? "#8c8c8c";
|
|
4925
5075
|
const charWidth = 6.5;
|
|
4926
5076
|
const padding = 10;
|
|
4927
5077
|
const labelWidth = Math.round(label.length * charWidth + padding);
|
|
@@ -4957,6 +5107,7 @@ function escapeXml(value) {
|
|
|
4957
5107
|
}
|
|
4958
5108
|
|
|
4959
5109
|
// src/witan/attestation.ts
|
|
5110
|
+
init_esm_shims();
|
|
4960
5111
|
import { createHash } from "crypto";
|
|
4961
5112
|
var ATTESTATION_LIMITATIONS = [
|
|
4962
5113
|
"Cejel measures observable repository signals; it is not a security or compliance guarantee.",
|
|
@@ -4970,6 +5121,19 @@ function hashWitanReport(report) {
|
|
|
4970
5121
|
}
|
|
4971
5122
|
function createWitanAttestation(report, options) {
|
|
4972
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
|
+
);
|
|
4973
5137
|
const outcome = report.verdict === "insufficient_source" ? {
|
|
4974
5138
|
status: "abstained",
|
|
4975
5139
|
reason: report.insufficientSourceReason
|
|
@@ -4990,7 +5154,7 @@ function createWitanAttestation(report, options) {
|
|
|
4990
5154
|
predicateType: WITAN_ATTESTATION_PREDICATE_TYPE,
|
|
4991
5155
|
predicate: {
|
|
4992
5156
|
tool: { name: "cejel", version: options.toolVersion },
|
|
4993
|
-
generatedAt:
|
|
5157
|
+
generatedAt: options.generatedAt,
|
|
4994
5158
|
rubricVersion: report.rubricVersion,
|
|
4995
5159
|
repository: {
|
|
4996
5160
|
productSlug: report.productSlug,
|
|
@@ -5001,6 +5165,7 @@ function createWitanAttestation(report, options) {
|
|
|
5001
5165
|
artifact: "report.json",
|
|
5002
5166
|
sha256: reportSha256
|
|
5003
5167
|
},
|
|
5168
|
+
externalSignalProvenance,
|
|
5004
5169
|
outcome,
|
|
5005
5170
|
assurance: {
|
|
5006
5171
|
status: "unsigned",
|
|
@@ -5033,9 +5198,6 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
5033
5198
|
if (parsed.data.predicate.rubricVersion !== report.rubricVersion) {
|
|
5034
5199
|
errors.push("rubric version does not match report.json");
|
|
5035
5200
|
}
|
|
5036
|
-
if (parsed.data.predicate.generatedAt !== report.generatedAt) {
|
|
5037
|
-
errors.push("generated timestamp does not match report.json");
|
|
5038
|
-
}
|
|
5039
5201
|
if (parsed.data.predicate.repository.headSha !== report.repo.headSha) {
|
|
5040
5202
|
errors.push("repository revision does not match report.json");
|
|
5041
5203
|
}
|
|
@@ -5045,6 +5207,22 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
5045
5207
|
if (parsed.data.predicate.repository.url !== report.repo.url) {
|
|
5046
5208
|
errors.push("repository URL does not match report.json");
|
|
5047
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
|
+
}
|
|
5048
5226
|
const outcome = parsed.data.predicate.outcome;
|
|
5049
5227
|
if (report.verdict === "insufficient_source") {
|
|
5050
5228
|
if (outcome.status !== "abstained" || outcome.reason !== report.insufficientSourceReason) {
|
|
@@ -5056,15 +5234,17 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
5056
5234
|
return { valid: errors.length === 0, errors };
|
|
5057
5235
|
}
|
|
5058
5236
|
|
|
5237
|
+
// src/witan/index.ts
|
|
5238
|
+
init_esm_shims();
|
|
5239
|
+
|
|
5240
|
+
// src/witan/markdown.ts
|
|
5241
|
+
init_esm_shims();
|
|
5242
|
+
|
|
5059
5243
|
// src/witan/repo-signals.ts
|
|
5060
|
-
|
|
5244
|
+
init_esm_shims();
|
|
5061
5245
|
import { createHash as createHash2 } from "crypto";
|
|
5062
5246
|
import {
|
|
5063
|
-
closeSync,
|
|
5064
5247
|
lstatSync,
|
|
5065
|
-
openSync,
|
|
5066
|
-
readFileSync,
|
|
5067
|
-
readSync,
|
|
5068
5248
|
readdirSync,
|
|
5069
5249
|
realpathSync,
|
|
5070
5250
|
statSync
|
|
@@ -5072,11 +5252,13 @@ import {
|
|
|
5072
5252
|
import { basename, dirname, extname, join, relative, resolve, sep } from "path";
|
|
5073
5253
|
|
|
5074
5254
|
// src/witan/json-safe.ts
|
|
5255
|
+
init_esm_shims();
|
|
5075
5256
|
function stripBom(text) {
|
|
5076
5257
|
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
5077
5258
|
}
|
|
5078
5259
|
|
|
5079
5260
|
// src/witan/rubric-version.ts
|
|
5261
|
+
init_esm_shims();
|
|
5080
5262
|
var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
|
|
5081
5263
|
var WITAN_RUBRIC_VERSION_V10 = "witan-rubric-v10-2026-07-23";
|
|
5082
5264
|
var WITAN_RUBRIC_VERSION_V11 = "witan-rubric-v11-2026-07-23";
|
|
@@ -5093,12 +5275,41 @@ function usesV17DetectorClosure(rubricVersion) {
|
|
|
5093
5275
|
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18;
|
|
5094
5276
|
}
|
|
5095
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) {
|
|
5096
5306
|
assertRepoPathExists(options.repoPath);
|
|
5097
5307
|
const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5098
5308
|
const rubricVersion = options.rubricVersion ?? WITAN_RUBRIC_VERSION;
|
|
5309
|
+
const fileInventory = collectRepoFileInventory(options.repoPath);
|
|
5099
5310
|
const headSha = readGitHead(options.repoPath);
|
|
5100
|
-
const repoFiles =
|
|
5101
|
-
const inventoryFiles =
|
|
5311
|
+
const repoFiles = fileInventory.repoFiles;
|
|
5312
|
+
const inventoryFiles = fileInventory.inventoryFiles;
|
|
5102
5313
|
const ignoredTargetReason = repoFiles.length === 0 ? explainIgnoredScanTarget(options.repoPath) : void 0;
|
|
5103
5314
|
const usesV17Detectors = usesV17DetectorClosure(rubricVersion);
|
|
5104
5315
|
const usesV18NativeRls = rubricVersion === WITAN_RUBRIC_VERSION_V18;
|
|
@@ -5122,6 +5333,7 @@ function buildWitanInputFromRepo(options) {
|
|
|
5122
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);
|
|
5123
5334
|
const usesV47Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion);
|
|
5124
5335
|
const reviewableSourceProof = usesV17Detectors ? buildReviewableSourceProof(options.repoPath, inventoryFiles, headSha, rubricVersion) : void 0;
|
|
5336
|
+
const scanLimitations = new Set(fileInventory.scanLimitations);
|
|
5125
5337
|
const coreSignals = collectRepoSignals(
|
|
5126
5338
|
options.repoPath,
|
|
5127
5339
|
generatedAt,
|
|
@@ -5132,7 +5344,8 @@ function buildWitanInputFromRepo(options) {
|
|
|
5132
5344
|
usesV39Detectors,
|
|
5133
5345
|
usesV47Detectors,
|
|
5134
5346
|
usesV18NativeRls,
|
|
5135
|
-
reviewableSourceProof
|
|
5347
|
+
reviewableSourceProof,
|
|
5348
|
+
scanLimitations
|
|
5136
5349
|
);
|
|
5137
5350
|
const archetype = usesV17Detectors && reviewableSourceProof?.passes === true && isV17RescuableAbstention(gatedArchetype.abstentionKind) && coreSignals.some(isMeasuredCriterionSignal) ? {
|
|
5138
5351
|
archetype: isLikelyMonorepo(inventoryFiles) ? "monorepo" : "source",
|
|
@@ -5150,6 +5363,7 @@ function buildWitanInputFromRepo(options) {
|
|
|
5150
5363
|
...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {},
|
|
5151
5364
|
archetype: archetype.archetype,
|
|
5152
5365
|
...ignoredTargetReason ?? archetype.insufficientSourceReason ? { insufficientSourceReason: ignoredTargetReason ?? archetype.insufficientSourceReason } : {},
|
|
5366
|
+
...scanLimitations.size > 0 ? { scanLimitations: [...scanLimitations] } : {},
|
|
5153
5367
|
signals: [
|
|
5154
5368
|
...coreSignals,
|
|
5155
5369
|
...(options.domainCollectors ?? []).map((collect) => collect(options.repoPath, repoFiles)),
|
|
@@ -5622,26 +5836,21 @@ function isRubyPackageRecipeMetadataSource(repoPath, file) {
|
|
|
5622
5836
|
function readSemanticMetadataPrefix(repoPath, file) {
|
|
5623
5837
|
const absolutePath = join(repoPath, file);
|
|
5624
5838
|
if (!isRegularFile(absolutePath)) return null;
|
|
5625
|
-
|
|
5626
|
-
try {
|
|
5627
|
-
descriptor = openSync(absolutePath, "r");
|
|
5628
|
-
const buffer = Buffer.allocUnsafe(SEMANTIC_METADATA_PREFIX_BYTES_V14);
|
|
5629
|
-
const bytesRead = readSync(descriptor, buffer, 0, SEMANTIC_METADATA_PREFIX_BYTES_V14, 0);
|
|
5630
|
-
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
5631
|
-
} catch {
|
|
5632
|
-
return null;
|
|
5633
|
-
} finally {
|
|
5634
|
-
if (descriptor !== null) closeSync(descriptor);
|
|
5635
|
-
}
|
|
5839
|
+
return readRepoTextPrefix(absolutePath, SEMANTIC_METADATA_PREFIX_BYTES_V14);
|
|
5636
5840
|
}
|
|
5637
5841
|
function readRepresentativeSourceText(repoPath, file) {
|
|
5638
5842
|
const absolutePath = join(repoPath, file);
|
|
5639
5843
|
try {
|
|
5640
5844
|
if (!isRegularFile(absolutePath)) return null;
|
|
5641
5845
|
const size = statSync(absolutePath).size;
|
|
5642
|
-
if (size === 0
|
|
5643
|
-
|
|
5644
|
-
|
|
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);
|
|
5645
5854
|
return null;
|
|
5646
5855
|
}
|
|
5647
5856
|
}
|
|
@@ -5711,28 +5920,25 @@ function assertRepoPathExists(repoPath) {
|
|
|
5711
5920
|
}
|
|
5712
5921
|
}
|
|
5713
5922
|
function explainIgnoredScanTarget(repoPath) {
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
encoding: "utf8",
|
|
5719
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
5720
|
-
}).trim();
|
|
5721
|
-
} catch {
|
|
5722
|
-
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"));
|
|
5723
5927
|
}
|
|
5928
|
+
const top = topResult.stdout.trim();
|
|
5724
5929
|
if (!top) return void 0;
|
|
5725
5930
|
const topResolved = realpathSync(top);
|
|
5726
5931
|
const targetResolved = realpathSync(repoPath);
|
|
5727
5932
|
const target = relative(topResolved, targetResolved).split(sep).join("/");
|
|
5728
5933
|
if (!target || target.startsWith("../")) return void 0;
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
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"));
|
|
5736
5942
|
}
|
|
5737
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.`;
|
|
5738
5944
|
}
|
|
@@ -5746,48 +5952,74 @@ function buildNotApplicableSignal(criterionId, reason) {
|
|
|
5746
5952
|
notApplicable: true
|
|
5747
5953
|
};
|
|
5748
5954
|
}
|
|
5749
|
-
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()) {
|
|
5750
5956
|
const signals = [];
|
|
5751
|
-
const mono = resolveMonorepoContext(repoPath);
|
|
5752
|
-
const
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
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
|
+
)
|
|
5759
5969
|
);
|
|
5760
|
-
const a2Signal =
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
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
|
+
)
|
|
5769
5983
|
);
|
|
5770
|
-
const a3Signal =
|
|
5771
|
-
|
|
5772
|
-
repoPath,
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
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)
|
|
5777
6001
|
);
|
|
5778
|
-
const a5Signal = collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors);
|
|
5779
6002
|
const b1Signal = buildNotApplicableSignal(
|
|
5780
6003
|
"B1",
|
|
5781
6004
|
"Repository scans do not evaluate the dispatch-trace process dimension."
|
|
5782
6005
|
);
|
|
5783
|
-
const b2Signal = collectB2PrTraceEvidence(repoPath, repoFiles);
|
|
5784
|
-
const b3Signal =
|
|
5785
|
-
|
|
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
|
+
);
|
|
5786
6015
|
const b5Signal = buildNotApplicableSignal(
|
|
5787
6016
|
"B5",
|
|
5788
6017
|
"Repository scans do not evaluate the learning-trace process dimension."
|
|
5789
6018
|
);
|
|
5790
|
-
const b6Signal =
|
|
6019
|
+
const b6Signal = collectCriterion(
|
|
6020
|
+
"B6",
|
|
6021
|
+
() => collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detectors)
|
|
6022
|
+
);
|
|
5791
6023
|
for (const signal of [
|
|
5792
6024
|
a1Signal,
|
|
5793
6025
|
a2Signal,
|
|
@@ -6057,7 +6289,7 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
|
|
|
6057
6289
|
notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files.`
|
|
6058
6290
|
};
|
|
6059
6291
|
}
|
|
6060
|
-
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls) {
|
|
6292
|
+
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, scanLimitations) {
|
|
6061
6293
|
const evidence = [];
|
|
6062
6294
|
const findings = [];
|
|
6063
6295
|
const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
|
|
@@ -6103,6 +6335,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
6103
6335
|
repoPath,
|
|
6104
6336
|
useV27Detectors,
|
|
6105
6337
|
currentSecretFingerprintsByPath,
|
|
6338
|
+
scanLimitations,
|
|
6106
6339
|
useV33Detectors,
|
|
6107
6340
|
useV36Detectors,
|
|
6108
6341
|
useV39Detectors,
|
|
@@ -6119,7 +6352,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
6119
6352
|
const committedEnvFilePath = repoFiles.find(
|
|
6120
6353
|
(f) => /(?:^|\/)\.env(?:\.|$)/i.test(f) && !isEnvTemplatePath(f, useV47Detectors) && (!useV47Detectors || isV47AuthoredProductionPath(f)) && (!useV39Detectors || !hasTemplateOnlyEnvContent(repoPath, f))
|
|
6121
6354
|
);
|
|
6122
|
-
const hasEnvInHistory = committedEnvFilePath === void 0 && hasEnvPathInGitHistory(repoPath);
|
|
6355
|
+
const hasEnvInHistory = committedEnvFilePath === void 0 && hasEnvPathInGitHistory(repoPath, scanLimitations);
|
|
6123
6356
|
const dbLayerImportFile = migrationFiles.length === 0 ? implFiles.slice(0, 30).find((f) => fileContains(repoPath, f, DB_CLIENT_PATTERN)) : void 0;
|
|
6124
6357
|
const hasDataLayer = migrationFiles.length > 0 || dbLayerImportFile !== void 0;
|
|
6125
6358
|
const hasSecretsSurface = envExamples.length > 0 || committedEnvFilePath !== void 0 || gitignoreHasEnvRule || hasEnvInHistory;
|
|
@@ -7279,14 +7512,14 @@ function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
|
|
|
7279
7512
|
function findInsecureSecretCompareLine(repoPath, file, useV39Detectors = false) {
|
|
7280
7513
|
const fullPath = join(repoPath, file);
|
|
7281
7514
|
if (!isRegularFile(fullPath)) return null;
|
|
7282
|
-
const lines =
|
|
7515
|
+
const lines = readRepoText(fullPath, "utf8").split("\n");
|
|
7283
7516
|
const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line, useV39Detectors));
|
|
7284
7517
|
return index === -1 ? null : index + 1;
|
|
7285
7518
|
}
|
|
7286
7519
|
function findFirstMatchingLine(repoPath, file, pattern) {
|
|
7287
7520
|
const fullPath = join(repoPath, file);
|
|
7288
7521
|
if (!isRegularFile(fullPath)) return null;
|
|
7289
|
-
const lines =
|
|
7522
|
+
const lines = readRepoText(fullPath, "utf8").split("\n");
|
|
7290
7523
|
const index = lines.findIndex((line) => pattern.test(line));
|
|
7291
7524
|
return index === -1 ? null : index + 1;
|
|
7292
7525
|
}
|
|
@@ -7294,7 +7527,7 @@ function lineAt(repoPath, file, line) {
|
|
|
7294
7527
|
if (line < 1) return null;
|
|
7295
7528
|
const fullPath = join(repoPath, file);
|
|
7296
7529
|
if (!isRegularFile(fullPath)) return null;
|
|
7297
|
-
return
|
|
7530
|
+
return readRepoText(fullPath, "utf8").split("\n")[line - 1] ?? null;
|
|
7298
7531
|
}
|
|
7299
7532
|
var UNSORTED_JSON_SIGN_PATTERN = /\.update\s*\(\s*JSON\.stringify\s*\(|\.sign\s*\([^)]*JSON\.stringify\s*\(/;
|
|
7300
7533
|
var UNSORTED_JSON_DIRECT_SIGN_PATTERN = /\.sign\s*\([^)]*JSON\.stringify\s*\(/;
|
|
@@ -7303,7 +7536,7 @@ var JSON_STRINGIFY_ASSIGNMENT_PATTERN = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*
|
|
|
7303
7536
|
function findUnsortedJsonHmacLine(repoPath, file) {
|
|
7304
7537
|
const fullPath = join(repoPath, file);
|
|
7305
7538
|
if (!isRegularFile(fullPath)) return null;
|
|
7306
|
-
const contents =
|
|
7539
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7307
7540
|
const directSign = findFirstMatchingLine(repoPath, file, UNSORTED_JSON_DIRECT_SIGN_PATTERN);
|
|
7308
7541
|
if (directSign !== null) return directSign;
|
|
7309
7542
|
const directConstructorUpdate = findFirstMatchingLine(
|
|
@@ -7384,27 +7617,92 @@ function isEnvTemplatePath(path, useV47Detectors = false) {
|
|
|
7384
7617
|
}
|
|
7385
7618
|
var HARD_EXCLUDED_PATH_PATTERN = /(^|\/)(?:\.git|\.venv|venv|site-packages|node_modules|dist|build|\.next|__pycache__|vendor|\.terraform|coverage)(?:\/|$)/;
|
|
7386
7619
|
var COVERAGE_PERCENT_PATTERN = /(?:statements|branches|lines|functions|fail_under)\s*[:=]\s*["']?(\d+(?:\.\d+)?)/gi;
|
|
7387
|
-
function
|
|
7388
|
-
|
|
7389
|
-
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) {
|
|
7390
7632
|
const files = [];
|
|
7391
|
-
|
|
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);
|
|
7392
7660
|
return files.filter((file) => !isHardExcludedPath(file)).sort();
|
|
7393
7661
|
}
|
|
7394
|
-
function
|
|
7395
|
-
|
|
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
|
+
};
|
|
7396
7686
|
}
|
|
7397
|
-
function
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
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
|
+
);
|
|
7406
7703
|
return null;
|
|
7407
7704
|
}
|
|
7705
|
+
const top = topResult.stdout.trim();
|
|
7408
7706
|
if (!top) return null;
|
|
7409
7707
|
const rootResolved = resolve(top);
|
|
7410
7708
|
const repoResolved = (() => {
|
|
@@ -7416,7 +7714,7 @@ function resolveMonorepoContext(repoPath) {
|
|
|
7416
7714
|
})();
|
|
7417
7715
|
if (rootResolved === repoResolved) return null;
|
|
7418
7716
|
if (!repoResolved.startsWith(rootResolved + sep)) return null;
|
|
7419
|
-
const rootFiles = listGitTrackedFiles(rootResolved);
|
|
7717
|
+
const rootFiles = listGitTrackedFiles(rootResolved, scanLimitations);
|
|
7420
7718
|
if (!rootFiles || rootFiles.length === 0) return null;
|
|
7421
7719
|
const subRel = repoResolved.slice(rootResolved.length + 1).split(sep).join("/");
|
|
7422
7720
|
const subPrefix = subRel ? `${subRel}/` : "";
|
|
@@ -7426,50 +7724,119 @@ function resolveMonorepoContext(repoPath) {
|
|
|
7426
7724
|
if (sharedFiles.length === 0) return null;
|
|
7427
7725
|
return { root: rootResolved, sharedFiles };
|
|
7428
7726
|
}
|
|
7429
|
-
function listGitTrackedFiles(repoPath, includeHardExcluded = false) {
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
cwd: repoPath,
|
|
7438
|
-
encoding: "utf8",
|
|
7439
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
7440
|
-
}).trim();
|
|
7441
|
-
if (!output) return [];
|
|
7442
|
-
return output.split("\n").filter(
|
|
7443
|
-
(file) => file.length > 0 && (includeHardExcluded || !isHardExcludedPath(file)) && isRegularFile(join(repoPath, file))
|
|
7444
|
-
).sort();
|
|
7445
|
-
} catch {
|
|
7446
|
-
return null;
|
|
7447
|
-
}
|
|
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;
|
|
7448
7735
|
}
|
|
7449
|
-
function visitRepoDir(repoPath, dirPath, files) {
|
|
7736
|
+
function visitRepoDir(repoPath, dirPath, files, rootDevice) {
|
|
7450
7737
|
let entries;
|
|
7451
7738
|
try {
|
|
7452
7739
|
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
7453
|
-
} catch {
|
|
7740
|
+
} catch (error) {
|
|
7741
|
+
recordFilesystemSkip(dirPath, error, true);
|
|
7454
7742
|
return;
|
|
7455
7743
|
}
|
|
7456
7744
|
for (const entry of entries) {
|
|
7457
|
-
if (shouldSkipDir(entry.name)) continue;
|
|
7458
7745
|
const fullPath = join(dirPath, entry.name);
|
|
7746
|
+
if (shouldSkipDir(entry.name)) {
|
|
7747
|
+
recordContentSkip(fullPath, "denied_path");
|
|
7748
|
+
continue;
|
|
7749
|
+
}
|
|
7459
7750
|
if (entry.isDirectory()) {
|
|
7460
|
-
|
|
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");
|
|
7461
7765
|
continue;
|
|
7462
7766
|
}
|
|
7463
|
-
if (!entry.isFile()) continue;
|
|
7464
7767
|
let size;
|
|
7465
7768
|
try {
|
|
7466
7769
|
size = statSync(fullPath).size;
|
|
7467
|
-
} catch {
|
|
7770
|
+
} catch (error) {
|
|
7771
|
+
recordFilesystemSkip(fullPath, error);
|
|
7468
7772
|
continue;
|
|
7469
7773
|
}
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
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);
|
|
7473
7840
|
}
|
|
7474
7841
|
function shouldSkipDir(name) {
|
|
7475
7842
|
return [
|
|
@@ -7496,7 +7863,7 @@ function collectContentBasedCppTestFiles(repoPath, repoFiles) {
|
|
|
7496
7863
|
return candidates.filter((file) => {
|
|
7497
7864
|
const fullPath = join(repoPath, file);
|
|
7498
7865
|
if (!isRegularFile(fullPath)) return false;
|
|
7499
|
-
const contents =
|
|
7866
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7500
7867
|
return CPP_TEST_FRAMEWORK_PATTERN.test(contents) || CPP_TEST_MACRO_PATTERN.test(contents);
|
|
7501
7868
|
});
|
|
7502
7869
|
}
|
|
@@ -7516,7 +7883,7 @@ function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
|
|
|
7516
7883
|
for (const file of files) {
|
|
7517
7884
|
const fullPath = join(repoPath, file);
|
|
7518
7885
|
if (!isRegularFile(fullPath)) continue;
|
|
7519
|
-
const contents =
|
|
7886
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7520
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);
|
|
7521
7888
|
if (nonHollow) {
|
|
7522
7889
|
nonHollowCount += 1;
|
|
@@ -7537,7 +7904,7 @@ function countPatternMatches(repoPath, files, pattern) {
|
|
|
7537
7904
|
for (const file of files) {
|
|
7538
7905
|
const fullPath = join(repoPath, file);
|
|
7539
7906
|
if (!isRegularFile(fullPath)) continue;
|
|
7540
|
-
count += [...
|
|
7907
|
+
count += [...readRepoText(fullPath, "utf8").matchAll(matcher)].length;
|
|
7541
7908
|
}
|
|
7542
7909
|
return count;
|
|
7543
7910
|
}
|
|
@@ -7545,7 +7912,7 @@ function readCoveragePercent(repoPath, files) {
|
|
|
7545
7912
|
for (const file of files) {
|
|
7546
7913
|
const fullPath = join(repoPath, file);
|
|
7547
7914
|
if (!isRegularFile(fullPath)) continue;
|
|
7548
|
-
const contents =
|
|
7915
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7549
7916
|
const summaryPercent = parseCoverageSummaryPercent(contents);
|
|
7550
7917
|
if (summaryPercent !== null) return summaryPercent;
|
|
7551
7918
|
const thresholdPercent = parseCoverageThresholdPercent(contents);
|
|
@@ -7635,7 +8002,7 @@ function readDependencySpecs(repoPath, manifests) {
|
|
|
7635
8002
|
}
|
|
7636
8003
|
function readGoModDependencySpecs(path) {
|
|
7637
8004
|
const specs = [];
|
|
7638
|
-
const contents =
|
|
8005
|
+
const contents = readRepoText(path, "utf8");
|
|
7639
8006
|
const blockPattern = /require\s*\(([^)]+)\)/gs;
|
|
7640
8007
|
const singlePattern = /^require\s+(\S+)\s+(\S+)/gm;
|
|
7641
8008
|
for (const blockMatch of contents.matchAll(blockPattern)) {
|
|
@@ -7656,7 +8023,7 @@ function readGoModDependencySpecs(path) {
|
|
|
7656
8023
|
}
|
|
7657
8024
|
function readCargoTomlDependencySpecs(path) {
|
|
7658
8025
|
const specs = [];
|
|
7659
|
-
const contents =
|
|
8026
|
+
const contents = readRepoText(path, "utf8");
|
|
7660
8027
|
const depSectionPattern = /^\[(?:dependencies|dev-dependencies|build-dependencies)\]([\s\S]*?)(?=^\[|(?![\s\S]))/gm;
|
|
7661
8028
|
for (const sectionMatch of contents.matchAll(depSectionPattern)) {
|
|
7662
8029
|
const section = sectionMatch[1] ?? "";
|
|
@@ -7678,7 +8045,7 @@ function readCargoTomlDependencySpecs(path) {
|
|
|
7678
8045
|
}
|
|
7679
8046
|
function readPyprojectDependencySpecs(path) {
|
|
7680
8047
|
const specs = [];
|
|
7681
|
-
const contents =
|
|
8048
|
+
const contents = readRepoText(path, "utf8");
|
|
7682
8049
|
const poetrySectionPattern = /^\[tool\.poetry\.dependencies\]([\s\S]*?)(?=^\[|(?![\s\S]))/gm;
|
|
7683
8050
|
for (const sectionMatch of contents.matchAll(poetrySectionPattern)) {
|
|
7684
8051
|
const section = sectionMatch[1] ?? "";
|
|
@@ -7712,7 +8079,7 @@ function readPyprojectDependencySpecs(path) {
|
|
|
7712
8079
|
}
|
|
7713
8080
|
function readPipfileDependencySpecs(path) {
|
|
7714
8081
|
const specs = [];
|
|
7715
|
-
const contents =
|
|
8082
|
+
const contents = readRepoText(path, "utf8");
|
|
7716
8083
|
const sectionPattern = /^\[(?:packages|dev-packages)\]([\s\S]*?)(?=^\[|$(?![\s\S]))/gm;
|
|
7717
8084
|
for (const sectionMatch of contents.matchAll(sectionPattern)) {
|
|
7718
8085
|
for (const line of (sectionMatch[1] ?? "").split("\n")) {
|
|
@@ -7725,7 +8092,7 @@ function readPipfileDependencySpecs(path) {
|
|
|
7725
8092
|
return specs;
|
|
7726
8093
|
}
|
|
7727
8094
|
function readVcpkgJsonDependencySpecs(path) {
|
|
7728
|
-
const parsed = parseJsonObject(
|
|
8095
|
+
const parsed = parseJsonObject(readRepoText(path, "utf8"));
|
|
7729
8096
|
if (!parsed) return [];
|
|
7730
8097
|
const rawDeps = Array.isArray(parsed.dependencies) ? parsed.dependencies : [];
|
|
7731
8098
|
const specs = [];
|
|
@@ -7741,7 +8108,7 @@ function readVcpkgJsonDependencySpecs(path) {
|
|
|
7741
8108
|
}
|
|
7742
8109
|
function readConanfileTxtDependencySpecs(path) {
|
|
7743
8110
|
const specs = [];
|
|
7744
|
-
const contents =
|
|
8111
|
+
const contents = readRepoText(path, "utf8");
|
|
7745
8112
|
const requiresMatch = /^\[requires\]([\s\S]*?)(?=^\[|$(?![\s\S]))/m.exec(contents);
|
|
7746
8113
|
if (!requiresMatch) return specs;
|
|
7747
8114
|
for (const line of (requiresMatch[1] ?? "").split("\n")) {
|
|
@@ -7754,7 +8121,7 @@ function readConanfileTxtDependencySpecs(path) {
|
|
|
7754
8121
|
}
|
|
7755
8122
|
function readConanfilePyDependencySpecs(path) {
|
|
7756
8123
|
const specs = [];
|
|
7757
|
-
const contents =
|
|
8124
|
+
const contents = readRepoText(path, "utf8");
|
|
7758
8125
|
for (const match of contents.matchAll(/self\.requires\s*\(\s*["']([\w.\-]+)\/([\w.\-]+)["']/g)) {
|
|
7759
8126
|
specs.push({ name: match[1] ?? "", version: match[2] ?? "" });
|
|
7760
8127
|
}
|
|
@@ -7768,7 +8135,7 @@ function readConanfilePyDependencySpecs(path) {
|
|
|
7768
8135
|
}
|
|
7769
8136
|
function readCMakeDependencySpecs(path) {
|
|
7770
8137
|
const specs = [];
|
|
7771
|
-
const contents =
|
|
8138
|
+
const contents = readRepoText(path, "utf8");
|
|
7772
8139
|
for (const match of contents.matchAll(/find_package\s*\(\s*([\w]+)(?:\s+([\d.]+))?/gi)) {
|
|
7773
8140
|
const name = match[1];
|
|
7774
8141
|
if (!name) continue;
|
|
@@ -7783,7 +8150,7 @@ function readCMakeDependencySpecs(path) {
|
|
|
7783
8150
|
return specs;
|
|
7784
8151
|
}
|
|
7785
8152
|
function readPackageJsonDependencySpecs(packageJsonPath) {
|
|
7786
|
-
const parsed = parseJsonObject(
|
|
8153
|
+
const parsed = parseJsonObject(readRepoText(packageJsonPath, "utf8"));
|
|
7787
8154
|
if (!parsed) return [];
|
|
7788
8155
|
const optionalPeerNames = /* @__PURE__ */ new Set();
|
|
7789
8156
|
const peerDependenciesMeta = parsed.peerDependenciesMeta;
|
|
@@ -7810,7 +8177,7 @@ function readPackageJsonDependencySpecs(packageJsonPath) {
|
|
|
7810
8177
|
return specs;
|
|
7811
8178
|
}
|
|
7812
8179
|
function readRequirementsDependencySpecs(path) {
|
|
7813
|
-
return
|
|
8180
|
+
return readRepoText(path, "utf8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((line) => {
|
|
7814
8181
|
const [name = line, version = ""] = line.split(/[<>=~!]=?/, 2);
|
|
7815
8182
|
return { name: name.trim(), version: line.includes("==") ? version.trim() : line };
|
|
7816
8183
|
});
|
|
@@ -7828,7 +8195,7 @@ function isPinnedDependencyVersion(version) {
|
|
|
7828
8195
|
function workflowTargetsDefaultBranch(repoPath, file) {
|
|
7829
8196
|
const fullPath = join(repoPath, file);
|
|
7830
8197
|
if (!isRegularFile(fullPath)) return false;
|
|
7831
|
-
const contents =
|
|
8198
|
+
const contents = readRepoText(fullPath, "utf8").toLowerCase();
|
|
7832
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);
|
|
7833
8200
|
}
|
|
7834
8201
|
function isPrMergeCommit(commit) {
|
|
@@ -7982,7 +8349,7 @@ function deriveRlsPolicyScopeInventory(repoPath, repoFiles) {
|
|
|
7982
8349
|
for (const file of candidatePolicyFiles) {
|
|
7983
8350
|
let content;
|
|
7984
8351
|
try {
|
|
7985
|
-
content =
|
|
8352
|
+
content = readRepoText(join(repoPath, file), "utf8");
|
|
7986
8353
|
} catch {
|
|
7987
8354
|
continue;
|
|
7988
8355
|
}
|
|
@@ -8086,7 +8453,7 @@ function findClaimRealityReconciliationArtifacts(repoPath, repoFiles) {
|
|
|
8086
8453
|
function isAuthenticatedClaimRealityArtifact(repoPath, file) {
|
|
8087
8454
|
const fullPath = join(repoPath, file);
|
|
8088
8455
|
if (!isRegularFile(fullPath)) return false;
|
|
8089
|
-
const contents =
|
|
8456
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
8090
8457
|
if (/\.json$/i.test(file)) {
|
|
8091
8458
|
return parseJsonObject(contents)?.artifactKind === "claim_reality_reconciliation";
|
|
8092
8459
|
}
|
|
@@ -8125,7 +8492,7 @@ function isCoverageConfig(repoPath, file) {
|
|
|
8125
8492
|
function packageJsonHasCoverageTooling(repoPath, file) {
|
|
8126
8493
|
const fullPath = join(repoPath, file);
|
|
8127
8494
|
if (!isRegularFile(fullPath)) return false;
|
|
8128
|
-
const parsed = parseJsonObject(
|
|
8495
|
+
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
8129
8496
|
if (!parsed) return false;
|
|
8130
8497
|
const scripts = parsed.scripts;
|
|
8131
8498
|
if (typeof scripts === "object" && scripts !== null && !Array.isArray(scripts)) {
|
|
@@ -8599,7 +8966,7 @@ function findRuntimeContainerEntrypointFile(repoPath, repoFiles) {
|
|
|
8599
8966
|
return repoFiles.filter((file) => /(^|\/)Dockerfile$/i.test(file) && isAuthoredProductionPath(file)).find((file) => {
|
|
8600
8967
|
const fullPath = join(repoPath, file);
|
|
8601
8968
|
if (!isRegularFile(fullPath)) return false;
|
|
8602
|
-
const lines =
|
|
8969
|
+
const lines = readRepoText(fullPath, "utf8").split(/\r?\n/);
|
|
8603
8970
|
const finalStageStart = lines.reduce(
|
|
8604
8971
|
(last, line, index) => /^\s*FROM\b/i.test(line) ? index : last,
|
|
8605
8972
|
0
|
|
@@ -8648,7 +9015,7 @@ function packageJsonHasAuditScript(repoPath, manifest) {
|
|
|
8648
9015
|
function packageJsonHasHeavyTestDependency(repoPath, manifest) {
|
|
8649
9016
|
const fullPath = join(repoPath, manifest);
|
|
8650
9017
|
if (!isRegularFile(fullPath)) return false;
|
|
8651
|
-
const parsed = parseJsonObject(
|
|
9018
|
+
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
8652
9019
|
if (!parsed) return false;
|
|
8653
9020
|
const names = [
|
|
8654
9021
|
...Object.keys(isRecord(parsed.dependencies) ? parsed.dependencies : {}),
|
|
@@ -8659,7 +9026,7 @@ function packageJsonHasHeavyTestDependency(repoPath, manifest) {
|
|
|
8659
9026
|
function packageJsonDescribesPackagedApplication(repoPath, manifest, repoFiles) {
|
|
8660
9027
|
const fullPath = join(repoPath, manifest);
|
|
8661
9028
|
if (!isRegularFile(fullPath)) return false;
|
|
8662
|
-
const parsed = parseJsonObject(
|
|
9029
|
+
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
8663
9030
|
if (!parsed) return false;
|
|
8664
9031
|
const scripts = isRecord(parsed.scripts) ? Object.values(parsed.scripts) : [];
|
|
8665
9032
|
const dependencies = {
|
|
@@ -8684,7 +9051,7 @@ function findTauriApplicationMetadata(repoPath, manifest, repoFiles) {
|
|
|
8684
9051
|
if (!repoFiles.includes(file)) return false;
|
|
8685
9052
|
const fullPath = join(repoPath, file);
|
|
8686
9053
|
if (!isRegularFile(fullPath)) return false;
|
|
8687
|
-
const config = parseJsonObject(
|
|
9054
|
+
const config = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
8688
9055
|
const packageMetadata = isRecord(config?.package) ? config.package : null;
|
|
8689
9056
|
return isNonEmptyString(config?.productName) || isNonEmptyString(config?.identifier) || packageMetadata != null && isNonEmptyString(packageMetadata.productName);
|
|
8690
9057
|
});
|
|
@@ -8731,12 +9098,12 @@ function isRegularFile(path) {
|
|
|
8731
9098
|
function fileContains(repoPath, file, pattern) {
|
|
8732
9099
|
const fullPath = join(repoPath, file);
|
|
8733
9100
|
if (!isRegularFile(fullPath)) return false;
|
|
8734
|
-
return pattern.test(
|
|
9101
|
+
return pattern.test(readRepoText(fullPath, "utf8"));
|
|
8735
9102
|
}
|
|
8736
9103
|
function findCommittedSecretInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
8737
9104
|
const fullPath = join(repoPath, file);
|
|
8738
9105
|
if (!isRegularFile(fullPath)) return null;
|
|
8739
|
-
const contents =
|
|
9106
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
8740
9107
|
const isTestCredentialConfiguration = useV39Detectors && isLikelyTestCredentialConfiguration(file, contents);
|
|
8741
9108
|
const secretScanContents = prepareCredentialScanContents(
|
|
8742
9109
|
contents,
|
|
@@ -8829,7 +9196,7 @@ function isLikelyTestCredentialConfiguration(file, contents) {
|
|
|
8829
9196
|
function hasTemplateOnlyEnvContent(repoPath, file) {
|
|
8830
9197
|
const fullPath = join(repoPath, file);
|
|
8831
9198
|
if (!isRegularFile(fullPath)) return false;
|
|
8832
|
-
const substantiveLines =
|
|
9199
|
+
const substantiveLines = readRepoText(fullPath, "utf8").split(/\r?\n/).filter((line) => line.trim().length > 0 && !/^\s*#/.test(line));
|
|
8833
9200
|
if (substantiveLines.length === 0) return true;
|
|
8834
9201
|
const assignments = substantiveLines.map(
|
|
8835
9202
|
(line) => /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=\s*(.*?)\s*$/.exec(line)?.[1] ?? null
|
|
@@ -8844,7 +9211,7 @@ function hasTemplateOnlyEnvContent(repoPath, file) {
|
|
|
8844
9211
|
function findSecretFingerprintsInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
8845
9212
|
const fullPath = join(repoPath, file);
|
|
8846
9213
|
if (!isRegularFile(fullPath)) return [];
|
|
8847
|
-
const contents =
|
|
9214
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
8848
9215
|
const scanContents = prepareCredentialScanContents(
|
|
8849
9216
|
contents,
|
|
8850
9217
|
file,
|
|
@@ -8995,8 +9362,18 @@ function secretEvidenceLabel(prefix, match) {
|
|
|
8995
9362
|
function isSecretIdentifierCharacter(characterCode) {
|
|
8996
9363
|
return characterCode >= 48 && characterCode <= 57 || characterCode >= 65 && characterCode <= 90 || characterCode === 95 || characterCode >= 97 && characterCode <= 122;
|
|
8997
9364
|
}
|
|
8998
|
-
|
|
8999
|
-
|
|
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
|
+
);
|
|
9000
9377
|
const head = excludeHead ? readGitHead(repoPath) : null;
|
|
9001
9378
|
let scannedFiles = 0;
|
|
9002
9379
|
let envPathEvidence = null;
|
|
@@ -9009,7 +9386,7 @@ function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFinger
|
|
|
9009
9386
|
return { evidence: null, envPathEvidence, truncated: true };
|
|
9010
9387
|
}
|
|
9011
9388
|
scannedFiles += 1;
|
|
9012
|
-
const contents = readGitBlob(repoPath, entry.commit, entry.path);
|
|
9389
|
+
const contents = readGitBlob(repoPath, entry.commit, entry.path, scanLimitations);
|
|
9013
9390
|
if (!contents) continue;
|
|
9014
9391
|
const scanContents = prepareCredentialScanContents(
|
|
9015
9392
|
contents,
|
|
@@ -9052,7 +9429,7 @@ function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFinger
|
|
|
9052
9429
|
}
|
|
9053
9430
|
return { evidence: null, envPathEvidence, truncated: false };
|
|
9054
9431
|
}
|
|
9055
|
-
function readCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
9432
|
+
function readCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors = false) {
|
|
9056
9433
|
const entries = [];
|
|
9057
9434
|
const seen = /* @__PURE__ */ new Set();
|
|
9058
9435
|
const appendEntry = (entry) => {
|
|
@@ -9061,14 +9438,12 @@ function readCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
|
9061
9438
|
seen.add(key);
|
|
9062
9439
|
entries.push(entry);
|
|
9063
9440
|
};
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
const output = execFileSync(
|
|
9071
|
-
"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(
|
|
9072
9447
|
[
|
|
9073
9448
|
"diff-tree",
|
|
9074
9449
|
"--stdin",
|
|
@@ -9076,39 +9451,53 @@ function readCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
|
9076
9451
|
"--name-only",
|
|
9077
9452
|
"-r",
|
|
9078
9453
|
"--diff-filter=AM",
|
|
9079
|
-
"
|
|
9454
|
+
"-z",
|
|
9455
|
+
"--pretty=format:commit:%H%x00"
|
|
9080
9456
|
],
|
|
9081
|
-
{
|
|
9082
|
-
cwd: repoPath,
|
|
9083
|
-
encoding: "utf8",
|
|
9084
|
-
input: commits,
|
|
9085
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
9086
|
-
}
|
|
9457
|
+
{ cwd: repoPath, input: commitsResult.stdout }
|
|
9087
9458
|
);
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
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 });
|
|
9093
9479
|
}
|
|
9094
|
-
const path = line.trim();
|
|
9095
|
-
if (!currentCommit || !path) continue;
|
|
9096
|
-
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
9097
|
-
appendEntry({ commit: currentCommit, path });
|
|
9098
9480
|
}
|
|
9099
|
-
} catch {
|
|
9100
9481
|
}
|
|
9101
|
-
for (const entry of readDeletedCredentialHistoryEntries(
|
|
9482
|
+
for (const entry of readDeletedCredentialHistoryEntries(
|
|
9483
|
+
repoPath,
|
|
9484
|
+
scanLimitations,
|
|
9485
|
+
useV36Detectors
|
|
9486
|
+
)) {
|
|
9102
9487
|
appendEntry(entry);
|
|
9103
9488
|
}
|
|
9104
9489
|
return entries;
|
|
9105
9490
|
}
|
|
9106
|
-
function readDeletedCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
9107
|
-
const deletedPaths = readDeletedCredentialHistoryPaths(
|
|
9491
|
+
function readDeletedCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors = false) {
|
|
9492
|
+
const deletedPaths = readDeletedCredentialHistoryPaths(
|
|
9493
|
+
repoPath,
|
|
9494
|
+
scanLimitations,
|
|
9495
|
+
useV36Detectors
|
|
9496
|
+
);
|
|
9108
9497
|
const entries = [];
|
|
9109
9498
|
const seen = /* @__PURE__ */ new Set();
|
|
9110
9499
|
for (const path of deletedPaths) {
|
|
9111
|
-
for (const commit of readHistoryCommitsForPath(repoPath, path)) {
|
|
9500
|
+
for (const commit of readHistoryCommitsForPath(repoPath, path, scanLimitations)) {
|
|
9112
9501
|
const key = `${commit}:${path}`;
|
|
9113
9502
|
if (seen.has(key)) continue;
|
|
9114
9503
|
seen.add(key);
|
|
@@ -9117,47 +9506,51 @@ function readDeletedCredentialHistoryEntries(repoPath, useV36Detectors = false)
|
|
|
9117
9506
|
}
|
|
9118
9507
|
return entries;
|
|
9119
9508
|
}
|
|
9120
|
-
function readDeletedCredentialHistoryPaths(repoPath, useV36Detectors = false) {
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
"
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
if (!path || seen.has(path)) continue;
|
|
9137
|
-
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
9138
|
-
seen.add(path);
|
|
9139
|
-
paths.push(path);
|
|
9140
|
-
}
|
|
9141
|
-
return paths;
|
|
9142
|
-
} 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");
|
|
9143
9525
|
return [];
|
|
9144
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;
|
|
9145
9542
|
}
|
|
9146
|
-
function readHistoryCommitsForPath(repoPath, path) {
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
9155
|
-
}
|
|
9156
|
-
);
|
|
9157
|
-
return output.split("\n").map((line) => line.trim()).filter((line) => /^[0-9a-f]{40}$/i.test(line));
|
|
9158
|
-
} 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");
|
|
9159
9551
|
return [];
|
|
9160
9552
|
}
|
|
9553
|
+
return historyResult.stdout.split("\n").map((line) => line.trim()).filter((line) => /^[0-9a-f]{40}$/i.test(line));
|
|
9161
9554
|
}
|
|
9162
9555
|
function isCredentialHistoryPath(path, _useV36Detectors = false) {
|
|
9163
9556
|
if (isHardExcludedPath(path)) return false;
|
|
@@ -9174,17 +9567,23 @@ function isEnvHistoryPath(path, useV36Detectors = false, useV47Detectors = false
|
|
|
9174
9567
|
const basename3 = path.toLowerCase().split("/").at(-1) ?? path.toLowerCase();
|
|
9175
9568
|
return (useV36Detectors ? /^\.env(?:\.|$)/ : /\.env(?:\.|$)/).test(basename3);
|
|
9176
9569
|
}
|
|
9177
|
-
function readGitBlob(repoPath, commit, path) {
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
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
|
+
);
|
|
9186
9584
|
return null;
|
|
9187
9585
|
}
|
|
9586
|
+
return blobResult.stdout;
|
|
9188
9587
|
}
|
|
9189
9588
|
var ALL_CAPS_SNAKE_PLACEHOLDER_PATTERN = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/;
|
|
9190
9589
|
function isPlaceholderSecretValue(value) {
|
|
@@ -9214,7 +9613,7 @@ function hasSuspiciousDependencies(repoPath, manifest, useV47Detectors = false)
|
|
|
9214
9613
|
if (!manifest.endsWith("package.json")) return false;
|
|
9215
9614
|
const fullPath = join(repoPath, manifest);
|
|
9216
9615
|
if (!isRegularFile(fullPath)) return false;
|
|
9217
|
-
const parsed = parseJsonObject(
|
|
9616
|
+
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
9218
9617
|
if (!parsed) return false;
|
|
9219
9618
|
const dependencies = isRecord(parsed.dependencies) ? parsed.dependencies : {};
|
|
9220
9619
|
const devDependencies = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
|
|
@@ -9230,24 +9629,26 @@ function hasSuspiciousDependencies(repoPath, manifest, useV47Detectors = false)
|
|
|
9230
9629
|
return placeholderIdentity && placeholderMetadata;
|
|
9231
9630
|
}
|
|
9232
9631
|
function readRecentCommits(repoPath) {
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
return { sha, subject };
|
|
9243
|
-
}).filter((commit) => commit.sha.length >= 7);
|
|
9244
|
-
} catch {
|
|
9245
|
-
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"));
|
|
9246
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);
|
|
9247
9648
|
}
|
|
9248
9649
|
function readPackageScripts(packageJsonPath) {
|
|
9249
9650
|
if (!isRegularFile(packageJsonPath)) return /* @__PURE__ */ new Map();
|
|
9250
|
-
const parsed = parseJsonObject(
|
|
9651
|
+
const parsed = parseJsonObject(readRepoText(packageJsonPath, "utf8"));
|
|
9251
9652
|
const scripts = parsed?.scripts;
|
|
9252
9653
|
if (!isRecord(scripts)) return /* @__PURE__ */ new Map();
|
|
9253
9654
|
return new Map(
|
|
@@ -9258,7 +9659,7 @@ function readPackageScripts(packageJsonPath) {
|
|
|
9258
9659
|
}
|
|
9259
9660
|
function evidenceForRelative(repoPath, path, kind, label) {
|
|
9260
9661
|
const fullPath = join(repoPath, path);
|
|
9261
|
-
const contents = isRegularFile(fullPath) ?
|
|
9662
|
+
const contents = isRegularFile(fullPath) ? readRepoText(fullPath, "utf8") : "";
|
|
9262
9663
|
return {
|
|
9263
9664
|
kind,
|
|
9264
9665
|
label,
|
|
@@ -9271,7 +9672,7 @@ function evidenceForRelative(repoPath, path, kind, label) {
|
|
|
9271
9672
|
}
|
|
9272
9673
|
function evidenceForRelativeAtLine(repoPath, path, kind, label, line) {
|
|
9273
9674
|
const fullPath = join(repoPath, path);
|
|
9274
|
-
const contents = isRegularFile(fullPath) ?
|
|
9675
|
+
const contents = isRegularFile(fullPath) ? readRepoText(fullPath, "utf8") : "";
|
|
9275
9676
|
return {
|
|
9276
9677
|
kind,
|
|
9277
9678
|
label,
|
|
@@ -9298,43 +9699,46 @@ function firstMeaningfulLine(contents) {
|
|
|
9298
9699
|
return index === -1 ? 1 : index + 1;
|
|
9299
9700
|
}
|
|
9300
9701
|
function readGitHead(repoPath) {
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
9306
|
-
}).trim();
|
|
9307
|
-
} catch {
|
|
9308
|
-
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"));
|
|
9309
9706
|
}
|
|
9707
|
+
return headResult.stdout.trim() || null;
|
|
9310
9708
|
}
|
|
9311
|
-
function hasEnvPathInGitHistory(repoPath) {
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
9320
|
-
maxBuffer: 512e3
|
|
9321
|
-
}
|
|
9322
|
-
).trim();
|
|
9323
|
-
if (!output) return false;
|
|
9324
|
-
return output.split("\n").some((p) => /(?:^|\/)\.env(?:\.|$)/i.test(p.trim()));
|
|
9325
|
-
} 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");
|
|
9326
9717
|
return false;
|
|
9327
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
|
+
});
|
|
9328
9729
|
}
|
|
9329
9730
|
|
|
9330
9731
|
// src/witan/public-scan.ts
|
|
9732
|
+
init_esm_shims();
|
|
9331
9733
|
import { resolve as resolve2 } from "path";
|
|
9332
9734
|
|
|
9333
9735
|
// src/witan/ingest.ts
|
|
9334
|
-
|
|
9736
|
+
init_esm_shims();
|
|
9737
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
|
|
9335
9738
|
import { join as join2 } from "path";
|
|
9336
9739
|
|
|
9337
9740
|
// src/witan/finding-limits.ts
|
|
9741
|
+
init_esm_shims();
|
|
9338
9742
|
var RULE_ID_MAX = 200;
|
|
9339
9743
|
var MESSAGE_MAX = 500;
|
|
9340
9744
|
var LOCATION_MAX = 700;
|
|
@@ -9353,7 +9757,8 @@ function clampFinding(finding) {
|
|
|
9353
9757
|
}
|
|
9354
9758
|
|
|
9355
9759
|
// src/witan/generic-adapter.ts
|
|
9356
|
-
|
|
9760
|
+
init_esm_shims();
|
|
9761
|
+
import { readFileSync } from "fs";
|
|
9357
9762
|
var VALID_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
9358
9763
|
"A1",
|
|
9359
9764
|
"A2",
|
|
@@ -9400,7 +9805,8 @@ function parseGenericJson(raw) {
|
|
|
9400
9805
|
}
|
|
9401
9806
|
|
|
9402
9807
|
// src/witan/sarif-adapter.ts
|
|
9403
|
-
|
|
9808
|
+
init_esm_shims();
|
|
9809
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
9404
9810
|
var DIMENSION_RULES = [
|
|
9405
9811
|
// A2 — injection / secrets / auth (high weight)
|
|
9406
9812
|
{
|
|
@@ -9545,7 +9951,8 @@ function parseSarifJson(raw, extraDimensionRules) {
|
|
|
9545
9951
|
}
|
|
9546
9952
|
|
|
9547
9953
|
// src/witan/scorecard-adapter.ts
|
|
9548
|
-
|
|
9954
|
+
init_esm_shims();
|
|
9955
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
9549
9956
|
var CHECK_RULES = {
|
|
9550
9957
|
// B2 — PR outcome traceability
|
|
9551
9958
|
"Branch-Protection": { dimension: "B2", weight: 0.8 },
|
|
@@ -9654,10 +10061,18 @@ function rawToolName(raw) {
|
|
|
9654
10061
|
if (typeof obj.tool === "string" && obj.tool.length > 0) return obj.tool;
|
|
9655
10062
|
return "scorecard";
|
|
9656
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
|
+
}
|
|
9657
10072
|
function parseIngestFile(filePath, options = {}) {
|
|
9658
10073
|
let raw;
|
|
9659
10074
|
try {
|
|
9660
|
-
raw = JSON.parse(stripBom(
|
|
10075
|
+
raw = JSON.parse(stripBom(readFileSync4(filePath, "utf8")));
|
|
9661
10076
|
} catch (error) {
|
|
9662
10077
|
const message = error instanceof Error ? error.message : String(error);
|
|
9663
10078
|
throw new Error(`Cejel: could not parse ingest file as JSON: ${filePath} (${message})`);
|
|
@@ -9681,6 +10096,8 @@ function parseIngestFile(filePath, options = {}) {
|
|
|
9681
10096
|
}
|
|
9682
10097
|
return signals.map((signal) => ({
|
|
9683
10098
|
...signal,
|
|
10099
|
+
source: sanitizeIngestSource(signal.source),
|
|
10100
|
+
provenance: options.provenance ?? "operator_supplied",
|
|
9684
10101
|
findings: signal.findings.map(clampFinding)
|
|
9685
10102
|
}));
|
|
9686
10103
|
}
|
|
@@ -9689,7 +10106,7 @@ function expandIngestPattern(pattern) {
|
|
|
9689
10106
|
const lastSlash = pattern.lastIndexOf("/");
|
|
9690
10107
|
const dir = lastSlash === -1 ? "." : pattern.slice(0, lastSlash);
|
|
9691
10108
|
const filePattern = lastSlash === -1 ? pattern : pattern.slice(lastSlash + 1);
|
|
9692
|
-
const regexSource = filePattern.replace(/[
|
|
10109
|
+
const regexSource = filePattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
9693
10110
|
const regex = new RegExp(`^${regexSource}$`);
|
|
9694
10111
|
let entries;
|
|
9695
10112
|
try {
|
|
@@ -9710,7 +10127,11 @@ function discoverIngestInputs(repoPath) {
|
|
|
9710
10127
|
return entries.filter((name) => /\.(sarif|json)$/i.test(name)).sort().map((name) => join2(inputsDir, name));
|
|
9711
10128
|
}
|
|
9712
10129
|
|
|
10130
|
+
// src/witan/scoring.ts
|
|
10131
|
+
init_esm_shims();
|
|
10132
|
+
|
|
9713
10133
|
// src/witan/rubric.ts
|
|
10134
|
+
init_esm_shims();
|
|
9714
10135
|
var WITAN_RUBRIC = [
|
|
9715
10136
|
{
|
|
9716
10137
|
id: "A1",
|
|
@@ -9777,6 +10198,9 @@ var MAX_SIGNAL_ADJUSTMENT = 0.8;
|
|
|
9777
10198
|
function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
9778
10199
|
const parsedInput = WitanReportInputSchema.parse(input);
|
|
9779
10200
|
const signalsByCriterion = mergeSignalsByCriterion(parsedInput.signals);
|
|
10201
|
+
const readFailureAbstentions = new Set(
|
|
10202
|
+
[...signalsByCriterion.values()].filter((signal) => signal.insufficientData === true).map((signal) => signal.criterionId)
|
|
10203
|
+
);
|
|
9780
10204
|
const hasInputSignals = inputSignals != null && inputSignals.length > 0;
|
|
9781
10205
|
const inputSignalsByDimension = /* @__PURE__ */ new Map();
|
|
9782
10206
|
if (hasInputSignals && inputSignals) {
|
|
@@ -9840,7 +10264,8 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
9840
10264
|
const categoryScoreMap = {};
|
|
9841
10265
|
for (const category of categoryOrder) {
|
|
9842
10266
|
categoryScoreMap[category] = averageScore(
|
|
9843
|
-
criteria.filter((criterion) => criterion.category === category)
|
|
10267
|
+
criteria.filter((criterion) => criterion.category === category),
|
|
10268
|
+
readFailureAbstentions
|
|
9844
10269
|
);
|
|
9845
10270
|
}
|
|
9846
10271
|
const firstCategory = categoryOrder[0];
|
|
@@ -9863,7 +10288,6 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
9863
10288
|
productSlug: parsedInput.productSlug,
|
|
9864
10289
|
productDisplayName: parsedInput.productDisplayName,
|
|
9865
10290
|
repo: parsedInput.repo,
|
|
9866
|
-
generatedAt: parsedInput.generatedAt,
|
|
9867
10291
|
rubricVersion: parsedInput.rubricVersion,
|
|
9868
10292
|
verdict: abstained ? "insufficient_source" : witanVerdictForScore(overallScore),
|
|
9869
10293
|
codeTrustScore: abstained ? null : codeTrustScore,
|
|
@@ -9871,6 +10295,8 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
9871
10295
|
overallScore: abstained ? null : overallScore,
|
|
9872
10296
|
criteria,
|
|
9873
10297
|
...consumedSignals.length > 0 ? { consumedSignals } : {},
|
|
10298
|
+
...parsedInput.scanLimitations.length > 0 ? { scanLimitations: parsedInput.scanLimitations } : {},
|
|
10299
|
+
...parsedInput.contentReadSummary ? { contentReadSummary: parsedInput.contentReadSummary } : {},
|
|
9874
10300
|
// Only surface the full per-category map for rubrics with more than two buckets —
|
|
9875
10301
|
// the default two-category rubric is fully represented by codeTrustScore/processTrustScore.
|
|
9876
10302
|
...!abstained && categoryOrder.length > 2 ? { categoryScores: categoryScoreMap } : {},
|
|
@@ -9896,6 +10322,7 @@ function applySignalAdjustment(nativeScore, dimension, signals) {
|
|
|
9896
10322
|
totalRawAdj += signalAdj;
|
|
9897
10323
|
summaries.push({
|
|
9898
10324
|
source: signal.source,
|
|
10325
|
+
provenance: signal.provenance ?? "operator_supplied",
|
|
9899
10326
|
dimension,
|
|
9900
10327
|
findingCount: signal.findings.length,
|
|
9901
10328
|
severityBreakdown: { critical: criticalCount, warning: warningCount, info: infoCount },
|
|
@@ -9961,8 +10388,9 @@ function mergeSignalsByCriterion(signals) {
|
|
|
9961
10388
|
findings: [...existing.findings, ...signal.findings],
|
|
9962
10389
|
metrics: [...existing.metrics ?? [], ...signal.metrics ?? []],
|
|
9963
10390
|
notes: signal.notes ?? existing.notes,
|
|
9964
|
-
//
|
|
9965
|
-
|
|
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 } : {}
|
|
9966
10394
|
});
|
|
9967
10395
|
}
|
|
9968
10396
|
return merged;
|
|
@@ -9983,6 +10411,15 @@ function scoreCriterion(signal, rubricVersion) {
|
|
|
9983
10411
|
metrics: []
|
|
9984
10412
|
};
|
|
9985
10413
|
}
|
|
10414
|
+
if (signal.insufficientData === true) {
|
|
10415
|
+
return {
|
|
10416
|
+
score: 0,
|
|
10417
|
+
status: unmeasuredStatus(rubricVersion),
|
|
10418
|
+
evidence: [],
|
|
10419
|
+
findings: [],
|
|
10420
|
+
metrics: []
|
|
10421
|
+
};
|
|
10422
|
+
}
|
|
9986
10423
|
if (signal.notApplicable === true) {
|
|
9987
10424
|
return {
|
|
9988
10425
|
score: 0,
|
|
@@ -10358,9 +10795,9 @@ function ensureFindingsExplainStatus(criterionId, score, status, findings, evide
|
|
|
10358
10795
|
}
|
|
10359
10796
|
];
|
|
10360
10797
|
}
|
|
10361
|
-
function averageScore(criteria) {
|
|
10798
|
+
function averageScore(criteria, readFailureAbstentions = /* @__PURE__ */ new Set()) {
|
|
10362
10799
|
const applicable = criteria.filter(
|
|
10363
|
-
(criterion) => criterion.status !== "not_applicable" && criterion.status !== "insufficient_data"
|
|
10800
|
+
(criterion) => criterion.status !== "not_applicable" && (criterion.status !== "insufficient_data" || readFailureAbstentions.has(criterion.id))
|
|
10364
10801
|
);
|
|
10365
10802
|
if (applicable.length === 0) return 0;
|
|
10366
10803
|
const total = applicable.reduce((sum, criterion) => sum + criterion.score, 0);
|
|
@@ -10395,7 +10832,7 @@ function scoreRepoWithPublicCejel(options) {
|
|
|
10395
10832
|
}
|
|
10396
10833
|
function resolvePublicIngestSignals(options) {
|
|
10397
10834
|
const seen = /* @__PURE__ */ new Set();
|
|
10398
|
-
const
|
|
10835
|
+
const explicitFiles = [];
|
|
10399
10836
|
for (const pattern of options.ingestPatterns ?? []) {
|
|
10400
10837
|
const matches = expandIngestPattern(pattern);
|
|
10401
10838
|
if (matches.length === 0 && options.warnOnEmptyIngestMatch) {
|
|
@@ -10406,20 +10843,28 @@ function resolvePublicIngestSignals(options) {
|
|
|
10406
10843
|
const resolved = resolve2(match);
|
|
10407
10844
|
if (seen.has(resolved)) continue;
|
|
10408
10845
|
seen.add(resolved);
|
|
10409
|
-
|
|
10846
|
+
explicitFiles.push(match);
|
|
10410
10847
|
}
|
|
10411
10848
|
}
|
|
10849
|
+
const signals = explicitFiles.flatMap(
|
|
10850
|
+
(file) => parseIngestFile(file, { provenance: "operator_supplied" })
|
|
10851
|
+
);
|
|
10852
|
+
if (!options.autoDiscoverIngest) return signals;
|
|
10412
10853
|
for (const discovered of discoverIngestInputs(options.repoPath)) {
|
|
10413
10854
|
const resolved = resolve2(discovered);
|
|
10414
10855
|
if (seen.has(resolved)) continue;
|
|
10415
10856
|
seen.add(resolved);
|
|
10416
|
-
|
|
10857
|
+
signals.push(...parseIngestFile(discovered, { provenance: "auto_discovered" }));
|
|
10417
10858
|
}
|
|
10418
|
-
return
|
|
10859
|
+
return signals;
|
|
10419
10860
|
}
|
|
10420
10861
|
|
|
10862
|
+
// src/scan.ts
|
|
10863
|
+
init_esm_shims();
|
|
10864
|
+
|
|
10421
10865
|
// src/product-identity.ts
|
|
10422
|
-
|
|
10866
|
+
init_esm_shims();
|
|
10867
|
+
import { existsSync, readFileSync as readFileSync5 } from "fs";
|
|
10423
10868
|
import { basename as basename2, join as join3 } from "path";
|
|
10424
10869
|
var SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/;
|
|
10425
10870
|
var FALLBACK_SLUG = "repo";
|
|
@@ -10436,7 +10881,7 @@ function deriveProductIdentity(repoPath) {
|
|
|
10436
10881
|
function readPackageName(packageJsonPath) {
|
|
10437
10882
|
if (!existsSync(packageJsonPath)) return void 0;
|
|
10438
10883
|
try {
|
|
10439
|
-
const parsed = JSON.parse(
|
|
10884
|
+
const parsed = JSON.parse(readFileSync5(packageJsonPath, "utf8"));
|
|
10440
10885
|
if (typeof parsed === "object" && parsed !== null && "name" in parsed) {
|
|
10441
10886
|
const name = parsed.name;
|
|
10442
10887
|
if (typeof name === "string" && name.trim().length > 0) return name;
|
|
@@ -10453,7 +10898,11 @@ function slugify(raw) {
|
|
|
10453
10898
|
return FALLBACK_SLUG;
|
|
10454
10899
|
}
|
|
10455
10900
|
|
|
10901
|
+
// src/summary.ts
|
|
10902
|
+
init_esm_shims();
|
|
10903
|
+
|
|
10456
10904
|
// src/finding-presentation.ts
|
|
10905
|
+
init_esm_shims();
|
|
10457
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\.$/;
|
|
10458
10907
|
var REMEDIATION_BY_METRIC2 = {
|
|
10459
10908
|
test_to_source_ratio: "add concrete regression tests for the implementation surface",
|
|
@@ -10565,13 +11014,14 @@ function buildWitanCliSummary(report) {
|
|
|
10565
11014
|
const common = {
|
|
10566
11015
|
productSlug: report.productSlug,
|
|
10567
11016
|
productDisplayName: report.productDisplayName,
|
|
10568
|
-
generatedAt: report.generatedAt,
|
|
10569
11017
|
findingCount: allFindings.length,
|
|
10570
11018
|
topFindings: sorted.slice(0, TOP_FINDINGS_LIMIT),
|
|
10571
11019
|
contributingSources,
|
|
10572
11020
|
externalSources,
|
|
10573
11021
|
externalFindingCount: allExternalFindings.length,
|
|
10574
|
-
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 } : {}
|
|
10575
11025
|
};
|
|
10576
11026
|
if (report.verdict === "insufficient_source") {
|
|
10577
11027
|
return {
|
|
@@ -10595,21 +11045,24 @@ function buildWitanCliSummary(report) {
|
|
|
10595
11045
|
// src/scan.ts
|
|
10596
11046
|
function runCejelScan(options) {
|
|
10597
11047
|
const identity = deriveProductIdentity(options.repoPath);
|
|
11048
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10598
11049
|
const report = scoreRepoWithPublicCejel({
|
|
10599
11050
|
productSlug: identity.productSlug,
|
|
10600
11051
|
productDisplayName: options.productDisplayName ?? identity.productDisplayName,
|
|
10601
11052
|
repoPath: options.repoPath,
|
|
11053
|
+
generatedAt,
|
|
10602
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,
|
|
10603
11059
|
warnOnEmptyIngestMatch: options.warnOnEmptyIngestMatch
|
|
10604
11060
|
});
|
|
10605
11061
|
const summary = buildWitanCliSummary(report);
|
|
10606
|
-
return { report, summary };
|
|
11062
|
+
return { report, summary, generatedAt };
|
|
10607
11063
|
}
|
|
10608
11064
|
|
|
10609
11065
|
export {
|
|
10610
|
-
__commonJS,
|
|
10611
|
-
__export,
|
|
10612
|
-
__toESM,
|
|
10613
11066
|
ZodOptional,
|
|
10614
11067
|
ZodFirstPartyTypeKind,
|
|
10615
11068
|
objectType,
|