@cejel/cejel 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/dist/{chunk-MBYVOPJR.js → chunk-QQVCIG4N.js} +151 -68
- package/dist/index.js +1 -1
- package/dist/mcp/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -278,9 +278,11 @@ separately produced structured report. We neither score you on evidence the publ
|
|
|
278
278
|
cannot collect nor award ourselves points for evidence you cannot contest.
|
|
279
279
|
|
|
280
280
|
**Where we were wrong.** Calibrating this rubric against real repositories — and running the
|
|
281
|
-
board itself like a stranger would — surfaced
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
board itself like a stranger would — surfaced the errors below in our own tool, every one found
|
|
282
|
+
by us and every one fixed. The list is its own count; we do not keep a tally in this sentence,
|
|
283
|
+
because a number typed by hand beside a list that grows is exactly the kind of unchecked claim
|
|
284
|
+
this tool exists to catch. We publish them because a scoring tool that has never been wrong is a
|
|
285
|
+
scoring tool that has not been checked:
|
|
284
286
|
|
|
285
287
|
- **Django, scored a false critical on dependency hygiene.** We applied application-shaped
|
|
286
288
|
expectations to a library. Archetype now gates the dimension.
|
|
@@ -315,6 +317,23 @@ that has never been wrong is a scoring tool that has not been checked:
|
|
|
315
317
|
- **Version 0.1.1 rejected `--help` and `--version` as unknown flags, and read `-h` as a
|
|
316
318
|
directory path.** Version 0.1.2 handles both aliases before positional-path parsing and
|
|
317
319
|
derives its printed version from the package manifest.
|
|
320
|
+
- **We reported a timing side-channel in our own test fixtures.** Pointed at its own
|
|
321
|
+
repository, Cejel's secret-handling dimension flagged a "non-constant-time secret
|
|
322
|
+
comparison" — in string literals inside a test file, the deliberately-insecure example code
|
|
323
|
+
that exists to prove the detector fires. It read a *file*, not a comparison, and reported a
|
|
324
|
+
vulnerability in the test written to catch that vulnerability. Two sub-rules of the same
|
|
325
|
+
dimension held two different private notions of what "production code" meant, and the one
|
|
326
|
+
that had none was the louder. Version 0.1.3 gives every rule one shared, derived answer to
|
|
327
|
+
that question, and reports the real line of a real match or nothing at all.
|
|
328
|
+
- **We fixed the COBOL false-verdict, shipped it, and it was still broken.** An earlier release
|
|
329
|
+
corrected a repository written in a language we cannot read being scored anyway. The fixture
|
|
330
|
+
that proved the fix contained COBOL and nothing else. The first real COBOL repository we
|
|
331
|
+
pointed it at — AWS's mainframe credit-card demo — carried nine incidental shell scripts
|
|
332
|
+
among 329 files, and those nine were enough to short-circuit the abstention path entirely:
|
|
333
|
+
a 99%-COBOL codebase was scored on its deploy scripts and drew a confident `0.0`,
|
|
334
|
+
"Unverified". Every real legacy repository has a deploy script, so the fix that passed every
|
|
335
|
+
test was unreachable for essentially all of them. Recognised source must now be *dominant*,
|
|
336
|
+
not merely present. **A fixture cleaner than reality proves nothing.**
|
|
318
337
|
|
|
319
338
|
Every one was a trust failure produced by *us* — false alarms about other people's code,
|
|
320
339
|
silent omissions, inconsistent presentation, or a home-only scoring path — and we would
|
|
@@ -5446,7 +5446,10 @@ var WitanEvidencePointerSchema = external_exports.object({
|
|
|
5446
5446
|
label: external_exports.string().min(1).max(180),
|
|
5447
5447
|
url: external_exports.string().url().optional(),
|
|
5448
5448
|
path: external_exports.string().min(1).max(700).optional(),
|
|
5449
|
-
|
|
5449
|
+
// Explicit null means the finding is file-scoped and no real match position was measured —
|
|
5450
|
+
// never a fabricated line (e.g. `1`) standing in for a position the detector never found
|
|
5451
|
+
// (goal_cejel_a2_one_notion_of_production_code_2026-07-13).
|
|
5452
|
+
line: external_exports.number().int().positive().nullable().optional(),
|
|
5450
5453
|
contentHash: external_exports.string().min(8).max(160).optional(),
|
|
5451
5454
|
capturedAt: external_exports.string().datetime({ offset: true }).optional()
|
|
5452
5455
|
}).strict().refine((value) => Boolean(value.url ?? value.path ?? value.contentHash), {
|
|
@@ -6600,6 +6603,7 @@ var BUNDLED_BINARY_EXTENSION_PATTERN = /\.(vsix|zip|tgz|whl|exe|dmg|pkg|msi|jar|
|
|
|
6600
6603
|
var DOCS_OR_MEDIA_EXTENSION_PATTERN = /\.(md|mdx|rst|png|jpe?g|gif|svg|webp|ico|pdf)$/i;
|
|
6601
6604
|
var KNOWN_NON_SOURCE_EXTENSION_PATTERN = /\.(json|jsonc|json5|ya?ml|toml|txt|lock|xml|csv|tsv|ini|cfg|conf|properties|env|sum|mod|graphql|gql|proto|editorconfig|gitignore|gitattributes|npmrc|nvmrc|dockerignore|log|txt\.license)$/i;
|
|
6602
6605
|
var INGEST_POINTER = "To assess a closed/bundled tool, ingest its scanner output via --ingest <sarif|scorecard>.";
|
|
6606
|
+
var SOURCE_DOMINANCE_RATIO_THRESHOLD = 0.2;
|
|
6603
6607
|
function classifyRepoArchetype(repoFiles) {
|
|
6604
6608
|
const totalFileCount = repoFiles.length;
|
|
6605
6609
|
const sourceFileCount = repoFiles.filter((file) => SOURCE_EXTENSION_PATTERN.test(file)).length;
|
|
@@ -6611,57 +6615,83 @@ function classifyRepoArchetype(repoFiles) {
|
|
|
6611
6615
|
insufficientSourceReason: "Repository has no tracked files \u2014 there is nothing to certify."
|
|
6612
6616
|
};
|
|
6613
6617
|
}
|
|
6614
|
-
|
|
6618
|
+
const unrecognisedSourceFiles = collectUnrecognisedSourceFiles(repoFiles);
|
|
6619
|
+
if (sourceFileCount === 0) {
|
|
6620
|
+
const binaryFiles = repoFiles.filter((file) => BUNDLED_BINARY_EXTENSION_PATTERN.test(file));
|
|
6621
|
+
if (binaryFiles.length > 0) {
|
|
6622
|
+
const sample = binaryFiles[0];
|
|
6623
|
+
return {
|
|
6624
|
+
archetype: "binary_only",
|
|
6625
|
+
sourceFileCount,
|
|
6626
|
+
totalFileCount,
|
|
6627
|
+
insufficientSourceReason: `${sourceFileCount} source file(s) found among ${totalFileCount} tracked file(s); repo appears to be a binary/bundled-distribution tree (e.g. ${sample ? basename(sample) : "a packaged artifact"}) \u2014 cejel rates source, not binaries. ${INGEST_POINTER}`
|
|
6628
|
+
};
|
|
6629
|
+
}
|
|
6630
|
+
const docsFiles = repoFiles.filter((file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file));
|
|
6631
|
+
if (docsFiles.length > 0 && docsFiles.length === totalFileCount) {
|
|
6632
|
+
return {
|
|
6633
|
+
archetype: "docs_only",
|
|
6634
|
+
sourceFileCount,
|
|
6635
|
+
totalFileCount,
|
|
6636
|
+
insufficientSourceReason: `${sourceFileCount} source file(s) found among ${totalFileCount} tracked file(s); repo appears to be a docs/distribution tree (README/markdown/media), not a source tree \u2014 cejel rates source, not docs. ${INGEST_POINTER}`
|
|
6637
|
+
};
|
|
6638
|
+
}
|
|
6639
|
+
if (unrecognisedSourceFiles.length > 0) {
|
|
6640
|
+
return unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles);
|
|
6641
|
+
}
|
|
6642
|
+
return { archetype: "source", sourceFileCount, totalFileCount };
|
|
6643
|
+
}
|
|
6644
|
+
if (unrecognisedSourceFiles.length === 0) {
|
|
6615
6645
|
return {
|
|
6616
6646
|
archetype: isLikelyMonorepo(repoFiles) ? "monorepo" : "source",
|
|
6617
6647
|
sourceFileCount,
|
|
6618
6648
|
totalFileCount
|
|
6619
6649
|
};
|
|
6620
6650
|
}
|
|
6621
|
-
const
|
|
6622
|
-
if (
|
|
6623
|
-
const sample = binaryFiles[0];
|
|
6651
|
+
const dominanceRatio = sourceFileCount / (sourceFileCount + unrecognisedSourceFiles.length);
|
|
6652
|
+
if (dominanceRatio >= SOURCE_DOMINANCE_RATIO_THRESHOLD) {
|
|
6624
6653
|
return {
|
|
6625
|
-
archetype: "
|
|
6626
|
-
sourceFileCount,
|
|
6627
|
-
totalFileCount,
|
|
6628
|
-
insufficientSourceReason: `${sourceFileCount} source file(s) found among ${totalFileCount} tracked file(s); repo appears to be a binary/bundled-distribution tree (e.g. ${sample ? basename(sample) : "a packaged artifact"}) \u2014 cejel rates source, not binaries. ${INGEST_POINTER}`
|
|
6629
|
-
};
|
|
6630
|
-
}
|
|
6631
|
-
const docsFiles = repoFiles.filter((file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file));
|
|
6632
|
-
if (docsFiles.length > 0 && docsFiles.length === totalFileCount) {
|
|
6633
|
-
return {
|
|
6634
|
-
archetype: "docs_only",
|
|
6635
|
-
sourceFileCount,
|
|
6636
|
-
totalFileCount,
|
|
6637
|
-
insufficientSourceReason: `${sourceFileCount} source file(s) found among ${totalFileCount} tracked file(s); repo appears to be a docs/distribution tree (README/markdown/media), not a source tree \u2014 cejel rates source, not docs. ${INGEST_POINTER}`
|
|
6638
|
-
};
|
|
6639
|
-
}
|
|
6640
|
-
const unrecognisedExtensions = collectUnrecognisedSourceExtensions(repoFiles);
|
|
6641
|
-
if (unrecognisedExtensions.length > 0) {
|
|
6642
|
-
const sample = unrecognisedExtensions.slice(0, 6).join(", ");
|
|
6643
|
-
return {
|
|
6644
|
-
archetype: "unrecognised_ecosystem",
|
|
6654
|
+
archetype: isLikelyMonorepo(repoFiles) ? "monorepo" : "source",
|
|
6645
6655
|
sourceFileCount,
|
|
6646
|
-
totalFileCount
|
|
6647
|
-
insufficientSourceReason: `Cejel does not yet read this repository's source language(s) (${sample}) \u2014 0 of ${totalFileCount} tracked file(s) matched a recognised source extension. Cejel abstains from a verdict rather than score a language it cannot parse; the Criterion Profile and Measured coverage below show exactly which dimensions were and were not measured. ${INGEST_POINTER}`
|
|
6656
|
+
totalFileCount
|
|
6648
6657
|
};
|
|
6649
6658
|
}
|
|
6650
|
-
return
|
|
6659
|
+
return unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles);
|
|
6651
6660
|
}
|
|
6652
|
-
function
|
|
6653
|
-
|
|
6654
|
-
for (const file of repoFiles) {
|
|
6661
|
+
function collectUnrecognisedSourceFiles(repoFiles) {
|
|
6662
|
+
return repoFiles.filter((file) => {
|
|
6655
6663
|
const base = basename(file);
|
|
6656
|
-
|
|
6664
|
+
if (!/\.([a-zA-Z0-9_]+)$/.test(base)) return false;
|
|
6665
|
+
if (KNOWN_NON_SOURCE_EXTENSION_PATTERN.test(base)) return false;
|
|
6666
|
+
if (DOCS_OR_MEDIA_EXTENSION_PATTERN.test(base)) return false;
|
|
6667
|
+
if (BUNDLED_BINARY_EXTENSION_PATTERN.test(base)) return false;
|
|
6668
|
+
if (SOURCE_EXTENSION_PATTERN.test(base)) return false;
|
|
6669
|
+
return true;
|
|
6670
|
+
});
|
|
6671
|
+
}
|
|
6672
|
+
function extensionsByFileCountDescending(files) {
|
|
6673
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6674
|
+
for (const file of files) {
|
|
6675
|
+
const match = /\.([a-zA-Z0-9_]+)$/.exec(basename(file));
|
|
6657
6676
|
const ext = match?.[1];
|
|
6658
6677
|
if (!ext) continue;
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
if (BUNDLED_BINARY_EXTENSION_PATTERN.test(base)) continue;
|
|
6662
|
-
found.add(`.${ext.toLowerCase()}`);
|
|
6678
|
+
const key = `.${ext.toLowerCase()}`;
|
|
6679
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
6663
6680
|
}
|
|
6664
|
-
return [...
|
|
6681
|
+
return [...counts.entries()].sort(([extA, countA], [extB, countB]) => countB - countA || extA.localeCompare(extB)).map(([ext]) => ext);
|
|
6682
|
+
}
|
|
6683
|
+
function unrecognisedEcosystemResult(sourceFileCount, totalFileCount, unrecognisedSourceFiles) {
|
|
6684
|
+
const sample = extensionsByFileCountDescending(unrecognisedSourceFiles).slice(0, 6).join(", ");
|
|
6685
|
+
const candidateCount = sourceFileCount + unrecognisedSourceFiles.length;
|
|
6686
|
+
const dominancePct = candidateCount > 0 ? (sourceFileCount / candidateCount * 100).toFixed(1) : "0.0";
|
|
6687
|
+
const thresholdPct = (SOURCE_DOMINANCE_RATIO_THRESHOLD * 100).toFixed(0);
|
|
6688
|
+
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`;
|
|
6689
|
+
return {
|
|
6690
|
+
archetype: "unrecognised_ecosystem",
|
|
6691
|
+
sourceFileCount,
|
|
6692
|
+
totalFileCount,
|
|
6693
|
+
insufficientSourceReason: `Cejel does not yet read this repository's dominant source language(s) (${sample}) \u2014 ${measuredClause}. Cejel abstains from a verdict rather than score a repository whose recognised source is incidental rather than dominant; the Criterion Profile and Measured coverage below show exactly which dimensions were and were not measured. ${INGEST_POINTER}`
|
|
6694
|
+
};
|
|
6665
6695
|
}
|
|
6666
6696
|
function isLikelyMonorepo(repoFiles) {
|
|
6667
6697
|
const packageRoots = new Set(
|
|
@@ -7106,7 +7136,8 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
7106
7136
|
const findings = [];
|
|
7107
7137
|
let hasSurface = false;
|
|
7108
7138
|
let insecureFound = false;
|
|
7109
|
-
const
|
|
7139
|
+
const productionImplFiles = implFiles.filter(isProductionSourcePath);
|
|
7140
|
+
const timingSafeFile = productionImplFiles.find(
|
|
7110
7141
|
(file) => fileContains(repoPath, file, TIMING_SAFE_COMPARE_PATTERN)
|
|
7111
7142
|
);
|
|
7112
7143
|
if (timingSafeFile) {
|
|
@@ -7120,7 +7151,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
7120
7151
|
)
|
|
7121
7152
|
);
|
|
7122
7153
|
}
|
|
7123
|
-
const canonicalSignFile =
|
|
7154
|
+
const canonicalSignFile = productionImplFiles.find(
|
|
7124
7155
|
(file) => fileContains(repoPath, file, CANONICAL_SERIALIZE_PATTERN) && fileContains(repoPath, file, SIGN_OR_HMAC_CALL_PATTERN)
|
|
7125
7156
|
);
|
|
7126
7157
|
if (canonicalSignFile) {
|
|
@@ -7134,20 +7165,31 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
7134
7165
|
)
|
|
7135
7166
|
);
|
|
7136
7167
|
}
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
)
|
|
7168
|
+
let insecureCompareFile = null;
|
|
7169
|
+
let insecureCompareLine = null;
|
|
7170
|
+
for (const file of implFiles) {
|
|
7171
|
+
const line = findInsecureSecretCompareLine(repoPath, file);
|
|
7172
|
+
if (line != null) {
|
|
7173
|
+
insecureCompareFile = file;
|
|
7174
|
+
insecureCompareLine = line;
|
|
7175
|
+
break;
|
|
7176
|
+
}
|
|
7177
|
+
}
|
|
7140
7178
|
if (insecureCompareFile) {
|
|
7141
|
-
|
|
7142
|
-
|
|
7179
|
+
const isTestPath = isTestOrFixturePath(insecureCompareFile);
|
|
7180
|
+
if (!isTestPath) {
|
|
7181
|
+
hasSurface = true;
|
|
7182
|
+
insecureFound = true;
|
|
7183
|
+
}
|
|
7143
7184
|
findings.push({
|
|
7144
|
-
severity: "warning",
|
|
7145
|
-
summary: "A secret/HMAC/signature value appears compared with a plain equality operator instead of a constant-time comparison \u2014 potential timing side-channel.",
|
|
7146
|
-
evidence:
|
|
7185
|
+
severity: isTestPath ? "info" : "warning",
|
|
7186
|
+
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.",
|
|
7187
|
+
evidence: evidenceForRelativeAtLine(
|
|
7147
7188
|
repoPath,
|
|
7148
7189
|
insecureCompareFile,
|
|
7149
7190
|
"artifact",
|
|
7150
|
-
"Non-constant-time secret comparison"
|
|
7191
|
+
"Non-constant-time secret comparison",
|
|
7192
|
+
insecureCompareLine
|
|
7151
7193
|
)
|
|
7152
7194
|
});
|
|
7153
7195
|
}
|
|
@@ -7155,16 +7197,25 @@ function collectCryptoHygieneEvidence(repoPath, implFiles) {
|
|
|
7155
7197
|
(file) => fileContains(repoPath, file, UNSORTED_JSON_SIGN_PATTERN) && !fileContains(repoPath, file, CANONICAL_SERIALIZE_PATTERN)
|
|
7156
7198
|
);
|
|
7157
7199
|
if (unsortedSignFile) {
|
|
7158
|
-
|
|
7159
|
-
|
|
7200
|
+
const isTestPath = isTestOrFixturePath(unsortedSignFile);
|
|
7201
|
+
if (!isTestPath) {
|
|
7202
|
+
hasSurface = true;
|
|
7203
|
+
insecureFound = true;
|
|
7204
|
+
}
|
|
7205
|
+
const unsortedSignLine = findFirstMatchingLine(
|
|
7206
|
+
repoPath,
|
|
7207
|
+
unsortedSignFile,
|
|
7208
|
+
UNSORTED_JSON_SIGN_PATTERN
|
|
7209
|
+
);
|
|
7160
7210
|
findings.push({
|
|
7161
|
-
severity: "warning",
|
|
7162
|
-
summary: "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.",
|
|
7163
|
-
evidence:
|
|
7211
|
+
severity: isTestPath ? "info" : "warning",
|
|
7212
|
+
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.",
|
|
7213
|
+
evidence: evidenceForRelativeAtLine(
|
|
7164
7214
|
repoPath,
|
|
7165
7215
|
unsortedSignFile,
|
|
7166
7216
|
"artifact",
|
|
7167
|
-
"Signing over unsorted JSON serialization"
|
|
7217
|
+
"Signing over unsorted JSON serialization",
|
|
7218
|
+
unsortedSignLine
|
|
7168
7219
|
)
|
|
7169
7220
|
});
|
|
7170
7221
|
}
|
|
@@ -7880,11 +7931,20 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles) {
|
|
|
7880
7931
|
var CPP_TEST_FRAMEWORK_PATTERN = /<gtest\/gtest\.h>|<catch2\/|<doctest\/|<boost\/test\/|"gtest\/gtest\.h"|"catch2\//;
|
|
7881
7932
|
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/;
|
|
7882
7933
|
var CPP_CONTENT_SCAN_LIMIT = 150;
|
|
7883
|
-
var
|
|
7884
|
-
var
|
|
7934
|
+
var NON_PRODUCTION_DIR_PATTERN = /(^|\/)(__tests__|__mocks__|__fixtures__|tests?|fixtures?|testdata|e2e|cypress|examples?|samples?|demos?)(\/|$)/i;
|
|
7935
|
+
var NON_PRODUCTION_FILE_SUFFIX_PATTERN = /\.(test|spec|stories)\.[^/]+$/i;
|
|
7885
7936
|
function isTestOrFixturePath(path) {
|
|
7886
|
-
return
|
|
7937
|
+
return NON_PRODUCTION_DIR_PATTERN.test(path) || NON_PRODUCTION_FILE_SUFFIX_PATTERN.test(path);
|
|
7938
|
+
}
|
|
7939
|
+
function isProductionSourcePath(path) {
|
|
7940
|
+
return !isTestOrFixturePath(path);
|
|
7887
7941
|
}
|
|
7942
|
+
function isTestFile(file) {
|
|
7943
|
+
return /(^|\/)(__tests__|test|tests|spec)\/.*\.(?:[cm]?[jt]sx?|py|go|rs|java|rb|cpp|cc|cxx)$/.test(
|
|
7944
|
+
file
|
|
7945
|
+
) || /\.(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);
|
|
7946
|
+
}
|
|
7947
|
+
var NAME_SHAPED_TEST_FILE_PATTERN = /\.(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)$/;
|
|
7888
7948
|
var TEST_RUNNER_PATTERN = /\b(vitest|jest|mocha|ava|tap|pytest|go test|cargo test|rspec|phpunit|gradle test|mvn test)\b/i;
|
|
7889
7949
|
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;
|
|
7890
7950
|
var CI_LINT_COMMAND_PATTERN = /\b(eslint|pylint|flake8|black|ruff)\b/i;
|
|
@@ -7917,7 +7977,28 @@ var HEAVY_TEST_DEPENDENCY_NAME_PATTERN = /^(?:jest|mocha|ava|tape|jasmine|karma)
|
|
|
7917
7977
|
var TIMING_SAFE_COMPARE_PATTERN = /\btiming[-_]?safe[-_]?(?:equal|compare)\b|crypto\.timingSafeEqual\s*\(/i;
|
|
7918
7978
|
var CANONICAL_SERIALIZE_PATTERN = /\bcanonicalize\s*\(|\bcanonicalJson\b|\bstableStringify\s*\(|json-stable-stringify|safe-stable-stringify|\bsortKeys\s*\(/i;
|
|
7919
7979
|
var SIGN_OR_HMAC_CALL_PATTERN = /\bcreateHmac\s*\(|\bcreateSign\s*\(|\.sign\s*\(/;
|
|
7920
|
-
var
|
|
7980
|
+
var INSECURE_SECRET_COMPARE_LINE_PATTERN = /\b\w*(?:hmac|signature|\bmac|digest)\w*\s*(===|!==)\s*(?!['"`]|undefined\b|null\b)([\w.]+(?:\s*[-+]\s*\d+)?)/i;
|
|
7981
|
+
var NUMERIC_SHAPED_OPERAND_PATTERN = /^-?\d+$|\.(?:length|len|size|count)\b|(?:length|index|idx|offset|count|size|len)$/i;
|
|
7982
|
+
function lineHasInsecureSecretCompare(line) {
|
|
7983
|
+
const match = INSECURE_SECRET_COMPARE_LINE_PATTERN.exec(line);
|
|
7984
|
+
const operand = match?.[2];
|
|
7985
|
+
if (!operand) return false;
|
|
7986
|
+
return !NUMERIC_SHAPED_OPERAND_PATTERN.test(operand.trim());
|
|
7987
|
+
}
|
|
7988
|
+
function findInsecureSecretCompareLine(repoPath, file) {
|
|
7989
|
+
const fullPath = join3(repoPath, file);
|
|
7990
|
+
if (!isRegularFile(fullPath)) return null;
|
|
7991
|
+
const lines = readFileSync6(fullPath, "utf8").split("\n");
|
|
7992
|
+
const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line));
|
|
7993
|
+
return index === -1 ? null : index + 1;
|
|
7994
|
+
}
|
|
7995
|
+
function findFirstMatchingLine(repoPath, file, pattern) {
|
|
7996
|
+
const fullPath = join3(repoPath, file);
|
|
7997
|
+
if (!isRegularFile(fullPath)) return null;
|
|
7998
|
+
const lines = readFileSync6(fullPath, "utf8").split("\n");
|
|
7999
|
+
const index = lines.findIndex((line) => pattern.test(line));
|
|
8000
|
+
return index === -1 ? null : index + 1;
|
|
8001
|
+
}
|
|
7921
8002
|
var UNSORTED_JSON_SIGN_PATTERN = /\.update\s*\(\s*JSON\.stringify\s*\(|\.sign\s*\([^)]*JSON\.stringify\s*\(/;
|
|
7922
8003
|
var KILL_SWITCH_NAME_FRAGMENT = "kill[_-]?switch|emergency[_-]?stop|circuit[_-]?breaker|safety[_-]?(?:toggle|gate|switch)|governance[_-]?(?:toggle|gate)";
|
|
7923
8004
|
var KILL_SWITCH_NAME_PATTERN = new RegExp(`\\b(?:${KILL_SWITCH_NAME_FRAGMENT})\\b`, "i");
|
|
@@ -8071,11 +8152,6 @@ function shouldSkipDir(name) {
|
|
|
8071
8152
|
function isHardExcludedPath(path) {
|
|
8072
8153
|
return HARD_EXCLUDED_PATH_PATTERN.test(path);
|
|
8073
8154
|
}
|
|
8074
|
-
function isTestFile(file) {
|
|
8075
|
-
return /(^|\/)(__tests__|test|tests|spec)\/.*\.(?:[cm]?[jt]sx?|py|go|rs|java|rb|cpp|cc|cxx)$/.test(
|
|
8076
|
-
file
|
|
8077
|
-
) || /\.(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);
|
|
8078
|
-
}
|
|
8079
8155
|
function collectContentBasedCppTestFiles(repoPath, repoFiles) {
|
|
8080
8156
|
const candidates = repoFiles.filter((file) => /\.(cpp|cc|cxx)$/.test(file) && !isTestFile(file)).slice(0, CPP_CONTENT_SCAN_LIMIT);
|
|
8081
8157
|
return candidates.filter((file) => {
|
|
@@ -8095,7 +8171,6 @@ var JS_TEST_ASSERTION_PATTERN = /(expect\(|assert\.|should\.|toEqual\(|toBe\(|as
|
|
|
8095
8171
|
var CPP_TEST_ASSERTION_PATTERN = /\bEXPECT_|ASSERT_EQ\b|ASSERT_TRUE\b|ASSERT_FALSE\b|\bREQUIRE\s*\(|\bCHECK\s*\(|\bTEST\s*\(|\bTEST_F\s*\(|\bTEST_CASE\s*\(|\bSECTION\s*\(/;
|
|
8096
8172
|
var MULTI_ECOSYSTEM_TEST_ASSERTION_PATTERN = /\.assert[A-Z_]\w*\s*\(|\bassert[A-Z]\w*\s*\(|\bpytest\.raises\b|\bself\.fail\s*\(|\bt\.(?:Error|Errorf|Fatal|Fatalf|Fail|FailNow)\b|\brequire\.[A-Z]\w*\s*\(|\b(?:debug_)?assert(?:_eq|_ne)?!\s*\(|\bassert_\w+\b|\$this->assert\w+\s*\(/;
|
|
8097
8173
|
var SUITE_LEVEL_SKIP_PATTERN = /\b(?:describe|suite|context)\.skip\s*\(/;
|
|
8098
|
-
var NAME_SHAPED_TEST_FILE_PATTERN = /\.(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)$/;
|
|
8099
8174
|
function measureNonHollowTestShare(repoPath, files) {
|
|
8100
8175
|
let nonHollowCount = 0;
|
|
8101
8176
|
let ratedCount = 0;
|
|
@@ -8450,7 +8525,6 @@ function isExplicitDeployTarget(file) {
|
|
|
8450
8525
|
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);
|
|
8451
8526
|
}
|
|
8452
8527
|
var MAIN_ENTRYPOINT_FILE_PATTERN = /(^|\/)(main|server|app|wsgi|asgi|index|entry|start|run)\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs)$/i;
|
|
8453
|
-
var EXAMPLE_OR_TEST_DIR_PATTERN = /(?:^|\/)(examples?|samples?|demos?|tests?|__tests__|spec|fixtures?)(?:\/|$)/i;
|
|
8454
8528
|
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*\(/;
|
|
8455
8529
|
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;
|
|
8456
8530
|
function isDeployableService(repoPath, repoFiles) {
|
|
@@ -8467,9 +8541,7 @@ function detectServerEntrypoint(repoPath, repoFiles) {
|
|
|
8467
8541
|
return findServerEntrypointFile(repoPath, repoFiles) !== null;
|
|
8468
8542
|
}
|
|
8469
8543
|
function findServerEntrypointFile(repoPath, repoFiles) {
|
|
8470
|
-
const candidates = repoFiles.filter(
|
|
8471
|
-
(file) => MAIN_ENTRYPOINT_FILE_PATTERN.test(file) && !EXAMPLE_OR_TEST_DIR_PATTERN.test(file)
|
|
8472
|
-
).slice(0, 30);
|
|
8544
|
+
const candidates = repoFiles.filter((file) => MAIN_ENTRYPOINT_FILE_PATTERN.test(file) && isProductionSourcePath(file)).slice(0, 30);
|
|
8473
8545
|
return candidates.find((file) => fileContains(repoPath, file, SERVER_ENTRYPOINT_PATTERN)) ?? null;
|
|
8474
8546
|
}
|
|
8475
8547
|
function isHealthCheckSignalFile(repoPath, file) {
|
|
@@ -8818,6 +8890,17 @@ function evidenceForRelative(repoPath, path, kind, label) {
|
|
|
8818
8890
|
} : {}
|
|
8819
8891
|
};
|
|
8820
8892
|
}
|
|
8893
|
+
function evidenceForRelativeAtLine(repoPath, path, kind, label, line) {
|
|
8894
|
+
const fullPath = join3(repoPath, path);
|
|
8895
|
+
const contents = isRegularFile(fullPath) ? readFileSync6(fullPath, "utf8") : "";
|
|
8896
|
+
return {
|
|
8897
|
+
kind,
|
|
8898
|
+
label,
|
|
8899
|
+
path,
|
|
8900
|
+
line,
|
|
8901
|
+
...contents ? { contentHash: createHash("sha256").update(contents).digest("hex") } : {}
|
|
8902
|
+
};
|
|
8903
|
+
}
|
|
8821
8904
|
function metric(name, label, value, max, weight, unit, description, kind) {
|
|
8822
8905
|
return {
|
|
8823
8906
|
name,
|
package/dist/index.js
CHANGED
package/dist/mcp/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
renderWitanBadgeSvg,
|
|
11
11
|
renderWitanHtmlReport,
|
|
12
12
|
runCejelScan
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-QQVCIG4N.js";
|
|
14
14
|
|
|
15
15
|
// ../../node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
16
16
|
var require_code = __commonJS({
|