@cejel/cejel 0.1.7 → 0.1.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 +56 -7
- package/dist/{chunk-OMIRVZSF.js → chunk-DVVBWBAA.js} +1606 -206
- package/dist/index.js +3580 -23
- package/dist/mcp/index.js +1 -1
- package/package.json +1 -1
|
@@ -4076,7 +4076,11 @@ var WITAN_RUBRIC_VERSION_V2 = "witan-rubric-v2-2026-07-12";
|
|
|
4076
4076
|
var WITAN_RUBRIC_VERSION_V3 = "witan-rubric-v3-2026-07-13";
|
|
4077
4077
|
var WITAN_RUBRIC_VERSION_V4 = "witan-rubric-v4-2026-07-18";
|
|
4078
4078
|
var WITAN_RUBRIC_VERSION_V5 = "witan-rubric-v5-2026-07-18";
|
|
4079
|
-
var
|
|
4079
|
+
var WITAN_RUBRIC_VERSION_V6 = "witan-rubric-v6-2026-07-21";
|
|
4080
|
+
var WITAN_RUBRIC_VERSION_V7 = "witan-rubric-v7-2026-07-21";
|
|
4081
|
+
var WITAN_RUBRIC_VERSION_V8 = "witan-rubric-v8-2026-07-21";
|
|
4082
|
+
var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
|
|
4083
|
+
var WITAN_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V9;
|
|
4080
4084
|
var WITAN_TRADING_RUBRIC_VERSION_V0 = "witan-trading-rubric-v0-2026-07-01";
|
|
4081
4085
|
var WITAN_CRITERION_IDS = [
|
|
4082
4086
|
"A1",
|
|
@@ -4144,6 +4148,8 @@ var WITAN_REPO_ARCHETYPES = [
|
|
|
4144
4148
|
"monorepo",
|
|
4145
4149
|
"docs_only",
|
|
4146
4150
|
"binary_only",
|
|
4151
|
+
"generated_only",
|
|
4152
|
+
"non_cohesive_source",
|
|
4147
4153
|
"unrecognised_ecosystem",
|
|
4148
4154
|
"empty"
|
|
4149
4155
|
];
|
|
@@ -4359,6 +4365,39 @@ var WitanAttestationStatementSchema = external_exports.object({
|
|
|
4359
4365
|
}).strict()
|
|
4360
4366
|
}).strict();
|
|
4361
4367
|
|
|
4368
|
+
// src/witan/coverage.ts
|
|
4369
|
+
var LOW_CONFIDENCE_COVERAGE_THRESHOLD = 0.5;
|
|
4370
|
+
function isMeasuredCriterionStatus(status) {
|
|
4371
|
+
return status !== "not_applicable" && status !== "insufficient_data";
|
|
4372
|
+
}
|
|
4373
|
+
function computeMeasuredCoverage(report) {
|
|
4374
|
+
const byCategory = [];
|
|
4375
|
+
const overall = { measured: 0, total: 0 };
|
|
4376
|
+
for (const criterion of report.criteria) {
|
|
4377
|
+
let bucket = byCategory.find((entry) => entry.category === criterion.category);
|
|
4378
|
+
if (!bucket) {
|
|
4379
|
+
bucket = { category: criterion.category, measured: 0, total: 0 };
|
|
4380
|
+
byCategory.push(bucket);
|
|
4381
|
+
}
|
|
4382
|
+
bucket.total += 1;
|
|
4383
|
+
overall.total += 1;
|
|
4384
|
+
if (isMeasuredCriterionStatus(criterion.status)) {
|
|
4385
|
+
bucket.measured += 1;
|
|
4386
|
+
overall.measured += 1;
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
const lowConfidence = [...byCategory, overall].some(
|
|
4390
|
+
(counts) => counts.total > 0 && counts.measured / counts.total < LOW_CONFIDENCE_COVERAGE_THRESHOLD
|
|
4391
|
+
);
|
|
4392
|
+
return { byCategory, overall, lowConfidence };
|
|
4393
|
+
}
|
|
4394
|
+
function shortCategoryLabel(category) {
|
|
4395
|
+
return category.replace(/_trust$/, "").replaceAll("_", " ");
|
|
4396
|
+
}
|
|
4397
|
+
function formatCoverageCounts(coverage) {
|
|
4398
|
+
return coverage.byCategory.map((entry) => `${shortCategoryLabel(entry.category)} ${entry.measured}/${entry.total}`).join(" \xB7 ");
|
|
4399
|
+
}
|
|
4400
|
+
|
|
4362
4401
|
// src/witan/external-findings.ts
|
|
4363
4402
|
var EXTERNAL_FINDINGS_DISPLAY_LIMIT = 10;
|
|
4364
4403
|
var SEVERITY_RANK = {
|
|
@@ -4423,6 +4462,7 @@ function renderWitanHtmlReport(report) {
|
|
|
4423
4462
|
const contributingSources = renderContributingSources(report.consumedSignals ?? []);
|
|
4424
4463
|
const externalSourceSummaries = summarizeExternalSources(report.consumedSignals ?? []);
|
|
4425
4464
|
const externalFindings = collectExternalFindings(report.consumedSignals ?? []);
|
|
4465
|
+
const coverage = computeMeasuredCoverage(report);
|
|
4426
4466
|
return `<!doctype html>
|
|
4427
4467
|
<html lang="en">
|
|
4428
4468
|
<head>
|
|
@@ -4456,9 +4496,10 @@ function renderWitanHtmlReport(report) {
|
|
|
4456
4496
|
<div class="score-unit">/ 4.0 overall</div>
|
|
4457
4497
|
<div class="verdict">${escapeHtml(renderVerdict(report.overallScore))}</div>
|
|
4458
4498
|
<div class="subscores">
|
|
4459
|
-
<span>Code ${
|
|
4460
|
-
<span>Process ${
|
|
4461
|
-
</div
|
|
4499
|
+
<span>Code ${renderCategoryScore(report.codeTrustScore, coverage, "code_trust")}</span>
|
|
4500
|
+
<span>Process ${renderCategoryScore(report.processTrustScore, coverage, "process_trust")}</span>
|
|
4501
|
+
</div>
|
|
4502
|
+
<div class="coverage-note">${escapeHtml(formatCoverageCounts(coverage))} measured${coverage.lowConfidence ? " \xB7 low confidence" : ""}</div>`}
|
|
4462
4503
|
</aside>
|
|
4463
4504
|
</div>
|
|
4464
4505
|
</header>
|
|
@@ -4556,10 +4597,17 @@ function renderCriterionCard(criterion) {
|
|
|
4556
4597
|
</article>`;
|
|
4557
4598
|
}
|
|
4558
4599
|
function renderMetric(metric2) {
|
|
4559
|
-
const max = metric2.max ? `/${formatMetricValue(metric2.max)}` : "";
|
|
4560
4600
|
const unit = metric2.unit ? ` ${escapeHtml(metric2.unit)}` : "";
|
|
4601
|
+
if (metric2.kind === "saturating_count" && metric2.max !== void 0 && metric2.value > metric2.max) {
|
|
4602
|
+
return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue(metric2.max)}${unit} (capped; ${formatMetricValue(metric2.value)} raw)</span>`;
|
|
4603
|
+
}
|
|
4604
|
+
const max = metric2.max ? `/${formatMetricValue(metric2.max)}` : "";
|
|
4561
4605
|
return `<strong>${escapeHtml(metric2.label)}</strong><span>${formatMetricValue(metric2.value)}${max}${unit}</span>`;
|
|
4562
4606
|
}
|
|
4607
|
+
function renderCategoryScore(score, coverage, category) {
|
|
4608
|
+
const categoryCoverage = coverage.byCategory.find((entry) => entry.category === category);
|
|
4609
|
+
return categoryCoverage !== void 0 && categoryCoverage.measured === 0 || score === null ? "not measured" : formatScore(score);
|
|
4610
|
+
}
|
|
4563
4611
|
function renderStatusChip(status) {
|
|
4564
4612
|
return `<span class="status" data-status="${status}">${status}</span>`;
|
|
4565
4613
|
}
|
|
@@ -4700,6 +4748,7 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
|
|
|
4700
4748
|
.verdict { margin-top: 12px; font-family: var(--serif); font-size: 30px; color: var(--periwinkle); }
|
|
4701
4749
|
.insufficient-note { margin-top: 10px; color: var(--muted); font-size: 13px; line-height: 1.5; }
|
|
4702
4750
|
.subscores { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; color: var(--muted); font-family: var(--mono); font-size: 12px; }
|
|
4751
|
+
.coverage-note { margin-top: 8px; color: var(--faint); font-family: var(--mono); font-size: 11px; }
|
|
4703
4752
|
.source-counts { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
|
4704
4753
|
.trust-grid, .evidence-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 28px; }
|
|
4705
4754
|
.external-findings-section { margin-top: 18px; }
|
|
@@ -4914,21 +4963,59 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
4914
4963
|
// src/witan/repo-signals.ts
|
|
4915
4964
|
import { execFileSync } from "child_process";
|
|
4916
4965
|
import { createHash as createHash2 } from "crypto";
|
|
4917
|
-
import {
|
|
4918
|
-
|
|
4966
|
+
import {
|
|
4967
|
+
closeSync,
|
|
4968
|
+
lstatSync,
|
|
4969
|
+
openSync,
|
|
4970
|
+
readFileSync,
|
|
4971
|
+
readSync,
|
|
4972
|
+
readdirSync,
|
|
4973
|
+
realpathSync,
|
|
4974
|
+
statSync
|
|
4975
|
+
} from "fs";
|
|
4976
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from "path";
|
|
4919
4977
|
|
|
4920
4978
|
// src/witan/json-safe.ts
|
|
4921
4979
|
function stripBom(text) {
|
|
4922
4980
|
return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
4923
4981
|
}
|
|
4924
4982
|
|
|
4983
|
+
// src/witan/rubric-version.ts
|
|
4984
|
+
var WITAN_RUBRIC_VERSION_V92 = "witan-rubric-v9-2026-07-22";
|
|
4985
|
+
var WITAN_RUBRIC_VERSION_V10 = "witan-rubric-v10-2026-07-23";
|
|
4986
|
+
var WITAN_RUBRIC_VERSION_V11 = "witan-rubric-v11-2026-07-23";
|
|
4987
|
+
var WITAN_RUBRIC_VERSION_V12 = "witan-rubric-v12-2026-07-23";
|
|
4988
|
+
var WITAN_RUBRIC_VERSION_V13 = "witan-rubric-v13-2026-07-23";
|
|
4989
|
+
var WITAN_RUBRIC_VERSION_V14 = "witan-rubric-v14-2026-07-23";
|
|
4990
|
+
var WITAN_RUBRIC_VERSION_V15 = "witan-rubric-v15-2026-07-24";
|
|
4991
|
+
|
|
4925
4992
|
// src/witan/repo-signals.ts
|
|
4926
4993
|
function buildWitanInputFromRepo(options) {
|
|
4927
4994
|
assertRepoPathExists(options.repoPath);
|
|
4928
4995
|
const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4996
|
+
const rubricVersion = options.rubricVersion ?? WITAN_RUBRIC_VERSION;
|
|
4929
4997
|
const headSha = readGitHead(options.repoPath);
|
|
4930
4998
|
const repoFiles = listRepoFiles(options.repoPath);
|
|
4931
|
-
const
|
|
4999
|
+
const inventoryFiles = listRepoInventory(options.repoPath, repoFiles);
|
|
5000
|
+
const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
|
|
5001
|
+
const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? applyReadableSourceRepresentationGate(
|
|
5002
|
+
options.repoPath,
|
|
5003
|
+
inventoryFiles,
|
|
5004
|
+
structuralArchetype,
|
|
5005
|
+
headSha,
|
|
5006
|
+
rubricVersion
|
|
5007
|
+
) : structuralArchetype;
|
|
5008
|
+
const archetype = rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 ? applySemanticSourceRepresentationGate(
|
|
5009
|
+
options.repoPath,
|
|
5010
|
+
inventoryFiles,
|
|
5011
|
+
readableArchetype,
|
|
5012
|
+
rubricVersion
|
|
5013
|
+
) : readableArchetype;
|
|
5014
|
+
const usesV8Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || 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;
|
|
5015
|
+
const usesV33Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || 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;
|
|
5016
|
+
const usesV36Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V10 || 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;
|
|
5017
|
+
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;
|
|
5018
|
+
const usesV47Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V15;
|
|
4932
5019
|
return {
|
|
4933
5020
|
productSlug: options.productSlug,
|
|
4934
5021
|
productDisplayName: options.productDisplayName,
|
|
@@ -4941,7 +5028,16 @@ function buildWitanInputFromRepo(options) {
|
|
|
4941
5028
|
archetype: archetype.archetype,
|
|
4942
5029
|
...archetype.insufficientSourceReason ? { insufficientSourceReason: archetype.insufficientSourceReason } : {},
|
|
4943
5030
|
signals: [
|
|
4944
|
-
...collectRepoSignals(
|
|
5031
|
+
...collectRepoSignals(
|
|
5032
|
+
options.repoPath,
|
|
5033
|
+
generatedAt,
|
|
5034
|
+
repoFiles,
|
|
5035
|
+
usesV8Detectors,
|
|
5036
|
+
usesV33Detectors,
|
|
5037
|
+
usesV36Detectors,
|
|
5038
|
+
usesV39Detectors,
|
|
5039
|
+
usesV47Detectors
|
|
5040
|
+
),
|
|
4945
5041
|
...(options.domainCollectors ?? []).map((collect) => collect(options.repoPath, repoFiles)),
|
|
4946
5042
|
...options.additionalSignals ?? []
|
|
4947
5043
|
]
|
|
@@ -4966,7 +5062,7 @@ var SOURCE_LANGUAGES = [
|
|
|
4966
5062
|
{ name: "Dart", tier: "partial", extensions: ["dart"] },
|
|
4967
5063
|
{ name: "Elixir", tier: "partial", extensions: ["ex", "exs"] },
|
|
4968
5064
|
{ name: "Scala", tier: "partial", extensions: ["scala"] },
|
|
4969
|
-
{ name: "shell", tier: "unmodelled", extensions: ["sh", "bash", "zsh"] },
|
|
5065
|
+
{ name: "shell", tier: "unmodelled", extensions: ["sh", "bash", "zsh", "bat", "cmd"] },
|
|
4970
5066
|
{ name: "R", tier: "unmodelled", extensions: ["r"] },
|
|
4971
5067
|
{ name: "Lua", tier: "unmodelled", extensions: ["lua"] },
|
|
4972
5068
|
{ name: "Julia", tier: "unmodelled", extensions: ["jl"] },
|
|
@@ -4980,20 +5076,73 @@ var SOURCE_LANGUAGES = [
|
|
|
4980
5076
|
{ name: "Nim", tier: "unmodelled", extensions: ["nim"] },
|
|
4981
5077
|
{ name: "Zig", tier: "unmodelled", extensions: ["zig"] },
|
|
4982
5078
|
{ name: "F#", tier: "unmodelled", extensions: ["fs", "fsx"] },
|
|
4983
|
-
{ name: "Groovy", tier: "unmodelled", extensions: ["groovy", "gvy"] }
|
|
5079
|
+
{ name: "Groovy", tier: "unmodelled", extensions: ["groovy", "gvy"] },
|
|
5080
|
+
{ name: "Fortran", tier: "unmodelled", extensions: ["f", "for", "f77", "f90", "f95", "f03"] },
|
|
5081
|
+
{ name: "CUDA/HIP", tier: "unmodelled", extensions: ["cu", "cuh", "hip"] },
|
|
5082
|
+
{
|
|
5083
|
+
name: "Web templates/styles",
|
|
5084
|
+
tier: "unmodelled",
|
|
5085
|
+
extensions: [
|
|
5086
|
+
"html",
|
|
5087
|
+
"htm",
|
|
5088
|
+
"css",
|
|
5089
|
+
"scss",
|
|
5090
|
+
"sass",
|
|
5091
|
+
"less",
|
|
5092
|
+
"hbs",
|
|
5093
|
+
"handlebars",
|
|
5094
|
+
"variables",
|
|
5095
|
+
"overrides",
|
|
5096
|
+
"vue",
|
|
5097
|
+
"svelte",
|
|
5098
|
+
"astro",
|
|
5099
|
+
"erb",
|
|
5100
|
+
"haml",
|
|
5101
|
+
"slim",
|
|
5102
|
+
"erubis",
|
|
5103
|
+
"hamlit"
|
|
5104
|
+
]
|
|
5105
|
+
}
|
|
5106
|
+
];
|
|
5107
|
+
var SOURCE_LANGUAGES_V10 = [
|
|
5108
|
+
...SOURCE_LANGUAGES,
|
|
5109
|
+
{ name: "COBOL", tier: "unmodelled", extensions: ["cob", "cbl", "cpy"] },
|
|
5110
|
+
{ name: "MATLAB/Objective-C", tier: "unmodelled", extensions: ["m"] }
|
|
4984
5111
|
];
|
|
4985
5112
|
var SOURCE_EXTENSION_PATTERN = new RegExp(
|
|
4986
5113
|
`\\.(${SOURCE_LANGUAGES.flatMap((language) => language.extensions).join("|")})$`,
|
|
4987
5114
|
"i"
|
|
4988
5115
|
);
|
|
4989
|
-
var
|
|
5116
|
+
var SOURCE_EXTENSION_PATTERN_V10 = new RegExp(
|
|
5117
|
+
`\\.(${SOURCE_LANGUAGES_V10.flatMap((language) => language.extensions).join("|")})$`,
|
|
5118
|
+
"i"
|
|
5119
|
+
);
|
|
5120
|
+
function isRecognizedSourcePath(path, rubricVersion = WITAN_RUBRIC_VERSION) {
|
|
5121
|
+
return (rubricVersion === WITAN_RUBRIC_VERSION_V10 || 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 ? SOURCE_EXTENSION_PATTERN_V10 : SOURCE_EXTENSION_PATTERN).test(path);
|
|
5122
|
+
}
|
|
5123
|
+
var BUNDLED_BINARY_EXTENSION_PATTERN = /\.(vsix|zip|tgz|whl|exe|dmg|pkg|msi|jar|war|apk|ipa|dll|dylib|ttf|otf|woff2?|eot|eps)$/i;
|
|
4990
5124
|
var DOCS_OR_MEDIA_EXTENSION_PATTERN = /\.(md|mdx|rst|png|jpe?g|gif|svg|webp|ico|pdf)$/i;
|
|
4991
|
-
var
|
|
5125
|
+
var ARTIFACT_DOMINANCE_EXTENSION_PATTERN = /\.(vsix|zip|tgz|whl|exe|dmg|pkg|msi|jar|war|apk|ipa|dll|dylib|ttf|otf|woff2?|eot|eps|png|jpe?g|gif|svg|webp|ico|pdf)$/i;
|
|
5126
|
+
var DOC_DISTRIBUTION_COMPANION_PATTERN = /(^|\/)(LICENSE|LICENCE|NOTICE|COPYING|AUTHORS|CONTRIBUTORS|CODE_OF_CONDUCT)(\.[A-Za-z0-9]+)?$/i;
|
|
5127
|
+
var GENERATED_OR_VENDOR_PATH_PATTERN = /(^|\/)(vendor|vendors|dist|build|generated|gen|coverage|node_modules|site-packages|\.venv|venv|\.next|__pycache__|\.terraform|public\/assets|static\/assets)(\/|$)|(^|\/)[^/]+\.min\.(?:js|css)$/i;
|
|
5128
|
+
var INDEPENDENT_CATALOG_PATH_PATTERN = /(^|\/)(solutions?|exercises?|challenges?|algorithms?)(\/|$)/i;
|
|
5129
|
+
var KNOWN_NON_SOURCE_EXTENSION_PATTERN = /\.(json|jsonc|json5|ya?ml|toml|txt|lock|xml|csv|tsv|ini|cfg|conf|cnf|properties|env|sum|mod|graphql|gql|proto|sarif|editorconfig|gitignore|gitattributes|npmrc|nvmrc|dockerignore|prettierignore|prettierrc|log|stderr|snap|grit|po|mo|dbf|shp|shx|egg|pem|key|csr|ai|txt\.license|pf|in|case|common|fortls)$/i;
|
|
5130
|
+
var V13_NON_SOURCE_EXTENSION_PATTERN = /\.(?:\d+|class|py[co]|gz|bz2|xz|7z|rar|mat|npy|npz|h5|hdf5|onnx|pt|pth|ckpt|safetensors|crt|cer|der|p12|pfx|diff|patch|plist|pbxproj|xcworkspacedata|xcbuild|xcplayground|xcscheme|xcsettings|xcconfig|xcprivacy|xcstrings|csproj|fsproj|vbproj|sln|slnf|props|targets|projitems|shproj|pubxml|resw|resources|storyboard|xib|entitlements|modulemap|podspec|nuspec|appxmanifest|webmanifest|manifest|map|fls|aux|bbl|bib|ocm|tdb|db|sqlite|sqlcipher\d*|bin|dat|pdb|prj|resolved|sim|input|list|fig|drawio|puml|sketch|icns|flac|wav|mp3|mp4|mov|avi|mkv|nv21|xls[xb]?|mex[a-z0-9_]*|gitconfig|profile|yapf|config|desktop|typed|example|bak|_|test(?:_[a-z0-9]+)*_location_override|xamltest|tld(?:_[a-z0-9]+)?|xsd|cmake|gradle|mk|am|pro|upri|dockerfile|dockergen|makefile|alpine|android|debian|ubuntu\d+|cuda\d+_ubuntu\d+|manylinux[a-z0-9_]*)$/i;
|
|
4992
5131
|
var INGEST_POINTER = "To assess a closed/bundled tool, ingest its scanner output via --ingest <sarif|scorecard>.";
|
|
4993
|
-
var
|
|
4994
|
-
|
|
5132
|
+
var SOURCE_DOMINANCE_RATIO_THRESHOLD_V8 = 0.2;
|
|
5133
|
+
var SOURCE_DOMINANCE_RATIO_THRESHOLD_V9 = 0.8;
|
|
5134
|
+
var SOURCE_DOMINANCE_RATIO_THRESHOLD_V10 = 0.5;
|
|
5135
|
+
var SOURCE_DOMINANCE_RATIO_THRESHOLD_V11 = 0.2;
|
|
5136
|
+
var GENERATED_SOURCE_MINIMUM = 8;
|
|
5137
|
+
var GENERATED_SOURCE_RATIO_THRESHOLD = 0.8;
|
|
5138
|
+
var GENERATED_AUTHORED_MAXIMUM = 3;
|
|
5139
|
+
var CATALOG_SOURCE_MINIMUM = 12;
|
|
5140
|
+
var CATALOG_SOURCE_RATIO_THRESHOLD = 0.9;
|
|
5141
|
+
var CATALOG_OUTSIDE_MAXIMUM = 2;
|
|
5142
|
+
function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION) {
|
|
4995
5143
|
const totalFileCount = repoFiles.length;
|
|
4996
|
-
const
|
|
5144
|
+
const sourceFiles = repoFiles.filter((path) => isRecognizedSourcePath(path, rubricVersion));
|
|
5145
|
+
const sourceFileCount = sourceFiles.length;
|
|
4997
5146
|
if (totalFileCount === 0) {
|
|
4998
5147
|
return {
|
|
4999
5148
|
archetype: "empty",
|
|
@@ -5002,7 +5151,55 @@ function classifyRepoArchetype(repoFiles) {
|
|
|
5002
5151
|
insufficientSourceReason: "Repository has no tracked files \u2014 there is nothing to certify."
|
|
5003
5152
|
};
|
|
5004
5153
|
}
|
|
5005
|
-
const
|
|
5154
|
+
const generatedSourceFiles = sourceFiles.filter(
|
|
5155
|
+
(file) => GENERATED_OR_VENDOR_PATH_PATTERN.test(file)
|
|
5156
|
+
);
|
|
5157
|
+
if (sourceFileCount >= GENERATED_SOURCE_MINIMUM && generatedSourceFiles.length / sourceFileCount >= GENERATED_SOURCE_RATIO_THRESHOLD && sourceFileCount - generatedSourceFiles.length <= GENERATED_AUTHORED_MAXIMUM) {
|
|
5158
|
+
return {
|
|
5159
|
+
archetype: "generated_only",
|
|
5160
|
+
sourceFileCount,
|
|
5161
|
+
totalFileCount,
|
|
5162
|
+
insufficientSourceReason: `${generatedSourceFiles.length} of ${sourceFileCount} source-shaped file(s) are under generated, vendor, build, distribution, coverage, or bundled-asset paths; only ${sourceFileCount - generatedSourceFiles.length} authored source-shaped file(s) remain. Cejel abstains because generated/vendor output is not a reviewable implementation surface. ${INGEST_POINTER}`
|
|
5163
|
+
};
|
|
5164
|
+
}
|
|
5165
|
+
const catalogSourceFiles = sourceFiles.filter(
|
|
5166
|
+
(file) => INDEPENDENT_CATALOG_PATH_PATTERN.test(file)
|
|
5167
|
+
);
|
|
5168
|
+
if (sourceFileCount >= CATALOG_SOURCE_MINIMUM && catalogSourceFiles.length / sourceFileCount >= CATALOG_SOURCE_RATIO_THRESHOLD && sourceFileCount - catalogSourceFiles.length <= CATALOG_OUTSIDE_MAXIMUM) {
|
|
5169
|
+
return {
|
|
5170
|
+
archetype: "non_cohesive_source",
|
|
5171
|
+
sourceFileCount,
|
|
5172
|
+
totalFileCount,
|
|
5173
|
+
insufficientSourceReason: `${catalogSourceFiles.length} of ${sourceFileCount} source-shaped file(s) are under independent solution, exercise, challenge, or algorithm catalog paths; only ${sourceFileCount - catalogSourceFiles.length} source-shaped file(s) remain outside that catalog. Cejel abstains because the repository does not expose one cohesive product implementation to score. ${INGEST_POINTER}`
|
|
5174
|
+
};
|
|
5175
|
+
}
|
|
5176
|
+
if (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) {
|
|
5177
|
+
const authoredSourceFiles = sourceFiles.filter(isAuthoredProductionPath);
|
|
5178
|
+
const documentationFiles = repoFiles.filter(
|
|
5179
|
+
(file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file)
|
|
5180
|
+
);
|
|
5181
|
+
if (sourceFileCount > 0 && authoredSourceFiles.length === 0 && documentationFiles.length > 0) {
|
|
5182
|
+
return {
|
|
5183
|
+
archetype: "docs_only",
|
|
5184
|
+
sourceFileCount,
|
|
5185
|
+
totalFileCount,
|
|
5186
|
+
insufficientSourceReason: `${sourceFileCount} source-shaped file(s) are tests/fixtures around a documentation tree; no authored product source remains. Cejel abstains because documentation tests do not support a product-source headline score. ${INGEST_POINTER}`
|
|
5187
|
+
};
|
|
5188
|
+
}
|
|
5189
|
+
const artifactFiles = repoFiles.filter(
|
|
5190
|
+
(file) => ARTIFACT_DOMINANCE_EXTENSION_PATTERN.test(file)
|
|
5191
|
+
);
|
|
5192
|
+
const materialFiles = authoredSourceFiles.length + artifactFiles.length;
|
|
5193
|
+
if (artifactFiles.length >= 10 && (authoredSourceFiles.length <= 10 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) && materialFiles > 0 && authoredSourceFiles.length / materialFiles <= 0.1) {
|
|
5194
|
+
return {
|
|
5195
|
+
archetype: "binary_only",
|
|
5196
|
+
sourceFileCount,
|
|
5197
|
+
totalFileCount,
|
|
5198
|
+
insufficientSourceReason: `${authoredSourceFiles.length} authored source-shaped file(s) accompany ${artifactFiles.length} packaged, font, image, or media artifact(s); the source is sparse tooling rather than a representative product implementation. Cejel abstains because incidental tooling cannot support a product-source headline score. ${INGEST_POINTER}`
|
|
5199
|
+
};
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
const unrecognisedSourceFiles = collectUnrecognisedSourceFiles(repoFiles, rubricVersion);
|
|
5006
5203
|
if (sourceFileCount === 0) {
|
|
5007
5204
|
const binaryFiles = repoFiles.filter((file) => BUNDLED_BINARY_EXTENSION_PATTERN.test(file));
|
|
5008
5205
|
if (binaryFiles.length > 0) {
|
|
@@ -5015,7 +5212,10 @@ function classifyRepoArchetype(repoFiles) {
|
|
|
5015
5212
|
};
|
|
5016
5213
|
}
|
|
5017
5214
|
const docsFiles = repoFiles.filter((file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file));
|
|
5018
|
-
|
|
5215
|
+
const docsDistributionFiles = repoFiles.filter(
|
|
5216
|
+
(file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file) || DOC_DISTRIBUTION_COMPANION_PATTERN.test(file)
|
|
5217
|
+
);
|
|
5218
|
+
if (docsFiles.length > 0 && (docsDistributionFiles.length === totalFileCount || (rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || 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) && unrecognisedSourceFiles.length === 0)) {
|
|
5019
5219
|
return {
|
|
5020
5220
|
archetype: "docs_only",
|
|
5021
5221
|
sourceFileCount,
|
|
@@ -5024,7 +5224,12 @@ function classifyRepoArchetype(repoFiles) {
|
|
|
5024
5224
|
};
|
|
5025
5225
|
}
|
|
5026
5226
|
if (unrecognisedSourceFiles.length > 0) {
|
|
5027
|
-
return unrecognisedEcosystemResult(
|
|
5227
|
+
return unrecognisedEcosystemResult(
|
|
5228
|
+
sourceFileCount,
|
|
5229
|
+
totalFileCount,
|
|
5230
|
+
unrecognisedSourceFiles,
|
|
5231
|
+
sourceDominanceThreshold(rubricVersion)
|
|
5232
|
+
);
|
|
5028
5233
|
}
|
|
5029
5234
|
return { archetype: "source", sourceFileCount, totalFileCount };
|
|
5030
5235
|
}
|
|
@@ -5036,25 +5241,255 @@ function classifyRepoArchetype(repoFiles) {
|
|
|
5036
5241
|
};
|
|
5037
5242
|
}
|
|
5038
5243
|
const dominanceRatio = sourceFileCount / (sourceFileCount + unrecognisedSourceFiles.length);
|
|
5039
|
-
|
|
5244
|
+
const dominanceThreshold = sourceDominanceThreshold(rubricVersion);
|
|
5245
|
+
if (dominanceRatio >= dominanceThreshold) {
|
|
5040
5246
|
return {
|
|
5041
5247
|
archetype: isLikelyMonorepo(repoFiles) ? "monorepo" : "source",
|
|
5042
5248
|
sourceFileCount,
|
|
5043
5249
|
totalFileCount
|
|
5044
5250
|
};
|
|
5045
5251
|
}
|
|
5046
|
-
return unrecognisedEcosystemResult(
|
|
5252
|
+
return unrecognisedEcosystemResult(
|
|
5253
|
+
sourceFileCount,
|
|
5254
|
+
totalFileCount,
|
|
5255
|
+
unrecognisedSourceFiles,
|
|
5256
|
+
dominanceThreshold
|
|
5257
|
+
);
|
|
5047
5258
|
}
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5259
|
+
var READABLE_SOURCE_REPRESENTATION_THRESHOLD_V13 = 0.8;
|
|
5260
|
+
var READABLE_SOURCE_REPRESENTATION_SAMPLE_LIMIT_V13 = 32;
|
|
5261
|
+
var READABLE_SOURCE_REPRESENTATION_FILE_BYTES_V13 = 256 * 1024;
|
|
5262
|
+
var SEMANTIC_AUXILIARY_SOURCE_MINIMUM_V14 = 20;
|
|
5263
|
+
var SEMANTIC_AUXILIARY_SOURCE_RATIO_V14 = 0.8;
|
|
5264
|
+
var SEMANTIC_METADATA_PREFIX_BYTES_V14 = 16 * 1024;
|
|
5265
|
+
var SEMANTIC_AUXILIARY_PATH_PATTERN_V14 = /(^|\/)(?:locales?|i18n|l10n|translations?|styles?|themes?)(\/|$)|\.(?:css|scss|sass|less|styl)$/i;
|
|
5266
|
+
var HTML_SOURCE_EXTENSION_PATTERN = /\.(?:html?|xhtml)$/i;
|
|
5267
|
+
var HTML_META_REFRESH_PATTERN = /<meta\b(?=[^>]*\bhttp-equiv\s*=\s*["']?\s*refresh\b)(?=[^>]*\bcontent\s*=\s*["'][^"']*\burl\s*=)[^>]*>/i;
|
|
5268
|
+
var RUBY_PACKAGE_RECIPE_CLASS_PATTERN = /\bclass\s+[A-Za-z0-9_:]+\s*<\s*(?:Formula|Cask)\b/;
|
|
5269
|
+
var RUBY_PACKAGE_RECIPE_URL_PATTERN = /^\s*url\s+["'][^"']+["']/m;
|
|
5270
|
+
var RUBY_PACKAGE_RECIPE_PATH_PATTERN = /(^|\/)(?:Formula|Casks?)(\/|$)/;
|
|
5271
|
+
function selectProportionalRepresentativeSourceFiles(files, rankKey, limit) {
|
|
5272
|
+
const eligibleFiles = [...new Set(files)];
|
|
5273
|
+
const slotCount = Math.min(Math.max(Math.floor(limit), 0), eligibleFiles.length);
|
|
5274
|
+
if (slotCount === 0) return [];
|
|
5275
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5276
|
+
for (const file of eligibleFiles) {
|
|
5277
|
+
const extension = extname(file).toLowerCase() || "(none)";
|
|
5278
|
+
const bucket = grouped.get(extension) ?? [];
|
|
5279
|
+
bucket.push(file);
|
|
5280
|
+
grouped.set(extension, bucket);
|
|
5281
|
+
}
|
|
5282
|
+
const groups = [...grouped.entries()].map(([extension, groupFiles]) => {
|
|
5283
|
+
const quota = groupFiles.length / eligibleFiles.length * slotCount;
|
|
5284
|
+
return {
|
|
5285
|
+
extension,
|
|
5286
|
+
files: [...groupFiles].sort(
|
|
5287
|
+
(left, right) => representativeSourceRank(rankKey, left).localeCompare(
|
|
5288
|
+
representativeSourceRank(rankKey, right)
|
|
5289
|
+
)
|
|
5290
|
+
),
|
|
5291
|
+
allocation: Math.floor(quota),
|
|
5292
|
+
remainder: quota - Math.floor(quota)
|
|
5293
|
+
};
|
|
5294
|
+
}).sort(
|
|
5295
|
+
(left, right) => right.files.length - left.files.length || left.extension.localeCompare(right.extension)
|
|
5296
|
+
);
|
|
5297
|
+
let remaining = slotCount - groups.reduce((sum, group) => sum + group.allocation, 0);
|
|
5298
|
+
const remainderOrder = [...groups].sort(
|
|
5299
|
+
(left, right) => right.remainder - left.remainder || right.files.length - left.files.length || left.extension.localeCompare(right.extension)
|
|
5300
|
+
);
|
|
5301
|
+
for (const group of remainderOrder) {
|
|
5302
|
+
if (remaining === 0) break;
|
|
5303
|
+
if (group.allocation >= group.files.length) continue;
|
|
5304
|
+
group.allocation += 1;
|
|
5305
|
+
remaining -= 1;
|
|
5306
|
+
}
|
|
5307
|
+
const selected = [];
|
|
5308
|
+
for (let offset = 0; selected.length < slotCount; offset += 1) {
|
|
5309
|
+
let added = false;
|
|
5310
|
+
for (const group of groups) {
|
|
5311
|
+
if (offset >= group.allocation) continue;
|
|
5312
|
+
const file = group.files[offset];
|
|
5313
|
+
if (!file) continue;
|
|
5314
|
+
selected.push(file);
|
|
5315
|
+
added = true;
|
|
5316
|
+
}
|
|
5317
|
+
if (!added) break;
|
|
5318
|
+
}
|
|
5319
|
+
return selected;
|
|
5320
|
+
}
|
|
5321
|
+
function isRepresentativeSourceText(contents) {
|
|
5322
|
+
if (contents.trim().length === 0) return false;
|
|
5323
|
+
if (contents.includes("\0") || contents.includes("\uFFFD")) return false;
|
|
5324
|
+
let disallowedControls = 0;
|
|
5325
|
+
for (const character of contents) {
|
|
5326
|
+
const code = character.charCodeAt(0);
|
|
5327
|
+
if (code < 32 && character !== "\n" && character !== "\r" && character !== " ") {
|
|
5328
|
+
disallowedControls += 1;
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
return disallowedControls / contents.length <= 0.01;
|
|
5332
|
+
}
|
|
5333
|
+
function representativeSourceRank(rankKey, file) {
|
|
5334
|
+
return createHash2("sha256").update(`${rankKey}:${file}`).digest("hex");
|
|
5335
|
+
}
|
|
5336
|
+
function applyReadableSourceRepresentationGate(repoPath, repoFiles, classification, headSha, rubricVersion) {
|
|
5337
|
+
if (classification.archetype !== "source" && classification.archetype !== "monorepo") {
|
|
5338
|
+
return classification;
|
|
5339
|
+
}
|
|
5340
|
+
const authoredSourceFiles = repoFiles.filter(
|
|
5341
|
+
(path) => isAuthoredProductionPath(path) && (isRecognizedSourcePath(path, rubricVersion) || isUnrecognisedSourcePath(path, rubricVersion) || /\.ipynb$/i.test(path))
|
|
5342
|
+
);
|
|
5343
|
+
if (authoredSourceFiles.length === 0) return classification;
|
|
5344
|
+
const sample = selectProportionalRepresentativeSourceFiles(
|
|
5345
|
+
authoredSourceFiles,
|
|
5346
|
+
`witan-v13-readable-source:${headSha ?? "unversioned"}`,
|
|
5347
|
+
READABLE_SOURCE_REPRESENTATION_SAMPLE_LIMIT_V13
|
|
5348
|
+
);
|
|
5349
|
+
const sampledByExtension = /* @__PURE__ */ new Map();
|
|
5350
|
+
for (const file of sample) {
|
|
5351
|
+
const extension = extname(file).toLowerCase() || "(none)";
|
|
5352
|
+
const counts = sampledByExtension.get(extension) ?? { sampled: 0, readable: 0 };
|
|
5353
|
+
counts.sampled += 1;
|
|
5354
|
+
const contents = readRepresentativeSourceText(repoPath, file);
|
|
5355
|
+
if (contents !== null && isRepresentativeSourceText(contents)) {
|
|
5356
|
+
counts.readable += 1;
|
|
5357
|
+
}
|
|
5358
|
+
sampledByExtension.set(extension, counts);
|
|
5359
|
+
}
|
|
5360
|
+
const sourceCountByExtension = /* @__PURE__ */ new Map();
|
|
5361
|
+
for (const file of authoredSourceFiles) {
|
|
5362
|
+
const extension = extname(file).toLowerCase() || "(none)";
|
|
5363
|
+
sourceCountByExtension.set(extension, (sourceCountByExtension.get(extension) ?? 0) + 1);
|
|
5364
|
+
}
|
|
5365
|
+
const readableSourceFileCount = [...sourceCountByExtension.entries()].reduce(
|
|
5366
|
+
(total, [extension, sourceCount]) => {
|
|
5367
|
+
const sampled = sampledByExtension.get(extension);
|
|
5368
|
+
if (!sampled || sampled.sampled === 0) return total;
|
|
5369
|
+
return total + sourceCount * (sampled.readable / sampled.sampled);
|
|
5370
|
+
},
|
|
5371
|
+
0
|
|
5372
|
+
);
|
|
5373
|
+
const readableRatio = readableSourceFileCount / authoredSourceFiles.length;
|
|
5374
|
+
if (readableRatio >= READABLE_SOURCE_REPRESENTATION_THRESHOLD_V13) return classification;
|
|
5375
|
+
const readableCountLabel = Number.isInteger(readableSourceFileCount) ? String(readableSourceFileCount) : readableSourceFileCount.toFixed(2);
|
|
5376
|
+
const readablePct = (readableRatio * 100).toFixed(1);
|
|
5377
|
+
const thresholdPct = (READABLE_SOURCE_REPRESENTATION_THRESHOLD_V13 * 100).toFixed(0);
|
|
5378
|
+
return {
|
|
5379
|
+
archetype: "non_cohesive_source",
|
|
5380
|
+
sourceFileCount: classification.sourceFileCount,
|
|
5381
|
+
totalFileCount: classification.totalFileCount,
|
|
5382
|
+
insufficientSourceReason: `Readable source representation covers ${readableCountLabel} of ${authoredSourceFiles.length} authored source-shaped file(s) (${readablePct}%); this is below the ${thresholdPct}% representation threshold. Cejel abstains because unreadable, compressed, binary-shaped, or opaque source families cannot support a representative headline score. ${INGEST_POINTER}`
|
|
5383
|
+
};
|
|
5384
|
+
}
|
|
5385
|
+
function applySemanticSourceRepresentationGate(repoPath, repoFiles, classification, rubricVersion) {
|
|
5386
|
+
if (classification.archetype !== "source" && classification.archetype !== "monorepo") {
|
|
5387
|
+
return classification;
|
|
5388
|
+
}
|
|
5389
|
+
const authoredSourceFiles = repoFiles.filter(
|
|
5390
|
+
(path) => isAuthoredProductionPath(path) && (isRecognizedSourcePath(path, rubricVersion) || isUnrecognisedSourcePath(path, rubricVersion) || /\.ipynb$/i.test(path))
|
|
5391
|
+
);
|
|
5392
|
+
if (authoredSourceFiles.length === 0) return classification;
|
|
5393
|
+
const auxiliaryFiles = authoredSourceFiles.filter(
|
|
5394
|
+
(path) => SEMANTIC_AUXILIARY_PATH_PATTERN_V14.test(path)
|
|
5395
|
+
);
|
|
5396
|
+
const packageMetadataFiles = authoredSourceFiles.filter(
|
|
5397
|
+
(path) => isRubyPackageRecipeMetadataSource(repoPath, path)
|
|
5398
|
+
);
|
|
5399
|
+
const redirectMetadataFiles = authoredSourceFiles.filter(
|
|
5400
|
+
(path) => isHtmlRedirectMetadataSource(repoPath, path)
|
|
5401
|
+
);
|
|
5402
|
+
const auxiliarySet = /* @__PURE__ */ new Set([
|
|
5403
|
+
...auxiliaryFiles,
|
|
5404
|
+
...packageMetadataFiles,
|
|
5405
|
+
...redirectMetadataFiles
|
|
5406
|
+
]);
|
|
5407
|
+
const representativeImplementationCount = authoredSourceFiles.filter(
|
|
5408
|
+
(path) => !auxiliarySet.has(path)
|
|
5409
|
+
).length;
|
|
5410
|
+
if (representativeImplementationCount === 0) {
|
|
5411
|
+
const detail = redirectMetadataFiles.length > 0 ? `${redirectMetadataFiles.length} redirect metadata file(s)` : packageMetadataFiles.length > 0 ? `${packageMetadataFiles.length} package metadata file(s)` : `${auxiliaryFiles.length} localization or style metadata file(s)`;
|
|
5412
|
+
return {
|
|
5413
|
+
archetype: "non_cohesive_source",
|
|
5414
|
+
sourceFileCount: classification.sourceFileCount,
|
|
5415
|
+
totalFileCount: classification.totalFileCount,
|
|
5416
|
+
insufficientSourceReason: `${detail} provide no representative product implementation. Cejel abstains because auxiliary metadata or payload files cannot support a product-source headline score. ${INGEST_POINTER}`
|
|
5417
|
+
};
|
|
5418
|
+
}
|
|
5419
|
+
const auxiliaryCount = auxiliarySet.size;
|
|
5420
|
+
const auxiliaryRatio = auxiliaryCount / authoredSourceFiles.length;
|
|
5421
|
+
if (authoredSourceFiles.length >= SEMANTIC_AUXILIARY_SOURCE_MINIMUM_V14 && auxiliaryRatio >= SEMANTIC_AUXILIARY_SOURCE_RATIO_V14) {
|
|
5422
|
+
return {
|
|
5423
|
+
archetype: "non_cohesive_source",
|
|
5424
|
+
sourceFileCount: classification.sourceFileCount,
|
|
5425
|
+
totalFileCount: classification.totalFileCount,
|
|
5426
|
+
insufficientSourceReason: `${auxiliaryCount} of ${authoredSourceFiles.length} authored source-shaped file(s) are localization, style, package, or redirect auxiliary material. Cejel abstains because an auxiliary-dominated tree does not expose representative product implementation for a headline score. ${INGEST_POINTER}`
|
|
5427
|
+
};
|
|
5428
|
+
}
|
|
5429
|
+
return classification;
|
|
5430
|
+
}
|
|
5431
|
+
function isHtmlRedirectMetadataSource(repoPath, file) {
|
|
5432
|
+
if (!HTML_SOURCE_EXTENSION_PATTERN.test(file)) return false;
|
|
5433
|
+
const contents = readSemanticMetadataPrefix(repoPath, file);
|
|
5434
|
+
if (contents === null) return false;
|
|
5435
|
+
const operativeContents = contents.replace(/<!--[\s\S]*?-->/g, "");
|
|
5436
|
+
if (!HTML_META_REFRESH_PATTERN.test(operativeContents)) return false;
|
|
5437
|
+
return !/<(?:script|form|main|article|section|canvas|template)\b/i.test(operativeContents);
|
|
5438
|
+
}
|
|
5439
|
+
function isRubyPackageRecipeMetadataSource(repoPath, file) {
|
|
5440
|
+
if (!/\.rb$/i.test(file) || !RUBY_PACKAGE_RECIPE_PATH_PATTERN.test(file)) return false;
|
|
5441
|
+
const contents = readSemanticMetadataPrefix(repoPath, file);
|
|
5442
|
+
const operativeContents = contents?.replace(/^\s*#.*$/gm, "");
|
|
5443
|
+
return operativeContents !== void 0 && RUBY_PACKAGE_RECIPE_CLASS_PATTERN.test(operativeContents) && RUBY_PACKAGE_RECIPE_URL_PATTERN.test(operativeContents);
|
|
5444
|
+
}
|
|
5445
|
+
function readSemanticMetadataPrefix(repoPath, file) {
|
|
5446
|
+
const absolutePath = join(repoPath, file);
|
|
5447
|
+
if (!isRegularFile(absolutePath)) return null;
|
|
5448
|
+
let descriptor = null;
|
|
5449
|
+
try {
|
|
5450
|
+
descriptor = openSync(absolutePath, "r");
|
|
5451
|
+
const buffer = Buffer.allocUnsafe(SEMANTIC_METADATA_PREFIX_BYTES_V14);
|
|
5452
|
+
const bytesRead = readSync(descriptor, buffer, 0, SEMANTIC_METADATA_PREFIX_BYTES_V14, 0);
|
|
5453
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
5454
|
+
} catch {
|
|
5455
|
+
return null;
|
|
5456
|
+
} finally {
|
|
5457
|
+
if (descriptor !== null) closeSync(descriptor);
|
|
5458
|
+
}
|
|
5459
|
+
}
|
|
5460
|
+
function readRepresentativeSourceText(repoPath, file) {
|
|
5461
|
+
const absolutePath = join(repoPath, file);
|
|
5462
|
+
try {
|
|
5463
|
+
if (!isRegularFile(absolutePath)) return null;
|
|
5464
|
+
const size = statSync(absolutePath).size;
|
|
5465
|
+
if (size === 0 || size > READABLE_SOURCE_REPRESENTATION_FILE_BYTES_V13) return null;
|
|
5466
|
+
return readFileSync(absolutePath, "utf8");
|
|
5467
|
+
} catch {
|
|
5468
|
+
return null;
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
function sourceDominanceThreshold(rubricVersion) {
|
|
5472
|
+
if (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) {
|
|
5473
|
+
return SOURCE_DOMINANCE_RATIO_THRESHOLD_V11;
|
|
5474
|
+
}
|
|
5475
|
+
if (rubricVersion === WITAN_RUBRIC_VERSION_V10) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V10;
|
|
5476
|
+
if (rubricVersion === WITAN_RUBRIC_VERSION_V92) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V9;
|
|
5477
|
+
return SOURCE_DOMINANCE_RATIO_THRESHOLD_V8;
|
|
5478
|
+
}
|
|
5479
|
+
function collectUnrecognisedSourceFiles(repoFiles, rubricVersion) {
|
|
5480
|
+
return repoFiles.filter((path) => isUnrecognisedSourcePath(path, rubricVersion));
|
|
5481
|
+
}
|
|
5482
|
+
function isUnrecognisedSourcePath(file, rubricVersion = WITAN_RUBRIC_VERSION) {
|
|
5483
|
+
const base = basename(file);
|
|
5484
|
+
if (!/\.([a-zA-Z0-9_]+)$/.test(base)) return false;
|
|
5485
|
+
if (KNOWN_NON_SOURCE_EXTENSION_PATTERN.test(base)) return false;
|
|
5486
|
+
if ((rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15) && V13_NON_SOURCE_EXTENSION_PATTERN.test(base)) {
|
|
5487
|
+
return false;
|
|
5488
|
+
}
|
|
5489
|
+
if (DOCS_OR_MEDIA_EXTENSION_PATTERN.test(base)) return false;
|
|
5490
|
+
if (BUNDLED_BINARY_EXTENSION_PATTERN.test(base)) return false;
|
|
5491
|
+
if (isRecognizedSourcePath(base, rubricVersion)) return false;
|
|
5492
|
+
return true;
|
|
5058
5493
|
}
|
|
5059
5494
|
function extensionsByFileCountDescending(files) {
|
|
5060
5495
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -5067,11 +5502,11 @@ function extensionsByFileCountDescending(files) {
|
|
|
5067
5502
|
}
|
|
5068
5503
|
return [...counts.entries()].sort(([extA, countA], [extB, countB]) => countB - countA || extA.localeCompare(extB)).map(([ext]) => ext);
|
|
5069
5504
|
}
|
|
5070
|
-
function unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles) {
|
|
5505
|
+
function unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles, dominanceThreshold = SOURCE_DOMINANCE_RATIO_THRESHOLD_V9) {
|
|
5071
5506
|
const sample = extensionsByFileCountDescending(unrecognisedSourceFiles).slice(0, 6).join(", ");
|
|
5072
5507
|
const candidateCount = sourceFileCount + unrecognisedSourceFiles.length;
|
|
5073
5508
|
const dominancePct = candidateCount > 0 ? (sourceFileCount / candidateCount * 100).toFixed(1) : "0.0";
|
|
5074
|
-
const thresholdPct = (
|
|
5509
|
+
const thresholdPct = (dominanceThreshold * 100).toFixed(0);
|
|
5075
5510
|
const measuredClause = sourceFileCount > 0 ? `${sourceFileCount} of ${candidateCount} source-shaped file(s) (${dominancePct}%) are in a language Cejel reads \u2014 below the ${thresholdPct}% dominance threshold a score would need to be meaningful (${totalFileCount} tracked files in total; manifests, lockfiles, docs, media and bundled binaries are excluded from both sides of the ratio)` : `0 of ${totalFileCount} tracked file(s) matched a recognised source extension`;
|
|
5076
5511
|
return {
|
|
5077
5512
|
archetype: "unrecognised_ecosystem",
|
|
@@ -5107,14 +5542,34 @@ function buildNotApplicableSignal(criterionId, reason) {
|
|
|
5107
5542
|
notApplicable: true
|
|
5108
5543
|
};
|
|
5109
5544
|
}
|
|
5110
|
-
function collectRepoSignals(repoPath, generatedAt, repoFiles) {
|
|
5545
|
+
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors) {
|
|
5111
5546
|
const signals = [];
|
|
5112
5547
|
const mono = resolveMonorepoContext(repoPath);
|
|
5113
|
-
const a1Signal = collectA1TestIntegrityEvidence(
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5548
|
+
const a1Signal = collectA1TestIntegrityEvidence(
|
|
5549
|
+
repoPath,
|
|
5550
|
+
repoFiles,
|
|
5551
|
+
useV27Detectors,
|
|
5552
|
+
useV33Detectors,
|
|
5553
|
+
useV36Detectors
|
|
5554
|
+
);
|
|
5555
|
+
const a2Signal = collectA2IsolationEvidence(
|
|
5556
|
+
repoPath,
|
|
5557
|
+
repoFiles,
|
|
5558
|
+
useV27Detectors,
|
|
5559
|
+
useV33Detectors,
|
|
5560
|
+
useV36Detectors,
|
|
5561
|
+
useV39Detectors,
|
|
5562
|
+
useV47Detectors
|
|
5563
|
+
);
|
|
5564
|
+
const a3Signal = collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors);
|
|
5565
|
+
const a4Signal = collectA4DependencyEvidence(
|
|
5566
|
+
repoPath,
|
|
5567
|
+
repoFiles,
|
|
5568
|
+
mono,
|
|
5569
|
+
useV27Detectors,
|
|
5570
|
+
useV47Detectors
|
|
5571
|
+
);
|
|
5572
|
+
const a5Signal = collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors);
|
|
5118
5573
|
const b1Signal = buildNotApplicableSignal(
|
|
5119
5574
|
"B1",
|
|
5120
5575
|
"Repository scans do not evaluate the dispatch-trace process dimension."
|
|
@@ -5126,7 +5581,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles) {
|
|
|
5126
5581
|
"B5",
|
|
5127
5582
|
"Repository scans do not evaluate the learning-trace process dimension."
|
|
5128
5583
|
);
|
|
5129
|
-
const b6Signal = collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles);
|
|
5584
|
+
const b6Signal = collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detectors);
|
|
5130
5585
|
for (const signal of [
|
|
5131
5586
|
a1Signal,
|
|
5132
5587
|
a2Signal,
|
|
@@ -5144,22 +5599,27 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles) {
|
|
|
5144
5599
|
}
|
|
5145
5600
|
return signals;
|
|
5146
5601
|
}
|
|
5147
|
-
function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
5602
|
+
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors) {
|
|
5148
5603
|
const evidence = [];
|
|
5149
5604
|
const findings = [];
|
|
5150
|
-
const
|
|
5151
|
-
const allTestFiles = [
|
|
5152
|
-
...repoFiles.filter(isTestFile),
|
|
5153
|
-
...contentCppTests.filter((file) => !isTestFile(file))
|
|
5154
|
-
];
|
|
5605
|
+
const allTestFiles = findConcreteTestFiles(repoPath, repoFiles);
|
|
5155
5606
|
const testFiles = allTestFiles.slice(0, 8);
|
|
5156
5607
|
const sourceFiles = repoFiles.filter(isSourceFile);
|
|
5157
|
-
const runnerFiles = repoFiles.filter(
|
|
5158
|
-
|
|
5608
|
+
const runnerFiles = repoFiles.filter(
|
|
5609
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isTestRunnerConfig(repoPath, file)
|
|
5610
|
+
).slice(0, 6);
|
|
5611
|
+
const configuredRunnerFiles = useV33Detectors ? findConfiguredTestRunnerFiles(repoPath, repoFiles) : runnerFiles;
|
|
5612
|
+
const allCoverageFiles = findCoverageConfigFiles(repoPath, repoFiles, useV27Detectors);
|
|
5613
|
+
const coverageFiles = allCoverageFiles.slice(0, 4);
|
|
5159
5614
|
const packageJson = findRootPackageJson(repoFiles);
|
|
5160
5615
|
const packageScripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
|
|
5616
|
+
const packageJsonFiles = useV27Detectors ? repoFiles.filter(
|
|
5617
|
+
(file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
|
|
5618
|
+
) : packageJson ? [packageJson] : [];
|
|
5161
5619
|
const coveragePercent = readCoveragePercent(repoPath, coverageFiles);
|
|
5162
|
-
const ciWorkflows = repoFiles.filter(
|
|
5620
|
+
const ciWorkflows = repoFiles.filter(
|
|
5621
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isCiWorkflow(file)
|
|
5622
|
+
);
|
|
5163
5623
|
const ciTestWorkflow = ciWorkflows.find(
|
|
5164
5624
|
(file) => fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN)
|
|
5165
5625
|
);
|
|
@@ -5178,7 +5638,9 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5178
5638
|
const usesLeanBuiltInTestRunner = packageJson != null && [...packageScripts.values()].some(
|
|
5179
5639
|
(script) => LEAN_TEST_RUNNER_SCRIPT_PATTERN.test(script)
|
|
5180
5640
|
) || testFiles.some((file) => fileContains(repoPath, file, LEAN_TEST_RUNNER_IMPORT_PATTERN));
|
|
5181
|
-
const hasHeavyTestDependency =
|
|
5641
|
+
const hasHeavyTestDependency = packageJsonFiles.some(
|
|
5642
|
+
(manifest) => packageJsonHasHeavyTestDependency(repoPath, manifest)
|
|
5643
|
+
);
|
|
5182
5644
|
const isLeanTestToolchain = usesLeanBuiltInTestRunner && !hasHeavyTestDependency;
|
|
5183
5645
|
for (const file of testFiles) {
|
|
5184
5646
|
evidence.push(evidenceForRelative(repoPath, file, "test_run", "Detected test file"));
|
|
@@ -5198,7 +5660,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5198
5660
|
);
|
|
5199
5661
|
}
|
|
5200
5662
|
const scheduledHealthWorkflow = ciWorkflows.find(
|
|
5201
|
-
(file) => fileContains(repoPath, file, SCHEDULE_TRIGGER_PATTERN) && fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN)
|
|
5663
|
+
(file) => fileContains(repoPath, file, SCHEDULE_TRIGGER_PATTERN) && fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN) && (!useV36Detectors || SCHEDULED_HEALTH_PATH_PATTERN.test(file) || fileContains(repoPath, file, SCHEDULED_HEALTH_INTENT_PATTERN))
|
|
5202
5664
|
);
|
|
5203
5665
|
if (scheduledHealthWorkflow) {
|
|
5204
5666
|
const isPublished = fileContains(
|
|
@@ -5231,7 +5693,12 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5231
5693
|
}
|
|
5232
5694
|
if (evidence.length === 0 && findings.length === 0) return null;
|
|
5233
5695
|
if (testFiles.length === 0) {
|
|
5234
|
-
const fallback =
|
|
5696
|
+
const fallback = configuredRunnerFiles[0] ? evidenceForRelative(
|
|
5697
|
+
repoPath,
|
|
5698
|
+
configuredRunnerFiles[0],
|
|
5699
|
+
"test_run",
|
|
5700
|
+
"Configured test runner"
|
|
5701
|
+
) : useV33Detectors ? void 0 : evidence[0] ?? findings[0]?.evidence;
|
|
5235
5702
|
if (fallback) {
|
|
5236
5703
|
findings.push({
|
|
5237
5704
|
severity: "warning",
|
|
@@ -5239,7 +5706,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5239
5706
|
evidence: fallback
|
|
5240
5707
|
});
|
|
5241
5708
|
}
|
|
5242
|
-
} else if (coverageFiles.length === 0) {
|
|
5709
|
+
} else if (coverageFiles.length === 0 && (!useV33Detectors || configuredRunnerFiles.length > 0)) {
|
|
5243
5710
|
if (isLeanTestToolchain) {
|
|
5244
5711
|
const leanEvidenceFile = packageJson ?? testFiles[0];
|
|
5245
5712
|
if (leanEvidenceFile) {
|
|
@@ -5310,27 +5777,67 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles) {
|
|
|
5310
5777
|
notes: "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
|
|
5311
5778
|
};
|
|
5312
5779
|
}
|
|
5313
|
-
function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
5780
|
+
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors) {
|
|
5314
5781
|
const evidence = [];
|
|
5315
5782
|
const findings = [];
|
|
5316
5783
|
const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
|
|
5317
|
-
const envExamples = repoFiles.filter((file) => isEnvTemplatePath(file));
|
|
5784
|
+
const envExamples = repoFiles.filter((file) => isEnvTemplatePath(file, useV47Detectors));
|
|
5318
5785
|
const migrationFiles = repoFiles.filter(
|
|
5319
5786
|
(file) => /(^|\/)(migrations?|drizzle|prisma)\//.test(file)
|
|
5320
5787
|
);
|
|
5321
|
-
|
|
5322
|
-
|
|
5788
|
+
let committedSecret;
|
|
5789
|
+
for (const path of repoFiles) {
|
|
5790
|
+
const authoredProductionPath = useV47Detectors ? isV47AuthoredProductionPath(path) : useV39Detectors ? isV39AuthoredProductionPath(path) : isAuthoredProductionPath(path);
|
|
5791
|
+
if (isIgnoredScanFile(path, useV47Detectors) || useV33Detectors && !authoredProductionPath) {
|
|
5792
|
+
continue;
|
|
5793
|
+
}
|
|
5794
|
+
const match = findCommittedSecretInFile(
|
|
5795
|
+
repoPath,
|
|
5796
|
+
path,
|
|
5797
|
+
useV36Detectors,
|
|
5798
|
+
useV39Detectors,
|
|
5799
|
+
useV47Detectors
|
|
5800
|
+
);
|
|
5801
|
+
if (!match) continue;
|
|
5802
|
+
committedSecret = { path, match };
|
|
5803
|
+
break;
|
|
5804
|
+
}
|
|
5805
|
+
const currentSecretFingerprintsByPath = /* @__PURE__ */ new Map();
|
|
5806
|
+
if (useV27Detectors) {
|
|
5807
|
+
for (const path of repoFiles.filter((candidate) => isCredentialHistoryPath(candidate))) {
|
|
5808
|
+
currentSecretFingerprintsByPath.set(
|
|
5809
|
+
path,
|
|
5810
|
+
new Set(
|
|
5811
|
+
findSecretFingerprintsInFile(
|
|
5812
|
+
repoPath,
|
|
5813
|
+
path,
|
|
5814
|
+
useV36Detectors,
|
|
5815
|
+
useV39Detectors,
|
|
5816
|
+
useV47Detectors
|
|
5817
|
+
)
|
|
5818
|
+
)
|
|
5819
|
+
);
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5822
|
+
const historySecretScan = committedSecret && !useV27Detectors ? null : collectHistorySecretEvidence(
|
|
5823
|
+
repoPath,
|
|
5824
|
+
useV27Detectors,
|
|
5825
|
+
currentSecretFingerprintsByPath,
|
|
5826
|
+
useV33Detectors,
|
|
5827
|
+
useV36Detectors,
|
|
5828
|
+
useV39Detectors,
|
|
5829
|
+
useV47Detectors
|
|
5323
5830
|
);
|
|
5324
|
-
const historySecretScan = committedSecret ? null : collectHistorySecretEvidence(repoPath);
|
|
5325
5831
|
const hasConfirmedSecretFinding = committedSecret != null || historySecretScan?.evidence != null;
|
|
5326
5832
|
const cryptoHygiene = collectCryptoHygieneEvidence(
|
|
5327
5833
|
repoPath,
|
|
5328
|
-
repoFiles.filter(isImplementationFile).slice(0, 60)
|
|
5834
|
+
repoFiles.filter(isImplementationFile).slice(0, 60),
|
|
5835
|
+
useV39Detectors
|
|
5329
5836
|
);
|
|
5330
5837
|
const implFiles = repoFiles.filter(isImplementationFile);
|
|
5331
5838
|
const gitignoreHasEnvRule = gitignore != null && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m);
|
|
5332
5839
|
const committedEnvFilePath = repoFiles.find(
|
|
5333
|
-
(f) => /(?:^|\/)\.env(?:\.|$)/i.test(f) && !isEnvTemplatePath(f)
|
|
5840
|
+
(f) => /(?:^|\/)\.env(?:\.|$)/i.test(f) && !isEnvTemplatePath(f, useV47Detectors) && (!useV47Detectors || isV47AuthoredProductionPath(f)) && (!useV39Detectors || !hasTemplateOnlyEnvContent(repoPath, f))
|
|
5334
5841
|
);
|
|
5335
5842
|
const hasEnvInHistory = committedEnvFilePath === void 0 && hasEnvPathInGitHistory(repoPath);
|
|
5336
5843
|
const dbLayerImportFile = migrationFiles.length === 0 ? implFiles.slice(0, 30).find((f) => fileContains(repoPath, f, DB_CLIENT_PATTERN)) : void 0;
|
|
@@ -5365,20 +5872,17 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5365
5872
|
} else if (dbLayerImportFile != null) {
|
|
5366
5873
|
evidence.push(evidenceForRelative(repoPath, dbLayerImportFile, "artifact", "DB client import"));
|
|
5367
5874
|
}
|
|
5368
|
-
const
|
|
5369
|
-
const
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
const
|
|
5875
|
+
const v47RlsPolicyFiles = useV47Detectors ? findRlsPolicyFiles(repoPath, repoFiles) : [];
|
|
5876
|
+
const v47TenantStorageFiles = useV47Detectors ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
|
|
5877
|
+
const rlsMigration = useV47Detectors ? v47RlsPolicyFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, RLS_PATTERN));
|
|
5878
|
+
const tenantScopePattern = useV39Detectors ? TENANT_SCOPE_PATTERN_V11 : useV36Detectors ? TENANT_SCOPE_PATTERN_V10 : TENANT_SCOPE_PATTERN;
|
|
5879
|
+
const tenantScopedFile = useV47Detectors ? v47TenantStorageFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, tenantScopePattern));
|
|
5880
|
+
const rlsPolicyCount = useV47Detectors ? v47RlsPolicyFiles.length : countPatternMatches(
|
|
5373
5881
|
repoPath,
|
|
5374
5882
|
migrationFiles,
|
|
5375
5883
|
/create policy|enable row level security|force row level security/gi
|
|
5376
5884
|
);
|
|
5377
|
-
const tenantScopedMigrationFileCount = countFilesContaining(
|
|
5378
|
-
repoPath,
|
|
5379
|
-
migrationFiles,
|
|
5380
|
-
TENANT_SCOPE_PATTERN
|
|
5381
|
-
);
|
|
5885
|
+
const tenantScopedMigrationFileCount = useV47Detectors ? v47TenantStorageFiles.length : countFilesContaining(repoPath, migrationFiles, tenantScopePattern);
|
|
5382
5886
|
const gitignoreEnvFile = gitignore && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m) ? gitignore : null;
|
|
5383
5887
|
const envHandlingDepth = (envExamples.length > 0 ? 1 : 0) + (gitignoreEnvFile ? 1 : 0) + (countFilesContaining(repoPath, repoFiles.filter(isImplementationFile), ENV_READ_PATTERN) > 0 ? 1 : 0);
|
|
5384
5888
|
const isMultiTenant = tenantScopedMigrationFileCount > 0;
|
|
@@ -5401,15 +5905,31 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5401
5905
|
);
|
|
5402
5906
|
}
|
|
5403
5907
|
if (committedSecret) {
|
|
5404
|
-
const isTestPath = isTestOrFixturePath(committedSecret);
|
|
5908
|
+
const isTestPath = isTestOrFixturePath(committedSecret.path);
|
|
5909
|
+
const isDefaultAdmin = committedSecret.match.kind === "default_admin";
|
|
5405
5910
|
findings.push({
|
|
5406
5911
|
severity: isTestPath ? "info" : "critical",
|
|
5407
|
-
summary: isTestPath ? `Secret-shaped value in a test/fixture file (${committedSecret}) \u2014 likely fixture data, not a production leak; verify.` : "Secret-shaped value appears committed in the scanned repository.",
|
|
5912
|
+
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.",
|
|
5913
|
+
evidence: evidenceForRelativeAtLine(
|
|
5914
|
+
repoPath,
|
|
5915
|
+
committedSecret.path,
|
|
5916
|
+
"secret_scan",
|
|
5917
|
+
secretEvidenceLabel(
|
|
5918
|
+
isDefaultAdmin ? "Committed default administrative credential" : "Committed secret-shaped value",
|
|
5919
|
+
committedSecret.match
|
|
5920
|
+
),
|
|
5921
|
+
committedSecret.match.line
|
|
5922
|
+
)
|
|
5923
|
+
});
|
|
5924
|
+
} else if (committedEnvFilePath) {
|
|
5925
|
+
findings.push({
|
|
5926
|
+
severity: useV33Detectors ? "info" : "warning",
|
|
5927
|
+
summary: "A non-template .env file is committed in the current repository tree; no secret-shaped value was detected.",
|
|
5408
5928
|
evidence: evidenceForRelative(
|
|
5409
5929
|
repoPath,
|
|
5410
|
-
|
|
5930
|
+
committedEnvFilePath,
|
|
5411
5931
|
"secret_scan",
|
|
5412
|
-
"Committed secret
|
|
5932
|
+
"Committed .env file (no confirmed secret value found)"
|
|
5413
5933
|
)
|
|
5414
5934
|
});
|
|
5415
5935
|
}
|
|
@@ -5423,7 +5943,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5423
5943
|
});
|
|
5424
5944
|
} else if (historySecretScan?.truncated) {
|
|
5425
5945
|
findings.push({
|
|
5426
|
-
severity: "warning",
|
|
5946
|
+
severity: useV33Detectors ? "info" : "warning",
|
|
5427
5947
|
summary: "History secret scan reached the credential-path safety valve before scanning every matching blob.",
|
|
5428
5948
|
evidence: {
|
|
5429
5949
|
kind: "secret_scan",
|
|
@@ -5434,7 +5954,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5434
5954
|
});
|
|
5435
5955
|
} else if (historySecretScan?.envPathEvidence) {
|
|
5436
5956
|
findings.push({
|
|
5437
|
-
severity: "warning",
|
|
5957
|
+
severity: useV33Detectors ? "info" : "warning",
|
|
5438
5958
|
summary: "A non-template .env file was tracked in git history; no secret-shaped value was detected.",
|
|
5439
5959
|
evidence: historySecretScan.envPathEvidence
|
|
5440
5960
|
});
|
|
@@ -5492,7 +6012,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5492
6012
|
metric(
|
|
5493
6013
|
"tenant_scope_ratio",
|
|
5494
6014
|
"Tenant-scoped schema ratio",
|
|
5495
|
-
countFilesContaining(repoPath, migrationFiles,
|
|
6015
|
+
countFilesContaining(repoPath, migrationFiles, tenantScopePattern),
|
|
5496
6016
|
Math.max(migrationFiles.length, 1),
|
|
5497
6017
|
0.25,
|
|
5498
6018
|
"ratio",
|
|
@@ -5518,12 +6038,14 @@ function collectA2IsolationEvidence(repoPath, repoFiles) {
|
|
|
5518
6038
|
notes: "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported."
|
|
5519
6039
|
};
|
|
5520
6040
|
}
|
|
5521
|
-
function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
6041
|
+
function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = false) {
|
|
5522
6042
|
const evidence = [];
|
|
5523
6043
|
const findings = [];
|
|
5524
6044
|
let hasSurface = false;
|
|
5525
6045
|
let insecureFound = false;
|
|
5526
|
-
const productionImplFiles = implFiles.filter(
|
|
6046
|
+
const productionImplFiles = implFiles.filter(
|
|
6047
|
+
(file) => useV39Detectors ? isV39AuthoredProductionPath(file) : isProductionSourcePath(file)
|
|
6048
|
+
);
|
|
5527
6049
|
const timingSafeFile = productionImplFiles.find(
|
|
5528
6050
|
(file) => fileContains(repoPath, file, TIMING_SAFE_COMPARE_PATTERN)
|
|
5529
6051
|
);
|
|
@@ -5554,8 +6076,9 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5554
6076
|
}
|
|
5555
6077
|
let insecureCompareFile = null;
|
|
5556
6078
|
let insecureCompareLine = null;
|
|
5557
|
-
|
|
5558
|
-
|
|
6079
|
+
const compareFiles = useV39Detectors ? productionImplFiles : implFiles;
|
|
6080
|
+
for (const file of compareFiles) {
|
|
6081
|
+
const line = findInsecureSecretCompareLine(repoPath, file, useV39Detectors);
|
|
5559
6082
|
if (line != null) {
|
|
5560
6083
|
insecureCompareFile = file;
|
|
5561
6084
|
insecureCompareLine = line;
|
|
@@ -5563,6 +6086,12 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5563
6086
|
}
|
|
5564
6087
|
}
|
|
5565
6088
|
if (insecureCompareFile) {
|
|
6089
|
+
const insecureCompareSourceLine = lineAt(
|
|
6090
|
+
repoPath,
|
|
6091
|
+
insecureCompareFile,
|
|
6092
|
+
insecureCompareLine ?? 0
|
|
6093
|
+
);
|
|
6094
|
+
const comparesDefaultSentinel = useV39Detectors && insecureCompareSourceLine !== null && DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN.test(insecureCompareSourceLine);
|
|
5566
6095
|
const isTestPath = isTestOrFixturePath(insecureCompareFile);
|
|
5567
6096
|
if (!isTestPath) {
|
|
5568
6097
|
hasSurface = true;
|
|
@@ -5570,7 +6099,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5570
6099
|
}
|
|
5571
6100
|
findings.push({
|
|
5572
6101
|
severity: isTestPath ? "info" : "warning",
|
|
5573
|
-
summary: isTestPath ? `A secret/HMAC/signature comparison via plain equality appears in a test/fixture file (${insecureCompareFile}) \u2014 likely a test assertion, not a production timing leak; verify.` : "A secret/HMAC/signature value appears compared with a plain equality operator instead of a constant-time comparison \u2014 potential timing side-channel.",
|
|
6102
|
+
summary: isTestPath ? `A secret/HMAC/signature comparison via plain equality appears in a test/fixture file (${insecureCompareFile}) \u2014 likely a test assertion, not a production timing leak; verify.` : comparesDefaultSentinel ? "A supplied secret is compared with a configured default-secret sentinel using ordinary equality; verify whether this comparison belongs on a constant-time authentication path." : "A secret/HMAC/signature value appears compared with a plain equality operator instead of a constant-time comparison \u2014 potential timing side-channel.",
|
|
5574
6103
|
evidence: evidenceForRelativeAtLine(
|
|
5575
6104
|
repoPath,
|
|
5576
6105
|
insecureCompareFile,
|
|
@@ -5580,8 +6109,9 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5580
6109
|
)
|
|
5581
6110
|
});
|
|
5582
6111
|
}
|
|
5583
|
-
const
|
|
5584
|
-
|
|
6112
|
+
const unsortedFiles = useV39Detectors ? productionImplFiles : implFiles;
|
|
6113
|
+
const unsortedSignFile = unsortedFiles.find(
|
|
6114
|
+
(file) => (useV39Detectors ? fileHasVariableUnsortedJsonHmac(repoPath, file) : fileContains(repoPath, file, UNSORTED_JSON_SIGN_PATTERN)) && !fileContains(repoPath, file, CANONICAL_SERIALIZE_PATTERN)
|
|
5585
6115
|
);
|
|
5586
6116
|
if (unsortedSignFile) {
|
|
5587
6117
|
const isTestPath = isTestOrFixturePath(unsortedSignFile);
|
|
@@ -5589,11 +6119,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5589
6119
|
hasSurface = true;
|
|
5590
6120
|
insecureFound = true;
|
|
5591
6121
|
}
|
|
5592
|
-
const unsortedSignLine = findFirstMatchingLine(
|
|
5593
|
-
repoPath,
|
|
5594
|
-
unsortedSignFile,
|
|
5595
|
-
UNSORTED_JSON_SIGN_PATTERN
|
|
5596
|
-
);
|
|
6122
|
+
const unsortedSignLine = useV39Detectors ? findUnsortedJsonHmacLine(repoPath, unsortedSignFile) : findFirstMatchingLine(repoPath, unsortedSignFile, UNSORTED_JSON_SIGN_PATTERN);
|
|
5597
6123
|
findings.push({
|
|
5598
6124
|
severity: isTestPath ? "info" : "warning",
|
|
5599
6125
|
summary: isTestPath ? `Signing over unsorted JSON serialization appears in a test/fixture file (${unsortedSignFile}) \u2014 likely a test assertion, not a production signing gap; verify.` : "A signature/HMAC appears computed directly over JSON.stringify output with no canonical key ordering in the same file \u2014 signatures may not verify across equivalent payloads.",
|
|
@@ -5608,36 +6134,53 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
5608
6134
|
}
|
|
5609
6135
|
return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
|
|
5610
6136
|
}
|
|
5611
|
-
function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
5612
|
-
if (!isDeployableService(repoPath, repoFiles)) {
|
|
6137
|
+
function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors) {
|
|
6138
|
+
if (!isDeployableService(repoPath, repoFiles, useV27Detectors)) {
|
|
6139
|
+
const dockerApplicabilityNote = useV27Detectors ? "A Dockerfile without an explicit runtime start/service command is ambiguous and does not qualify." : "Dockerfile alone is ambiguous and does not qualify.";
|
|
5613
6140
|
return buildNotApplicableSignal(
|
|
5614
6141
|
"A3",
|
|
5615
|
-
|
|
6142
|
+
`No deployable-service surface detected \u2014 production-readiness not applicable to this library/CLI archetype. Signals checked: production server entrypoint (HTTP/RPC port binding in main/server/app files, outside examples/tests/demo dirs), deploy config (vercel.json, render.yaml, fly.toml, Procfile, app.yaml, serverless.yml, docker-compose, k8s/helm manifests), CI deploy job (fly deploy, kubectl apply, helm install/upgrade, docker push). ${dockerApplicabilityNote}`
|
|
5616
6143
|
);
|
|
5617
6144
|
}
|
|
5618
6145
|
const evidence = [];
|
|
5619
6146
|
const findings = [];
|
|
5620
6147
|
const packageJson = findRootPackageJson(repoFiles);
|
|
5621
6148
|
const scripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
|
|
5622
|
-
const workflows = repoFiles.filter(
|
|
6149
|
+
const workflows = repoFiles.filter(
|
|
6150
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isCiWorkflow(file)
|
|
6151
|
+
);
|
|
5623
6152
|
const workflow = workflows[0];
|
|
5624
|
-
const deployConfigs = repoFiles.filter(
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
const
|
|
6153
|
+
const deployConfigs = repoFiles.filter(
|
|
6154
|
+
(file) => (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file)) && isDeployConfig(file)
|
|
6155
|
+
);
|
|
6156
|
+
const releaseDeployConfigs = repoFiles.filter(
|
|
6157
|
+
(file) => (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file)) && isExplicitDeployTarget(file)
|
|
6158
|
+
);
|
|
6159
|
+
const releaseDeployConfig = releaseDeployConfigs[0];
|
|
6160
|
+
const containerBuildConfig = deployConfigs.find((file) => /(^|\/)Dockerfile$/.test(file));
|
|
6161
|
+
const envTemplate = repoFiles.find(
|
|
6162
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isEnvTemplatePath(file)
|
|
6163
|
+
);
|
|
6164
|
+
const healthChecks = repoFiles.filter(
|
|
6165
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isHealthCheckSignalFile(repoPath, file)
|
|
6166
|
+
);
|
|
5628
6167
|
const healthCheck = healthChecks[0];
|
|
5629
6168
|
const errorBoundaries = repoFiles.filter(
|
|
5630
|
-
(file) => /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
|
|
6169
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
|
|
5631
6170
|
);
|
|
5632
6171
|
const errorBoundary = errorBoundaries[0];
|
|
5633
6172
|
const observabilityCount = countFilesContaining(
|
|
5634
6173
|
repoPath,
|
|
5635
|
-
repoFiles.filter(
|
|
6174
|
+
repoFiles.filter(
|
|
6175
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file)
|
|
6176
|
+
),
|
|
5636
6177
|
/sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail/i
|
|
5637
6178
|
);
|
|
5638
6179
|
const rollbackSafetyCount = countFilesContaining(
|
|
5639
6180
|
repoPath,
|
|
5640
|
-
repoFiles.filter(
|
|
6181
|
+
repoFiles.filter(
|
|
6182
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /(^|\/)(docs|migrations?|drizzle|prisma|scripts)\//.test(file)
|
|
6183
|
+
),
|
|
5641
6184
|
/rollback|roll back|migration safety|down migration|reversible|undo migration/i
|
|
5642
6185
|
);
|
|
5643
6186
|
if (packageJson && (scripts.has("build") || scripts.has("typecheck"))) {
|
|
@@ -5646,9 +6189,23 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
|
5646
6189
|
);
|
|
5647
6190
|
}
|
|
5648
6191
|
if (workflow) evidence.push(evidenceForRelative(repoPath, workflow, "ci_run", "CI workflow"));
|
|
5649
|
-
if (
|
|
6192
|
+
if (releaseDeployConfig)
|
|
6193
|
+
evidence.push(
|
|
6194
|
+
evidenceForRelative(
|
|
6195
|
+
repoPath,
|
|
6196
|
+
releaseDeployConfig,
|
|
6197
|
+
"prod_check",
|
|
6198
|
+
"Release deploy configuration"
|
|
6199
|
+
)
|
|
6200
|
+
);
|
|
6201
|
+
if (containerBuildConfig)
|
|
5650
6202
|
evidence.push(
|
|
5651
|
-
evidenceForRelative(
|
|
6203
|
+
evidenceForRelative(
|
|
6204
|
+
repoPath,
|
|
6205
|
+
containerBuildConfig,
|
|
6206
|
+
"prod_check",
|
|
6207
|
+
"Container build configuration"
|
|
6208
|
+
)
|
|
5652
6209
|
);
|
|
5653
6210
|
if (envTemplate)
|
|
5654
6211
|
evidence.push(evidenceForRelative(repoPath, envTemplate, "prod_check", "Environment template"));
|
|
@@ -5659,7 +6216,8 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
|
5659
6216
|
if (errorBoundary)
|
|
5660
6217
|
evidence.push(evidenceForRelative(repoPath, errorBoundary, "prod_check", "Error boundary"));
|
|
5661
6218
|
if (evidence.length === 0) {
|
|
5662
|
-
const serverEntrypoint = findServerEntrypointFile(repoPath, repoFiles);
|
|
6219
|
+
const serverEntrypoint = findServerEntrypointFile(repoPath, repoFiles, useV27Detectors);
|
|
6220
|
+
const runtimeContainer = useV27Detectors ? findRuntimeContainerEntrypointFile(repoPath, repoFiles) : null;
|
|
5663
6221
|
if (serverEntrypoint) {
|
|
5664
6222
|
evidence.push(
|
|
5665
6223
|
evidenceForRelative(
|
|
@@ -5669,15 +6227,24 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
|
5669
6227
|
"Production server entrypoint"
|
|
5670
6228
|
)
|
|
5671
6229
|
);
|
|
6230
|
+
} else if (runtimeContainer) {
|
|
6231
|
+
evidence.push(
|
|
6232
|
+
evidenceForRelative(
|
|
6233
|
+
repoPath,
|
|
6234
|
+
runtimeContainer,
|
|
6235
|
+
"prod_check",
|
|
6236
|
+
"Runtime container entrypoint"
|
|
6237
|
+
)
|
|
6238
|
+
);
|
|
5672
6239
|
}
|
|
5673
6240
|
}
|
|
5674
6241
|
if (evidence.length === 0) return null;
|
|
5675
|
-
if (!workflow &&
|
|
6242
|
+
if (!workflow && releaseDeployConfigs.length === 0) {
|
|
5676
6243
|
const firstEvidence = evidence[0];
|
|
5677
6244
|
if (!firstEvidence) return null;
|
|
5678
6245
|
findings.push({
|
|
5679
6246
|
severity: "info",
|
|
5680
|
-
summary: "
|
|
6247
|
+
summary: "A deployable service surface exists, but no CI or release-deployment automation was detected.",
|
|
5681
6248
|
evidence: firstEvidence
|
|
5682
6249
|
});
|
|
5683
6250
|
}
|
|
@@ -5735,7 +6302,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles) {
|
|
|
5735
6302
|
]
|
|
5736
6303
|
};
|
|
5737
6304
|
}
|
|
5738
|
-
function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
|
|
6305
|
+
function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors, useV47Detectors) {
|
|
5739
6306
|
const evidence = [];
|
|
5740
6307
|
const findings = [];
|
|
5741
6308
|
const manifests = repoFiles.filter(isDependencyManifest);
|
|
@@ -5784,7 +6351,9 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
|
|
|
5784
6351
|
);
|
|
5785
6352
|
}
|
|
5786
6353
|
if (!manifest && !hasLockfile) return null;
|
|
5787
|
-
const
|
|
6354
|
+
const packagedApplicationFiles = useV27Detectors ? findPackagedApplicationPremiseFiles(repoPath, repoFiles) : [];
|
|
6355
|
+
const deployableServiceFiles = useV47Detectors ? findDeployableServicePremiseFiles(repoPath, repoFiles, true) : [];
|
|
6356
|
+
const isAppOrService = (useV47Detectors ? deployableServiceFiles.length > 0 : isDeployableService(repoPath, repoFiles, useV27Detectors)) || packagedApplicationFiles.length > 0;
|
|
5788
6357
|
if (isAppOrService && !hasLockfile && manifest) {
|
|
5789
6358
|
findings.push({
|
|
5790
6359
|
severity: "critical",
|
|
@@ -5792,7 +6361,7 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
|
|
|
5792
6361
|
evidence: evidenceForRelative(repoPath, manifest, "dependency_report", "Dependency manifest")
|
|
5793
6362
|
});
|
|
5794
6363
|
}
|
|
5795
|
-
if (manifest && hasSuspiciousDependencies(repoPath, manifest)) {
|
|
6364
|
+
if (manifest && hasSuspiciousDependencies(repoPath, manifest, useV47Detectors)) {
|
|
5796
6365
|
findings.push({
|
|
5797
6366
|
severity: "critical",
|
|
5798
6367
|
summary: "Dependency manifest contains obviously suspicious placeholder dependency names.",
|
|
@@ -5874,7 +6443,7 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono) {
|
|
|
5874
6443
|
positiveEvidence: evidence,
|
|
5875
6444
|
findings,
|
|
5876
6445
|
metrics: isAppOrService ? appMetrics : libraryMetrics,
|
|
5877
|
-
notes: isAppOrService ?
|
|
6446
|
+
notes: isAppOrService ? `A4 scored against app/service norms (${packagedApplicationFiles.length > 0 ? "packaged application" : "deploy surface"} detected): pinned dependencies and a lockfile are required for reproducible installs.` : "A4 scored against library/CLI norms (no deploy surface detected \u2014 same archetype line as A3): declared version ranges are correct library behavior; a committed lockfile is credited but not required."
|
|
5878
6447
|
};
|
|
5879
6448
|
}
|
|
5880
6449
|
var LIBRARY_DEPENDENCY_COUNT_SANE_MAX = 120;
|
|
@@ -5891,15 +6460,19 @@ function hasDeclaredVersionConstraint(dependency) {
|
|
|
5891
6460
|
if (/[~^<>=!]/.test(trimmed)) return true;
|
|
5892
6461
|
return /^v?\d/.test(trimmed);
|
|
5893
6462
|
}
|
|
5894
|
-
function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
5895
|
-
const
|
|
6463
|
+
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors) {
|
|
6464
|
+
const reconciliationArtifacts = useV27Detectors ? findClaimRealityReconciliationArtifacts(repoPath, repoFiles) : [];
|
|
6465
|
+
const claimSourceFiles = useV27Detectors ? findClaimSourceFiles(repoFiles) : repoFiles.filter(
|
|
6466
|
+
(file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
|
|
6467
|
+
);
|
|
6468
|
+
const claimDoc = useV27Detectors ? claimSourceFiles[0] ?? reconciliationArtifacts[0] : repoFiles.find((file) => /^(?:README|readme)\.md$/.test(file)) ?? repoFiles.find((file) => /^docs\/[^/]+\.(?:md|mdx)$/.test(file)) ?? repoFiles.find((file) => /(^|\/)claim[_-]reality[_-]reconciliation\.md$/i.test(file));
|
|
5896
6469
|
if (!claimDoc) {
|
|
5897
6470
|
return buildNotApplicableSignal(
|
|
5898
6471
|
"A5",
|
|
5899
6472
|
"No README or docs found \u2014 nothing is claimed about this repo, so there is nothing for A5 to reconcile against."
|
|
5900
6473
|
);
|
|
5901
6474
|
}
|
|
5902
|
-
const implementationFiles = repoFiles
|
|
6475
|
+
const implementationFiles = findClaimImplementationFiles(repoFiles, useV27Detectors);
|
|
5903
6476
|
if (implementationFiles.length === 0) return null;
|
|
5904
6477
|
const implementationFile = implementationFiles[0];
|
|
5905
6478
|
if (!implementationFile) return null;
|
|
@@ -5912,10 +6485,21 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
|
5912
6485
|
"Code presence for claim reconciliation"
|
|
5913
6486
|
)
|
|
5914
6487
|
];
|
|
6488
|
+
for (const artifact of reconciliationArtifacts.slice(0, 3)) {
|
|
6489
|
+
if (artifact === claimDoc) continue;
|
|
6490
|
+
evidence.push(
|
|
6491
|
+
evidenceForRelative(
|
|
6492
|
+
repoPath,
|
|
6493
|
+
artifact,
|
|
6494
|
+
"claim_reconciliation",
|
|
6495
|
+
"Dedicated claim-reality reconciliation artifact"
|
|
6496
|
+
)
|
|
6497
|
+
);
|
|
6498
|
+
}
|
|
5915
6499
|
const firstEvidence = evidence[0];
|
|
5916
6500
|
if (!firstEvidence) return null;
|
|
5917
|
-
const allDocFiles = repoFiles.filter(
|
|
5918
|
-
(file) => /^README\.md$/.test(file) || /^docs\/[^/]+\.(md|mdx)$/.test(file)
|
|
6501
|
+
const allDocFiles = useV27Detectors ? claimSourceFiles : repoFiles.filter(
|
|
6502
|
+
(file) => /^README\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
|
|
5919
6503
|
);
|
|
5920
6504
|
const boundedImplCount = Math.min(implementationFiles.length, 12);
|
|
5921
6505
|
const boundedDocCount = Math.min(allDocFiles.length, 8);
|
|
@@ -5940,7 +6524,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
|
5940
6524
|
return {
|
|
5941
6525
|
criterionId: "A5",
|
|
5942
6526
|
positiveEvidence: evidence,
|
|
5943
|
-
findings: [
|
|
6527
|
+
findings: reconciliationArtifacts.length > 0 ? [] : [
|
|
5944
6528
|
{
|
|
5945
6529
|
// Proxy-only A5: cap at warning (never critical) — a static proxy is lower-confidence
|
|
5946
6530
|
// than a curated registry or reconciliation artifact. Downgraded to info when the repo
|
|
@@ -5952,12 +6536,11 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
|
5952
6536
|
}
|
|
5953
6537
|
],
|
|
5954
6538
|
// Recalibrated (goal_cejel_launch_hardening_combined_2026-07-06, Phase 3 H1):
|
|
5955
|
-
// reconciliation_artifact_depth is
|
|
5956
|
-
//
|
|
5957
|
-
//
|
|
5958
|
-
//
|
|
5959
|
-
//
|
|
5960
|
-
// something a well-run external repo can actually earn; claim_source_depth's
|
|
6539
|
+
// reconciliation_artifact_depth is usually 0 on the generic-proxy path, so its prior 0.3
|
|
6540
|
+
// weight was a floor drag on external repos regardless of how well they otherwise document
|
|
6541
|
+
// and back claims. Weight remains 0.15 (still a real ding when a content-authenticated,
|
|
6542
|
+
// generically named reconciliation artifact is absent) and is redistributed to the two
|
|
6543
|
+
// metrics that every well-run external repo can earn; claim_source_depth's
|
|
5961
6544
|
// denominator lowered from 8 to 4 (README + a couple of top-level docs is already a
|
|
5962
6545
|
// reasonably documented repo, not a fraction of an inflated 8-file bar).
|
|
5963
6546
|
metrics: [
|
|
@@ -5983,7 +6566,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles) {
|
|
|
5983
6566
|
metric(
|
|
5984
6567
|
"reconciliation_artifact_depth",
|
|
5985
6568
|
"Reconciliation artifact depth",
|
|
5986
|
-
|
|
6569
|
+
reconciliationArtifacts.length,
|
|
5987
6570
|
3,
|
|
5988
6571
|
0.15,
|
|
5989
6572
|
"artifacts",
|
|
@@ -6166,7 +6749,7 @@ function collectB4AuditEvidence(repoPath, repoFiles, generatedAt) {
|
|
|
6166
6749
|
]
|
|
6167
6750
|
};
|
|
6168
6751
|
}
|
|
6169
|
-
function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
|
|
6752
|
+
function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detectors = false) {
|
|
6170
6753
|
const evidence = [];
|
|
6171
6754
|
const findings = [];
|
|
6172
6755
|
const docFiles = repoFiles.filter((file) => /\.(md|mdx)$/i.test(file));
|
|
@@ -6189,12 +6772,13 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
|
|
|
6189
6772
|
(file) => fileContains(repoPath, file, SQL_EXEC_PATTERN)
|
|
6190
6773
|
);
|
|
6191
6774
|
const ungatedEscalationFiles = executableFiles.filter((file) => {
|
|
6775
|
+
if (useV39Detectors && !fileHasExecutedPrivilegeEscalation(repoPath, file)) return false;
|
|
6192
6776
|
const hasEscalation = fileContains(repoPath, file, ROLE_MEMBERSHIP_GRANT_PATTERN) || fileContains(repoPath, file, SUPERUSER_ESCALATION_PATTERN);
|
|
6193
6777
|
if (!hasEscalation) return false;
|
|
6194
6778
|
return !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN);
|
|
6195
6779
|
});
|
|
6196
6780
|
const productionUngatedEscalationFiles = ungatedEscalationFiles.filter(
|
|
6197
|
-
(file) => !isTestOrFixturePath(file)
|
|
6781
|
+
(file) => useV39Detectors ? isV39AuthoredProductionPath(file) : !isTestOrFixturePath(file)
|
|
6198
6782
|
);
|
|
6199
6783
|
if (humanGateDoc) {
|
|
6200
6784
|
evidence.push(
|
|
@@ -6236,11 +6820,10 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
|
|
|
6236
6820
|
)
|
|
6237
6821
|
);
|
|
6238
6822
|
}
|
|
6239
|
-
for (const file of
|
|
6240
|
-
const isTestPath = isTestOrFixturePath(file);
|
|
6823
|
+
for (const file of productionUngatedEscalationFiles.slice(0, 5)) {
|
|
6241
6824
|
findings.push({
|
|
6242
|
-
severity:
|
|
6243
|
-
summary:
|
|
6825
|
+
severity: "critical",
|
|
6826
|
+
summary: "Role-membership GRANT or SUPERUSER escalation executes in code with no documented human gate.",
|
|
6244
6827
|
evidence: evidenceForRelative(
|
|
6245
6828
|
repoPath,
|
|
6246
6829
|
file,
|
|
@@ -6299,7 +6882,7 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
|
|
|
6299
6882
|
1,
|
|
6300
6883
|
hasPrivilegedOpsSurface ? 0.3 : 0.4,
|
|
6301
6884
|
"clean",
|
|
6302
|
-
"Penalizes code that executes a role-membership GRANT or SUPERUSER escalation with no documented human gate (test/fixture
|
|
6885
|
+
"Penalizes code that executes a role-membership GRANT or SUPERUSER escalation with no documented human gate (test/fixture SQL is excluded from this production-code measurement)."
|
|
6303
6886
|
),
|
|
6304
6887
|
metric(
|
|
6305
6888
|
"protected_path_review_gate",
|
|
@@ -6319,39 +6902,62 @@ var CPP_TEST_FRAMEWORK_PATTERN = /<gtest\/gtest\.h>|<catch2\/|<doctest\/|<boost\
|
|
|
6319
6902
|
var CPP_TEST_MACRO_PATTERN = /\bTEST\s*\(|\bTEST_F\s*\(|\bTEST_CASE\s*\(|\bSECTION\s*\(|\bCATCH_|BOOST_AUTO_TEST_CASE\b|BOOST_FIXTURE_TEST_CASE\b/;
|
|
6320
6903
|
var CPP_CONTENT_SCAN_LIMIT = 150;
|
|
6321
6904
|
var NON_PRODUCTION_DIR_PATTERN = /(^|\/)(__tests__|__mocks__|__fixtures__|tests?|fixtures?|testdata|e2e|cypress|examples?|samples?|demos?)(\/|$)/i;
|
|
6322
|
-
var NON_PRODUCTION_FILE_SUFFIX_PATTERN = /\.(test|spec|stories)\.[^/]
|
|
6905
|
+
var NON_PRODUCTION_FILE_SUFFIX_PATTERN = /\.(test|spec|stories)\.[^/]+$|_test\.go$|(^|\/)test_.*\.py$/i;
|
|
6323
6906
|
function isTestOrFixturePath(path) {
|
|
6324
6907
|
return NON_PRODUCTION_DIR_PATTERN.test(path) || NON_PRODUCTION_FILE_SUFFIX_PATTERN.test(path);
|
|
6325
6908
|
}
|
|
6326
6909
|
function isProductionSourcePath(path) {
|
|
6327
6910
|
return !isTestOrFixturePath(path);
|
|
6328
6911
|
}
|
|
6912
|
+
function isAuthoredProductionPath(path) {
|
|
6913
|
+
return isProductionSourcePath(path) && !GENERATED_OR_VENDOR_PATH_PATTERN.test(path);
|
|
6914
|
+
}
|
|
6915
|
+
var V39_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN = /(^|\/)(?:docs?|documentation|e2e[-_]?tests?)(\/|$)|(^|\/)(?:test|tests)[._-][^/]+$|(^|\/)[^/]*(?:appium|e2e[-_]?tests?)[^/]*$/i;
|
|
6916
|
+
function isV39AuthoredProductionPath(path) {
|
|
6917
|
+
return isAuthoredProductionPath(path) && !V39_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN.test(path);
|
|
6918
|
+
}
|
|
6919
|
+
var V47_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN = /(^|\/)(?:auto[-_]?gen(?:erated)?|generated|i18n|locales?|cookbooks?|lessons?)(\/|$)|\.(?:md|mdx|rst|adoc)$/i;
|
|
6920
|
+
function isV47AuthoredProductionPath(path) {
|
|
6921
|
+
return isV39AuthoredProductionPath(path) && !V47_NON_PRODUCTION_CREDENTIAL_PATH_PATTERN.test(path);
|
|
6922
|
+
}
|
|
6923
|
+
var TEST_DIRECTORY_SOURCE_PATTERN = new RegExp(
|
|
6924
|
+
`(^|/)(?:__tests__|tests?|specs?|[^/]+(?:Tests|Specs))/(?:.*\\.)?(?:${SOURCE_LANGUAGES.flatMap((language) => language.extensions).join("|")})$`
|
|
6925
|
+
);
|
|
6329
6926
|
function isTestFile(file) {
|
|
6330
|
-
return
|
|
6331
|
-
|
|
6332
|
-
) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(file) || /(^|\/)test_.*\.py$/.test(file) || /_test\.go$/.test(file) || /(^|\/)test_.*\.(cpp|cc|cxx)$/.test(file) || /_(test|tests)\.(cpp|cc|cxx)$/.test(file) || /Tests?\.(java|kt)$/.test(file) || /(_test|_spec)\.(rs|rb|php|swift|kt)$/.test(file);
|
|
6927
|
+
return TEST_DIRECTORY_SOURCE_PATTERN.test(file) || // AVA's default discovery includes test.js and test-*.js at the scanned package root.
|
|
6928
|
+
// Keep this root-anchored: nested files need an existing test-directory/suffix convention.
|
|
6929
|
+
/^test(?:-[^/]+)?\.[cm]?[jt]sx?$/.test(file) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(file) || /(^|\/)test_.*\.py$/.test(file) || /_test\.go$/.test(file) || /(^|\/)test_.*\.(cpp|cc|cxx)$/.test(file) || /_(test|tests)\.(cpp|cc|cxx)$/.test(file) || /Tests?\.(java|kt)$/.test(file) || /(_test|_spec)\.(rs|rb|php|swift|kt)$/.test(file);
|
|
6333
6930
|
}
|
|
6334
|
-
var NAME_SHAPED_TEST_FILE_PATTERN =
|
|
6335
|
-
var TEST_RUNNER_PATTERN = /\b(vitest|jest|mocha|ava|tap|pytest|go test|cargo test|rspec|phpunit|gradle test|mvn test)\b/i;
|
|
6336
|
-
var CI_TEST_COMMAND_PATTERN = /\b(pytest|go\s+test|cargo\s+test|mvn\s+test|gradle\s+test|ctest|make\s+test|jest|vitest|mocha|rspec|phpunit|pnpm\s+test|npm\s+test|yarn\s+test|node\s+--test|node:test)\b/i;
|
|
6931
|
+
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)$/;
|
|
6932
|
+
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;
|
|
6933
|
+
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;
|
|
6337
6934
|
var CI_LINT_COMMAND_PATTERN = /\b(eslint|pylint|flake8|black|ruff)\b/i;
|
|
6338
6935
|
var CI_TYPECHECK_COMMAND_PATTERN = /\b(tsc|mypy|pyright)\b/i;
|
|
6339
6936
|
var SCHEDULE_TRIGGER_PATTERN = /(^|\n)\s*schedule:\s*\r?\n\s*-\s*cron:/;
|
|
6340
6937
|
var PUBLISHED_RESULT_MARKER_PATTERN = /actions\/deploy-pages|actions\/upload-pages-artifact|peaceiris\/actions-gh-pages|git\s+push\b|create-or-update-comment/i;
|
|
6341
6938
|
var EPHEMERAL_ARTIFACT_ONLY_PATTERN = /actions\/upload-artifact/i;
|
|
6939
|
+
var SCHEDULED_HEALTH_PATH_PATTERN = /(?:health|smoke|monitor|status|uptime|canary)/i;
|
|
6940
|
+
var SCHEDULED_HEALTH_INTENT_PATTERN = /\b(?:product[- ]?health|health check|smoke test|monitor(?:ing)?|uptime|canary|fault detection)\b/i;
|
|
6342
6941
|
var RLS_PATTERN = /row level security|enable rls|create policy|tenant[_-]?id|studio[_-]?id/i;
|
|
6343
6942
|
var DB_CLIENT_PATTERN = /\b(pg|mysql|mysql2|sqlite3|mongodb|mongoose|sequelize|drizzle|prisma|knex|typeorm|redis|ioredis|better-sqlite3|asyncpg|psycopg2?|sqlalchemy|pymongo|aiosqlite|database\/sql|gorm|sqlx|diesel|rusqlite|ActiveRecord|Sequel|PDO|JDBC|Hibernate)\b/i;
|
|
6344
6943
|
var ENV_READ_PATTERN = /process\.env\b|Deno\.env\b|os\.environ\b|getenv\s*\(|std::env::|ENV\[/;
|
|
6345
6944
|
var TENANT_SCOPE_PATTERN = /tenantId|tenant_id|studioId|studio_id|organizationId|org_id|workspaceId/i;
|
|
6945
|
+
var TENANT_SCOPE_PATTERN_V10 = /tenantId|tenant_id|studioId|studio_id|organizationId|organization_id|org_id|accountTenantId|account_tenant_id/i;
|
|
6946
|
+
var TENANT_SCOPE_PATTERN_V11 = /tenantId|tenant_id|accountTenantId|account_tenant_id/i;
|
|
6346
6947
|
var HUMAN_GATE_MARKER_PATTERN = /human[- ]executed|human[- ]gated|operator[- ]run|never agent-run|agents? (?:never|does not|do not) (?:hold|execute|run) (?:prod(?:uction)?\s+)?(?:admin|db) (?:credential|privilege)/i;
|
|
6347
6948
|
var ROLE_MEMBERSHIP_GRANT_PATTERN = /\bGRANT\s+(?!SELECT\b|INSERT\b|UPDATE\b|DELETE\b|USAGE\b|ALL\b|EXECUTE\b|TRIGGER\b|REFERENCES\b|CREATE\b|CONNECT\b|TEMP(?:ORARY)?\b)[A-Za-z_]\w*\s+TO\b/i;
|
|
6348
6949
|
var SUPERUSER_ESCALATION_PATTERN = /\b(ALTER|CREATE)\s+(ROLE|USER)\s+\w+[^;]*\bSUPERUSER\b/i;
|
|
6349
6950
|
var SQL_EXEC_PATTERN = /\.execute\s*\(|sql\.raw\s*\(/;
|
|
6951
|
+
var EXECUTED_ESCALATION_LITERAL_PATTERN = /(?:\.execute|sql\.raw)\s*\(\s*(?:[rubf]+)?["'`][^"'`]{0,500}(?:GRANT\s+(?!SELECT\b|INSERT\b|UPDATE\b|DELETE\b|USAGE\b|ALL\b|EXECUTE\b|TRIGGER\b|REFERENCES\b|CREATE\b|CONNECT\b|TEMP(?:ORARY)?\b)[A-Za-z_]\w*\s+TO\b|(?:ALTER|CREATE)\s+(?:ROLE|USER)\s+\w+[^;]*\bSUPERUSER\b)/i;
|
|
6952
|
+
function fileHasExecutedPrivilegeEscalation(repoPath, file) {
|
|
6953
|
+
return fileContains(repoPath, file, EXECUTED_ESCALATION_LITERAL_PATTERN);
|
|
6954
|
+
}
|
|
6350
6955
|
var GATED_PRIVILEGE_CHECK_PATTERN = /pg_has_role\s*\(|has_role\s*\(|is not a member of/i;
|
|
6351
6956
|
var SET_ROLE_PATTERN = /\bset\s+(local\s+)?role\b/i;
|
|
6352
6957
|
var REQUIRED_REVIEW_PATTERN = /required review|branch protection|protected branch/i;
|
|
6353
6958
|
var HISTORY_SECRET_SCAN_CREDENTIAL_BLOB_LIMIT = 5e3;
|
|
6354
|
-
var
|
|
6959
|
+
var SECRET_IDENTIFIER_KEYWORD_PATTERN = /secret|token|api[_-]?key|password|access[_-]?key/gi;
|
|
6960
|
+
var SINGLE_WHITESPACE_PATTERN = /\s/u;
|
|
6355
6961
|
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,})$/;
|
|
6356
6962
|
var GENERIC_SECRET_SHAPE_PATTERN = /^[A-Za-z0-9/+_-]{40,}$/;
|
|
6357
6963
|
var KNOWN_HEX_DIGEST_LENGTHS = /* @__PURE__ */ new Set([32, 40, 64, 128]);
|
|
@@ -6360,23 +6966,25 @@ var NEGATIVE_SPACE_SECTION_PATTERN = /^#{1,6}\s*(?:security\s+)?(?:limitations?|
|
|
|
6360
6966
|
var NEGATIVE_SPACE_PHRASE_PATTERN = /does not (?:protect|defend|guard|cover)s? against|not (?:covered|protected|defended) by this|out of scope for this (?:library|package|tool|sdk|project)/i;
|
|
6361
6967
|
var LEAN_TEST_RUNNER_SCRIPT_PATTERN = /\bnode\s+(?:--test\b|--experimental-test-coverage\b)/;
|
|
6362
6968
|
var LEAN_TEST_RUNNER_IMPORT_PATTERN = /from\s+['"]node:test['"]|require\(\s*['"]node:test['"]\s*\)/;
|
|
6363
|
-
var HEAVY_TEST_DEPENDENCY_NAME_PATTERN = /^(?:jest|mocha|ava|tape|jasmine|karma)(?:$|-|\/)/i;
|
|
6969
|
+
var HEAVY_TEST_DEPENDENCY_NAME_PATTERN = /^(?:(?:jest|mocha|ava|tape|jasmine|karma|vitest|cypress|playwright)(?:$|-|\/)|@(?:vitest|playwright)\/)/i;
|
|
6364
6970
|
var TIMING_SAFE_COMPARE_PATTERN = /\btiming[-_]?safe[-_]?(?:equal|compare)\b|crypto\.timingSafeEqual\s*\(/i;
|
|
6365
6971
|
var CANONICAL_SERIALIZE_PATTERN = /\bcanonicalize\s*\(|\bcanonicalJson\b|\bstableStringify\s*\(|json-stable-stringify|safe-stable-stringify|\bsortKeys\s*\(/i;
|
|
6366
6972
|
var SIGN_OR_HMAC_CALL_PATTERN = /\bcreateHmac\s*\(|\bcreateSign\s*\(|\.sign\s*\(/;
|
|
6367
6973
|
var INSECURE_SECRET_COMPARE_LINE_PATTERN = /\b\w*(?:hmac|signature|\bmac|digest)\w*\s*(===|!==)\s*(?!['"`]|undefined\b|null\b)([\w.]+(?:\s*[-+]\s*\d+)?)/i;
|
|
6974
|
+
var DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN = /\b(?:secret|token|password|apiKey)\w*\s*(?:===|!==|==|!=)\s*[\w.]*default(?:Secret|Token|Password|ApiKey)\b/i;
|
|
6368
6975
|
var NUMERIC_SHAPED_OPERAND_PATTERN = /^-?\d+$|\.(?:length|len|size|count)\b|(?:length|index|idx|offset|count|size|len)$/i;
|
|
6369
|
-
function lineHasInsecureSecretCompare(line) {
|
|
6976
|
+
function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
|
|
6977
|
+
if (useV39Detectors && DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN.test(line)) return true;
|
|
6370
6978
|
const match = INSECURE_SECRET_COMPARE_LINE_PATTERN.exec(line);
|
|
6371
6979
|
const operand = match?.[2];
|
|
6372
6980
|
if (!operand) return false;
|
|
6373
6981
|
return !NUMERIC_SHAPED_OPERAND_PATTERN.test(operand.trim());
|
|
6374
6982
|
}
|
|
6375
|
-
function findInsecureSecretCompareLine(repoPath, file) {
|
|
6983
|
+
function findInsecureSecretCompareLine(repoPath, file, useV39Detectors = false) {
|
|
6376
6984
|
const fullPath = join(repoPath, file);
|
|
6377
6985
|
if (!isRegularFile(fullPath)) return null;
|
|
6378
6986
|
const lines = readFileSync(fullPath, "utf8").split("\n");
|
|
6379
|
-
const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line));
|
|
6987
|
+
const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line, useV39Detectors));
|
|
6380
6988
|
return index === -1 ? null : index + 1;
|
|
6381
6989
|
}
|
|
6382
6990
|
function findFirstMatchingLine(repoPath, file, pattern) {
|
|
@@ -6386,7 +6994,49 @@ function findFirstMatchingLine(repoPath, file, pattern) {
|
|
|
6386
6994
|
const index = lines.findIndex((line) => pattern.test(line));
|
|
6387
6995
|
return index === -1 ? null : index + 1;
|
|
6388
6996
|
}
|
|
6997
|
+
function lineAt(repoPath, file, line) {
|
|
6998
|
+
if (line < 1) return null;
|
|
6999
|
+
const fullPath = join(repoPath, file);
|
|
7000
|
+
if (!isRegularFile(fullPath)) return null;
|
|
7001
|
+
return readFileSync(fullPath, "utf8").split("\n")[line - 1] ?? null;
|
|
7002
|
+
}
|
|
6389
7003
|
var UNSORTED_JSON_SIGN_PATTERN = /\.update\s*\(\s*JSON\.stringify\s*\(|\.sign\s*\([^)]*JSON\.stringify\s*\(/;
|
|
7004
|
+
var UNSORTED_JSON_DIRECT_SIGN_PATTERN = /\.sign\s*\([^)]*JSON\.stringify\s*\(/;
|
|
7005
|
+
var UNSORTED_JSON_CONSTRUCTOR_UPDATE_PATTERN = /\bcreate(?:Hmac|Sign)\s*\([^;\n]{0,256}\)\s*\.update\s*\(\s*JSON\.stringify\s*\(/;
|
|
7006
|
+
var JSON_STRINGIFY_ASSIGNMENT_PATTERN = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*JSON\.stringify\s*\(/g;
|
|
7007
|
+
function findUnsortedJsonHmacLine(repoPath, file) {
|
|
7008
|
+
const fullPath = join(repoPath, file);
|
|
7009
|
+
if (!isRegularFile(fullPath)) return null;
|
|
7010
|
+
const contents = readFileSync(fullPath, "utf8");
|
|
7011
|
+
const directSign = findFirstMatchingLine(repoPath, file, UNSORTED_JSON_DIRECT_SIGN_PATTERN);
|
|
7012
|
+
if (directSign !== null) return directSign;
|
|
7013
|
+
const directConstructorUpdate = findFirstMatchingLine(
|
|
7014
|
+
repoPath,
|
|
7015
|
+
file,
|
|
7016
|
+
UNSORTED_JSON_CONSTRUCTOR_UPDATE_PATTERN
|
|
7017
|
+
);
|
|
7018
|
+
if (directConstructorUpdate !== null) return directConstructorUpdate;
|
|
7019
|
+
JSON_STRINGIFY_ASSIGNMENT_PATTERN.lastIndex = 0;
|
|
7020
|
+
let match = JSON_STRINGIFY_ASSIGNMENT_PATTERN.exec(contents);
|
|
7021
|
+
while (match !== null) {
|
|
7022
|
+
const identifier = match[1];
|
|
7023
|
+
if (identifier) {
|
|
7024
|
+
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7025
|
+
const constructorUpdate = new RegExp(
|
|
7026
|
+
`\\bcreate(?:Hmac|Sign)\\s*\\([^;\\n]{0,256}\\)\\s*\\.update\\s*\\(\\s*${escaped}\\b`
|
|
7027
|
+
);
|
|
7028
|
+
const usageWindow = contents.slice(match.index, match.index + 2048);
|
|
7029
|
+
if (constructorUpdate.test(usageWindow)) {
|
|
7030
|
+
return contents.slice(0, match.index).split("\n").length;
|
|
7031
|
+
}
|
|
7032
|
+
}
|
|
7033
|
+
match = JSON_STRINGIFY_ASSIGNMENT_PATTERN.exec(contents);
|
|
7034
|
+
}
|
|
7035
|
+
return null;
|
|
7036
|
+
}
|
|
7037
|
+
function fileHasVariableUnsortedJsonHmac(repoPath, file) {
|
|
7038
|
+
return findUnsortedJsonHmacLine(repoPath, file) !== null;
|
|
7039
|
+
}
|
|
6390
7040
|
var KILL_SWITCH_NAME_FRAGMENT = "kill[_-]?switch|emergency[_-]?stop|circuit[_-]?breaker|safety[_-]?(?:toggle|gate|switch)|governance[_-]?(?:toggle|gate)";
|
|
6391
7041
|
var KILL_SWITCH_NAME_PATTERN = new RegExp(`\\b(?:${KILL_SWITCH_NAME_FRAGMENT})\\b`, "i");
|
|
6392
7042
|
var KILL_SWITCH_FAIL_CLOSED_PATTERN = new RegExp(
|
|
@@ -6431,19 +7081,23 @@ function looksLikeSecretValue(value) {
|
|
|
6431
7081
|
return SECRET_VALUE_BRANDED_PATTERN.test(value) || isGenericSecretValue(value);
|
|
6432
7082
|
}
|
|
6433
7083
|
var PLACEHOLDER_SECRET_PATTERN = /(?:^|[-_])(?:your|example|sample|placeholder|dummy|changeme|replace(?:-?me)?|redacted)(?:$|[-_])|^x{3,}$|^\.\.\.|^\$\{[^}]+}$|^<[^>]+>$/i;
|
|
6434
|
-
var ENV_TEMPLATE_PATTERN = /(^|\/)\.env
|
|
6435
|
-
|
|
6436
|
-
|
|
7084
|
+
var ENV_TEMPLATE_PATTERN = /(^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template|dist)$/i;
|
|
7085
|
+
var ENV_TEMPLATE_PATTERN_V47 = /(^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template|dist|exemplo|ejemplo|exemple|esempio|beispiel|voorbeeld)$/i;
|
|
7086
|
+
function isEnvTemplatePath(path, useV47Detectors = false) {
|
|
7087
|
+
return (useV47Detectors ? ENV_TEMPLATE_PATTERN_V47 : ENV_TEMPLATE_PATTERN).test(path);
|
|
6437
7088
|
}
|
|
6438
7089
|
var HARD_EXCLUDED_PATH_PATTERN = /(^|\/)(?:\.git|\.venv|venv|site-packages|node_modules|dist|build|\.next|__pycache__|vendor|\.terraform|coverage)(?:\/|$)/;
|
|
6439
7090
|
var COVERAGE_PERCENT_PATTERN = /(?:statements|branches|lines|functions|fail_under)\s*[:=]\s*["']?(\d+(?:\.\d+)?)/gi;
|
|
6440
7091
|
function listRepoFiles(repoPath) {
|
|
6441
|
-
const gitFiles = listGitTrackedFiles(repoPath);
|
|
7092
|
+
const gitFiles = listGitTrackedFiles(repoPath, false);
|
|
6442
7093
|
if (gitFiles) return gitFiles;
|
|
6443
7094
|
const files = [];
|
|
6444
7095
|
visitRepoDir(repoPath, repoPath, files);
|
|
6445
7096
|
return files.filter((file) => !isHardExcludedPath(file)).sort();
|
|
6446
7097
|
}
|
|
7098
|
+
function listRepoInventory(repoPath, fallbackFiles) {
|
|
7099
|
+
return listGitTrackedFiles(repoPath, true) ?? [...fallbackFiles];
|
|
7100
|
+
}
|
|
6447
7101
|
function resolveMonorepoContext(repoPath) {
|
|
6448
7102
|
let top;
|
|
6449
7103
|
try {
|
|
@@ -6476,7 +7130,7 @@ function resolveMonorepoContext(repoPath) {
|
|
|
6476
7130
|
if (sharedFiles.length === 0) return null;
|
|
6477
7131
|
return { root: rootResolved, sharedFiles };
|
|
6478
7132
|
}
|
|
6479
|
-
function listGitTrackedFiles(repoPath) {
|
|
7133
|
+
function listGitTrackedFiles(repoPath, includeHardExcluded = false) {
|
|
6480
7134
|
try {
|
|
6481
7135
|
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
6482
7136
|
cwd: repoPath,
|
|
@@ -6490,7 +7144,7 @@ function listGitTrackedFiles(repoPath) {
|
|
|
6490
7144
|
}).trim();
|
|
6491
7145
|
if (!output) return [];
|
|
6492
7146
|
return output.split("\n").filter(
|
|
6493
|
-
(file) => file.length > 0 && !isHardExcludedPath(file) && isRegularFile(join(repoPath, file))
|
|
7147
|
+
(file) => file.length > 0 && (includeHardExcluded || !isHardExcludedPath(file)) && isRegularFile(join(repoPath, file))
|
|
6494
7148
|
).sort();
|
|
6495
7149
|
} catch {
|
|
6496
7150
|
return null;
|
|
@@ -6895,43 +7549,205 @@ function parseJsonObject(contents) {
|
|
|
6895
7549
|
function isRecord(value) {
|
|
6896
7550
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6897
7551
|
}
|
|
6898
|
-
function isTestRunnerConfig(file) {
|
|
6899
|
-
return /(^|\/)(vitest|jest|pytest|playwright|cypress|mocha)\.config\.[cm]?[jt]s$/.test(file) || /(^|\/)(pytest\.ini|tox\.ini
|
|
7552
|
+
function isTestRunnerConfig(repoPath, file) {
|
|
7553
|
+
return /(^|\/)(vitest|jest|pytest|playwright|cypress|mocha)\.config\.[cm]?[jt]s$/.test(file) || /(^|\/)(pytest\.ini|tox\.ini)$/.test(file) || /(^|\/)phpunit\.xml(\.dist)?$/.test(file) || /(^|\/)pyproject\.toml$/.test(file) && fileContains(repoPath, file, /^\[tool\.pytest(?:\.|\])/m) || /(^|\/)CMakeLists\.txt$/.test(file) && fileContains(repoPath, file, /\b(?:enable_testing|add_test|ctest_add_tests)\s*\(/i) || /(^|\/)Makefile$/.test(file) && fileContains(repoPath, file, /^\s*(?:test|tests|check)\s*:/m);
|
|
6900
7554
|
}
|
|
6901
|
-
function
|
|
6902
|
-
return
|
|
7555
|
+
function findCoverageConfigFiles(repoPath, repoFiles, authoredOnly = false) {
|
|
7556
|
+
return repoFiles.filter(
|
|
7557
|
+
(file) => (!authoredOnly || isAuthoredProductionPath(file)) && isCoverageConfig(repoPath, file)
|
|
7558
|
+
);
|
|
6903
7559
|
}
|
|
6904
|
-
function
|
|
6905
|
-
|
|
7560
|
+
function findConcreteTestFiles(repoPath, repoFiles) {
|
|
7561
|
+
const contentCppTests = collectContentBasedCppTestFiles(repoPath, repoFiles);
|
|
7562
|
+
return [...repoFiles.filter(isTestFile), ...contentCppTests.filter((file) => !isTestFile(file))];
|
|
7563
|
+
}
|
|
7564
|
+
function findConfiguredTestRunnerFiles(repoPath, repoFiles) {
|
|
7565
|
+
const configured = repoFiles.filter(
|
|
7566
|
+
(file) => isAuthoredProductionPath(file) && isTestRunnerConfig(repoPath, file)
|
|
7567
|
+
);
|
|
7568
|
+
for (const packageJson of repoFiles.filter(
|
|
7569
|
+
(file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
|
|
7570
|
+
)) {
|
|
7571
|
+
if ([...readPackageScripts(join(repoPath, packageJson)).values()].some(
|
|
7572
|
+
(script) => TEST_RUNNER_PATTERN.test(script)
|
|
7573
|
+
)) {
|
|
7574
|
+
configured.push(packageJson);
|
|
7575
|
+
}
|
|
7576
|
+
}
|
|
7577
|
+
configured.push(
|
|
7578
|
+
...repoFiles.filter((file) => isAuthoredProductionPath(file) && isCiWorkflow(file)).filter((file) => fileContains(repoPath, file, CI_TEST_COMMAND_PATTERN))
|
|
7579
|
+
);
|
|
7580
|
+
return [...new Set(configured)].sort();
|
|
7581
|
+
}
|
|
7582
|
+
function findTenantStoragePremiseFiles(repoPath, repoFiles) {
|
|
7583
|
+
return repoFiles.filter((file) => /(^|\/)(?:migrations?|drizzle|prisma)\/|\.(?:sql|prisma)$/i.test(file)).filter((file) => fileContains(repoPath, file, TENANT_SCOPE_PATTERN_V11));
|
|
7584
|
+
}
|
|
7585
|
+
function findRlsPolicyFiles(repoPath, repoFiles) {
|
|
7586
|
+
return repoFiles.filter((file) => /(^|\/)(?:migrations?|drizzle|prisma)\/|\.(?:sql|prisma)$/i.test(file)).filter(
|
|
7587
|
+
(file) => fileContains(
|
|
7588
|
+
repoPath,
|
|
7589
|
+
file,
|
|
7590
|
+
/\b(?:create\s+policy|enable\s+row\s+level\s+security|force\s+row\s+level\s+security)\b/i
|
|
7591
|
+
)
|
|
7592
|
+
);
|
|
7593
|
+
}
|
|
7594
|
+
function findDeployableServicePremiseFiles(repoPath, repoFiles, strictOperationalBoundary = false) {
|
|
7595
|
+
const explicitTargets = repoFiles.filter(
|
|
7596
|
+
(file) => isAuthoredProductionPath(file) && isExplicitDeployTarget(file) && (!strictOperationalBoundary || isV47OperationalApplicationPath(file))
|
|
7597
|
+
);
|
|
7598
|
+
const deployWorkflows = repoFiles.filter((file) => isAuthoredProductionPath(file) && isCiWorkflow(file)).filter((file) => fileContains(repoPath, file, CI_DEPLOY_JOB_PATTERN)).filter(
|
|
7599
|
+
(file) => !strictOperationalBoundary || !isDocumentationOnlyDeployWorkflow(repoPath, file)
|
|
7600
|
+
);
|
|
7601
|
+
const serverEntrypoint = findServerEntrypointFile(repoPath, repoFiles, true);
|
|
7602
|
+
const runtimeContainer = findRuntimeContainerEntrypointFile(repoPath, repoFiles);
|
|
7603
|
+
if (strictOperationalBoundary) {
|
|
7604
|
+
const runtimeFiles = [serverEntrypoint, runtimeContainer].filter(
|
|
7605
|
+
(file) => file != null && isV47OperationalApplicationPath(file)
|
|
7606
|
+
);
|
|
7607
|
+
const directApplicationTargets = explicitTargets.filter(
|
|
7608
|
+
(file) => !isInfrastructureOnlyDeployTarget(file)
|
|
7609
|
+
);
|
|
7610
|
+
const infrastructureTargets = runtimeFiles.length > 0 ? [...explicitTargets.filter(isInfrastructureOnlyDeployTarget), ...deployWorkflows] : [];
|
|
7611
|
+
return [...directApplicationTargets, ...runtimeFiles, ...infrastructureTargets];
|
|
7612
|
+
}
|
|
7613
|
+
return [
|
|
7614
|
+
...explicitTargets,
|
|
7615
|
+
...deployWorkflows,
|
|
7616
|
+
...serverEntrypoint && (!strictOperationalBoundary || isV47OperationalApplicationPath(serverEntrypoint)) ? [serverEntrypoint] : [],
|
|
7617
|
+
...runtimeContainer && (!strictOperationalBoundary || isV47OperationalApplicationPath(runtimeContainer)) ? [runtimeContainer] : []
|
|
7618
|
+
];
|
|
7619
|
+
}
|
|
7620
|
+
function isInfrastructureOnlyDeployTarget(file) {
|
|
7621
|
+
return /(^|\/)docker-compose\.ya?ml$/i.test(file) || /(^|\/)(?:k8s|kubernetes|manifests?)\/.+\.ya?ml$/i.test(file) || /(^|\/)(?:charts?|helm)\/.+\.ya?ml$/i.test(file);
|
|
7622
|
+
}
|
|
7623
|
+
var V47_NON_OPERATIONAL_APPLICATION_PATH_PATTERN = /(^|\/)(?:cookbooks?|lessons?|tutorials?|docs?|documentation|examples?|samples?|demos?)(\/|$)/i;
|
|
7624
|
+
function isV47OperationalApplicationPath(path) {
|
|
7625
|
+
return !V47_NON_OPERATIONAL_APPLICATION_PATH_PATTERN.test(path);
|
|
7626
|
+
}
|
|
7627
|
+
function isDocumentationOnlyDeployWorkflow(repoPath, file) {
|
|
7628
|
+
if (/(?:docs?|pages?)/i.test(basename(file))) return true;
|
|
7629
|
+
return fileContains(
|
|
7630
|
+
repoPath,
|
|
7631
|
+
file,
|
|
7632
|
+
/\b(?:deploy[-_ ]?docs?|mkdocs|docusaurus|vitepress|gh-pages|github pages)\b/i
|
|
7633
|
+
);
|
|
7634
|
+
}
|
|
7635
|
+
function findPackagedApplicationPremiseFiles(repoPath, repoFiles) {
|
|
7636
|
+
return repoFiles.filter((file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)).filter((file) => packageJsonDescribesPackagedApplication(repoPath, file, repoFiles));
|
|
7637
|
+
}
|
|
7638
|
+
function findClaimSourceFiles(repoFiles) {
|
|
7639
|
+
return repoFiles.filter(
|
|
7640
|
+
(file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
|
|
7641
|
+
).sort((left, right) => {
|
|
7642
|
+
const leftRootReadme = /^(?:README|readme)\.md$/.test(left);
|
|
7643
|
+
const rightRootReadme = /^(?:README|readme)\.md$/.test(right);
|
|
7644
|
+
return Number(rightRootReadme) - Number(leftRootReadme) || left.localeCompare(right);
|
|
7645
|
+
});
|
|
7646
|
+
}
|
|
7647
|
+
function findClaimImplementationFiles(repoFiles, authoredOnly = false) {
|
|
7648
|
+
return repoFiles.filter(
|
|
7649
|
+
(file) => isImplementationFile(file) && (!authoredOnly || isAuthoredProductionPath(file))
|
|
7650
|
+
);
|
|
7651
|
+
}
|
|
7652
|
+
function findClaimRealityReconciliationArtifacts(repoPath, repoFiles) {
|
|
7653
|
+
return repoFiles.filter((file) => /(^|\/)claim[-_]reality[-_]reconciliation\.(?:md|mdx|json)$/i.test(file)).filter((file) => isAuthenticatedClaimRealityArtifact(repoPath, file));
|
|
7654
|
+
}
|
|
7655
|
+
function isAuthenticatedClaimRealityArtifact(repoPath, file) {
|
|
7656
|
+
const fullPath = join(repoPath, file);
|
|
7657
|
+
if (!isRegularFile(fullPath)) return false;
|
|
7658
|
+
const contents = readFileSync(fullPath, "utf8");
|
|
7659
|
+
if (/\.json$/i.test(file)) {
|
|
7660
|
+
return parseJsonObject(contents)?.artifactKind === "claim_reality_reconciliation";
|
|
7661
|
+
}
|
|
7662
|
+
let inFence = false;
|
|
7663
|
+
for (const line of contents.split(/\r?\n/)) {
|
|
7664
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
7665
|
+
inFence = !inFence;
|
|
7666
|
+
continue;
|
|
7667
|
+
}
|
|
7668
|
+
if (!inFence && /^#\s+Claim Reality Reconciliation\s*$/i.test(line)) return true;
|
|
7669
|
+
}
|
|
7670
|
+
return false;
|
|
7671
|
+
}
|
|
7672
|
+
function isCoverageConfig(repoPath, file) {
|
|
7673
|
+
if (/(^|\/)(nyc|c8|coverage|codecov)\.(json|yml|yaml)$/.test(file) || /(^|\/)\.coveragerc$/.test(file) || /(^|\/)lcov\.info$/.test(file) || /(^|\/)\.coveralls\.yml$/.test(file)) {
|
|
7674
|
+
return true;
|
|
7675
|
+
}
|
|
7676
|
+
if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7677
|
+
return fileContains(repoPath, file, /\bcoverage\s*:/i);
|
|
7678
|
+
}
|
|
7679
|
+
if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7680
|
+
return fileContains(
|
|
7681
|
+
repoPath,
|
|
7682
|
+
file,
|
|
7683
|
+
/\b(?:collectCoverage|coverageThreshold|coverageProvider|coverageDirectory)\s*:/
|
|
7684
|
+
);
|
|
7685
|
+
}
|
|
7686
|
+
if (/(^|\/)pyproject\.toml$/.test(file)) {
|
|
7687
|
+
return fileContains(repoPath, file, /^\[tool\.coverage(?:\.|\])/m);
|
|
7688
|
+
}
|
|
7689
|
+
return false;
|
|
6906
7690
|
}
|
|
6907
7691
|
function isCiWorkflow(file) {
|
|
6908
7692
|
return /^\.github\/workflows\/.+\.ya?ml$/.test(file) || /^\.gitlab-ci\.ya?ml$/.test(file) || /^\.circleci\/config\.ya?ml$/.test(file) || /^azure-pipelines\.ya?ml$/.test(file) || /^Jenkinsfile$/.test(file);
|
|
6909
7693
|
}
|
|
6910
7694
|
function isDeployConfig(file) {
|
|
6911
|
-
return
|
|
7695
|
+
return /(^|\/)(vercel|netlify|fly|render|railway)\.(json|toml|ya?ml)$/.test(file) || /(^|\/)Dockerfile$/.test(file) || /(^|\/)docker-compose\.ya?ml$/.test(file) || /(^|\/)k8s\//.test(file);
|
|
6912
7696
|
}
|
|
6913
7697
|
function isExplicitDeployTarget(file) {
|
|
6914
|
-
return
|
|
7698
|
+
return /(^|\/)(vercel|netlify|fly|render|railway)\.(json|toml|ya?ml)$/.test(file) || /(^|\/)docker-compose\.ya?ml$/.test(file) || /(^|\/)Procfile$/.test(file) || /(^|\/)app\.ya?ml$/.test(file) || /(^|\/)serverless\.ya?ml$/.test(file) || /(^|\/)(k8s|kubernetes|manifests?)\/.+\.ya?ml$/i.test(file) || /(^|\/)(charts?|helm)\/.+\.ya?ml$/i.test(file);
|
|
6915
7699
|
}
|
|
6916
7700
|
var MAIN_ENTRYPOINT_FILE_PATTERN = /(^|\/)(main|server|app|wsgi|asgi|index|entry|start|run)\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs)$/i;
|
|
7701
|
+
var RACK_ENTRYPOINT_FILE_PATTERN = /(^|\/)(?:config\.ru|(?:main|server|app|start|run)\.rb)$/i;
|
|
6917
7702
|
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*\(/;
|
|
7703
|
+
var RACK_SERVER_ENTRYPOINT_PATTERN = /Rack::(?:Server|Handler(?:::\w+)?)\.(?:start|run)\s*\(/;
|
|
7704
|
+
var RACK_CONFIG_RUN_PATTERN = /^\s*run\s+(?:(?:[A-Z]\w*(?:::\w+)*(?:\.new)?|lambda)\b|->)/m;
|
|
7705
|
+
var RUNTIME_CONTAINER_COMMAND_PATTERN = /^\s*(?:CMD|ENTRYPOINT)\s+.*(?:\b(?:start|serve|server|qgis|nginx|apache|gunicorn|uvicorn)\b|manage\.py\s+runserver).*$/im;
|
|
7706
|
+
var NON_RUNTIME_CONTAINER_COMMAND_PATTERN = /\b(?:test|lint|build|compile|package|check)\b/i;
|
|
6918
7707
|
var CI_DEPLOY_JOB_PATTERN = /(?:^\s{2,4}deploy\s*:|\b(?:fly|heroku|vercel|netlify|railway|render)\s+deploy\b|kubectl\s+apply\b|helm\s+(?:install|upgrade|deploy)\b|docker\s+push\b)/im;
|
|
6919
|
-
function isDeployableService(repoPath, repoFiles) {
|
|
6920
|
-
if (repoFiles.some(
|
|
6921
|
-
|
|
6922
|
-
|
|
7708
|
+
function isDeployableService(repoPath, repoFiles, useV27Detectors) {
|
|
7709
|
+
if (repoFiles.some(
|
|
7710
|
+
(file) => (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file)) && isExplicitDeployTarget(file)
|
|
7711
|
+
)) {
|
|
7712
|
+
return true;
|
|
7713
|
+
}
|
|
7714
|
+
if (detectCiDeployJob(repoPath, repoFiles, useV27Detectors)) return true;
|
|
7715
|
+
if (detectServerEntrypoint(repoPath, repoFiles, useV27Detectors)) return true;
|
|
7716
|
+
if (useV27Detectors && findRuntimeContainerEntrypointFile(repoPath, repoFiles)) return true;
|
|
6923
7717
|
return false;
|
|
6924
7718
|
}
|
|
6925
|
-
function detectCiDeployJob(repoPath, repoFiles) {
|
|
6926
|
-
const workflows = repoFiles.filter(
|
|
7719
|
+
function detectCiDeployJob(repoPath, repoFiles, authoredOnly = false) {
|
|
7720
|
+
const workflows = repoFiles.filter(
|
|
7721
|
+
(file) => (!authoredOnly || isAuthoredProductionPath(file)) && isCiWorkflow(file)
|
|
7722
|
+
);
|
|
6927
7723
|
return workflows.some((file) => fileContains(repoPath, file, CI_DEPLOY_JOB_PATTERN));
|
|
6928
7724
|
}
|
|
6929
|
-
function detectServerEntrypoint(repoPath, repoFiles) {
|
|
6930
|
-
return findServerEntrypointFile(repoPath, repoFiles) !== null;
|
|
7725
|
+
function detectServerEntrypoint(repoPath, repoFiles, useV27Detectors) {
|
|
7726
|
+
return findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) !== null;
|
|
6931
7727
|
}
|
|
6932
|
-
function findServerEntrypointFile(repoPath, repoFiles) {
|
|
6933
|
-
const candidates = repoFiles.filter(
|
|
6934
|
-
|
|
7728
|
+
function findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) {
|
|
7729
|
+
const candidates = repoFiles.filter(
|
|
7730
|
+
(file) => (MAIN_ENTRYPOINT_FILE_PATTERN.test(file) || useV27Detectors && RACK_ENTRYPOINT_FILE_PATTERN.test(file)) && (useV27Detectors ? isAuthoredProductionPath(file) : isProductionSourcePath(file))
|
|
7731
|
+
).slice(0, 30);
|
|
7732
|
+
return candidates.find(
|
|
7733
|
+
(file) => fileContains(repoPath, file, SERVER_ENTRYPOINT_PATTERN) || useV27Detectors && fileContains(repoPath, file, RACK_SERVER_ENTRYPOINT_PATTERN) || useV27Detectors && /\.ru$/i.test(file) && fileContains(repoPath, file, RACK_CONFIG_RUN_PATTERN)
|
|
7734
|
+
) ?? null;
|
|
7735
|
+
}
|
|
7736
|
+
function findRuntimeContainerEntrypointFile(repoPath, repoFiles) {
|
|
7737
|
+
return repoFiles.filter((file) => /(^|\/)Dockerfile$/i.test(file) && isAuthoredProductionPath(file)).find((file) => {
|
|
7738
|
+
const fullPath = join(repoPath, file);
|
|
7739
|
+
if (!isRegularFile(fullPath)) return false;
|
|
7740
|
+
const lines = readFileSync(fullPath, "utf8").split(/\r?\n/);
|
|
7741
|
+
const finalStageStart = lines.reduce(
|
|
7742
|
+
(last, line, index) => /^\s*FROM\b/i.test(line) ? index : last,
|
|
7743
|
+
0
|
|
7744
|
+
);
|
|
7745
|
+
const finalStageLines = lines.slice(finalStageStart);
|
|
7746
|
+
const entrypoint = [...finalStageLines].reverse().find((line) => /^\s*ENTRYPOINT\b/i.test(line));
|
|
7747
|
+
const command = [...finalStageLines].reverse().find((line) => /^\s*CMD\b/i.test(line));
|
|
7748
|
+
const effectiveCommand = [entrypoint, command].filter(Boolean).join(" ");
|
|
7749
|
+
return effectiveCommand.length > 0 && RUNTIME_CONTAINER_COMMAND_PATTERN.test(effectiveCommand) && !NON_RUNTIME_CONTAINER_COMMAND_PATTERN.test(effectiveCommand);
|
|
7750
|
+
}) ?? null;
|
|
6935
7751
|
}
|
|
6936
7752
|
function isHealthCheckSignalFile(repoPath, file) {
|
|
6937
7753
|
if (/docs\/|audit|architecture|adr/i.test(file)) return false;
|
|
@@ -6978,6 +7794,42 @@ function packageJsonHasHeavyTestDependency(repoPath, manifest) {
|
|
|
6978
7794
|
];
|
|
6979
7795
|
return names.some((name) => HEAVY_TEST_DEPENDENCY_NAME_PATTERN.test(name));
|
|
6980
7796
|
}
|
|
7797
|
+
function packageJsonDescribesPackagedApplication(repoPath, manifest, repoFiles) {
|
|
7798
|
+
const fullPath = join(repoPath, manifest);
|
|
7799
|
+
if (!isRegularFile(fullPath)) return false;
|
|
7800
|
+
const parsed = parseJsonObject(readFileSync(fullPath, "utf8"));
|
|
7801
|
+
if (!parsed) return false;
|
|
7802
|
+
const scripts = isRecord(parsed.scripts) ? Object.values(parsed.scripts) : [];
|
|
7803
|
+
const dependencies = {
|
|
7804
|
+
...isRecord(parsed.dependencies) ? parsed.dependencies : {},
|
|
7805
|
+
...isRecord(parsed.devDependencies) ? parsed.devDependencies : {}
|
|
7806
|
+
};
|
|
7807
|
+
const build = isRecord(parsed.build) ? parsed.build : null;
|
|
7808
|
+
const hasDesktopRuntime = Object.keys(dependencies).some(
|
|
7809
|
+
(name) => name.toLowerCase() === "electron" || /^@tauri-apps\/(?:api|plugin-)/i.test(name)
|
|
7810
|
+
);
|
|
7811
|
+
const hasPackagingCommand = scripts.some(
|
|
7812
|
+
(script) => typeof script === "string" && /\b(?:electron-builder|electron-packager|tauri\s+build)\b/i.test(script)
|
|
7813
|
+
);
|
|
7814
|
+
const hasApplicationMetadata = isNonEmptyString(parsed.productName) || isNonEmptyString(parsed.main) || build != null && isNonEmptyString(build.appId) || findTauriApplicationMetadata(repoPath, manifest, repoFiles);
|
|
7815
|
+
return hasDesktopRuntime && hasPackagingCommand && hasApplicationMetadata;
|
|
7816
|
+
}
|
|
7817
|
+
function findTauriApplicationMetadata(repoPath, manifest, repoFiles) {
|
|
7818
|
+
const manifestDirectory = dirname(manifest);
|
|
7819
|
+
const prefix = manifestDirectory === "." ? "" : `${manifestDirectory}/`;
|
|
7820
|
+
const candidates = [`${prefix}src-tauri/tauri.conf.json`, `${prefix}tauri.conf.json`];
|
|
7821
|
+
return candidates.some((file) => {
|
|
7822
|
+
if (!repoFiles.includes(file)) return false;
|
|
7823
|
+
const fullPath = join(repoPath, file);
|
|
7824
|
+
if (!isRegularFile(fullPath)) return false;
|
|
7825
|
+
const config = parseJsonObject(readFileSync(fullPath, "utf8"));
|
|
7826
|
+
const packageMetadata = isRecord(config?.package) ? config.package : null;
|
|
7827
|
+
return isNonEmptyString(config?.productName) || isNonEmptyString(config?.identifier) || packageMetadata != null && isNonEmptyString(packageMetadata.productName);
|
|
7828
|
+
});
|
|
7829
|
+
}
|
|
7830
|
+
function isNonEmptyString(value) {
|
|
7831
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
7832
|
+
}
|
|
6981
7833
|
function isImplementationFile(file) {
|
|
6982
7834
|
return /(^|\/)(src|app|lib|packages|cmd|include|source|Sources)\//.test(file) && /\.(ts|tsx|js|jsx|py|go|rs|java|rb|cpp|cc|cxx|c|h|hpp|kt|swift|php)$/.test(file);
|
|
6983
7835
|
}
|
|
@@ -7001,8 +7853,8 @@ function isSecurityPolicyOnlyAuditFile(file) {
|
|
|
7001
7853
|
function isFreshnessRatableAuditFile(file) {
|
|
7002
7854
|
return isAuditFile(file) && !isSecurityPolicyOnlyAuditFile(file);
|
|
7003
7855
|
}
|
|
7004
|
-
function isIgnoredScanFile(file) {
|
|
7005
|
-
return /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|README\.md|CHANGELOG\.md)$/.test(file) || isEnvTemplatePath(file);
|
|
7856
|
+
function isIgnoredScanFile(file, useV47Detectors = false) {
|
|
7857
|
+
return /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|README\.md|CHANGELOG\.md)$/.test(file) || isEnvTemplatePath(file, useV47Detectors);
|
|
7006
7858
|
}
|
|
7007
7859
|
function findRootPackageJson(repoFiles) {
|
|
7008
7860
|
return repoFiles.includes("package.json") ? "package.json" : null;
|
|
@@ -7019,46 +7871,315 @@ function fileContains(repoPath, file, pattern) {
|
|
|
7019
7871
|
if (!isRegularFile(fullPath)) return false;
|
|
7020
7872
|
return pattern.test(readFileSync(fullPath, "utf8"));
|
|
7021
7873
|
}
|
|
7022
|
-
function
|
|
7874
|
+
function findCommittedSecretInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
7023
7875
|
const fullPath = join(repoPath, file);
|
|
7024
|
-
if (!isRegularFile(fullPath)) return
|
|
7025
|
-
|
|
7876
|
+
if (!isRegularFile(fullPath)) return null;
|
|
7877
|
+
const contents = readFileSync(fullPath, "utf8");
|
|
7878
|
+
const isTestCredentialConfiguration = useV39Detectors && isLikelyTestCredentialConfiguration(file, contents);
|
|
7879
|
+
const secretScanContents = prepareCredentialScanContents(
|
|
7880
|
+
contents,
|
|
7881
|
+
file,
|
|
7882
|
+
useV36Detectors,
|
|
7883
|
+
useV39Detectors,
|
|
7884
|
+
useV47Detectors
|
|
7885
|
+
);
|
|
7886
|
+
const secret = findRealSecretAssignment(secretScanContents, /* @__PURE__ */ new Set(), {
|
|
7887
|
+
allowCredentialNamedDigest: useV36Detectors
|
|
7888
|
+
});
|
|
7889
|
+
if (secret) return secret;
|
|
7890
|
+
if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
|
|
7891
|
+
const weakCredential = findWeakExplicitEnvCredential(secretScanContents);
|
|
7892
|
+
if (weakCredential) return weakCredential;
|
|
7893
|
+
}
|
|
7894
|
+
return useV36Detectors && !isTestCredentialConfiguration ? findDefaultAdministrativeCredential(secretScanContents) : null;
|
|
7895
|
+
}
|
|
7896
|
+
function prepareCredentialScanContents(contents, file, useV36Detectors, useV39Detectors, useV47Detectors = false) {
|
|
7897
|
+
const withoutXamlEventNames = useV36Detectors && /\.xaml$/i.test(file) ? contents.replace(/\bTokenItem(?:Added|Removed)(?=\s*=)/gi, "EventHandler") : contents;
|
|
7898
|
+
const withoutDocumentationExamples = useV39Detectors ? stripCommentAndDocumentationExamples(withoutXamlEventNames) : withoutXamlEventNames;
|
|
7899
|
+
return useV47Detectors ? stripV47PublicIdentifiersAndNonCredentialValues(withoutDocumentationExamples, file) : withoutDocumentationExamples;
|
|
7900
|
+
}
|
|
7901
|
+
function stripV47PublicIdentifiersAndNonCredentialValues(contents, file) {
|
|
7902
|
+
const lines = contents.split(/\r?\n/);
|
|
7903
|
+
return lines.map((line, index) => {
|
|
7904
|
+
let sanitized = line.replace(
|
|
7905
|
+
/^(\s*(?:export\s+)?(?:PUBLIC_)?ALGOLIA_(?:PUBLIC_)?API_KEY\s*[:=]\s*)[^\s#]+/i,
|
|
7906
|
+
'$1""'
|
|
7907
|
+
);
|
|
7908
|
+
if (isPublicDocumentationSearchKeyContext(lines, index, file)) {
|
|
7909
|
+
sanitized = sanitized.replace(/(\bapiKey\s*[:=]\s*)(['"])[^'"]+\2/gi, "$1$2$2");
|
|
7910
|
+
}
|
|
7911
|
+
sanitized = sanitized.replace(
|
|
7912
|
+
/(\b[A-Za-z_][A-Za-z0-9_]*(?:token|api[_-]?key|access[_-]?key)[A-Za-z0-9_]*\s*[:=]\s*)(['"]?)0x[0-9a-f]{40}\2/gi,
|
|
7913
|
+
'$1""'
|
|
7914
|
+
);
|
|
7915
|
+
sanitized = sanitized.replace(
|
|
7916
|
+
/(\b[A-Za-z_][A-Za-z0-9_]*(?:token|api[_-]?key|access[_-]?key)[A-Za-z0-9_]*\s*[:=]\s*)(['"])([a-z][a-z0-9]*(?:_[a-z0-9]+){2,})\2/gi,
|
|
7917
|
+
(assignment, prefix, quote, value) => /\b(?:cost|input|output|context|model|limit|price|usage|audio)\b/i.test(
|
|
7918
|
+
value.replaceAll("_", " ")
|
|
7919
|
+
) ? `${prefix}${quote}${quote}` : assignment
|
|
7920
|
+
);
|
|
7921
|
+
return sanitized;
|
|
7922
|
+
}).join("\n");
|
|
7026
7923
|
}
|
|
7027
|
-
function
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
if (
|
|
7034
|
-
if (
|
|
7924
|
+
function isPublicDocumentationSearchKeyContext(lines, lineIndex, file) {
|
|
7925
|
+
const windowStart = Math.max(0, lineIndex - 6);
|
|
7926
|
+
const preceding = lines.slice(windowStart, lineIndex + 1);
|
|
7927
|
+
for (let index = preceding.length - 1; index >= 0; index -= 1) {
|
|
7928
|
+
const candidate = preceding[index] ?? "";
|
|
7929
|
+
if (/}/.test(candidate) && index < preceding.length - 1) return false;
|
|
7930
|
+
if (/\bdocsearch(?:Options)?\b/i.test(candidate)) return true;
|
|
7931
|
+
if (/\balgolia\b/i.test(candidate) && /(?:^|\/)(?:website|docs?)(?:\/|$)|(?:site[-_]?config|\.dumirc|docusaurus)/i.test(file)) {
|
|
7932
|
+
return true;
|
|
7933
|
+
}
|
|
7035
7934
|
}
|
|
7036
7935
|
return false;
|
|
7037
7936
|
}
|
|
7038
|
-
function
|
|
7039
|
-
|
|
7937
|
+
function stripCommentAndDocumentationExamples(contents) {
|
|
7938
|
+
let blockCommentEnd = null;
|
|
7939
|
+
return contents.split(/\r?\n/).map((line) => {
|
|
7940
|
+
const trimmed = line.trimStart();
|
|
7941
|
+
if (blockCommentEnd) {
|
|
7942
|
+
if (trimmed.includes(blockCommentEnd)) blockCommentEnd = null;
|
|
7943
|
+
return "";
|
|
7944
|
+
}
|
|
7945
|
+
if (trimmed.startsWith("/*")) {
|
|
7946
|
+
if (!trimmed.includes("*/")) blockCommentEnd = "*/";
|
|
7947
|
+
return "";
|
|
7948
|
+
}
|
|
7949
|
+
if (trimmed.startsWith("<!--")) {
|
|
7950
|
+
if (!trimmed.includes("-->")) blockCommentEnd = "-->";
|
|
7951
|
+
return "";
|
|
7952
|
+
}
|
|
7953
|
+
if (/^(?:\/\/|#|\*)/.test(trimmed)) return "";
|
|
7954
|
+
if (/\.description\s*\(/i.test(line) && /api[_-]?key|token|password|secret/i.test(line)) {
|
|
7955
|
+
return "";
|
|
7956
|
+
}
|
|
7957
|
+
return line;
|
|
7958
|
+
}).join("\n");
|
|
7959
|
+
}
|
|
7960
|
+
function isLikelyTestCredentialConfiguration(file, contents) {
|
|
7961
|
+
if (!/\.(?:ya?ml|json|toml|ini|conf)$/i.test(file) && !/(?:^|\/)docker-compose/i.test(file)) {
|
|
7962
|
+
return false;
|
|
7963
|
+
}
|
|
7964
|
+
const testMarkers = contents.match(/(?:^|[^A-Za-z])(?:tests?|testing|e2e|appium|fixture)(?:[^A-Za-z]|$)/gi)?.length ?? 0;
|
|
7965
|
+
return testMarkers >= 2;
|
|
7966
|
+
}
|
|
7967
|
+
function hasTemplateOnlyEnvContent(repoPath, file) {
|
|
7968
|
+
const fullPath = join(repoPath, file);
|
|
7969
|
+
if (!isRegularFile(fullPath)) return false;
|
|
7970
|
+
const substantiveLines = readFileSync(fullPath, "utf8").split(/\r?\n/).filter((line) => line.trim().length > 0 && !/^\s*#/.test(line));
|
|
7971
|
+
if (substantiveLines.length === 0) return true;
|
|
7972
|
+
const assignments = substantiveLines.map(
|
|
7973
|
+
(line) => /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=\s*(.*?)\s*$/.exec(line)?.[1] ?? null
|
|
7974
|
+
);
|
|
7975
|
+
if (assignments.some((value) => value === null)) return false;
|
|
7976
|
+
return assignments.every((raw) => {
|
|
7977
|
+
if (raw === null) return false;
|
|
7978
|
+
const value = raw.replace(/^['"]|['"]$/g, "").trim();
|
|
7979
|
+
return value.length === 0 || isPlaceholderSecretValue(value);
|
|
7980
|
+
});
|
|
7981
|
+
}
|
|
7982
|
+
function findSecretFingerprintsInFile(repoPath, file, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
7983
|
+
const fullPath = join(repoPath, file);
|
|
7984
|
+
if (!isRegularFile(fullPath)) return [];
|
|
7985
|
+
const contents = readFileSync(fullPath, "utf8");
|
|
7986
|
+
const scanContents = prepareCredentialScanContents(
|
|
7987
|
+
contents,
|
|
7988
|
+
file,
|
|
7989
|
+
useV36Detectors,
|
|
7990
|
+
useV39Detectors,
|
|
7991
|
+
useV47Detectors
|
|
7992
|
+
);
|
|
7993
|
+
const fingerprints = findRealSecretAssignments(
|
|
7994
|
+
scanContents,
|
|
7995
|
+
/* @__PURE__ */ new Set(),
|
|
7996
|
+
Number.POSITIVE_INFINITY,
|
|
7997
|
+
{ allowCredentialNamedDigest: useV36Detectors }
|
|
7998
|
+
).map(({ valueFingerprint }) => valueFingerprint);
|
|
7999
|
+
if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
|
|
8000
|
+
const weakCredential = findWeakExplicitEnvCredential(scanContents);
|
|
8001
|
+
if (weakCredential) fingerprints.push(weakCredential.valueFingerprint);
|
|
8002
|
+
}
|
|
8003
|
+
return [...new Set(fingerprints)];
|
|
8004
|
+
}
|
|
8005
|
+
function findRealSecretAssignment(contents, excludedValueFingerprints = /* @__PURE__ */ new Set(), options = {}) {
|
|
8006
|
+
return findRealSecretAssignments(contents, excludedValueFingerprints, 1, options)[0] ?? null;
|
|
8007
|
+
}
|
|
8008
|
+
function findRealSecretAssignments(contents, excludedValueFingerprints = /* @__PURE__ */ new Set(), maximumMatches = Number.POSITIVE_INFINITY, options = {}) {
|
|
8009
|
+
const matches = [];
|
|
8010
|
+
SECRET_IDENTIFIER_KEYWORD_PATTERN.lastIndex = 0;
|
|
8011
|
+
let match = SECRET_IDENTIFIER_KEYWORD_PATTERN.exec(contents);
|
|
8012
|
+
let line = 1;
|
|
8013
|
+
let lineCursor = 0;
|
|
8014
|
+
while (match !== null) {
|
|
8015
|
+
const keyword = match[0];
|
|
8016
|
+
const keywordStart = match.index;
|
|
8017
|
+
while (lineCursor < keywordStart) {
|
|
8018
|
+
if (contents.charCodeAt(lineCursor) === 10) line += 1;
|
|
8019
|
+
lineCursor += 1;
|
|
8020
|
+
}
|
|
8021
|
+
let cursor = keywordStart + keyword.length;
|
|
8022
|
+
while (cursor < contents.length && isSecretIdentifierCharacter(contents.charCodeAt(cursor))) {
|
|
8023
|
+
cursor += 1;
|
|
8024
|
+
}
|
|
8025
|
+
const identifierEnd = cursor;
|
|
8026
|
+
let identifierStart = keywordStart;
|
|
8027
|
+
while (identifierStart > 0 && isSecretIdentifierCharacter(contents.charCodeAt(identifierStart - 1))) {
|
|
8028
|
+
identifierStart -= 1;
|
|
8029
|
+
}
|
|
8030
|
+
const identifier = contents.slice(identifierStart, identifierEnd);
|
|
8031
|
+
let nextSearchIndex = identifierEnd;
|
|
8032
|
+
while (cursor < contents.length && SINGLE_WHITESPACE_PATTERN.test(contents[cursor] ?? "")) {
|
|
8033
|
+
cursor += 1;
|
|
8034
|
+
}
|
|
8035
|
+
const delimiter = contents[cursor];
|
|
8036
|
+
if (delimiter === ":" || delimiter === "=") {
|
|
8037
|
+
cursor += 1;
|
|
8038
|
+
while (cursor < contents.length && SINGLE_WHITESPACE_PATTERN.test(contents[cursor] ?? "")) {
|
|
8039
|
+
cursor += 1;
|
|
8040
|
+
}
|
|
8041
|
+
if (contents[cursor] === "'" || contents[cursor] === '"') cursor += 1;
|
|
8042
|
+
const valueStart = cursor;
|
|
8043
|
+
while (cursor < contents.length) {
|
|
8044
|
+
const character = contents[cursor] ?? "";
|
|
8045
|
+
if (character === "'" || character === '"' || SINGLE_WHITESPACE_PATTERN.test(character)) {
|
|
8046
|
+
break;
|
|
8047
|
+
}
|
|
8048
|
+
cursor += 1;
|
|
8049
|
+
}
|
|
8050
|
+
if (cursor > valueStart) {
|
|
8051
|
+
const value = contents.slice(valueStart, cursor).replace(/[,;)]$/, "");
|
|
8052
|
+
nextSearchIndex = cursor;
|
|
8053
|
+
if (!isPlaceholderSecretValue(value) && (looksLikeSecretValue(value) || options.allowCredentialNamedDigest === true && isLikelyDigestOrHash(value) && isExplicitCredentialIdentifier(identifier))) {
|
|
8054
|
+
const valueFingerprint = createHash2("sha256").update(value).digest("hex");
|
|
8055
|
+
const characterClasses = [
|
|
8056
|
+
/[a-z]/.test(value) ? "lower" : "",
|
|
8057
|
+
/[A-Z]/.test(value) ? "upper" : "",
|
|
8058
|
+
/\d/.test(value) ? "digit" : "",
|
|
8059
|
+
/[^A-Za-z0-9]/.test(value) ? "symbol" : ""
|
|
8060
|
+
].filter(Boolean).join("+");
|
|
8061
|
+
if (!excludedValueFingerprints.has(valueFingerprint)) {
|
|
8062
|
+
matches.push({
|
|
8063
|
+
identifier,
|
|
8064
|
+
line,
|
|
8065
|
+
valueLength: value.length,
|
|
8066
|
+
characterClasses: characterClasses || "other",
|
|
8067
|
+
valueFingerprint
|
|
8068
|
+
});
|
|
8069
|
+
if (matches.length >= maximumMatches) return matches;
|
|
8070
|
+
}
|
|
8071
|
+
}
|
|
8072
|
+
}
|
|
8073
|
+
}
|
|
8074
|
+
SECRET_IDENTIFIER_KEYWORD_PATTERN.lastIndex = nextSearchIndex;
|
|
8075
|
+
match = SECRET_IDENTIFIER_KEYWORD_PATTERN.exec(contents);
|
|
8076
|
+
}
|
|
8077
|
+
return matches;
|
|
8078
|
+
}
|
|
8079
|
+
function isExplicitCredentialIdentifier(identifier) {
|
|
8080
|
+
return /^(?:github|gitlab|api|access)[_-]?(?:token|key)$/i.test(identifier);
|
|
8081
|
+
}
|
|
8082
|
+
function findWeakExplicitEnvCredential(contents) {
|
|
8083
|
+
const lines = contents.split(/\r?\n/);
|
|
8084
|
+
for (const [index, line] of lines.entries()) {
|
|
8085
|
+
if (/^\s*#/.test(line)) continue;
|
|
8086
|
+
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*(?:PASSWORD|PASSWD|PWD|SECRET(?:_KEY)?|API_KEY|TOKEN))\s*=\s*['"]?([^\s#'"]+)['"]?/i.exec(
|
|
8087
|
+
line
|
|
8088
|
+
);
|
|
8089
|
+
const identifier = match?.[1];
|
|
8090
|
+
const value = match?.[2];
|
|
8091
|
+
if (!identifier || !value || isPlaceholderSecretValue(value)) continue;
|
|
8092
|
+
const minimumLength = /(?:API_KEY|TOKEN)$/i.test(identifier) ? 12 : 6;
|
|
8093
|
+
if (value.length < minimumLength) continue;
|
|
8094
|
+
const characterClasses = [
|
|
8095
|
+
/[a-z]/.test(value) ? "lower" : "",
|
|
8096
|
+
/[A-Z]/.test(value) ? "upper" : "",
|
|
8097
|
+
/\d/.test(value) ? "digit" : "",
|
|
8098
|
+
/[^A-Za-z0-9]/.test(value) ? "symbol" : ""
|
|
8099
|
+
].filter(Boolean).join("+");
|
|
8100
|
+
return {
|
|
8101
|
+
identifier,
|
|
8102
|
+
line: index + 1,
|
|
8103
|
+
valueLength: value.length,
|
|
8104
|
+
characterClasses: characterClasses || "other",
|
|
8105
|
+
valueFingerprint: createHash2("sha256").update(value).digest("hex")
|
|
8106
|
+
};
|
|
8107
|
+
}
|
|
8108
|
+
return null;
|
|
8109
|
+
}
|
|
8110
|
+
function findDefaultAdministrativeCredential(contents) {
|
|
8111
|
+
const lines = contents.split(/\r?\n/);
|
|
8112
|
+
for (const [index, line] of lines.entries()) {
|
|
8113
|
+
const match = /\b([A-Za-z0-9_]*(?:ADMIN|ROOT)[A-Za-z0-9_]*(?:PASSWORD|PASSWD|PWD))\s*[:=]\s*["']?(admin|root|password)["']?(?:\s|$)/i.exec(
|
|
8114
|
+
line
|
|
8115
|
+
);
|
|
8116
|
+
const identifier = match?.[1];
|
|
8117
|
+
const value = match?.[2];
|
|
8118
|
+
if (!identifier || !value) continue;
|
|
8119
|
+
return {
|
|
8120
|
+
identifier,
|
|
8121
|
+
line: index + 1,
|
|
8122
|
+
valueLength: value.length,
|
|
8123
|
+
characterClasses: "lower",
|
|
8124
|
+
valueFingerprint: createHash2("sha256").update(value).digest("hex"),
|
|
8125
|
+
kind: "default_admin"
|
|
8126
|
+
};
|
|
8127
|
+
}
|
|
8128
|
+
return null;
|
|
8129
|
+
}
|
|
8130
|
+
function secretEvidenceLabel(prefix, match) {
|
|
8131
|
+
return `${prefix} (value redacted; length ${match.valueLength}; classes ${match.characterClasses})`;
|
|
8132
|
+
}
|
|
8133
|
+
function isSecretIdentifierCharacter(characterCode) {
|
|
8134
|
+
return characterCode >= 48 && characterCode <= 57 || characterCode >= 65 && characterCode <= 90 || characterCode === 95 || characterCode >= 97 && characterCode <= 122;
|
|
8135
|
+
}
|
|
8136
|
+
function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFingerprintsByPath, authoredProductionOnly = false, useV36Detectors = false, useV39Detectors = false, useV47Detectors = false) {
|
|
8137
|
+
const historyEntries = readCredentialHistoryEntries(repoPath, useV36Detectors);
|
|
8138
|
+
const head = excludeHead ? readGitHead(repoPath) : null;
|
|
7040
8139
|
let scannedFiles = 0;
|
|
7041
8140
|
let envPathEvidence = null;
|
|
7042
8141
|
for (const entry of historyEntries) {
|
|
8142
|
+
if (head && entry.commit === head) continue;
|
|
8143
|
+
if (authoredProductionOnly && !(useV47Detectors ? isV47AuthoredProductionPath(entry.path) : useV39Detectors ? isV39AuthoredProductionPath(entry.path) : isAuthoredProductionPath(entry.path))) {
|
|
8144
|
+
continue;
|
|
8145
|
+
}
|
|
7043
8146
|
if (scannedFiles >= HISTORY_SECRET_SCAN_CREDENTIAL_BLOB_LIMIT) {
|
|
7044
8147
|
return { evidence: null, envPathEvidence, truncated: true };
|
|
7045
8148
|
}
|
|
7046
8149
|
scannedFiles += 1;
|
|
7047
8150
|
const contents = readGitBlob(repoPath, entry.commit, entry.path);
|
|
7048
8151
|
if (!contents) continue;
|
|
7049
|
-
|
|
8152
|
+
const scanContents = prepareCredentialScanContents(
|
|
8153
|
+
contents,
|
|
8154
|
+
entry.path,
|
|
8155
|
+
useV36Detectors,
|
|
8156
|
+
useV39Detectors,
|
|
8157
|
+
useV47Detectors
|
|
8158
|
+
);
|
|
8159
|
+
const currentFingerprints = currentSecretFingerprintsByPath.get(entry.path) ?? /* @__PURE__ */ new Set();
|
|
8160
|
+
let secretMatch = findRealSecretAssignment(scanContents, currentFingerprints, {
|
|
8161
|
+
allowCredentialNamedDigest: useV36Detectors
|
|
8162
|
+
});
|
|
8163
|
+
if (!secretMatch && useV39Detectors && isEnvHistoryPath(entry.path, useV36Detectors, useV47Detectors)) {
|
|
8164
|
+
const weakCredential = findWeakExplicitEnvCredential(scanContents);
|
|
8165
|
+
if (weakCredential && !currentFingerprints.has(weakCredential.valueFingerprint)) {
|
|
8166
|
+
secretMatch = weakCredential;
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
8169
|
+
if (secretMatch) {
|
|
7050
8170
|
return {
|
|
7051
8171
|
evidence: {
|
|
7052
8172
|
kind: "secret_scan",
|
|
7053
|
-
label: "Committed secret in recent git history",
|
|
8173
|
+
label: secretEvidenceLabel("Committed secret in recent git history", secretMatch),
|
|
7054
8174
|
path: entry.path,
|
|
8175
|
+
line: secretMatch.line,
|
|
7055
8176
|
contentHash: entry.commit
|
|
7056
8177
|
},
|
|
7057
8178
|
envPathEvidence,
|
|
7058
8179
|
truncated: false
|
|
7059
8180
|
};
|
|
7060
8181
|
}
|
|
7061
|
-
if (!envPathEvidence && isEnvHistoryPath(entry.path)) {
|
|
8182
|
+
if (!envPathEvidence && isEnvHistoryPath(entry.path, useV36Detectors, useV47Detectors)) {
|
|
7062
8183
|
envPathEvidence = {
|
|
7063
8184
|
kind: "secret_scan",
|
|
7064
8185
|
label: ".env file (not a template) tracked in git history; no confirmed secret value found",
|
|
@@ -7069,7 +8190,7 @@ function collectHistorySecretEvidence(repoPath) {
|
|
|
7069
8190
|
}
|
|
7070
8191
|
return { evidence: null, envPathEvidence, truncated: false };
|
|
7071
8192
|
}
|
|
7072
|
-
function readCredentialHistoryEntries(repoPath) {
|
|
8193
|
+
function readCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
7073
8194
|
const entries = [];
|
|
7074
8195
|
const seen = /* @__PURE__ */ new Set();
|
|
7075
8196
|
const appendEntry = (entry) => {
|
|
@@ -7110,18 +8231,18 @@ function readCredentialHistoryEntries(repoPath) {
|
|
|
7110
8231
|
}
|
|
7111
8232
|
const path = line.trim();
|
|
7112
8233
|
if (!currentCommit || !path) continue;
|
|
7113
|
-
if (!isCredentialHistoryPath(path)) continue;
|
|
8234
|
+
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
7114
8235
|
appendEntry({ commit: currentCommit, path });
|
|
7115
8236
|
}
|
|
7116
8237
|
} catch {
|
|
7117
8238
|
}
|
|
7118
|
-
for (const entry of readDeletedCredentialHistoryEntries(repoPath)) {
|
|
8239
|
+
for (const entry of readDeletedCredentialHistoryEntries(repoPath, useV36Detectors)) {
|
|
7119
8240
|
appendEntry(entry);
|
|
7120
8241
|
}
|
|
7121
8242
|
return entries;
|
|
7122
8243
|
}
|
|
7123
|
-
function readDeletedCredentialHistoryEntries(repoPath) {
|
|
7124
|
-
const deletedPaths = readDeletedCredentialHistoryPaths(repoPath);
|
|
8244
|
+
function readDeletedCredentialHistoryEntries(repoPath, useV36Detectors = false) {
|
|
8245
|
+
const deletedPaths = readDeletedCredentialHistoryPaths(repoPath, useV36Detectors);
|
|
7125
8246
|
const entries = [];
|
|
7126
8247
|
const seen = /* @__PURE__ */ new Set();
|
|
7127
8248
|
for (const path of deletedPaths) {
|
|
@@ -7134,7 +8255,7 @@ function readDeletedCredentialHistoryEntries(repoPath) {
|
|
|
7134
8255
|
}
|
|
7135
8256
|
return entries;
|
|
7136
8257
|
}
|
|
7137
|
-
function readDeletedCredentialHistoryPaths(repoPath) {
|
|
8258
|
+
function readDeletedCredentialHistoryPaths(repoPath, useV36Detectors = false) {
|
|
7138
8259
|
try {
|
|
7139
8260
|
const output = execFileSync(
|
|
7140
8261
|
"git",
|
|
@@ -7151,7 +8272,7 @@ function readDeletedCredentialHistoryPaths(repoPath) {
|
|
|
7151
8272
|
if (!line.startsWith("D ")) continue;
|
|
7152
8273
|
const path = line.slice(2).trim();
|
|
7153
8274
|
if (!path || seen.has(path)) continue;
|
|
7154
|
-
if (!isCredentialHistoryPath(path)) continue;
|
|
8275
|
+
if (!isCredentialHistoryPath(path, useV36Detectors)) continue;
|
|
7155
8276
|
seen.add(path);
|
|
7156
8277
|
paths.push(path);
|
|
7157
8278
|
}
|
|
@@ -7176,7 +8297,7 @@ function readHistoryCommitsForPath(repoPath, path) {
|
|
|
7176
8297
|
return [];
|
|
7177
8298
|
}
|
|
7178
8299
|
}
|
|
7179
|
-
function isCredentialHistoryPath(path) {
|
|
8300
|
+
function isCredentialHistoryPath(path, _useV36Detectors = false) {
|
|
7180
8301
|
if (isHardExcludedPath(path)) return false;
|
|
7181
8302
|
const lowerPath = path.toLowerCase();
|
|
7182
8303
|
const basename3 = lowerPath.split("/").at(-1) ?? lowerPath;
|
|
@@ -7185,10 +8306,11 @@ function isCredentialHistoryPath(path) {
|
|
|
7185
8306
|
}
|
|
7186
8307
|
return lowerPath.includes("secret") || lowerPath.includes("credential") || basename3.endsWith(".pem") || basename3.endsWith(".key") || basename3 === "id_rsa" || basename3.endsWith(".p12") || basename3.endsWith(".pfx");
|
|
7187
8308
|
}
|
|
7188
|
-
function isEnvHistoryPath(path) {
|
|
7189
|
-
if (isEnvTemplatePath(path)) return false;
|
|
8309
|
+
function isEnvHistoryPath(path, useV36Detectors = false, useV47Detectors = false) {
|
|
8310
|
+
if (isEnvTemplatePath(path, useV47Detectors)) return false;
|
|
8311
|
+
if (useV47Detectors && !isV47AuthoredProductionPath(path)) return false;
|
|
7190
8312
|
const basename3 = path.toLowerCase().split("/").at(-1) ?? path.toLowerCase();
|
|
7191
|
-
return /\.env(?:\.|$)
|
|
8313
|
+
return (useV36Detectors ? /^\.env(?:\.|$)/ : /\.env(?:\.|$)/).test(basename3);
|
|
7192
8314
|
}
|
|
7193
8315
|
function readGitBlob(repoPath, commit, path) {
|
|
7194
8316
|
try {
|
|
@@ -7226,7 +8348,7 @@ function isFillerRunSecretValue(value) {
|
|
|
7226
8348
|
function isSyntheticFixtureSecretValue(value) {
|
|
7227
8349
|
return value.includes("abcdefghijklmnopqrstuvwxyz") || value.includes("0123456789abcdefghijklmnopqrstuvwxyz") || value.includes("abcdefghijklmnopqrstuvwxyz0123456789") || value.includes("abcdefghijklmnopqrst");
|
|
7228
8350
|
}
|
|
7229
|
-
function hasSuspiciousDependencies(repoPath, manifest) {
|
|
8351
|
+
function hasSuspiciousDependencies(repoPath, manifest, useV47Detectors = false) {
|
|
7230
8352
|
if (!manifest.endsWith("package.json")) return false;
|
|
7231
8353
|
const fullPath = join(repoPath, manifest);
|
|
7232
8354
|
if (!isRegularFile(fullPath)) return false;
|
|
@@ -7235,9 +8357,15 @@ function hasSuspiciousDependencies(repoPath, manifest) {
|
|
|
7235
8357
|
const dependencies = isRecord(parsed.dependencies) ? parsed.dependencies : {};
|
|
7236
8358
|
const devDependencies = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
|
|
7237
8359
|
const names = [...Object.keys(dependencies), ...Object.keys(devDependencies)];
|
|
7238
|
-
|
|
8360
|
+
if (names.some(
|
|
7239
8361
|
(name) => /^(todo|fake|placeholder|example)-|does-not-exist| hallucinated/i.test(name)
|
|
7240
|
-
)
|
|
8362
|
+
)) {
|
|
8363
|
+
return true;
|
|
8364
|
+
}
|
|
8365
|
+
if (!useV47Detectors || typeof parsed.name !== "string") return false;
|
|
8366
|
+
const placeholderIdentity = /^(?:example|sample|placeholder|todo|fake)(?:-|$)/i.test(parsed.name);
|
|
8367
|
+
const placeholderMetadata = typeof parsed.author === "string" && /^(?:your name|example|placeholder)$/i.test(parsed.author) || typeof parsed.description === "string" && /^(?:package description|example|placeholder|todo)$/i.test(parsed.description);
|
|
8368
|
+
return placeholderIdentity && placeholderMetadata;
|
|
7241
8369
|
}
|
|
7242
8370
|
function readRecentCommits(repoPath) {
|
|
7243
8371
|
try {
|
|
@@ -7819,7 +8947,12 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
|
7819
8947
|
);
|
|
7820
8948
|
consumedSignals.push(...summaries);
|
|
7821
8949
|
const finalScore = adjustment === 0 ? scored.score : adjustedScore;
|
|
7822
|
-
const finalStatus =
|
|
8950
|
+
const finalStatus = statusAfterInputAdjustment(
|
|
8951
|
+
scored.status,
|
|
8952
|
+
scored.score,
|
|
8953
|
+
adjustment,
|
|
8954
|
+
parsedInput.rubricVersion
|
|
8955
|
+
);
|
|
7823
8956
|
return {
|
|
7824
8957
|
id,
|
|
7825
8958
|
title: rubricCriterion.title,
|
|
@@ -7916,6 +9049,31 @@ function statusForScore(score) {
|
|
|
7916
9049
|
if (score > 0) return "critical";
|
|
7917
9050
|
return "critical";
|
|
7918
9051
|
}
|
|
9052
|
+
function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
|
|
9053
|
+
if (adjustment === 0) return nativeStatus;
|
|
9054
|
+
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V92 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && 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) {
|
|
9055
|
+
return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
|
|
9056
|
+
}
|
|
9057
|
+
const statusFloor = {
|
|
9058
|
+
verified: 3.5,
|
|
9059
|
+
info: 2.5,
|
|
9060
|
+
warning: 1.5,
|
|
9061
|
+
critical: 0
|
|
9062
|
+
};
|
|
9063
|
+
const severity = {
|
|
9064
|
+
verified: 0,
|
|
9065
|
+
info: 1,
|
|
9066
|
+
warning: 2,
|
|
9067
|
+
critical: 3
|
|
9068
|
+
};
|
|
9069
|
+
const floor = statusFloor[nativeStatus];
|
|
9070
|
+
const nativeSeverity = severity[nativeStatus];
|
|
9071
|
+
if (floor === void 0 || nativeSeverity === void 0) return nativeStatus;
|
|
9072
|
+
const adjustedSemanticStatus = statusForScore(
|
|
9073
|
+
roundScore(Math.max(0, Math.max(nativeScore, floor) - adjustment))
|
|
9074
|
+
);
|
|
9075
|
+
return (severity[adjustedSemanticStatus] ?? nativeSeverity) > nativeSeverity ? adjustedSemanticStatus : nativeStatus;
|
|
9076
|
+
}
|
|
7919
9077
|
function mergeSignalsByCriterion(signals) {
|
|
7920
9078
|
const merged = /* @__PURE__ */ new Map();
|
|
7921
9079
|
for (const signal of signals) {
|
|
@@ -7937,7 +9095,7 @@ function mergeSignalsByCriterion(signals) {
|
|
|
7937
9095
|
return merged;
|
|
7938
9096
|
}
|
|
7939
9097
|
function usesMetricScoring(rubricVersion) {
|
|
7940
|
-
return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
|
|
9098
|
+
return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || 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_TRADING_RUBRIC_VERSION_V0;
|
|
7941
9099
|
}
|
|
7942
9100
|
function unmeasuredStatus(rubricVersion) {
|
|
7943
9101
|
return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
|
|
@@ -7979,12 +9137,25 @@ function scoreCriterion(signal, rubricVersion) {
|
|
|
7979
9137
|
if (usesMetricScoring(rubricVersion)) {
|
|
7980
9138
|
const measuredMetrics = metrics.length > 0 ? metrics : fallbackMetricsForEvidence(evidence, findings);
|
|
7981
9139
|
const score = capScoreForFindings(scoreMetrics(measuredMetrics), findings);
|
|
7982
|
-
const status = statusForScoreAndFindings(
|
|
9140
|
+
const status = statusForScoreAndFindings(
|
|
9141
|
+
signal.criterionId,
|
|
9142
|
+
score,
|
|
9143
|
+
findings,
|
|
9144
|
+
measuredMetrics,
|
|
9145
|
+
rubricVersion
|
|
9146
|
+
);
|
|
7983
9147
|
return {
|
|
7984
9148
|
score,
|
|
7985
9149
|
status,
|
|
7986
9150
|
evidence,
|
|
7987
|
-
findings: ensureFindingsExplainStatus(
|
|
9151
|
+
findings: ensureFindingsExplainStatus(
|
|
9152
|
+
signal.criterionId,
|
|
9153
|
+
score,
|
|
9154
|
+
status,
|
|
9155
|
+
findings,
|
|
9156
|
+
evidence,
|
|
9157
|
+
rubricVersion
|
|
9158
|
+
),
|
|
7988
9159
|
metrics: measuredMetrics
|
|
7989
9160
|
};
|
|
7990
9161
|
}
|
|
@@ -8053,23 +9224,251 @@ function capScoreForFindings(score, findings) {
|
|
|
8053
9224
|
if (findings.some((finding) => finding.severity === "info")) return Math.min(score, 3.4);
|
|
8054
9225
|
return score;
|
|
8055
9226
|
}
|
|
8056
|
-
function statusForScoreAndFindings(score, findings) {
|
|
9227
|
+
function statusForScoreAndFindings(criterionId, score, findings, metrics, rubricVersion) {
|
|
8057
9228
|
if (findings.some((finding) => finding.severity === "critical")) return "critical";
|
|
9229
|
+
if (rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || 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) {
|
|
9230
|
+
const calibrated = 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 ? calibratedCriterionStateV11(criterionId, metrics) : rubricVersion === WITAN_RUBRIC_VERSION_V92 || rubricVersion === WITAN_RUBRIC_VERSION_V10 ? calibratedCriterionStateV9(criterionId, metrics) : calibratedCriterionStateV8(criterionId, metrics);
|
|
9231
|
+
if (calibrated && calibrated !== "verified") return calibrated;
|
|
9232
|
+
if (calibrated === "verified" && !findings.some((finding) => finding.severity === "warning")) {
|
|
9233
|
+
return "verified";
|
|
9234
|
+
}
|
|
9235
|
+
}
|
|
8058
9236
|
if (findings.some((finding) => finding.severity === "warning")) return "warning";
|
|
8059
9237
|
if (score >= 3.5 && findings.length === 0) return "verified";
|
|
8060
9238
|
if (score >= 2.5) return "info";
|
|
8061
9239
|
if (score >= 1.5) return "warning";
|
|
8062
9240
|
return "critical";
|
|
8063
9241
|
}
|
|
8064
|
-
function
|
|
8065
|
-
|
|
9242
|
+
function calibratedCriterionStateV8(criterionId, metrics) {
|
|
9243
|
+
const values = new Map(metrics.map(({ name, value: value2 }) => [name, value2]));
|
|
9244
|
+
if (values.size !== metrics.length) return void 0;
|
|
9245
|
+
const value = (name) => values.get(name);
|
|
9246
|
+
const positive = (name) => (value(name) ?? 0) > 0;
|
|
9247
|
+
if (criterionId === "A1") {
|
|
9248
|
+
if (!values.has("non_hollow_test_share") || !values.has("verification_script_ratio")) {
|
|
9249
|
+
return void 0;
|
|
9250
|
+
}
|
|
9251
|
+
if (!positive("non_hollow_test_share")) return "critical";
|
|
9252
|
+
return positive("verification_script_ratio") ? "verified" : "warning";
|
|
9253
|
+
}
|
|
9254
|
+
if (criterionId === "A2") {
|
|
9255
|
+
if (!values.has("secret_cleanliness") || !values.has("env_handling_depth")) return void 0;
|
|
9256
|
+
if (!positive("secret_cleanliness")) return "critical";
|
|
9257
|
+
return (value("env_handling_depth") ?? 0) >= 2 ? "verified" : "warning";
|
|
9258
|
+
}
|
|
9259
|
+
if (criterionId === "A3") {
|
|
9260
|
+
if (!values.has("observability_depth") || !values.has("rollback_safety_depth")) {
|
|
9261
|
+
return void 0;
|
|
9262
|
+
}
|
|
9263
|
+
const coveredSurfaces = Number(positive("observability_depth")) + Number(positive("rollback_safety_depth"));
|
|
9264
|
+
return coveredSurfaces === 2 ? "verified" : coveredSurfaces === 1 ? "warning" : "critical";
|
|
9265
|
+
}
|
|
9266
|
+
if (criterionId === "A4") {
|
|
9267
|
+
if (!values.has("dependency_automation_ratio") || !values.has("declared_version_range_ratio") && !values.has("pinned_dependency_ratio")) {
|
|
9268
|
+
return void 0;
|
|
9269
|
+
}
|
|
9270
|
+
const dependencyCountSanity = value("dependency_count_sanity");
|
|
9271
|
+
if (dependencyCountSanity !== void 0) {
|
|
9272
|
+
if (dependencyCountSanity <= 0) return "critical";
|
|
9273
|
+
if (dependencyCountSanity < 0.9) return "warning";
|
|
9274
|
+
}
|
|
9275
|
+
const hasVersionControl = positive("declared_version_range_ratio") || positive("pinned_dependency_ratio");
|
|
9276
|
+
const hasLockfile = positive("lockfile_coverage");
|
|
9277
|
+
const hasAutomation = positive("dependency_automation_ratio");
|
|
9278
|
+
if (!hasVersionControl && !hasLockfile && !hasAutomation) return "critical";
|
|
9279
|
+
return hasAutomation && (hasVersionControl || hasLockfile) ? "verified" : "warning";
|
|
9280
|
+
}
|
|
9281
|
+
if (criterionId === "B2") {
|
|
9282
|
+
if (!values.has("pr_trace_primitives") || !values.has("pr_merge_ratio")) return void 0;
|
|
9283
|
+
return positive("pr_trace_primitives") && positive("pr_merge_ratio") ? "verified" : "warning";
|
|
9284
|
+
}
|
|
9285
|
+
if (criterionId === "B3") {
|
|
9286
|
+
if (!values.has("ci_script_depth") || !values.has("default_branch_ci_depth")) return void 0;
|
|
9287
|
+
const coveredSurfaces = Number(positive("ci_script_depth")) + Number(positive("default_branch_ci_depth"));
|
|
9288
|
+
return coveredSurfaces === 2 ? "verified" : coveredSurfaces === 1 ? "warning" : "critical";
|
|
9289
|
+
}
|
|
9290
|
+
if (criterionId === "B4") {
|
|
9291
|
+
if (!values.has("audit_artifact_depth") || !values.has("audit_freshness_depth")) {
|
|
9292
|
+
return void 0;
|
|
9293
|
+
}
|
|
9294
|
+
if (!positive("audit_artifact_depth")) return "critical";
|
|
9295
|
+
return positive("audit_freshness_depth") ? "verified" : "warning";
|
|
9296
|
+
}
|
|
9297
|
+
if (criterionId === "B6") {
|
|
9298
|
+
if (!values.has("human_gate_documented") || !values.has("fail_closed_privilege_check")) {
|
|
9299
|
+
return void 0;
|
|
9300
|
+
}
|
|
9301
|
+
return positive("human_gate_documented") || positive("fail_closed_privilege_check") ? "verified" : "critical";
|
|
9302
|
+
}
|
|
9303
|
+
return void 0;
|
|
9304
|
+
}
|
|
9305
|
+
function calibratedCriterionStateV9(criterionId, metrics) {
|
|
9306
|
+
const values = new Map(metrics.map(({ name, value: value2 }) => [name, value2]));
|
|
9307
|
+
if (values.size !== metrics.length) return void 0;
|
|
9308
|
+
const value = (name) => values.get(name);
|
|
9309
|
+
const positive = (name) => (value(name) ?? 0) > 0;
|
|
9310
|
+
if (criterionId === "A1") {
|
|
9311
|
+
if (!values.has("non_hollow_test_share") || !values.has("verification_script_ratio")) {
|
|
9312
|
+
return void 0;
|
|
9313
|
+
}
|
|
9314
|
+
const nonHollowTests = value("non_hollow_test_share") ?? 0;
|
|
9315
|
+
if (nonHollowTests === 0) return "critical";
|
|
9316
|
+
return positive("verification_script_ratio") && nonHollowTests >= 3 ? "verified" : "warning";
|
|
9317
|
+
}
|
|
9318
|
+
if (criterionId === "A2") {
|
|
9319
|
+
if (!values.has("secret_cleanliness") || !values.has("env_handling_depth")) return void 0;
|
|
9320
|
+
if (!positive("secret_cleanliness")) return "critical";
|
|
9321
|
+
return (value("env_handling_depth") ?? 0) >= 2 ? "verified" : "warning";
|
|
9322
|
+
}
|
|
9323
|
+
if (criterionId === "A3") {
|
|
9324
|
+
if (!values.has("prod_readiness_primitives") || !values.has("prod_workflow_depth") || !values.has("observability_depth") || !values.has("rollback_safety_depth")) {
|
|
9325
|
+
return void 0;
|
|
9326
|
+
}
|
|
9327
|
+
const observability = value("observability_depth") ?? 0;
|
|
9328
|
+
const rollback = value("rollback_safety_depth") ?? 0;
|
|
9329
|
+
if (observability > 0 && rollback > 0) {
|
|
9330
|
+
return (value("prod_readiness_primitives") ?? 0) >= 4 && observability >= 2 && rollback >= 2 ? "verified" : "warning";
|
|
9331
|
+
}
|
|
9332
|
+
if (observability > 0 || rollback > 0) return "warning";
|
|
9333
|
+
return (value("prod_readiness_primitives") ?? 0) >= 2 && (value("prod_workflow_depth") ?? 0) >= 8 ? "warning" : "critical";
|
|
9334
|
+
}
|
|
9335
|
+
if (criterionId === "A4") {
|
|
9336
|
+
if (!values.has("dependency_automation_ratio") || !values.has("declared_version_range_ratio") && !values.has("pinned_dependency_ratio")) {
|
|
9337
|
+
return void 0;
|
|
9338
|
+
}
|
|
9339
|
+
const dependencyCountSanity = value("dependency_count_sanity");
|
|
9340
|
+
if (dependencyCountSanity !== void 0) {
|
|
9341
|
+
if (dependencyCountSanity <= 0.25) return "critical";
|
|
9342
|
+
if (dependencyCountSanity < 0.5) return "warning";
|
|
9343
|
+
}
|
|
9344
|
+
const hasVersionControl = positive("declared_version_range_ratio") || positive("pinned_dependency_ratio");
|
|
9345
|
+
const hasLockfile = positive("lockfile_coverage");
|
|
9346
|
+
const hasAutomation = positive("dependency_automation_ratio");
|
|
9347
|
+
if (!hasVersionControl && !hasLockfile && !hasAutomation) return "warning";
|
|
9348
|
+
return hasAutomation && (hasVersionControl || hasLockfile) ? "verified" : "warning";
|
|
9349
|
+
}
|
|
9350
|
+
if (criterionId === "B2") {
|
|
9351
|
+
if (!values.has("pr_trace_primitives") || !values.has("pr_merge_ratio")) return void 0;
|
|
9352
|
+
const primitives = value("pr_trace_primitives") ?? 0;
|
|
9353
|
+
const mergedOutcomes = value("pr_merge_ratio") ?? 0;
|
|
9354
|
+
if (mergedOutcomes === 0) return primitives <= 2 ? "critical" : "warning";
|
|
9355
|
+
return mergedOutcomes >= 4 ? "verified" : "warning";
|
|
9356
|
+
}
|
|
9357
|
+
if (criterionId === "B3") {
|
|
9358
|
+
if (!values.has("ci_script_depth") || !values.has("default_branch_ci_depth")) return void 0;
|
|
9359
|
+
const scriptDepth = value("ci_script_depth") ?? 0;
|
|
9360
|
+
const branchDepth = value("default_branch_ci_depth") ?? 0;
|
|
9361
|
+
if (scriptDepth === 0 && branchDepth === 0) return "critical";
|
|
9362
|
+
return scriptDepth >= 2 && branchDepth > 0 ? "verified" : "warning";
|
|
9363
|
+
}
|
|
9364
|
+
if (criterionId === "B4") {
|
|
9365
|
+
if (!values.has("audit_artifact_depth") || !values.has("audit_freshness_depth")) {
|
|
9366
|
+
return void 0;
|
|
9367
|
+
}
|
|
9368
|
+
const artifacts = value("audit_artifact_depth") ?? 0;
|
|
9369
|
+
const freshness = value("audit_freshness_depth") ?? 0;
|
|
9370
|
+
if (artifacts === 0) return "critical";
|
|
9371
|
+
return artifacts >= 2 || freshness >= 2 ? "verified" : "warning";
|
|
9372
|
+
}
|
|
9373
|
+
if (criterionId === "B6") {
|
|
9374
|
+
const humanGate = value("human_gate_documented") ?? 0;
|
|
9375
|
+
const failClosed = value("fail_closed_privilege_check") ?? 0;
|
|
9376
|
+
if (humanGate > 0 || failClosed > 0) return "verified";
|
|
9377
|
+
if (positive("privilege_escalation_cleanliness") || positive("protected_path_review_gate")) {
|
|
9378
|
+
return "warning";
|
|
9379
|
+
}
|
|
9380
|
+
if (values.has("human_gate_documented") || values.has("fail_closed_privilege_check") || values.has("privilege_escalation_cleanliness") || values.has("protected_path_review_gate")) {
|
|
9381
|
+
return "critical";
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9384
|
+
return void 0;
|
|
9385
|
+
}
|
|
9386
|
+
function calibratedCriterionStateV11(criterionId, metrics) {
|
|
9387
|
+
const values = new Map(metrics.map(({ name, value: value2 }) => [name, value2]));
|
|
9388
|
+
if (values.size !== metrics.length) return void 0;
|
|
9389
|
+
const value = (name) => values.get(name);
|
|
9390
|
+
const positive = (name) => (value(name) ?? 0) > 0;
|
|
9391
|
+
if (criterionId === "A1") {
|
|
9392
|
+
if (!values.has("non_hollow_test_share") || !values.has("verification_script_ratio")) {
|
|
9393
|
+
return void 0;
|
|
9394
|
+
}
|
|
9395
|
+
if (!positive("non_hollow_test_share")) return "critical";
|
|
9396
|
+
return positive("verification_script_ratio") ? "verified" : "warning";
|
|
9397
|
+
}
|
|
9398
|
+
if (criterionId === "A2") {
|
|
9399
|
+
if (!values.has("secret_cleanliness") || !values.has("env_handling_depth")) return void 0;
|
|
9400
|
+
if (!positive("secret_cleanliness")) return "critical";
|
|
9401
|
+
return positive("env_handling_depth") ? "verified" : "warning";
|
|
9402
|
+
}
|
|
9403
|
+
if (criterionId === "A3") {
|
|
9404
|
+
if (!values.has("prod_readiness_primitives") || !values.has("prod_workflow_depth") || !values.has("observability_depth") || !values.has("rollback_safety_depth")) {
|
|
9405
|
+
return void 0;
|
|
9406
|
+
}
|
|
9407
|
+
const primitives = value("prod_readiness_primitives") ?? 0;
|
|
9408
|
+
const workflow = value("prod_workflow_depth") ?? 0;
|
|
9409
|
+
const observability = value("observability_depth") ?? 0;
|
|
9410
|
+
const rollback = value("rollback_safety_depth") ?? 0;
|
|
9411
|
+
if (observability > 0 && rollback > 0) return primitives >= 3 ? "verified" : "warning";
|
|
9412
|
+
return primitives > 0 && workflow > 0 ? "warning" : "critical";
|
|
9413
|
+
}
|
|
9414
|
+
if (criterionId === "A4") {
|
|
9415
|
+
if (!values.has("dependency_automation_ratio") || !values.has("declared_version_range_ratio") && !values.has("pinned_dependency_ratio")) {
|
|
9416
|
+
return void 0;
|
|
9417
|
+
}
|
|
9418
|
+
const hasVersionControl = positive("declared_version_range_ratio") || positive("pinned_dependency_ratio");
|
|
9419
|
+
const hasLockfile = positive("lockfile_coverage");
|
|
9420
|
+
const hasAutomation = positive("dependency_automation_ratio");
|
|
9421
|
+
if (!hasVersionControl && !hasLockfile && !hasAutomation) return "critical";
|
|
9422
|
+
return hasAutomation && (hasVersionControl || hasLockfile) ? "verified" : "warning";
|
|
9423
|
+
}
|
|
9424
|
+
if (criterionId === "B2") {
|
|
9425
|
+
if (!values.has("pr_trace_primitives") || !values.has("pr_merge_ratio")) return void 0;
|
|
9426
|
+
if (!positive("pr_trace_primitives")) return "critical";
|
|
9427
|
+
return positive("pr_merge_ratio") ? "verified" : "warning";
|
|
9428
|
+
}
|
|
9429
|
+
if (criterionId === "B3") {
|
|
9430
|
+
if (!values.has("ci_script_depth") || !values.has("default_branch_ci_depth")) return void 0;
|
|
9431
|
+
const covered = Number(positive("ci_script_depth")) + Number(positive("default_branch_ci_depth"));
|
|
9432
|
+
return covered === 2 ? "verified" : covered === 1 ? "warning" : "critical";
|
|
9433
|
+
}
|
|
9434
|
+
if (criterionId === "B4") {
|
|
9435
|
+
if (!values.has("audit_artifact_depth") || !values.has("audit_freshness_depth")) {
|
|
9436
|
+
return void 0;
|
|
9437
|
+
}
|
|
9438
|
+
if (!positive("audit_artifact_depth")) return "critical";
|
|
9439
|
+
return positive("audit_freshness_depth") ? "verified" : "warning";
|
|
9440
|
+
}
|
|
9441
|
+
if (criterionId === "B6") {
|
|
9442
|
+
const humanGate = positive("human_gate_documented");
|
|
9443
|
+
const failClosed = positive("fail_closed_privilege_check");
|
|
9444
|
+
const cleanAndReviewed = positive("privilege_escalation_cleanliness") && positive("protected_path_review_gate");
|
|
9445
|
+
if (humanGate || failClosed || cleanAndReviewed) return "verified";
|
|
9446
|
+
if (positive("privilege_escalation_cleanliness") || positive("protected_path_review_gate")) {
|
|
9447
|
+
return "warning";
|
|
9448
|
+
}
|
|
9449
|
+
if (values.has("human_gate_documented") || values.has("fail_closed_privilege_check") || values.has("privilege_escalation_cleanliness") || values.has("protected_path_review_gate")) {
|
|
9450
|
+
return "critical";
|
|
9451
|
+
}
|
|
9452
|
+
}
|
|
9453
|
+
return void 0;
|
|
9454
|
+
}
|
|
9455
|
+
function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
|
|
9456
|
+
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V92 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && 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 && findings.length > 0) {
|
|
9457
|
+
return [...findings];
|
|
9458
|
+
}
|
|
8066
9459
|
if (status !== "critical" && status !== "warning") return [...findings];
|
|
9460
|
+
if (findings.some(
|
|
9461
|
+
({ severity }) => severity === "critical" || status === "warning" && severity === "warning"
|
|
9462
|
+
)) {
|
|
9463
|
+
return [...findings];
|
|
9464
|
+
}
|
|
8067
9465
|
const anchor = evidence[0] ?? {
|
|
8068
9466
|
kind: "repository",
|
|
8069
9467
|
label: `${criterionId} metric-derived score`,
|
|
8070
9468
|
path: "."
|
|
8071
9469
|
};
|
|
8072
9470
|
return [
|
|
9471
|
+
...findings,
|
|
8073
9472
|
{
|
|
8074
9473
|
severity: status,
|
|
8075
9474
|
summary: `${criterionId} metric-derived score is ${score.toFixed(1)}/4.0, in the ${status} band \u2014 no single finding drove this; it reflects the combined metric weighting below.`,
|
|
@@ -8095,7 +9494,8 @@ function scoreRepoWithPublicCejel(options) {
|
|
|
8095
9494
|
productSlug: options.productSlug,
|
|
8096
9495
|
productDisplayName: options.productDisplayName,
|
|
8097
9496
|
repoPath: options.repoPath,
|
|
8098
|
-
...options.generatedAt ? { generatedAt: options.generatedAt } : {}
|
|
9497
|
+
...options.generatedAt ? { generatedAt: options.generatedAt } : {},
|
|
9498
|
+
...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {}
|
|
8099
9499
|
});
|
|
8100
9500
|
const inputSignals = resolvePublicIngestSignals(options);
|
|
8101
9501
|
return createWitanReport(input, inputSignals.length > 0 ? inputSignals : void 0);
|