@cejel/cejel 0.4.7 → 0.4.9
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 +59 -24
- package/dist/calibration/llm-detector.js +5 -5
- package/dist/{chunk-UYJBSQCV.js → chunk-GKYTLWQK.js} +1 -1
- package/dist/{chunk-OIGCYULH.js → chunk-IHT7B6L7.js} +1 -1
- package/dist/{chunk-EP7YYWEP.js → chunk-IN2LPGGU.js} +2 -2
- package/dist/{chunk-XHAETB7N.js → chunk-IQ42TE5H.js} +1 -1
- package/dist/{chunk-ZNVILRHK.js → chunk-PTZU6G4E.js} +65 -20
- package/dist/{chunk-QTLLASOO.js → chunk-TUUNJP27.js} +473 -172
- package/dist/index.js +7 -5
- package/dist/mcp/index.js +5 -5
- package/dist/packs/d-series/index.js +4 -4
- package/dist/packs/decision-contracts/index.js +3 -3
- package/package.json +12 -15
|
@@ -5,24 +5,26 @@ import {
|
|
|
5
5
|
describeGitFailure,
|
|
6
6
|
execGit,
|
|
7
7
|
isExpectedGitAbsence
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IHT7B6L7.js";
|
|
9
9
|
import {
|
|
10
10
|
external_exports
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-GKYTLWQK.js";
|
|
12
12
|
import {
|
|
13
13
|
MAX_REPOSITORY_CONTENT_BYTES,
|
|
14
|
+
abstainSignalOnWithheldPaths,
|
|
15
|
+
classifyAbstentionReason,
|
|
14
16
|
contentReadSignalKey,
|
|
15
17
|
init_esm_shims,
|
|
16
18
|
readRepoText,
|
|
17
19
|
readRepoTextPrefix,
|
|
18
20
|
recordContentSkip,
|
|
19
|
-
recordContentSkipForCriteria,
|
|
20
21
|
recordFilesystemSkip,
|
|
22
|
+
registerWithheldRepoPath,
|
|
21
23
|
trackContentReads,
|
|
22
24
|
withContentReadCriterion,
|
|
23
25
|
withContentReadSignal,
|
|
24
26
|
withContentReadSignals
|
|
25
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-PTZU6G4E.js";
|
|
26
28
|
|
|
27
29
|
// src/witan/schemas.ts
|
|
28
30
|
init_esm_shims();
|
|
@@ -162,6 +164,13 @@ var WitanCriterionMetricSchema = external_exports.object({
|
|
|
162
164
|
condition: external_exports.string().min(1).max(240).optional()
|
|
163
165
|
}).strict().optional()
|
|
164
166
|
}).strict();
|
|
167
|
+
var WitanContentReadSkipReasonSchema = external_exports.enum([
|
|
168
|
+
"unreadable",
|
|
169
|
+
"too_large",
|
|
170
|
+
"excluded_by_extension",
|
|
171
|
+
"denied_path",
|
|
172
|
+
"non_regular_file"
|
|
173
|
+
]);
|
|
165
174
|
var WitanCriterionSignalSchema = external_exports.object({
|
|
166
175
|
criterionId: WitanCriterionIdSchema,
|
|
167
176
|
positiveEvidence: external_exports.array(WitanEvidencePointerSchema).default([]),
|
|
@@ -171,9 +180,19 @@ var WitanCriterionSignalSchema = external_exports.object({
|
|
|
171
180
|
notApplicable: external_exports.literal(true).optional(),
|
|
172
181
|
// A collector reached relevant files but could not read their contents. This is a
|
|
173
182
|
// measurement gap, never negative evidence: scoreCriterion maps it to insufficient_data.
|
|
174
|
-
insufficientData: external_exports.literal(true).optional()
|
|
183
|
+
insufficientData: external_exports.literal(true).optional(),
|
|
184
|
+
// Why insufficientData is true. Omitted (or 'unreadable'/'denied_path') means an
|
|
185
|
+
// environmental read failure — evidence Cejel expected to see and could not; that stays in
|
|
186
|
+
// the composite denominator at score 0 so losing evidence never flatters the certificate
|
|
187
|
+
// (ADR-0001 does not cover this case — it is genuine evidence loss). 'too_large',
|
|
188
|
+
// 'excluded_by_extension', and 'non_regular_file' are self-imposed coverage limits Cejel
|
|
189
|
+
// chose itself: those are excluded from the composite exactly like ordinary
|
|
190
|
+
// insufficient_data (ADR-0001: coverage is disclosed, never discounts a score).
|
|
191
|
+
insufficientDataReason: WitanContentReadSkipReasonSchema.optional()
|
|
175
192
|
}).strict().refine((signal) => !(signal.notApplicable && signal.insufficientData), {
|
|
176
193
|
message: "A criterion cannot be both not applicable and insufficient data."
|
|
194
|
+
}).refine((signal) => signal.insufficientDataReason === void 0 || signal.insufficientData === true, {
|
|
195
|
+
message: "insufficientDataReason may only be set alongside insufficientData: true."
|
|
177
196
|
});
|
|
178
197
|
var WitanContentReadSummarySchema = external_exports.object({
|
|
179
198
|
skipped: external_exports.number().int().nonnegative(),
|
|
@@ -296,12 +315,29 @@ var WitanReportCommonSchema = external_exports.object({
|
|
|
296
315
|
productDisplayName: external_exports.string().min(1).max(120),
|
|
297
316
|
repo: WitanRepoRefSchema,
|
|
298
317
|
rubricVersion: external_exports.string().min(1).max(120),
|
|
318
|
+
// The BEHAVIOURAL identity of the rubric named above, beside its NAME. `rubricVersion` is an
|
|
319
|
+
// identifier the tool's author controls and can leave unchanged across a scoring change (this
|
|
320
|
+
// happened under witan-rubric-v17-2026-07-24 in 0.4.9 — see leaderboard/RUBRIC_CHANGELOG.md).
|
|
321
|
+
// This field is the sha256 of the scoring-relevant output of a fixed committed fixture corpus
|
|
322
|
+
// under that rubric, so two certificates naming the same rubric can be checked for behavioural
|
|
323
|
+
// agreement rather than assumed into it. See src/witan/rubric-fingerprint.ts for exactly what
|
|
324
|
+
// the digest covers, what it excludes, and what it does not claim. Optional and absent —
|
|
325
|
+
// never fabricated — for any rubric this build has no measurement for, and absent on every
|
|
326
|
+
// report emitted before report format 1.2; those artifacts' attestations remain valid.
|
|
327
|
+
rubricBehaviourFingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
|
|
299
328
|
// Rubric-driven: length matches whatever rubric produced this report, not a fixed enum.
|
|
300
329
|
criteria: external_exports.array(WitanCriterionScoreSchema).min(1),
|
|
301
330
|
consumedSignals: external_exports.array(WitanConsumedSignalSummarySchema).optional(),
|
|
302
331
|
archetype: WitanRepoArchetypeSchema.optional(),
|
|
303
332
|
scanLimitations: external_exports.array(external_exports.string().min(1).max(1e3)).max(16).optional(),
|
|
304
|
-
contentReadSummary: WitanContentReadSummarySchema.optional()
|
|
333
|
+
contentReadSummary: WitanContentReadSummarySchema.optional(),
|
|
334
|
+
// The @cejel/cejel version that produced this report. Absent on reports from before this
|
|
335
|
+
// field existed and on any caller that does not supply one (createWitanReport never defaults
|
|
336
|
+
// it). Constant within an installed version, so re-running the same version on the same
|
|
337
|
+
// commit stays byte-stable (the exact property v0.3.1 removed generatedAt to protect); it
|
|
338
|
+
// differs across versions, which lets a consumer key incremental reuse on it
|
|
339
|
+
// (goal_cejel_0_4_8_abstention_scoring_fix_2026-09-08).
|
|
340
|
+
toolVersion: external_exports.string().min(1).max(80).optional()
|
|
305
341
|
});
|
|
306
342
|
var WitanScoredReportSchema = WitanReportCommonSchema.extend({
|
|
307
343
|
verdict: WitanScoredVerdictSchema,
|
|
@@ -342,7 +378,7 @@ var WitanReportSchema = external_exports.preprocess((value) => {
|
|
|
342
378
|
}, StrictWitanReportSchema);
|
|
343
379
|
var WITAN_ATTESTATION_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
|
|
344
380
|
var WITAN_ATTESTATION_PREDICATE_TYPE = "https://cejel.dev/attestations/scan/v1";
|
|
345
|
-
var WITAN_REPORT_FORMAT_VERSION = "1.
|
|
381
|
+
var WITAN_REPORT_FORMAT_VERSION = "1.2";
|
|
346
382
|
var WitanAttestationOutcomeSchema = external_exports.discriminatedUnion("status", [
|
|
347
383
|
external_exports.object({
|
|
348
384
|
status: external_exports.literal("scored"),
|
|
@@ -560,6 +596,7 @@ init_esm_shims();
|
|
|
560
596
|
// src/witan/certificate-presentation.ts
|
|
561
597
|
init_esm_shims();
|
|
562
598
|
var CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE = "Product name and slug are caller context and are excluded from certificate byte-comparison claims.";
|
|
599
|
+
var CERTIFICATE_SCOPE_NOTICE = "This certificate is a statement about the repository tree at the revision pinned above, not about the system that tree is part of. Evidence outside that tree \u2014 for example, a test suite kept in a separate repository, or CI configuration that changes into a different working directory before running its checks \u2014 is neither seen by this scan nor claimed to be absent.";
|
|
563
600
|
var CERTIFICATE_METRIC_REGISTRY = {
|
|
564
601
|
test_to_source_ratio: { displayLabel: "Test-to-source file ratio" },
|
|
565
602
|
coverage_percent: { displayLabel: "Static coverage percentage" },
|
|
@@ -1073,6 +1110,7 @@ function buildRelyingPartySummary(report) {
|
|
|
1073
1110
|
"Anything absent from the measured evidence was not established: no measurement and no finding are not clearance."
|
|
1074
1111
|
);
|
|
1075
1112
|
return {
|
|
1113
|
+
scope: CERTIFICATE_SCOPE_NOTICE,
|
|
1076
1114
|
examined: `Cejel examined the repository evidence recorded for ${report.productDisplayName}${revision} under ${report.rubricVersion}.`,
|
|
1077
1115
|
established: `The report established measured results for ${measured.length} of ${applicable.length} applicable rubric dimensions and recorded ${findings} evidence-backed finding${findings === 1 ? "" : "s"}.`,
|
|
1078
1116
|
notEstablished: gaps.join(" "),
|
|
@@ -1285,6 +1323,7 @@ function renderRelyingPartySummary(summary) {
|
|
|
1285
1323
|
return `<section class="relying-party-summary" aria-labelledby="relying-party-heading">
|
|
1286
1324
|
<h2 id="relying-party-heading">How to read this certificate</h2>
|
|
1287
1325
|
<dl>
|
|
1326
|
+
<div><dt>Scope</dt><dd>${escapeHtml(summary.scope)}</dd></div>
|
|
1288
1327
|
<div><dt>What was examined</dt><dd>${escapeHtml(summary.examined)}</dd></div>
|
|
1289
1328
|
<div><dt>What was established</dt><dd>${escapeHtml(summary.established)}</dd></div>
|
|
1290
1329
|
<div><dt>What was not established</dt><dd>${escapeHtml(summary.notEstablished)}</dd></div>
|
|
@@ -2007,29 +2046,56 @@ function inventoryAbsenceFinding(options) {
|
|
|
2007
2046
|
} : {}
|
|
2008
2047
|
};
|
|
2009
2048
|
}
|
|
2010
|
-
function wholesaleCriterionAbstention(signal) {
|
|
2049
|
+
function wholesaleCriterionAbstention(signal, reasons) {
|
|
2050
|
+
const { insufficientDataReason } = classifyAbstentionReason(reasons);
|
|
2011
2051
|
return {
|
|
2012
2052
|
criterionId: signal.criterionId,
|
|
2013
2053
|
positiveEvidence: [],
|
|
2014
2054
|
findings: [],
|
|
2015
2055
|
metrics: [],
|
|
2016
2056
|
insufficientData: true,
|
|
2017
|
-
|
|
2057
|
+
...insufficientDataReason !== void 0 ? { insufficientDataReason } : {},
|
|
2058
|
+
notes: insufficientDataReason !== void 0 ? "Cejel abstained on this criterion because relevant repository content was declined under the repository content size limit or excluded from scanning by policy. This is a disclosed coverage limit, not a read failure, and does not affect the composite score." : "Cejel abstained on this criterion because one or more relevant repository files could not be read. Resolve the content-read limitations and re-scan."
|
|
2018
2059
|
};
|
|
2019
2060
|
}
|
|
2061
|
+
function skipReasonsForCriterion(criterionId, tracked) {
|
|
2062
|
+
const combined = new Set(tracked.criterionSkipReasons.get(criterionId) ?? []);
|
|
2063
|
+
const prefix = contentReadSignalKey(criterionId, "");
|
|
2064
|
+
for (const [key, reasons] of tracked.signalSkipReasons) {
|
|
2065
|
+
if (!key.startsWith(prefix)) continue;
|
|
2066
|
+
for (const reason of reasons) combined.add(reason);
|
|
2067
|
+
}
|
|
2068
|
+
return combined;
|
|
2069
|
+
}
|
|
2070
|
+
function skipReasonsForSignal(criterionId, signalName, tracked) {
|
|
2071
|
+
const reasons = tracked.signalSkipReasons.get(contentReadSignalKey(criterionId, signalName));
|
|
2072
|
+
return reasons ? new Set(reasons) : void 0;
|
|
2073
|
+
}
|
|
2020
2074
|
function buildWitanInputFromRepo(options) {
|
|
2021
2075
|
const tracked = trackContentReads(options.repoPath, () => buildWitanInputFromRepoUntracked(options));
|
|
2022
2076
|
const affectedCriteria = tracked.affectedCriteria;
|
|
2023
2077
|
const rubricVersion = options.rubricVersion ?? WITAN_RUBRIC_VERSION;
|
|
2024
2078
|
if (rubricVersion !== WITAN_RUBRIC_VERSION_V23) {
|
|
2025
|
-
const
|
|
2026
|
-
|
|
2027
|
-
)
|
|
2028
|
-
|
|
2079
|
+
const readFailureCriteria = [];
|
|
2080
|
+
const coverageLimitedCriteria = [];
|
|
2081
|
+
const signals2 = (tracked.value.signals ?? []).map((signal) => {
|
|
2082
|
+
if (!affectedCriteria.has(signal.criterionId)) return signal;
|
|
2083
|
+
const reasons = skipReasonsForCriterion(signal.criterionId, tracked);
|
|
2084
|
+
const { insufficientDataReason } = classifyAbstentionReason(reasons);
|
|
2085
|
+
(insufficientDataReason !== void 0 ? coverageLimitedCriteria : readFailureCriteria).push(
|
|
2086
|
+
signal.criterionId
|
|
2087
|
+
);
|
|
2088
|
+
return wholesaleCriterionAbstention(signal, reasons);
|
|
2089
|
+
});
|
|
2029
2090
|
const scanLimitations2 = [...tracked.value.scanLimitations ?? []];
|
|
2030
|
-
if (
|
|
2091
|
+
if (readFailureCriteria.length > 0 && scanLimitations2.length < 16) {
|
|
2031
2092
|
scanLimitations2.push(
|
|
2032
|
-
`${
|
|
2093
|
+
`${readFailureCriteria.length} ${readFailureCriteria.length === 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.`
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
if (coverageLimitedCriteria.length > 0 && scanLimitations2.length < 16) {
|
|
2097
|
+
scanLimitations2.push(
|
|
2098
|
+
`${coverageLimitedCriteria.length} ${coverageLimitedCriteria.length === 1 ? "criterion" : "criteria"} abstained because relevant repository content was declined under the repository content size limit or excluded from scanning by policy \u2014 a disclosed coverage limit, not a read failure. This does not affect the composite score.`
|
|
2033
2099
|
);
|
|
2034
2100
|
}
|
|
2035
2101
|
return {
|
|
@@ -2039,45 +2105,92 @@ function buildWitanInputFromRepo(options) {
|
|
|
2039
2105
|
contentReadSummary: tracked.summary
|
|
2040
2106
|
};
|
|
2041
2107
|
}
|
|
2042
|
-
const
|
|
2043
|
-
const
|
|
2108
|
+
const wholesaleAbstainedReadFailureCriteria = [];
|
|
2109
|
+
const wholesaleAbstainedCoverageLimitedCriteria = [];
|
|
2110
|
+
const partiallyAbstainedReadFailureSignals = [];
|
|
2111
|
+
const partiallyAbstainedLargeFileSignals = [];
|
|
2112
|
+
const partiallyAbstainedOtherCoverageLimitedSignals = [];
|
|
2044
2113
|
const signals = (tracked.value.signals ?? []).map((signal) => {
|
|
2045
2114
|
if (!affectedCriteria.has(signal.criterionId)) return signal;
|
|
2046
2115
|
if (tracked.unattributedCriteria.has(signal.criterionId)) {
|
|
2047
|
-
|
|
2048
|
-
|
|
2116
|
+
const reasons = tracked.criterionSkipReasons.get(signal.criterionId);
|
|
2117
|
+
const { insufficientDataReason } = classifyAbstentionReason(reasons);
|
|
2118
|
+
(insufficientDataReason !== void 0 ? wholesaleAbstainedCoverageLimitedCriteria : wholesaleAbstainedReadFailureCriteria).push(signal.criterionId);
|
|
2119
|
+
return wholesaleCriterionAbstention(signal, reasons);
|
|
2049
2120
|
}
|
|
2050
2121
|
const prefix = contentReadSignalKey(signal.criterionId, "");
|
|
2051
2122
|
const affectedNames = [...tracked.affectedSignals].filter((key) => key.startsWith(prefix)).map((key) => key.slice(prefix.length));
|
|
2052
2123
|
if (affectedNames.length === 0) {
|
|
2053
|
-
|
|
2124
|
+
wholesaleAbstainedReadFailureCriteria.push(signal.criterionId);
|
|
2054
2125
|
return wholesaleCriterionAbstention(signal);
|
|
2055
2126
|
}
|
|
2056
2127
|
const affectedNameSet = new Set(affectedNames);
|
|
2057
2128
|
const metrics = (signal.metrics ?? []).filter((metric2) => !affectedNameSet.has(metric2.name));
|
|
2058
2129
|
const anythingSurvives = metrics.length > 0 || (signal.positiveEvidence?.length ?? 0) > 0 || (signal.findings?.length ?? 0) > 0;
|
|
2130
|
+
const droppedReasons = /* @__PURE__ */ new Set();
|
|
2131
|
+
for (const name of affectedNames) {
|
|
2132
|
+
for (const reason of skipReasonsForSignal(signal.criterionId, name, tracked) ?? []) {
|
|
2133
|
+
droppedReasons.add(reason);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2059
2136
|
if (!anythingSurvives) {
|
|
2060
|
-
|
|
2061
|
-
|
|
2137
|
+
const { insufficientDataReason } = classifyAbstentionReason(droppedReasons);
|
|
2138
|
+
(insufficientDataReason !== void 0 ? wholesaleAbstainedCoverageLimitedCriteria : wholesaleAbstainedReadFailureCriteria).push(signal.criterionId);
|
|
2139
|
+
return wholesaleCriterionAbstention(signal, droppedReasons);
|
|
2062
2140
|
}
|
|
2141
|
+
const nameBuckets = /* @__PURE__ */ new Map();
|
|
2063
2142
|
for (const name of affectedNames) {
|
|
2064
|
-
|
|
2065
|
-
|
|
2143
|
+
const nameReasons = skipReasonsForSignal(signal.criterionId, name, tracked);
|
|
2144
|
+
const { insufficientDataReason: nameReason } = classifyAbstentionReason(nameReasons);
|
|
2145
|
+
const bucket = nameReason === void 0 ? "read-failure" : nameReason === "too_large" ? "too-large" : "other-coverage";
|
|
2146
|
+
nameBuckets.set(name, bucket);
|
|
2147
|
+
(bucket === "read-failure" ? partiallyAbstainedReadFailureSignals : bucket === "too-large" ? partiallyAbstainedLargeFileSignals : partiallyAbstainedOtherCoverageLimitedSignals).push(`${signal.criterionId}.${name}`);
|
|
2148
|
+
}
|
|
2149
|
+
const distinctBuckets = new Set(nameBuckets.values());
|
|
2150
|
+
const uniformBucket = distinctBuckets.size === 1 ? [...distinctBuckets][0] : void 0;
|
|
2151
|
+
const affectedList = [...affectedNames].sort().join(", ");
|
|
2152
|
+
const notes = uniformBucket === "read-failure" ? `Cejel abstained on ${affectedList} in this criterion because that content could not be read; other signals in this criterion were computed from readable files and are unaffected.` : uniformBucket === "too-large" ? `Cejel declined ${affectedList} in this criterion under the repository content size limit; other signals in this criterion were computed from readable files and are unaffected. This is a disclosed coverage limit, not a read failure.` : uniformBucket === "other-coverage" ? `Cejel declined ${affectedList} in this criterion under an extension exclusion or a non-regular-file skip; other signals in this criterion were computed from readable files and are unaffected. This is a disclosed coverage limit, not a read failure.` : `Cejel abstained on ${affectedList} in this criterion for a mix of reasons \u2014 some content could not be read, some was declined under the repository content size limit or an extension exclusion; other signals in this criterion were computed from readable files and are unaffected.`;
|
|
2066
2153
|
return {
|
|
2067
2154
|
...signal,
|
|
2068
2155
|
metrics,
|
|
2069
|
-
notes
|
|
2156
|
+
notes
|
|
2070
2157
|
};
|
|
2071
2158
|
});
|
|
2072
2159
|
const scanLimitations = [...tracked.value.scanLimitations ?? []];
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2160
|
+
const candidateScanLimitations = [];
|
|
2161
|
+
if (wholesaleAbstainedReadFailureCriteria.length > 0) {
|
|
2162
|
+
candidateScanLimitations.push(
|
|
2163
|
+
`${wholesaleAbstainedReadFailureCriteria.length} ${wholesaleAbstainedReadFailureCriteria.length === 1 ? "criterion" : "criteria"} abstained because relevant repository content could not be read and the affected signal could not be isolated. Counts and errno classes are recorded in contentReadSummary; file paths are intentionally omitted.`
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
if (wholesaleAbstainedCoverageLimitedCriteria.length > 0) {
|
|
2167
|
+
candidateScanLimitations.push(
|
|
2168
|
+
`${wholesaleAbstainedCoverageLimitedCriteria.length} ${wholesaleAbstainedCoverageLimitedCriteria.length === 1 ? "criterion" : "criteria"} abstained because relevant repository content was declined under the repository content size limit or excluded from scanning by policy, and the affected signal could not be isolated. This is a disclosed coverage limit, not a read failure, and does not affect the composite score.`
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
if (partiallyAbstainedReadFailureSignals.length > 0) {
|
|
2172
|
+
candidateScanLimitations.push(
|
|
2173
|
+
`${partiallyAbstainedReadFailureSignals.length} ${partiallyAbstainedReadFailureSignals.length === 1 ? "signal" : "signals"} abstained because its own repository content could not be read, while other signals in the same criterion were unaffected: ${[...partiallyAbstainedReadFailureSignals].sort().join(", ")}.`
|
|
2076
2174
|
);
|
|
2077
2175
|
}
|
|
2078
|
-
if (
|
|
2176
|
+
if (partiallyAbstainedLargeFileSignals.length > 0) {
|
|
2177
|
+
candidateScanLimitations.push(
|
|
2178
|
+
`${partiallyAbstainedLargeFileSignals.length} ${partiallyAbstainedLargeFileSignals.length === 1 ? "signal" : "signals"} declined a large implementation file that exceeded the repository content size limit, while other signals in the same criterion were unaffected: ${[...partiallyAbstainedLargeFileSignals].sort().join(", ")}. That file is undercounted, not scored as clean or absent \u2014 this is a disclosed coverage limit, not a read failure.`
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
if (partiallyAbstainedOtherCoverageLimitedSignals.length > 0) {
|
|
2182
|
+
candidateScanLimitations.push(
|
|
2183
|
+
`${partiallyAbstainedOtherCoverageLimitedSignals.length} ${partiallyAbstainedOtherCoverageLimitedSignals.length === 1 ? "signal" : "signals"} declined under an extension exclusion or a non-regular-file skip, while other signals in the same criterion were unaffected: ${[...partiallyAbstainedOtherCoverageLimitedSignals].sort().join(", ")}. This is a disclosed coverage limit, not a read failure.`
|
|
2184
|
+
);
|
|
2185
|
+
}
|
|
2186
|
+
const availableSlots = Math.max(0, 16 - scanLimitations.length);
|
|
2187
|
+
if (candidateScanLimitations.length <= availableSlots) {
|
|
2188
|
+
scanLimitations.push(...candidateScanLimitations);
|
|
2189
|
+
} else if (availableSlots > 0) {
|
|
2190
|
+
const keep = availableSlots - 1;
|
|
2191
|
+
scanLimitations.push(...candidateScanLimitations.slice(0, keep));
|
|
2079
2192
|
scanLimitations.push(
|
|
2080
|
-
`${
|
|
2193
|
+
`${candidateScanLimitations.length - keep} additional disclosed scan limitation(s) omitted at the 16-entry cap \u2014 see contentReadSummary for full counts.`
|
|
2081
2194
|
);
|
|
2082
2195
|
}
|
|
2083
2196
|
return {
|
|
@@ -2107,6 +2220,8 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
2107
2220
|
const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2108
2221
|
const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2109
2222
|
const usesV23CommandCoverage = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2223
|
+
const usesV23PemPrivateKeyGrammar = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2224
|
+
const usesV23WithheldPathAbstention = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2110
2225
|
const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
|
|
2111
2226
|
const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion) ? applyReadableSourceRepresentationGate(
|
|
2112
2227
|
options.repoPath,
|
|
@@ -2143,6 +2258,8 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
2143
2258
|
usesV21ExecutedEscalations,
|
|
2144
2259
|
usesV22PackageStartEntrypoint,
|
|
2145
2260
|
usesV23CommandCoverage,
|
|
2261
|
+
usesV23PemPrivateKeyGrammar,
|
|
2262
|
+
usesV23WithheldPathAbstention,
|
|
2146
2263
|
reviewableSourceProof,
|
|
2147
2264
|
inventoryAbsenceContext,
|
|
2148
2265
|
scanLimitations
|
|
@@ -2754,7 +2871,7 @@ function buildNotApplicableSignal(criterionId, reason) {
|
|
|
2754
2871
|
notApplicable: true
|
|
2755
2872
|
};
|
|
2756
2873
|
}
|
|
2757
|
-
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23CommandCoverage, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
2874
|
+
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23CommandCoverage, useV23PemPrivateKeyGrammar, useV23WithheldPathAbstention, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
2758
2875
|
const signals = [];
|
|
2759
2876
|
const mono = resolveMonorepoContext(repoPath, scanLimitations);
|
|
2760
2877
|
const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
|
|
@@ -2781,6 +2898,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2781
2898
|
useV36Detectors,
|
|
2782
2899
|
useV39Detectors,
|
|
2783
2900
|
useV47Detectors,
|
|
2901
|
+
useV23PemPrivateKeyGrammar,
|
|
2784
2902
|
useV18NativeRls,
|
|
2785
2903
|
inventoryAbsenceContext,
|
|
2786
2904
|
scanLimitations
|
|
@@ -2794,6 +2912,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2794
2912
|
useV27Detectors,
|
|
2795
2913
|
useV20A3ExplicitGaps,
|
|
2796
2914
|
useV22PackageStartEntrypoint,
|
|
2915
|
+
useV23WithheldPathAbstention,
|
|
2797
2916
|
inventoryAbsenceContext
|
|
2798
2917
|
)
|
|
2799
2918
|
);
|
|
@@ -2814,6 +2933,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2814
2933
|
repoPath,
|
|
2815
2934
|
repoFiles,
|
|
2816
2935
|
useV27Detectors,
|
|
2936
|
+
useV23WithheldPathAbstention,
|
|
2817
2937
|
inventoryAbsenceContext
|
|
2818
2938
|
)
|
|
2819
2939
|
);
|
|
@@ -2916,12 +3036,16 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2916
3036
|
const ciHasTypecheckCommand = ciWorkflows.some(
|
|
2917
3037
|
(file) => fileContains(repoPath, file, CI_TYPECHECK_COMMAND_PATTERN)
|
|
2918
3038
|
);
|
|
2919
|
-
const
|
|
2920
|
-
|
|
2921
|
-
coverageAnalysis.hasCoverageCommand,
|
|
2922
|
-
packageScripts.has("lint") || ciHasLintCommand,
|
|
2923
|
-
|
|
2924
|
-
|
|
3039
|
+
const verificationScriptComponents = [
|
|
3040
|
+
{ label: "test command", count: hasCredibleTestScript(packageScripts) || Boolean(ciTestWorkflow) ? 1 : 0 },
|
|
3041
|
+
{ label: "coverage command", count: coverageAnalysis.hasCoverageCommand ? 1 : 0 },
|
|
3042
|
+
{ label: "lint command", count: packageScripts.has("lint") || ciHasLintCommand ? 1 : 0 },
|
|
3043
|
+
{
|
|
3044
|
+
label: "type-check command",
|
|
3045
|
+
count: packageScripts.has("typecheck") || ciHasTypecheckCommand ? 1 : 0
|
|
3046
|
+
}
|
|
3047
|
+
];
|
|
3048
|
+
const verificationScriptCount = verificationScriptComponents.filter((c) => c.count > 0).length;
|
|
2925
3049
|
const usesLeanBuiltInTestRunner = packageJson != null && [...packageScripts.values()].some(
|
|
2926
3050
|
(script) => LEAN_TEST_RUNNER_SCRIPT_PATTERN.test(script)
|
|
2927
3051
|
) || testFiles.some((file) => fileContains(repoPath, file, LEAN_TEST_RUNNER_IMPORT_PATTERN));
|
|
@@ -2985,7 +3109,9 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2985
3109
|
repoPath,
|
|
2986
3110
|
reviewableSourceProof,
|
|
2987
3111
|
repoFiles.length,
|
|
2988
|
-
inventoryAbsenceContext
|
|
3112
|
+
inventoryAbsenceContext,
|
|
3113
|
+
verificationScriptComponents,
|
|
3114
|
+
runnerFiles.length
|
|
2989
3115
|
) : null;
|
|
2990
3116
|
}
|
|
2991
3117
|
if (testFiles.length === 0) {
|
|
@@ -3085,19 +3211,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3085
3211
|
"saturating_count",
|
|
3086
3212
|
{
|
|
3087
3213
|
components: [
|
|
3088
|
-
|
|
3089
|
-
label: "test command",
|
|
3090
|
-
count: packageScripts.has("test") || Boolean(ciTestWorkflow) ? 1 : 0
|
|
3091
|
-
},
|
|
3092
|
-
{ label: "coverage command", count: coverageAnalysis.hasCoverageCommand ? 1 : 0 },
|
|
3093
|
-
{
|
|
3094
|
-
label: "lint command",
|
|
3095
|
-
count: packageScripts.has("lint") || ciHasLintCommand ? 1 : 0
|
|
3096
|
-
},
|
|
3097
|
-
{
|
|
3098
|
-
label: "type-check command",
|
|
3099
|
-
count: packageScripts.has("typecheck") || ciHasTypecheckCommand ? 1 : 0
|
|
3100
|
-
},
|
|
3214
|
+
...verificationScriptComponents,
|
|
3101
3215
|
{ label: "test-runner configuration", count: runnerFiles.length }
|
|
3102
3216
|
]
|
|
3103
3217
|
}
|
|
@@ -3115,7 +3229,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3115
3229
|
notes: coverageFiles.length === 0 && coverageAnalysis.unresolvedFiles.length > 0 ? `A1 is detected from real test files, test runner configuration, and optional coverage configuration. Coverage command reachability or flag ownership was unresolved in ${coverageAnalysis.unresolvedFiles.slice(0, 4).join(", ")}; the no-coverage absence claim was withheld.` : "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
|
|
3116
3230
|
};
|
|
3117
3231
|
}
|
|
3118
|
-
function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inventoryAbsenceContext) {
|
|
3232
|
+
function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inventoryAbsenceContext, verificationScriptComponents, runnerFileCount) {
|
|
3119
3233
|
const sourcePath = proof.firstReadablePath;
|
|
3120
3234
|
if (!sourcePath) {
|
|
3121
3235
|
throw new Error("Cejel invariant: a passing reviewable-source proof must have a readable path");
|
|
@@ -3167,7 +3281,12 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inve
|
|
|
3167
3281
|
metric(
|
|
3168
3282
|
"verification_script_ratio",
|
|
3169
3283
|
"Verification script ratio",
|
|
3170
|
-
|
|
3284
|
+
// Zero test FILES does not mean zero verification signal: a lint/typecheck/coverage
|
|
3285
|
+
// script or CI command is a real, independently-observable fact from package.json/CI
|
|
3286
|
+
// content and must be credited here the same way B3 credits it, or A1 flatly
|
|
3287
|
+
// contradicts B3 on the same repository fact (goal_cejel_detector_false_assertions_
|
|
3288
|
+
// 0_4_9_2026-09-14 finding 3).
|
|
3289
|
+
verificationScriptComponents.filter((c) => c.count > 0).length + runnerFileCount,
|
|
3171
3290
|
4,
|
|
3172
3291
|
0.25,
|
|
3173
3292
|
"ratio",
|
|
@@ -3175,11 +3294,8 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inve
|
|
|
3175
3294
|
"saturating_count",
|
|
3176
3295
|
{
|
|
3177
3296
|
components: [
|
|
3178
|
-
|
|
3179
|
-
{ label: "
|
|
3180
|
-
{ label: "lint command", count: 0 },
|
|
3181
|
-
{ label: "type-check command", count: 0 },
|
|
3182
|
-
{ label: "test-runner configuration", count: 0 }
|
|
3297
|
+
...verificationScriptComponents,
|
|
3298
|
+
{ label: "test-runner configuration", count: runnerFileCount }
|
|
3183
3299
|
]
|
|
3184
3300
|
}
|
|
3185
3301
|
),
|
|
@@ -3193,10 +3309,10 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inve
|
|
|
3193
3309
|
"No concrete test files were present to establish non-hollow test behavior."
|
|
3194
3310
|
)
|
|
3195
3311
|
],
|
|
3196
|
-
notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files.`
|
|
3312
|
+
notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files. Verification-script signals (lint/typecheck/coverage/CI commands) are independent of test-file presence and are still credited above where detected.`
|
|
3197
3313
|
};
|
|
3198
3314
|
}
|
|
3199
|
-
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, inventoryAbsenceContext, scanLimitations) {
|
|
3315
|
+
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV23PemPrivateKeyGrammar, useV18NativeRls, inventoryAbsenceContext, scanLimitations) {
|
|
3200
3316
|
const evidence = [];
|
|
3201
3317
|
const findings = [];
|
|
3202
3318
|
const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
|
|
@@ -3219,7 +3335,8 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
3219
3335
|
path,
|
|
3220
3336
|
useV36Detectors,
|
|
3221
3337
|
useV39Detectors,
|
|
3222
|
-
useV47Detectors
|
|
3338
|
+
useV47Detectors,
|
|
3339
|
+
useV23PemPrivateKeyGrammar
|
|
3223
3340
|
);
|
|
3224
3341
|
if (!found) continue;
|
|
3225
3342
|
match_ = { path, match: found };
|
|
@@ -3227,7 +3344,9 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
3227
3344
|
}
|
|
3228
3345
|
const fingerprintsByPath = /* @__PURE__ */ new Map();
|
|
3229
3346
|
if (useV27Detectors) {
|
|
3230
|
-
for (const path of repoFiles.filter(
|
|
3347
|
+
for (const path of repoFiles.filter(
|
|
3348
|
+
(candidate) => isCredentialHistoryPath(candidate, useV36Detectors, useV23PemPrivateKeyGrammar)
|
|
3349
|
+
)) {
|
|
3231
3350
|
fingerprintsByPath.set(
|
|
3232
3351
|
path,
|
|
3233
3352
|
new Set(
|
|
@@ -3236,7 +3355,8 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
3236
3355
|
path,
|
|
3237
3356
|
useV36Detectors,
|
|
3238
3357
|
useV39Detectors,
|
|
3239
|
-
useV47Detectors
|
|
3358
|
+
useV47Detectors,
|
|
3359
|
+
useV23PemPrivateKeyGrammar
|
|
3240
3360
|
)
|
|
3241
3361
|
)
|
|
3242
3362
|
);
|
|
@@ -3253,7 +3373,8 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
3253
3373
|
useV33Detectors,
|
|
3254
3374
|
useV36Detectors,
|
|
3255
3375
|
useV39Detectors,
|
|
3256
|
-
useV47Detectors
|
|
3376
|
+
useV47Detectors,
|
|
3377
|
+
useV23PemPrivateKeyGrammar
|
|
3257
3378
|
);
|
|
3258
3379
|
const hasConfirmedSecretFinding = committedSecret != null || historySecretScan?.evidence != null;
|
|
3259
3380
|
const cryptoHygiene = withContentReadSignal(
|
|
@@ -3380,15 +3501,16 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
3380
3501
|
if (committedSecret) {
|
|
3381
3502
|
const isTestPath = isTestOrFixturePath(committedSecret.path);
|
|
3382
3503
|
const isDefaultAdmin = committedSecret.match.kind === "default_admin";
|
|
3504
|
+
const isPemPrivateKey = committedSecret.match.kind === "pem_private_key";
|
|
3383
3505
|
findings.push({
|
|
3384
3506
|
severity: isTestPath ? "info" : "critical",
|
|
3385
|
-
summary: isDefaultAdmin ? isTestPath ? `Default administrative credential in a test/fixture file (${committedSecret.path}) \u2014 likely fixture data, not a production deployment default; verify.` : "An explicit default administrative credential appears committed in production configuration." : isTestPath ? `Secret-shaped value in a test/fixture file (${committedSecret.path}) \u2014 likely fixture data, not a production leak; verify.` : "Secret-shaped value appears committed in the scanned repository.",
|
|
3507
|
+
summary: isDefaultAdmin ? isTestPath ? `Default administrative credential in a test/fixture file (${committedSecret.path}) \u2014 likely fixture data, not a production deployment default; verify.` : "An explicit default administrative credential appears committed in production configuration." : isPemPrivateKey ? isTestPath ? `PEM-formatted private key in a test/fixture file (${committedSecret.path}) \u2014 likely fixture data, not a production leak; verify.` : "A PEM-formatted private key appears committed in the scanned repository." : isTestPath ? `Secret-shaped value in a test/fixture file (${committedSecret.path}) \u2014 likely fixture data, not a production leak; verify.` : "Secret-shaped value appears committed in the scanned repository.",
|
|
3386
3508
|
evidence: evidenceForRelativeAtLine(
|
|
3387
3509
|
repoPath,
|
|
3388
3510
|
committedSecret.path,
|
|
3389
3511
|
"secret_scan",
|
|
3390
3512
|
secretEvidenceLabel(
|
|
3391
|
-
isDefaultAdmin ? "Committed default administrative credential" : "Committed secret-shaped value",
|
|
3513
|
+
isDefaultAdmin ? "Committed default administrative credential" : isPemPrivateKey ? "Committed PEM-formatted private key" : "Committed secret-shaped value",
|
|
3392
3514
|
committedSecret.match
|
|
3393
3515
|
),
|
|
3394
3516
|
committedSecret.match.line
|
|
@@ -3419,9 +3541,10 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
3419
3541
|
if (historySecretScan?.evidence) {
|
|
3420
3542
|
const historyPath = historySecretScan.evidence.path ?? "";
|
|
3421
3543
|
const isTestPath = isTestOrFixturePath(historyPath);
|
|
3544
|
+
const isPemPrivateKey = historySecretScan.secretKind === "pem_private_key";
|
|
3422
3545
|
findings.push({
|
|
3423
3546
|
severity: isTestPath ? "info" : "critical",
|
|
3424
|
-
summary: isTestPath ? `Secret-shaped value in git history for a test/fixture file (${historyPath}) \u2014 likely fixture data, not a production leak; verify.` : "Secret-shaped value appears in recent git history.",
|
|
3547
|
+
summary: isPemPrivateKey ? isTestPath ? `PEM-formatted private key in git history for a test/fixture file (${historyPath}) \u2014 likely fixture data, not a production leak; verify.` : "A PEM-formatted private key appears in recent git history." : isTestPath ? `Secret-shaped value in git history for a test/fixture file (${historyPath}) \u2014 likely fixture data, not a production leak; verify.` : "Secret-shaped value appears in recent git history.",
|
|
3425
3548
|
evidence: historySecretScan.evidence
|
|
3426
3549
|
});
|
|
3427
3550
|
} else if (historySecretScan?.truncated) {
|
|
@@ -3646,7 +3769,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = fal
|
|
|
3646
3769
|
}
|
|
3647
3770
|
return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
|
|
3648
3771
|
}
|
|
3649
|
-
function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
3772
|
+
function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
3650
3773
|
const v20DirectHttpEntrypoint = useV20ExplicitGaps ? findV20DirectHttpServerEntrypointFile(repoPath, repoFiles) : null;
|
|
3651
3774
|
const v22PackageStartHttpEntrypoint = useV22PackageStartEntrypoint ? findV22PackageStartHttpServerEntrypointFile(repoPath, repoFiles) : null;
|
|
3652
3775
|
const v20RuntimeContainer = useV20ExplicitGaps ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, true) : null;
|
|
@@ -3684,9 +3807,11 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3684
3807
|
)
|
|
3685
3808
|
);
|
|
3686
3809
|
const healthCheck = healthChecks[0];
|
|
3810
|
+
const healthReadinessRouteReads = (file) => isAuthoredProductionPath(file) && isImplementationFile(file);
|
|
3811
|
+
const observabilityDepthReads = (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file);
|
|
3687
3812
|
const { hasV20HealthOrReadinessRoute, hasV22PackageStartHealthOrReadinessRoute } = withContentReadSignal("A3", "health_readiness_route", () => ({
|
|
3688
3813
|
hasV20HealthOrReadinessRoute: useV20ExplicitGaps && repoFiles.some(
|
|
3689
|
-
(file) =>
|
|
3814
|
+
(file) => healthReadinessRouteReads(file) && fileContains(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
|
|
3690
3815
|
),
|
|
3691
3816
|
hasV22PackageStartHealthOrReadinessRoute: v22PackageStartHttpEntrypoint !== null && fileContains(
|
|
3692
3817
|
repoPath,
|
|
@@ -3694,23 +3819,34 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3694
3819
|
V20_HEALTH_OR_READINESS_ROUTE_PATTERN
|
|
3695
3820
|
)
|
|
3696
3821
|
}));
|
|
3822
|
+
const healthReadinessRouteWithheld = useV23WithheldPathAbstention && useV20ExplicitGaps && abstainSignalOnWithheldPaths("A3", "health_readiness_route", healthReadinessRouteReads);
|
|
3697
3823
|
const serverEntrypoint = v22PackageStartHttpEntrypoint ?? findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) ?? v20DirectHttpEntrypoint ?? null;
|
|
3698
3824
|
const runtimeContainer = useV27Detectors ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, useV20ExplicitGaps) : null;
|
|
3699
3825
|
const errorBoundaries = repoFiles.filter(
|
|
3700
3826
|
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
|
|
3701
3827
|
);
|
|
3702
|
-
const
|
|
3828
|
+
const expressErrorMiddlewareFile = errorBoundaries.length > 0 ? void 0 : withContentReadSignal(
|
|
3829
|
+
"A3",
|
|
3830
|
+
"prod_readiness_primitives",
|
|
3831
|
+
() => repoFiles.find(
|
|
3832
|
+
(file) => observabilityDepthReads(file) && fileContains(repoPath, file, EXPRESS_ERROR_MIDDLEWARE_PATTERN) && fileContains(repoPath, file, EXPRESS_MIDDLEWARE_REACHABLE_PATTERN)
|
|
3833
|
+
)
|
|
3834
|
+
);
|
|
3835
|
+
const errorBoundary = errorBoundaries[0] ?? expressErrorMiddlewareFile;
|
|
3836
|
+
if (useV23WithheldPathAbstention && errorBoundaries.length === 0) {
|
|
3837
|
+
abstainSignalOnWithheldPaths("A3", "prod_readiness_primitives", observabilityDepthReads);
|
|
3838
|
+
}
|
|
3703
3839
|
const observabilityCount = withContentReadSignal(
|
|
3704
3840
|
"A3",
|
|
3705
3841
|
"observability_depth",
|
|
3706
|
-
() =>
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
),
|
|
3711
|
-
/sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail/i
|
|
3712
|
-
)
|
|
3842
|
+
() => countFilesMatchingAny(repoPath, repoFiles.filter(observabilityDepthReads), [
|
|
3843
|
+
/sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail|\b(?:correlation|request|trace)[-_]?id\b|\basynclocalstorage\b/i,
|
|
3844
|
+
/\b(?:pino|winston|bunyan|morgan)\b/
|
|
3845
|
+
])
|
|
3713
3846
|
);
|
|
3847
|
+
if (useV23WithheldPathAbstention) {
|
|
3848
|
+
abstainSignalOnWithheldPaths("A3", "observability_depth", observabilityDepthReads);
|
|
3849
|
+
}
|
|
3714
3850
|
const rollbackSafetyCount = withContentReadSignal(
|
|
3715
3851
|
"A3",
|
|
3716
3852
|
"rollback_safety_depth",
|
|
@@ -3757,8 +3893,17 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3757
3893
|
)
|
|
3758
3894
|
);
|
|
3759
3895
|
}
|
|
3760
|
-
if (errorBoundary)
|
|
3761
|
-
evidence.push(
|
|
3896
|
+
if (errorBoundary) {
|
|
3897
|
+
evidence.push(
|
|
3898
|
+
errorBoundary === expressErrorMiddlewareFile ? evidenceForRelativeAtLine(
|
|
3899
|
+
repoPath,
|
|
3900
|
+
errorBoundary,
|
|
3901
|
+
"prod_check",
|
|
3902
|
+
"Error boundary",
|
|
3903
|
+
findFirstMatchingLine(repoPath, errorBoundary, EXPRESS_ERROR_MIDDLEWARE_PATTERN)
|
|
3904
|
+
) : evidenceForRelative(repoPath, errorBoundary, "prod_check", "Error boundary")
|
|
3905
|
+
);
|
|
3906
|
+
}
|
|
3762
3907
|
if (evidence.length === 0) {
|
|
3763
3908
|
if (serverEntrypoint) {
|
|
3764
3909
|
evidence.push(
|
|
@@ -3812,7 +3957,12 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3812
3957
|
)
|
|
3813
3958
|
});
|
|
3814
3959
|
}
|
|
3815
|
-
} else if (serverEntrypoint &&
|
|
3960
|
+
} else if (serverEntrypoint && // An absence finding is an assertion that Cejel looked and found nothing. When a file this
|
|
3961
|
+
// signal would have read was withheld, Cejel did not look at everything it selected, so the
|
|
3962
|
+
// honest output is the recorded abstention above and no finding — never "declares no health
|
|
3963
|
+
// or readiness route". This is the whole defect: insufficient_data must not be emitted as a
|
|
3964
|
+
// finding of absence.
|
|
3965
|
+
!healthReadinessRouteWithheld && withContentReadSignal(
|
|
3816
3966
|
"A3",
|
|
3817
3967
|
"health_readiness_route",
|
|
3818
3968
|
() => fileContains(
|
|
@@ -3866,7 +4016,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3866
4016
|
deployConfigs.length > 0,
|
|
3867
4017
|
envTemplate,
|
|
3868
4018
|
healthChecks.length > 0,
|
|
3869
|
-
|
|
4019
|
+
errorBoundary !== void 0
|
|
3870
4020
|
].filter(Boolean).length,
|
|
3871
4021
|
6,
|
|
3872
4022
|
0.55,
|
|
@@ -3883,7 +4033,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3883
4033
|
{ label: "deployment configuration", count: deployConfigs.length > 0 ? 1 : 0 },
|
|
3884
4034
|
{ label: "environment template", count: envTemplate ? 1 : 0 },
|
|
3885
4035
|
{ label: "health or readiness signal", count: healthChecks.length > 0 ? 1 : 0 },
|
|
3886
|
-
{ label: "error boundary", count:
|
|
4036
|
+
{ label: "error boundary", count: errorBoundary !== void 0 ? 1 : 0 }
|
|
3887
4037
|
]
|
|
3888
4038
|
}
|
|
3889
4039
|
),
|
|
@@ -4127,7 +4277,26 @@ function hasDeclaredVersionConstraint(dependency) {
|
|
|
4127
4277
|
if (/[~^<>=!]/.test(trimmed)) return true;
|
|
4128
4278
|
return /^v?\d/.test(trimmed);
|
|
4129
4279
|
}
|
|
4130
|
-
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
4280
|
+
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, useV23WithheldPathAbstention, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
4281
|
+
const claimImplementationReads = claimImplementationFileReads(useV27Detectors);
|
|
4282
|
+
if (useV23WithheldPathAbstention) {
|
|
4283
|
+
abstainSignalOnWithheldPaths(
|
|
4284
|
+
"A5",
|
|
4285
|
+
"claim_match_rate",
|
|
4286
|
+
(file) => claimImplementationReads(file) || isClaimSourceFile(file)
|
|
4287
|
+
);
|
|
4288
|
+
abstainSignalOnWithheldPaths("A5", "claim_source_depth", isClaimSourceFile);
|
|
4289
|
+
abstainSignalOnWithheldPaths(
|
|
4290
|
+
"A5",
|
|
4291
|
+
"reconciliation_artifact_depth",
|
|
4292
|
+
isClaimRealityReconciliationPath
|
|
4293
|
+
);
|
|
4294
|
+
abstainSignalOnWithheldPaths(
|
|
4295
|
+
"A5",
|
|
4296
|
+
"negative_space_documentation",
|
|
4297
|
+
isNegativeSpaceDocCandidate
|
|
4298
|
+
);
|
|
4299
|
+
}
|
|
4131
4300
|
const reconciliationArtifacts = useV27Detectors ? findClaimRealityReconciliationArtifacts(repoPath, repoFiles) : [];
|
|
4132
4301
|
const claimSourceFiles = useV27Detectors ? findClaimSourceFiles(repoFiles) : repoFiles.filter(
|
|
4133
4302
|
(file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
|
|
@@ -4173,7 +4342,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, inv
|
|
|
4173
4342
|
const negativeSpaceCandidates = /* @__PURE__ */ new Set([
|
|
4174
4343
|
claimDoc,
|
|
4175
4344
|
...allDocFiles,
|
|
4176
|
-
...repoFiles.filter(
|
|
4345
|
+
...repoFiles.filter(isSecurityOrThreatModelDoc)
|
|
4177
4346
|
]);
|
|
4178
4347
|
const negativeSpaceDoc = withContentReadSignal(
|
|
4179
4348
|
"A5",
|
|
@@ -4261,7 +4430,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, inv
|
|
|
4261
4430
|
function collectB2PrTraceEvidence(repoPath, repoFiles) {
|
|
4262
4431
|
const workflows = repoFiles.filter(isCiWorkflow);
|
|
4263
4432
|
const prTemplate = repoFiles.find(
|
|
4264
|
-
(file) => /(^|\/)(pull_request_template|PULL_REQUEST_TEMPLATE)\.md
|
|
4433
|
+
(file) => /(^|\/)(pull_request_template|PULL_REQUEST_TEMPLATE)(\.md$|\/[^/]+\.md$)/.test(file)
|
|
4265
4434
|
);
|
|
4266
4435
|
const branchProtectionDoc = repoFiles.find(
|
|
4267
4436
|
(file) => /branch.*protection|review.*gate|CODEOWNERS/i.test(file)
|
|
@@ -4364,7 +4533,9 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
4364
4533
|
CI_TYPECHECK_COMMAND_PATTERN,
|
|
4365
4534
|
CI_BUILD_COMMAND_PATTERN
|
|
4366
4535
|
];
|
|
4367
|
-
const scriptDepth = ["test", "lint", "typecheck", "build"].filter(
|
|
4536
|
+
const scriptDepth = ["test", "lint", "typecheck", "build"].filter(
|
|
4537
|
+
(s) => s === "test" ? hasCredibleTestScript(scripts) : scripts.has(s)
|
|
4538
|
+
).length;
|
|
4368
4539
|
const { repoCategoryHits, rootCategoryHits } = withContentReadSignal(
|
|
4369
4540
|
"B3",
|
|
4370
4541
|
"ci_script_depth",
|
|
@@ -4397,7 +4568,7 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
4397
4568
|
components: [
|
|
4398
4569
|
...["test", "lint", "typecheck", "build"].map((name) => ({
|
|
4399
4570
|
label: `package script: ${name === "typecheck" ? "type-check" : name}`,
|
|
4400
|
-
count: scripts.has(name) ? 1 : 0
|
|
4571
|
+
count: (name === "test" ? hasCredibleTestScript(scripts) : scripts.has(name)) ? 1 : 0
|
|
4401
4572
|
})),
|
|
4402
4573
|
...CI_COMMAND_CATEGORIES.map((_pattern, index) => ({
|
|
4403
4574
|
label: `repository CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
@@ -4756,6 +4927,10 @@ function isTestFile(file, rubricVersion = WITAN_RUBRIC_VERSION) {
|
|
|
4756
4927
|
var NAME_SHAPED_TEST_FILE_PATTERN = /^test(?:-[^/]+)?\.[cm]?[jt]sx?$|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)__tests__\/|(^|\/)test_[^/]*\.py$|(^|\/)tests?\.py$|_test\.go$|(^|\/)test_[^/]*\.(cpp|cc|cxx)$|_(test|tests)\.(cpp|cc|cxx)$|Tests?\.(java|kt)$|(_test|_spec)\.(rs|rb|php|swift|kt)$/;
|
|
4757
4928
|
var NAME_SHAPED_TEST_FILE_PATTERN_V17 = /\.tftest\.hcl$|\.bats$|(^|\/)test[^/]*\.m$|_test\.m$/i;
|
|
4758
4929
|
var TEST_RUNNER_PATTERN = /\b(vitest|jest|mocha|ava|tap|pytest|go test|cargo test|rspec|phpunit|gradle test|mvn test|node\s+--test|node:test)\b/i;
|
|
4930
|
+
function hasCredibleTestScript(scripts) {
|
|
4931
|
+
const script = scripts.get("test");
|
|
4932
|
+
return script != null && TEST_RUNNER_PATTERN.test(script);
|
|
4933
|
+
}
|
|
4759
4934
|
var CI_TEST_COMMAND_PATTERN = /\b(pytest|go\s+test|cargo\s+test|mvn\s+test|gradle\s+test|dotnet\s+test|ctest|make\s+test|jest|vitest|mocha|rspec|phpunit|pnpm\s+(?:run\s+)?test|npm\s+(?:run\s+)?test|yarn\s+(?:run\s+)?test|node\s+--test|node:test)\b|--target[=\s]+test\b/i;
|
|
4760
4935
|
var CI_LINT_COMMAND_PATTERN = /\b(eslint|pylint|flake8|black|ruff)\b/i;
|
|
4761
4936
|
var CI_TYPECHECK_COMMAND_PATTERN = /\b(tsc|mypy|pyright)\b/i;
|
|
@@ -4803,6 +4978,7 @@ var GATED_PRIVILEGE_CHECK_PATTERN = /pg_has_role\s*\(|has_role\s*\(|is not a mem
|
|
|
4803
4978
|
var SET_ROLE_PATTERN = /\bset\s+(local\s+)?role\b/i;
|
|
4804
4979
|
var REQUIRED_REVIEW_PATTERN = /required review|branch protection|protected branch/i;
|
|
4805
4980
|
var HISTORY_SECRET_SCAN_CREDENTIAL_BLOB_LIMIT = 5e3;
|
|
4981
|
+
var V23_PEM_WIDENED_HISTORY_BLOB_LIMIT = 500;
|
|
4806
4982
|
var SECRET_IDENTIFIER_KEYWORD_PATTERN = /secret|token|api[_-]?key|password|access[_-]?key/gi;
|
|
4807
4983
|
var SINGLE_WHITESPACE_PATTERN = /\s/u;
|
|
4808
4984
|
var SECRET_VALUE_BRANDED_PATTERN = /^(?:sk-[A-Za-z0-9_-]{24,}|sk-ant-api\d{2}-[A-Za-z0-9_-]{80,}|ghp_[A-Za-z0-9_]{30,}|xox[baprs]-[A-Za-z0-9-]{24,}|AKIA[0-9A-Z]{16}|vcp_[A-Za-z0-9_:-]{24,})$/;
|
|
@@ -4928,8 +5104,8 @@ function looksLikeSecretValue(value) {
|
|
|
4928
5104
|
return SECRET_VALUE_BRANDED_PATTERN.test(value) || isGenericSecretValue(value);
|
|
4929
5105
|
}
|
|
4930
5106
|
var PLACEHOLDER_SECRET_PATTERN = /(?:^|[-_])(?:your|example|sample|placeholder|dummy|changeme|replace(?:-?me)?|redacted)(?:$|[-_])|^x{3,}$|^\.\.\.|^\$\{[^}]+}$|^<[^>]+>$/i;
|
|
4931
|
-
var ENV_TEMPLATE_PATTERN = /(^|\/)\.env(?:\.[^/]+)
|
|
4932
|
-
var ENV_TEMPLATE_PATTERN_V47 = /(^|\/)\.env(?:\.[^/]+)
|
|
5107
|
+
var ENV_TEMPLATE_PATTERN = /(^|\/)\.env(?:\.[^/]+)?\.(?:example|sample|template|dist)$/i;
|
|
5108
|
+
var ENV_TEMPLATE_PATTERN_V47 = /(^|\/)\.env(?:\.[^/]+)?\.(?:example|sample|template|dist|exemplo|ejemplo|exemple|esempio|beispiel|voorbeeld)$/i;
|
|
4933
5109
|
function isEnvTemplatePath(path, useV47Detectors = false) {
|
|
4934
5110
|
return (useV47Detectors ? ENV_TEMPLATE_PATTERN_V47 : ENV_TEMPLATE_PATTERN).test(path);
|
|
4935
5111
|
}
|
|
@@ -4946,32 +5122,34 @@ function readGitTrackedFileOutput(repoPath) {
|
|
|
4946
5122
|
if (workTree.stdout.trim() !== "true") return { ok: false, reason: "not_a_repo" };
|
|
4947
5123
|
return execGit(["ls-files", "--cached"], { cwd: repoPath });
|
|
4948
5124
|
}
|
|
4949
|
-
function parseGitTrackedFiles(repoPath, output, includeHardExcluded) {
|
|
5125
|
+
function parseGitTrackedFiles(repoPath, output, includeHardExcluded, registerWithheld = false) {
|
|
4950
5126
|
const files = [];
|
|
5127
|
+
const withhold = (file, reason) => {
|
|
5128
|
+
if (registerWithheld) registerWithheldRepoPath(file, reason);
|
|
5129
|
+
};
|
|
4951
5130
|
for (const file of output.trim().split("\n")) {
|
|
4952
5131
|
if (file.length === 0) continue;
|
|
4953
5132
|
const fullPath = join(repoPath, file);
|
|
4954
5133
|
if (!includeHardExcluded && isHardExcludedPath(file)) {
|
|
4955
5134
|
recordContentSkip(fullPath, "denied_path", true);
|
|
5135
|
+
withhold(file, "denied_path");
|
|
4956
5136
|
continue;
|
|
4957
5137
|
}
|
|
4958
5138
|
try {
|
|
4959
5139
|
const stat = lstatSync(fullPath);
|
|
4960
5140
|
if (!stat.isFile()) {
|
|
4961
5141
|
recordContentSkip(fullPath, "non_regular_file", true);
|
|
5142
|
+
withhold(file, "non_regular_file");
|
|
4962
5143
|
continue;
|
|
4963
5144
|
}
|
|
4964
5145
|
if (stat.size > MAX_REPOSITORY_CONTENT_BYTES) {
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
recordContentSkipForCriteria(fullPath, "too_large", affectedCriteria, true);
|
|
4968
|
-
} else {
|
|
4969
|
-
recordContentSkip(fullPath, "too_large", true);
|
|
4970
|
-
}
|
|
5146
|
+
recordContentSkip(fullPath, "too_large", true);
|
|
5147
|
+
withhold(file, "too_large");
|
|
4971
5148
|
continue;
|
|
4972
5149
|
}
|
|
4973
5150
|
} catch (error) {
|
|
4974
5151
|
recordFilesystemSkip(fullPath, error, false, true);
|
|
5152
|
+
withhold(file, "unreadable");
|
|
4975
5153
|
continue;
|
|
4976
5154
|
}
|
|
4977
5155
|
if (!isPotentialContentPath(file)) {
|
|
@@ -4981,39 +5159,6 @@ function parseGitTrackedFiles(repoPath, output, includeHardExcluded) {
|
|
|
4981
5159
|
}
|
|
4982
5160
|
return files.sort();
|
|
4983
5161
|
}
|
|
4984
|
-
function affectedCriteriaForUnavailablePath(file) {
|
|
4985
|
-
const affected = /* @__PURE__ */ new Set();
|
|
4986
|
-
if (isDependencyManifest(file) || isLockfile(file)) affected.add("A4");
|
|
4987
|
-
if (isCiWorkflow(file)) {
|
|
4988
|
-
affected.add("A1");
|
|
4989
|
-
affected.add("A3");
|
|
4990
|
-
affected.add("B2");
|
|
4991
|
-
affected.add("B3");
|
|
4992
|
-
}
|
|
4993
|
-
if (isDeployConfig(file)) {
|
|
4994
|
-
affected.add("A3");
|
|
4995
|
-
affected.add("B2");
|
|
4996
|
-
}
|
|
4997
|
-
if (isTestFile(file)) {
|
|
4998
|
-
affected.add("A1");
|
|
4999
|
-
affected.add("A2");
|
|
5000
|
-
}
|
|
5001
|
-
if (isImplementationFile(file)) {
|
|
5002
|
-
affected.add("A1");
|
|
5003
|
-
affected.add("A2");
|
|
5004
|
-
affected.add("A3");
|
|
5005
|
-
affected.add("B1");
|
|
5006
|
-
affected.add("B6");
|
|
5007
|
-
}
|
|
5008
|
-
if (/\.(?:md|mdx|rst|txt)$/i.test(file)) {
|
|
5009
|
-
affected.add("A5");
|
|
5010
|
-
affected.add("B4");
|
|
5011
|
-
affected.add("B5");
|
|
5012
|
-
affected.add("B6");
|
|
5013
|
-
}
|
|
5014
|
-
if (isAuditFile(file)) affected.add("B5");
|
|
5015
|
-
return [...affected];
|
|
5016
|
-
}
|
|
5017
5162
|
function directoryInventory(repoPath) {
|
|
5018
5163
|
const files = [];
|
|
5019
5164
|
const rootDevice = statSync(repoPath).dev;
|
|
@@ -5024,7 +5169,7 @@ function collectRepoFileInventory(repoPath) {
|
|
|
5024
5169
|
const tracked = readGitTrackedFileOutput(repoPath);
|
|
5025
5170
|
if (tracked.ok) {
|
|
5026
5171
|
return {
|
|
5027
|
-
repoFiles: parseGitTrackedFiles(repoPath, tracked.stdout, false),
|
|
5172
|
+
repoFiles: parseGitTrackedFiles(repoPath, tracked.stdout, false, true),
|
|
5028
5173
|
inventoryFiles: parseGitTrackedFiles(repoPath, tracked.stdout, true),
|
|
5029
5174
|
repoFileInventoryScope: "tracked-scan-eligible",
|
|
5030
5175
|
scanLimitations: []
|
|
@@ -5122,8 +5267,10 @@ function visitRepoDir(repoPath, dirPath, files, rootDevice) {
|
|
|
5122
5267
|
visitRepoDir(repoPath, fullPath, files, rootDevice);
|
|
5123
5268
|
continue;
|
|
5124
5269
|
}
|
|
5270
|
+
const repoRelativePath = relative(repoPath, fullPath);
|
|
5125
5271
|
if (!entry.isFile()) {
|
|
5126
5272
|
recordContentSkip(fullPath, "non_regular_file");
|
|
5273
|
+
registerWithheldRepoPath(repoRelativePath, "non_regular_file");
|
|
5127
5274
|
continue;
|
|
5128
5275
|
}
|
|
5129
5276
|
let size;
|
|
@@ -5131,16 +5278,12 @@ function visitRepoDir(repoPath, dirPath, files, rootDevice) {
|
|
|
5131
5278
|
size = statSync(fullPath).size;
|
|
5132
5279
|
} catch (error) {
|
|
5133
5280
|
recordFilesystemSkip(fullPath, error);
|
|
5281
|
+
registerWithheldRepoPath(repoRelativePath, "unreadable");
|
|
5134
5282
|
continue;
|
|
5135
5283
|
}
|
|
5136
|
-
const repoRelativePath = relative(repoPath, fullPath);
|
|
5137
5284
|
if (size > MAX_REPOSITORY_CONTENT_BYTES) {
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
recordContentSkipForCriteria(fullPath, "too_large", affectedCriteria);
|
|
5141
|
-
} else {
|
|
5142
|
-
recordContentSkip(fullPath, "too_large");
|
|
5143
|
-
}
|
|
5285
|
+
recordContentSkip(fullPath, "too_large");
|
|
5286
|
+
registerWithheldRepoPath(repoRelativePath, "too_large");
|
|
5144
5287
|
continue;
|
|
5145
5288
|
}
|
|
5146
5289
|
if (!isPotentialContentPath(repoRelativePath)) {
|
|
@@ -5264,6 +5407,9 @@ function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
|
|
|
5264
5407
|
function countFilesContaining(repoPath, files, pattern) {
|
|
5265
5408
|
return files.filter((file) => fileContains(repoPath, file, pattern)).length;
|
|
5266
5409
|
}
|
|
5410
|
+
function countFilesMatchingAny(repoPath, files, patterns) {
|
|
5411
|
+
return files.filter((file) => patterns.some((pattern) => fileContains(repoPath, file, pattern))).length;
|
|
5412
|
+
}
|
|
5267
5413
|
function countPatternMatches(repoPath, files, pattern) {
|
|
5268
5414
|
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
5269
5415
|
const matcher = new RegExp(pattern.source, flags);
|
|
@@ -5803,22 +5949,33 @@ function isDocumentationOnlyDeployWorkflow(repoPath, file) {
|
|
|
5803
5949
|
function findPackagedApplicationPremiseFiles(repoPath, repoFiles) {
|
|
5804
5950
|
return repoFiles.filter((file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)).filter((file) => packageJsonDescribesPackagedApplication(repoPath, file, repoFiles));
|
|
5805
5951
|
}
|
|
5952
|
+
function isClaimSourceFile(file) {
|
|
5953
|
+
return /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file);
|
|
5954
|
+
}
|
|
5955
|
+
function isClaimRealityReconciliationPath(file) {
|
|
5956
|
+
return /(^|\/)claim[-_]reality[-_]reconciliation\.(?:md|mdx|json)$/i.test(file);
|
|
5957
|
+
}
|
|
5958
|
+
function isSecurityOrThreatModelDoc(file) {
|
|
5959
|
+
return /(^|\/)(SECURITY|THREAT[_-]?MODEL)\.md$/i.test(file);
|
|
5960
|
+
}
|
|
5961
|
+
function isNegativeSpaceDocCandidate(file) {
|
|
5962
|
+
return isClaimSourceFile(file) || isSecurityOrThreatModelDoc(file);
|
|
5963
|
+
}
|
|
5964
|
+
function claimImplementationFileReads(authoredOnly) {
|
|
5965
|
+
return (file) => isImplementationFile(file) && (!authoredOnly || isAuthoredProductionPath(file));
|
|
5966
|
+
}
|
|
5806
5967
|
function findClaimSourceFiles(repoFiles) {
|
|
5807
|
-
return repoFiles.filter(
|
|
5808
|
-
(file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
|
|
5809
|
-
).sort((left, right) => {
|
|
5968
|
+
return repoFiles.filter(isClaimSourceFile).sort((left, right) => {
|
|
5810
5969
|
const leftRootReadme = /^(?:README|readme)\.md$/.test(left);
|
|
5811
5970
|
const rightRootReadme = /^(?:README|readme)\.md$/.test(right);
|
|
5812
5971
|
return Number(rightRootReadme) - Number(leftRootReadme) || left.localeCompare(right);
|
|
5813
5972
|
});
|
|
5814
5973
|
}
|
|
5815
5974
|
function findClaimImplementationFiles(repoFiles, authoredOnly = false) {
|
|
5816
|
-
return repoFiles.filter(
|
|
5817
|
-
(file) => isImplementationFile(file) && (!authoredOnly || isAuthoredProductionPath(file))
|
|
5818
|
-
);
|
|
5975
|
+
return repoFiles.filter(claimImplementationFileReads(authoredOnly));
|
|
5819
5976
|
}
|
|
5820
5977
|
function findClaimRealityReconciliationArtifacts(repoPath, repoFiles) {
|
|
5821
|
-
return repoFiles.filter(
|
|
5978
|
+
return repoFiles.filter(isClaimRealityReconciliationPath).filter((file) => isAuthenticatedClaimRealityArtifact(repoPath, file));
|
|
5822
5979
|
}
|
|
5823
5980
|
function isAuthenticatedClaimRealityArtifact(repoPath, file) {
|
|
5824
5981
|
const fullPath = join(repoPath, file);
|
|
@@ -6874,7 +7031,9 @@ var MAIN_ENTRYPOINT_FILE_PATTERN = /(^|\/)(main|server|app|wsgi|asgi|index|entry
|
|
|
6874
7031
|
var RACK_ENTRYPOINT_FILE_PATTERN = /(^|\/)(?:config\.ru|(?:main|server|app|start|run)\.rb)$/i;
|
|
6875
7032
|
var SERVER_ENTRYPOINT_PATTERN = /\bapp\.listen\s*\(|\bserver\.listen\s*\(\s*(?:PORT|port|\d+)|http\.ListenAndServe\s*\(|http\.ListenAndServeTLS\s*\(|axum::Server(?:::|\.)|actix_web::HttpServer(?:::|\.)|uvicorn\.run\s*\(/;
|
|
6876
7033
|
var V20_DIRECT_HTTP_SERVER_PATTERN = /\b(?:http|https)\.createServer\s*\([\s\S]{0,1500}?\)\.listen\s*\(\s*(?:PORT|port|\d+)/;
|
|
6877
|
-
var V20_HEALTH_OR_READINESS_ROUTE_PATTERN = /["'`]\/(?:health|ready|readiness|
|
|
7034
|
+
var V20_HEALTH_OR_READINESS_ROUTE_PATTERN = /["'`][\w${}./-]{0,60}\/(?:(?:health|ready|live)z?|readiness|liveness)(?=["'`/])/i;
|
|
7035
|
+
var EXPRESS_ERROR_MIDDLEWARE_PATTERN = /\(\s*(?:err|error)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:req|request)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:res|response)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*next(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*\)\s*(?:=>|\{)/i;
|
|
7036
|
+
var EXPRESS_MIDDLEWARE_REACHABLE_PATTERN = /\.use\s*\(|\bmodule\.exports\b|\bexport\s+(?:default\b|function\b|const\b)/;
|
|
6878
7037
|
var RACK_SERVER_ENTRYPOINT_PATTERN = /Rack::(?:Server|Handler(?:::\w+)?)\.(?:start|run)\s*\(/;
|
|
6879
7038
|
var RACK_CONFIG_RUN_PATTERN = /^\s*run\s+(?:(?:[A-Z]\w*(?:::\w+)*(?:\.new)?|lambda)\b|->)/m;
|
|
6880
7039
|
var RUNTIME_CONTAINER_COMMAND_PATTERN = /^\s*(?:CMD|ENTRYPOINT)\s+.*(?:\b(?:start|serve|server|qgis|nginx|apache|gunicorn|uvicorn)\b|manage\.py\s+runserver).*$/im;
|
|
@@ -7066,7 +7225,69 @@ function fileContains(repoPath, file, pattern) {
|
|
|
7066
7225
|
if (!isRegularFile(fullPath)) return false;
|
|
7067
7226
|
return pattern.test(readRepoText(fullPath, "utf8"));
|
|
7068
7227
|
}
|
|
7069
|
-
|
|
7228
|
+
var PEM_PRIVATE_KEY_ASSIGNMENT_PATTERN = /(?:["'])?([A-Za-z_$][\w$.-]*)(?:["'])?\s*[:=]\s*(?:\|[-+0-9]*\s*\r?\n[ \t]*)?["'`]?-----BEGIN((?: (?:RSA|DSA|EC|OPENSSH|ENCRYPTED))? PRIVATE KEY)-----([\s\S]{0,32768}?)-----END\2-----/g;
|
|
7229
|
+
var BARE_PEM_PRIVATE_KEY_PATTERN = /["'`]?-----BEGIN((?: (?:RSA|DSA|EC|OPENSSH|ENCRYPTED))? PRIVATE KEY)-----([\s\S]{0,32768}?)-----END\1-----/g;
|
|
7230
|
+
var BARE_PEM_KEY_FILE_PATTERN = /(?:^|\/)id_(?:rsa|dsa|ecdsa|ed25519)$|\.(?:pem|key)$/i;
|
|
7231
|
+
function isBarePemKeyFile(file) {
|
|
7232
|
+
return BARE_PEM_KEY_FILE_PATTERN.test(file);
|
|
7233
|
+
}
|
|
7234
|
+
var DEV_OR_SELF_SIGNED_KEY_CONTEXT_PATTERN = /(?<![A-Za-z0-9])(?:dev|development|local|localhost|self[-_]?signed|snakeoil|insecure|dummy|sample|demo|placeholder|fixture|generated)(?![A-Za-z0-9])/i;
|
|
7235
|
+
var ROTATED_OR_REVOKED_KEY_CONTEXT_PATTERN = /(?<![A-Za-z0-9])(?:rotated|revoked|expired|deprecated|compromised|invalidated|superseded)(?![A-Za-z0-9])|do[-_ ]not[-_ ]use|no longer valid/i;
|
|
7236
|
+
function isPlausiblePemKeyBody(rawBody) {
|
|
7237
|
+
const normalized = rawBody.replace(/\\[nr]|[\r\n\s]/g, "");
|
|
7238
|
+
if (normalized.length < 40) return false;
|
|
7239
|
+
if (!/^[A-Za-z0-9+/=]+$/.test(normalized)) return false;
|
|
7240
|
+
return !isPlaceholderSecretValue(normalized);
|
|
7241
|
+
}
|
|
7242
|
+
function scanForPemPrivateKeyMatch(pattern, contents, lines, hasIdentifierGroup) {
|
|
7243
|
+
pattern.lastIndex = 0;
|
|
7244
|
+
let match;
|
|
7245
|
+
while ((match = pattern.exec(contents)) !== null) {
|
|
7246
|
+
const full = match[0];
|
|
7247
|
+
const identifier = hasIdentifierGroup ? match[1] ?? "" : "";
|
|
7248
|
+
const body = (hasIdentifierGroup ? match[3] : match[2]) ?? "";
|
|
7249
|
+
const line = contents.slice(0, match.index).split("\n").length;
|
|
7250
|
+
const contextLines = lines.slice(Math.max(0, line - 5), line + 4).join("\n");
|
|
7251
|
+
const isDevOrSelfSigned = DEV_OR_SELF_SIGNED_KEY_CONTEXT_PATTERN.test(
|
|
7252
|
+
`${identifier}
|
|
7253
|
+
${contextLines}`
|
|
7254
|
+
);
|
|
7255
|
+
const isRotatedOrRevoked = ROTATED_OR_REVOKED_KEY_CONTEXT_PATTERN.test(contextLines);
|
|
7256
|
+
if (isPlausiblePemKeyBody(body) && !isDevOrSelfSigned && !isRotatedOrRevoked) {
|
|
7257
|
+
const value = full.slice(full.indexOf("-----BEGIN"));
|
|
7258
|
+
const characterClasses = [
|
|
7259
|
+
/[a-z]/.test(value) ? "lower" : "",
|
|
7260
|
+
/[A-Z]/.test(value) ? "upper" : "",
|
|
7261
|
+
/\d/.test(value) ? "digit" : "",
|
|
7262
|
+
/[^A-Za-z0-9]/.test(value) ? "symbol" : ""
|
|
7263
|
+
].filter(Boolean).join("+");
|
|
7264
|
+
return {
|
|
7265
|
+
identifier,
|
|
7266
|
+
line,
|
|
7267
|
+
valueLength: value.length,
|
|
7268
|
+
characterClasses: characterClasses || "other",
|
|
7269
|
+
valueFingerprint: createHash2("sha256").update(value).digest("hex"),
|
|
7270
|
+
kind: "pem_private_key"
|
|
7271
|
+
};
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7274
|
+
return null;
|
|
7275
|
+
}
|
|
7276
|
+
function findPemPrivateKeyAssignment(contents, file) {
|
|
7277
|
+
if (!contents.includes("-----BEGIN")) return null;
|
|
7278
|
+
if (DEV_OR_SELF_SIGNED_KEY_CONTEXT_PATTERN.test(file)) return null;
|
|
7279
|
+
const lines = contents.split("\n");
|
|
7280
|
+
const assignmentMatch = scanForPemPrivateKeyMatch(
|
|
7281
|
+
PEM_PRIVATE_KEY_ASSIGNMENT_PATTERN,
|
|
7282
|
+
contents,
|
|
7283
|
+
lines,
|
|
7284
|
+
true
|
|
7285
|
+
);
|
|
7286
|
+
if (assignmentMatch) return assignmentMatch;
|
|
7287
|
+
if (!isBarePemKeyFile(file)) return null;
|
|
7288
|
+
return scanForPemPrivateKeyMatch(BARE_PEM_PRIVATE_KEY_PATTERN, contents, lines, false);
|
|
7289
|
+
}
|
|
7290
|
+
function findCommittedSecretInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false, useV23PemPrivateKeyGrammar = false) {
|
|
7070
7291
|
const fullPath = join(repoPath, file);
|
|
7071
7292
|
if (!isRegularFile(fullPath)) return null;
|
|
7072
7293
|
const contents = readRepoText(fullPath, "utf8");
|
|
@@ -7082,6 +7303,10 @@ function findCommittedSecretInFile(repoPath, file, useV36Detectors = false, useV
|
|
|
7082
7303
|
allowCredentialNamedDigest: useV36Detectors
|
|
7083
7304
|
});
|
|
7084
7305
|
if (secret) return secret;
|
|
7306
|
+
if (useV23PemPrivateKeyGrammar) {
|
|
7307
|
+
const pemSecret = findPemPrivateKeyAssignment(secretScanContents, file);
|
|
7308
|
+
if (pemSecret) return pemSecret;
|
|
7309
|
+
}
|
|
7085
7310
|
if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
|
|
7086
7311
|
const weakCredential = findWeakExplicitEnvCredential(secretScanContents);
|
|
7087
7312
|
if (weakCredential) return weakCredential;
|
|
@@ -7174,7 +7399,7 @@ function hasTemplateOnlyEnvContent(repoPath, file) {
|
|
|
7174
7399
|
return value.length === 0 || isPlaceholderSecretValue(value);
|
|
7175
7400
|
});
|
|
7176
7401
|
}
|
|
7177
|
-
function findSecretFingerprintsInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
7402
|
+
function findSecretFingerprintsInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false, useV23PemPrivateKeyGrammar = false) {
|
|
7178
7403
|
const fullPath = join(repoPath, file);
|
|
7179
7404
|
if (!isRegularFile(fullPath)) return [];
|
|
7180
7405
|
const contents = readRepoText(fullPath, "utf8");
|
|
@@ -7191,6 +7416,10 @@ function findSecretFingerprintsInFile(repoPath, file, useV36Detectors = false, u
|
|
|
7191
7416
|
Number.POSITIVE_INFINITY,
|
|
7192
7417
|
{ allowCredentialNamedDigest: useV36Detectors }
|
|
7193
7418
|
).map(({ valueFingerprint }) => valueFingerprint);
|
|
7419
|
+
if (useV23PemPrivateKeyGrammar) {
|
|
7420
|
+
const pemSecret = findPemPrivateKeyAssignment(scanContents, file);
|
|
7421
|
+
if (pemSecret) fingerprints.push(pemSecret.valueFingerprint);
|
|
7422
|
+
}
|
|
7194
7423
|
if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
|
|
7195
7424
|
const weakCredential = findWeakExplicitEnvCredential(scanContents);
|
|
7196
7425
|
if (weakCredential) fingerprints.push(weakCredential.valueFingerprint);
|
|
@@ -7334,24 +7563,32 @@ function declareHistoryFailure(scanLimitations, reason, operation, maxBufferByte
|
|
|
7334
7563
|
`${describeGitFailure(reason, operation, maxBufferBytes)} Cejel continued with reduced Git history evidence.`
|
|
7335
7564
|
);
|
|
7336
7565
|
}
|
|
7337
|
-
function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFingerprintsByPath, scanLimitations, authoredProductionOnly = false, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
7566
|
+
function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFingerprintsByPath, scanLimitations, authoredProductionOnly = false, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false, useV23PemPrivateKeyGrammar = false) {
|
|
7338
7567
|
const historyEntries = readCredentialHistoryEntries(
|
|
7339
7568
|
repoPath,
|
|
7340
7569
|
scanLimitations,
|
|
7341
|
-
useV36Detectors
|
|
7570
|
+
useV36Detectors,
|
|
7571
|
+
useV23PemPrivateKeyGrammar
|
|
7342
7572
|
);
|
|
7343
7573
|
const head = excludeHead ? readGitHead(repoPath) : null;
|
|
7344
7574
|
let scannedFiles = 0;
|
|
7575
|
+
let v23WidenedOnlyScanned = 0;
|
|
7345
7576
|
let envPathEvidence = null;
|
|
7346
7577
|
for (const entry of historyEntries) {
|
|
7347
7578
|
if (head && entry.commit === head) continue;
|
|
7348
7579
|
if (authoredProductionOnly && !(useV47Detectors ? isV47AuthoredProductionPath(entry.path) : useV39Detectors ? isV39AuthoredProductionPath(entry.path) : isAuthoredProductionPath(entry.path))) {
|
|
7349
7580
|
continue;
|
|
7350
7581
|
}
|
|
7351
|
-
|
|
7352
|
-
|
|
7582
|
+
const isV23WidenedOnly = useV23PemPrivateKeyGrammar && isV23PemCredentialHistoryPath(historyPathBasename(entry.path.toLowerCase())) && !isCredentialHistoryPath(entry.path, useV36Detectors, false);
|
|
7583
|
+
if (isV23WidenedOnly) {
|
|
7584
|
+
if (v23WidenedOnlyScanned >= V23_PEM_WIDENED_HISTORY_BLOB_LIMIT) continue;
|
|
7585
|
+
v23WidenedOnlyScanned += 1;
|
|
7586
|
+
} else {
|
|
7587
|
+
if (scannedFiles >= HISTORY_SECRET_SCAN_CREDENTIAL_BLOB_LIMIT) {
|
|
7588
|
+
return { evidence: null, secretKind: null, envPathEvidence, truncated: true };
|
|
7589
|
+
}
|
|
7590
|
+
scannedFiles += 1;
|
|
7353
7591
|
}
|
|
7354
|
-
scannedFiles += 1;
|
|
7355
7592
|
const contents = readGitBlob(repoPath, entry.commit, entry.path, scanLimitations);
|
|
7356
7593
|
if (!contents) continue;
|
|
7357
7594
|
const scanContents = prepareCredentialScanContents(
|
|
@@ -7365,6 +7602,12 @@ function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFinger
|
|
|
7365
7602
|
let secretMatch = findRealSecretAssignment(scanContents, currentFingerprints, {
|
|
7366
7603
|
allowCredentialNamedDigest: useV36Detectors
|
|
7367
7604
|
});
|
|
7605
|
+
if (!secretMatch && useV23PemPrivateKeyGrammar) {
|
|
7606
|
+
const pemMatch = findPemPrivateKeyAssignment(scanContents, entry.path);
|
|
7607
|
+
if (pemMatch && !currentFingerprints.has(pemMatch.valueFingerprint)) {
|
|
7608
|
+
secretMatch = pemMatch;
|
|
7609
|
+
}
|
|
7610
|
+
}
|
|
7368
7611
|
if (!secretMatch && useV39Detectors && isEnvHistoryPath(entry.path, useV36Detectors, useV47Detectors)) {
|
|
7369
7612
|
const weakCredential = findWeakExplicitEnvCredential(scanContents);
|
|
7370
7613
|
if (weakCredential && !currentFingerprints.has(weakCredential.valueFingerprint)) {
|
|
@@ -7375,11 +7618,15 @@ function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFinger
|
|
|
7375
7618
|
return {
|
|
7376
7619
|
evidence: {
|
|
7377
7620
|
kind: "secret_scan",
|
|
7378
|
-
label: secretEvidenceLabel(
|
|
7621
|
+
label: secretEvidenceLabel(
|
|
7622
|
+
secretMatch.kind === "pem_private_key" ? "Committed PEM-formatted private key in recent git history" : "Committed secret in recent git history",
|
|
7623
|
+
secretMatch
|
|
7624
|
+
),
|
|
7379
7625
|
path: entry.path,
|
|
7380
7626
|
line: secretMatch.line,
|
|
7381
7627
|
contentHash: entry.commit
|
|
7382
7628
|
},
|
|
7629
|
+
secretKind: secretMatch.kind ?? null,
|
|
7383
7630
|
envPathEvidence,
|
|
7384
7631
|
truncated: false
|
|
7385
7632
|
};
|
|
@@ -7393,9 +7640,9 @@ function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFinger
|
|
|
7393
7640
|
};
|
|
7394
7641
|
}
|
|
7395
7642
|
}
|
|
7396
|
-
return { evidence: null, envPathEvidence, truncated: false };
|
|
7643
|
+
return { evidence: null, secretKind: null, envPathEvidence, truncated: false };
|
|
7397
7644
|
}
|
|
7398
|
-
function readCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors = false) {
|
|
7645
|
+
function readCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors = false, useV23PemPrivateKeyGrammar = false) {
|
|
7399
7646
|
const entries = [];
|
|
7400
7647
|
const seen = /* @__PURE__ */ new Set();
|
|
7401
7648
|
const appendEntry = (entry) => {
|
|
@@ -7440,7 +7687,7 @@ function readCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors
|
|
|
7440
7687
|
scanLimitations.add(CONTROL_CHARACTER_HISTORY_PATH_LIMITATION);
|
|
7441
7688
|
continue;
|
|
7442
7689
|
}
|
|
7443
|
-
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
7690
|
+
if (!isCredentialHistoryPath(path, useV36Detectors, useV23PemPrivateKeyGrammar)) continue;
|
|
7444
7691
|
appendEntry({ commit: currentCommit, path });
|
|
7445
7692
|
}
|
|
7446
7693
|
}
|
|
@@ -7448,17 +7695,19 @@ function readCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors
|
|
|
7448
7695
|
for (const entry of readDeletedCredentialHistoryEntries(
|
|
7449
7696
|
repoPath,
|
|
7450
7697
|
scanLimitations,
|
|
7451
|
-
useV36Detectors
|
|
7698
|
+
useV36Detectors,
|
|
7699
|
+
useV23PemPrivateKeyGrammar
|
|
7452
7700
|
)) {
|
|
7453
7701
|
appendEntry(entry);
|
|
7454
7702
|
}
|
|
7455
7703
|
return entries;
|
|
7456
7704
|
}
|
|
7457
|
-
function readDeletedCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors = false) {
|
|
7705
|
+
function readDeletedCredentialHistoryEntries(repoPath, scanLimitations, useV36Detectors = false, useV23PemPrivateKeyGrammar = false) {
|
|
7458
7706
|
const deletedPaths = readDeletedCredentialHistoryPaths(
|
|
7459
7707
|
repoPath,
|
|
7460
7708
|
scanLimitations,
|
|
7461
|
-
useV36Detectors
|
|
7709
|
+
useV36Detectors,
|
|
7710
|
+
useV23PemPrivateKeyGrammar
|
|
7462
7711
|
);
|
|
7463
7712
|
const entries = [];
|
|
7464
7713
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7472,7 +7721,7 @@ function readDeletedCredentialHistoryEntries(repoPath, scanLimitations, useV36De
|
|
|
7472
7721
|
}
|
|
7473
7722
|
return entries;
|
|
7474
7723
|
}
|
|
7475
|
-
function readDeletedCredentialHistoryPaths(repoPath, scanLimitations, useV36Detectors = false) {
|
|
7724
|
+
function readDeletedCredentialHistoryPaths(repoPath, scanLimitations, useV36Detectors = false, useV23PemPrivateKeyGrammar = false) {
|
|
7476
7725
|
const historyResult = execGit(
|
|
7477
7726
|
[
|
|
7478
7727
|
"log",
|
|
@@ -7500,7 +7749,7 @@ function readDeletedCredentialHistoryPaths(repoPath, scanLimitations, useV36Dete
|
|
|
7500
7749
|
continue;
|
|
7501
7750
|
}
|
|
7502
7751
|
if (!path || seen.has(path)) continue;
|
|
7503
|
-
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
7752
|
+
if (!isCredentialHistoryPath(path, useV36Detectors, useV23PemPrivateKeyGrammar)) continue;
|
|
7504
7753
|
seen.add(path);
|
|
7505
7754
|
paths.push(path);
|
|
7506
7755
|
}
|
|
@@ -7518,14 +7767,24 @@ function readHistoryCommitsForPath(repoPath, path, scanLimitations) {
|
|
|
7518
7767
|
}
|
|
7519
7768
|
return historyResult.stdout.split("\n").map((line) => line.trim()).filter((line) => /^[0-9a-f]{40}$/i.test(line));
|
|
7520
7769
|
}
|
|
7521
|
-
|
|
7770
|
+
var V23_KEY_SHAPED_JSON_PATH_PATTERN = /(?<![A-Za-z0-9])(?:service[-_]?account|private[-_]?key|creds?|key)(?![A-Za-z0-9])/i;
|
|
7771
|
+
function historyPathBasename(lowerPath) {
|
|
7772
|
+
return lowerPath.split("/").at(-1) ?? lowerPath;
|
|
7773
|
+
}
|
|
7774
|
+
function isV23PemCredentialHistoryPath(basename3) {
|
|
7775
|
+
return basename3.endsWith(".json") && V23_KEY_SHAPED_JSON_PATH_PATTERN.test(basename3);
|
|
7776
|
+
}
|
|
7777
|
+
function isCredentialHistoryPath(path, _useV36Detectors = false, useV23PemPrivateKeyGrammar = false) {
|
|
7522
7778
|
if (isHardExcludedPath(path)) return false;
|
|
7523
7779
|
const lowerPath = path.toLowerCase();
|
|
7524
|
-
const basename3 = lowerPath
|
|
7780
|
+
const basename3 = historyPathBasename(lowerPath);
|
|
7525
7781
|
if (/\.env(?:\.|$)/.test(basename3)) {
|
|
7526
7782
|
return true;
|
|
7527
7783
|
}
|
|
7528
|
-
|
|
7784
|
+
if (lowerPath.includes("secret") || lowerPath.includes("credential") || basename3.endsWith(".pem") || basename3.endsWith(".key") || basename3 === "id_rsa" || basename3.endsWith(".p12") || basename3.endsWith(".pfx")) {
|
|
7785
|
+
return true;
|
|
7786
|
+
}
|
|
7787
|
+
return useV23PemPrivateKeyGrammar && isV23PemCredentialHistoryPath(basename3);
|
|
7529
7788
|
}
|
|
7530
7789
|
function isEnvHistoryPath(path, useV36Detectors = false, useV47Detectors = false) {
|
|
7531
7790
|
if (isEnvTemplatePath(path, useV47Detectors)) return false;
|
|
@@ -8235,6 +8494,22 @@ function discoverIngestInputs(repoPath, maxMatches = Number.POSITIVE_INFINITY) {
|
|
|
8235
8494
|
// src/witan/scoring.ts
|
|
8236
8495
|
init_esm_shims();
|
|
8237
8496
|
|
|
8497
|
+
// src/witan/rubric-fingerprint.ts
|
|
8498
|
+
init_esm_shims();
|
|
8499
|
+
import { createHash as createHash3 } from "crypto";
|
|
8500
|
+
var WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS = Object.freeze({
|
|
8501
|
+
[WITAN_RUBRIC_VERSION_V17]: "sha256:ff0f01abe8c12daa60375d0e18c1aca4a1135a55ab2a80b15f4c037bfc18b4b1",
|
|
8502
|
+
[WITAN_RUBRIC_VERSION_V18]: "sha256:abc35df0fdc0749aa941f47b295059d06c96dd9800af7f79cfef2bc8dae917da",
|
|
8503
|
+
[WITAN_RUBRIC_VERSION_V19]: "sha256:f670fa2cc44beef066fa38767bd587e1a16fb28b03bc904756daaea5a6465e96",
|
|
8504
|
+
[WITAN_RUBRIC_VERSION_V20]: "sha256:9276ce2ce865c2d50c52882ee45bbf1df5fe3c8b6038858b4a147f7a4cedfa98",
|
|
8505
|
+
[WITAN_RUBRIC_VERSION_V21]: "sha256:d86411a7fe108bba4d2b8fe7ff9d4f7f69a60f11ca0c5c3fef8e88f66a9af53a",
|
|
8506
|
+
[WITAN_RUBRIC_VERSION_V22]: "sha256:1bb57dd98d29b43b170f3a4f1c8b3b379b433a2aa9df0821ab9d5115f48cfb2f",
|
|
8507
|
+
[WITAN_RUBRIC_VERSION_V23]: "sha256:b558c0aa4b2f85b734bed2e077298fd55851a1086234d678f10a71cf913ec3cf"
|
|
8508
|
+
});
|
|
8509
|
+
function rubricBehaviourFingerprint(rubricVersion) {
|
|
8510
|
+
return WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS[rubricVersion];
|
|
8511
|
+
}
|
|
8512
|
+
|
|
8238
8513
|
// src/witan/rubric.ts
|
|
8239
8514
|
init_esm_shims();
|
|
8240
8515
|
var WITAN_RUBRIC = [
|
|
@@ -8300,11 +8575,11 @@ function usesV17DetectorClosure2(rubricVersion) {
|
|
|
8300
8575
|
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
8301
8576
|
}
|
|
8302
8577
|
var MAX_SIGNAL_ADJUSTMENT = 0.8;
|
|
8303
|
-
function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
8578
|
+
function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC, toolVersion) {
|
|
8304
8579
|
const parsedInput = WitanReportInputSchema.parse(input);
|
|
8305
8580
|
const signalsByCriterion = mergeSignalsByCriterion(parsedInput.signals);
|
|
8306
8581
|
const readFailureAbstentions = new Set(
|
|
8307
|
-
[...signalsByCriterion.values()].filter((signal) => signal.insufficientData === true).map((signal) => signal.criterionId)
|
|
8582
|
+
[...signalsByCriterion.values()].filter((signal) => signal.insufficientData === true && !isSelfImposedAbstention(signal)).map((signal) => signal.criterionId)
|
|
8308
8583
|
);
|
|
8309
8584
|
const hasInputSignals = inputSignals != null && inputSignals.length > 0;
|
|
8310
8585
|
const inputSignalsByDimension = /* @__PURE__ */ new Map();
|
|
@@ -8394,6 +8669,13 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
8394
8669
|
productDisplayName: parsedInput.productDisplayName,
|
|
8395
8670
|
repo: parsedInput.repo,
|
|
8396
8671
|
rubricVersion: parsedInput.rubricVersion,
|
|
8672
|
+
// Behavioural identity beside the rubric's name, so a reader holding two certificates can
|
|
8673
|
+
// check whether the scoring that produced them agrees instead of inferring it from a string
|
|
8674
|
+
// the tool's author controls. Derived only from parsedInput.rubricVersion, so it is a
|
|
8675
|
+
// property of the (build, rubric) pair and not of this invocation: two runs of the same
|
|
8676
|
+
// installed version at the same revision stay byte-identical. Absent, never fabricated, for
|
|
8677
|
+
// a rubric this build carries no measurement for.
|
|
8678
|
+
...rubricBehaviourFingerprint(parsedInput.rubricVersion) ? { rubricBehaviourFingerprint: rubricBehaviourFingerprint(parsedInput.rubricVersion) } : {},
|
|
8397
8679
|
verdict: abstained ? "insufficient_source" : witanVerdictForScore(overallScore),
|
|
8398
8680
|
codeTrustScore: abstained ? null : codeTrustScore,
|
|
8399
8681
|
processTrustScore: abstained ? null : processTrustScore,
|
|
@@ -8409,7 +8691,8 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
8409
8691
|
// alter it (see classifyRepoArchetype in repo-signals.ts); per-criterion scoring above is
|
|
8410
8692
|
// unaffected by archetype, only presentation layers (badge/terminal/verdict) key off it.
|
|
8411
8693
|
...parsedInput.archetype ? { archetype: parsedInput.archetype } : {},
|
|
8412
|
-
...insufficientSourceReason ? { insufficientSourceReason } : {}
|
|
8694
|
+
...insufficientSourceReason ? { insufficientSourceReason } : {},
|
|
8695
|
+
...toolVersion ? { toolVersion } : {}
|
|
8413
8696
|
});
|
|
8414
8697
|
}
|
|
8415
8698
|
function applySignalAdjustment(nativeScore, dimension, signals) {
|
|
@@ -8479,6 +8762,14 @@ function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubri
|
|
|
8479
8762
|
);
|
|
8480
8763
|
return (severity[adjustedSemanticStatus] ?? nativeSeverity) > nativeSeverity ? adjustedSemanticStatus : nativeStatus;
|
|
8481
8764
|
}
|
|
8765
|
+
var SELF_IMPOSED_ABSTENTION_REASONS = /* @__PURE__ */ new Set([
|
|
8766
|
+
"too_large",
|
|
8767
|
+
"excluded_by_extension",
|
|
8768
|
+
"non_regular_file"
|
|
8769
|
+
]);
|
|
8770
|
+
function isSelfImposedAbstention(signal) {
|
|
8771
|
+
return signal.insufficientDataReason !== void 0 && SELF_IMPOSED_ABSTENTION_REASONS.has(signal.insufficientDataReason);
|
|
8772
|
+
}
|
|
8482
8773
|
function mergeSignalsByCriterion(signals) {
|
|
8483
8774
|
const merged = /* @__PURE__ */ new Map();
|
|
8484
8775
|
for (const signal of signals) {
|
|
@@ -8487,15 +8778,19 @@ function mergeSignalsByCriterion(signals) {
|
|
|
8487
8778
|
merged.set(signal.criterionId, signal);
|
|
8488
8779
|
continue;
|
|
8489
8780
|
}
|
|
8781
|
+
const existingAbstains = existing.insufficientData === true;
|
|
8782
|
+
const signalAbstains = signal.insufficientData === true;
|
|
8783
|
+
const abstentionReason = !existingAbstains && !signalAbstains ? void 0 : [existing, signal].filter((candidate) => candidate.insufficientData === true).some((candidate) => !isSelfImposedAbstention(candidate)) ? void 0 : signal.insufficientDataReason ?? existing.insufficientDataReason;
|
|
8490
8784
|
merged.set(signal.criterionId, {
|
|
8491
8785
|
criterionId: signal.criterionId,
|
|
8492
8786
|
positiveEvidence: [...existing.positiveEvidence, ...signal.positiveEvidence],
|
|
8493
8787
|
findings: [...existing.findings, ...signal.findings],
|
|
8494
8788
|
metrics: [...existing.metrics ?? [], ...signal.metrics ?? []],
|
|
8495
8789
|
notes: signal.notes ?? existing.notes,
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8790
|
+
...existingAbstains || signalAbstains ? {
|
|
8791
|
+
insufficientData: true,
|
|
8792
|
+
...abstentionReason !== void 0 ? { insufficientDataReason: abstentionReason } : {}
|
|
8793
|
+
} : existing.notApplicable === true || signal.notApplicable === true ? { notApplicable: true } : {}
|
|
8499
8794
|
});
|
|
8500
8795
|
}
|
|
8501
8796
|
return merged;
|
|
@@ -8931,7 +9226,12 @@ function scoreRepoWithPublicCejel(options) {
|
|
|
8931
9226
|
...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {}
|
|
8932
9227
|
});
|
|
8933
9228
|
const inputSignals = resolvePublicIngestSignals(options);
|
|
8934
|
-
const report = createWitanReport(
|
|
9229
|
+
const report = createWitanReport(
|
|
9230
|
+
input,
|
|
9231
|
+
inputSignals.length > 0 ? inputSignals : void 0,
|
|
9232
|
+
void 0,
|
|
9233
|
+
options.toolVersion
|
|
9234
|
+
);
|
|
8935
9235
|
if (report.verdict !== "insufficient_source" || !isWitanNoMeasurementAbstention(report)) {
|
|
8936
9236
|
return report;
|
|
8937
9237
|
}
|
|
@@ -9189,7 +9489,8 @@ function runCejelScan(options) {
|
|
|
9189
9489
|
// the repository. Direct public/leaderboard scans default this off because their targets
|
|
9190
9490
|
// are third-party repositories and must opt in explicitly.
|
|
9191
9491
|
autoDiscoverIngest: true,
|
|
9192
|
-
warnOnEmptyIngestMatch: options.warnOnEmptyIngestMatch
|
|
9492
|
+
warnOnEmptyIngestMatch: options.warnOnEmptyIngestMatch,
|
|
9493
|
+
...options.toolVersion ? { toolVersion: options.toolVersion } : {}
|
|
9193
9494
|
});
|
|
9194
9495
|
const summary = buildWitanCliSummary(report);
|
|
9195
9496
|
return { report, summary, generatedAt };
|