xploitscan 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/{api-MFCSQQ2Y.js → api-6FD27EJY.js} +2 -2
- package/dist/{chunk-4OFR3SIS.js → chunk-2VDAYFPC.js} +199 -26
- package/dist/chunk-2VDAYFPC.js.map +1 -0
- package/dist/index.js +72 -19
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-4OFR3SIS.js.map +0 -1
- /package/dist/{api-MFCSQQ2Y.js.map → api-6FD27EJY.js.map} +0 -0
package/README.md
CHANGED
|
@@ -155,6 +155,50 @@ legacy/** scanner
|
|
|
155
155
|
For a single reviewed-and-accepted finding, prefer an inline
|
|
156
156
|
`// VC<id>-OK: <reason>` comment instead of a file- or path-wide rule.
|
|
157
157
|
|
|
158
|
+
### Vendored and generated code
|
|
159
|
+
|
|
160
|
+
Some of what a scanner finds isn't code you wrote. AI app builders copy
|
|
161
|
+
shadcn/ui into your project verbatim; codegen writes into `__generated__/`;
|
|
162
|
+
your README contains example code that exists to show what *not* to do.
|
|
163
|
+
|
|
164
|
+
XploitScan scans all of it and reports everything it finds — but findings in
|
|
165
|
+
these paths don't count toward your grade or the CI exit code:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
**/components/ui/** **/shadcn/** **/ui/shadcn/**
|
|
169
|
+
**/vendor/** **/vendored/** **/third_party/**
|
|
170
|
+
**/generated/** **/__generated__/**
|
|
171
|
+
**/*.md **/*.mdx
|
|
172
|
+
.claude/** .agent/** .cursor/** .superpowers/** .windsurf/**
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
They still appear in every report, tagged `[not graded]`, with the count shown
|
|
176
|
+
next to the grade. Nothing is hidden — a grade that held findings out always
|
|
177
|
+
says so.
|
|
178
|
+
|
|
179
|
+
**Hardcoded credentials are the exception and are always graded**, in every
|
|
180
|
+
path above. A key committed to your repository is live no matter which
|
|
181
|
+
directory holds it, so "the generator wrote that file" doesn't apply. A
|
|
182
|
+
service-role key in `__generated__/client.ts` or a plaintext Kubernetes Secret
|
|
183
|
+
under `generated/` still counts against your grade.
|
|
184
|
+
|
|
185
|
+
Why: shadcn's `chart.tsx` writes CSS custom properties through
|
|
186
|
+
`dangerouslySetInnerHTML`, which trips a critical XSS rule. It ships
|
|
187
|
+
byte-identically in a large share of AI-scaffolded projects, and one critical
|
|
188
|
+
caps a project at grade D. Grading it means grading shadcn's code as yours.
|
|
189
|
+
|
|
190
|
+
To opt out and grade everything:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
xploitscan scan . --grade-vendored
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Or persistently, in `.xploitscanrc`:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{ "scan": { "gradeVendored": true } }
|
|
200
|
+
```
|
|
201
|
+
|
|
158
202
|
## Web Dashboard
|
|
159
203
|
|
|
160
204
|
Scan via the web at [xploitscan.com](https://xploitscan.com):
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
storeToken,
|
|
15
15
|
syncUser,
|
|
16
16
|
uploadScanResults
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-2VDAYFPC.js";
|
|
18
18
|
export {
|
|
19
19
|
checkUsage,
|
|
20
20
|
clearProRulesCache,
|
|
@@ -29,4 +29,4 @@ export {
|
|
|
29
29
|
syncUser,
|
|
30
30
|
uploadScanResults
|
|
31
31
|
};
|
|
32
|
-
//# sourceMappingURL=api-
|
|
32
|
+
//# sourceMappingURL=api-6FD27EJY.js.map
|
|
@@ -43469,6 +43469,95 @@ function getSnippet(content, line, contextLines = 2) {
|
|
|
43469
43469
|
}
|
|
43470
43470
|
return out.join("\n");
|
|
43471
43471
|
}
|
|
43472
|
+
var NON_APP_PATH_PATTERNS = [
|
|
43473
|
+
// 1. vendored UI component libraries
|
|
43474
|
+
"**/components/ui/**",
|
|
43475
|
+
"**/ui/shadcn/**",
|
|
43476
|
+
"**/shadcn/ui/**",
|
|
43477
|
+
"**/shadcn/**",
|
|
43478
|
+
// 2. documentation prose
|
|
43479
|
+
"**/*.md",
|
|
43480
|
+
"**/*.mdx",
|
|
43481
|
+
// 3. AI-agent and editor tooling artifacts. Each gets a `**/`-prefixed form
|
|
43482
|
+
// as well as the root-anchored one: these directories turn up per-package
|
|
43483
|
+
// in a monorepo, and a finding's `file` is occasionally an absolute path,
|
|
43484
|
+
// which makes even a repo-root `.claude/` a nested segment.
|
|
43485
|
+
".claude/**",
|
|
43486
|
+
".agent/**",
|
|
43487
|
+
".cursor/**",
|
|
43488
|
+
".superpowers/**",
|
|
43489
|
+
".windsurf/**",
|
|
43490
|
+
"**/.claude/**",
|
|
43491
|
+
"**/.agent/**",
|
|
43492
|
+
"**/.cursor/**",
|
|
43493
|
+
"**/.superpowers/**",
|
|
43494
|
+
"**/.windsurf/**",
|
|
43495
|
+
// 4. explicitly third-party or machine-generated trees
|
|
43496
|
+
"**/vendor/**",
|
|
43497
|
+
"**/vendored/**",
|
|
43498
|
+
"**/third_party/**",
|
|
43499
|
+
"**/generated/**",
|
|
43500
|
+
"**/__generated__/**"
|
|
43501
|
+
];
|
|
43502
|
+
function globToRegExp(pattern) {
|
|
43503
|
+
const segments = pattern.split("/");
|
|
43504
|
+
let src = "^";
|
|
43505
|
+
for (let i = 0; i < segments.length; i++) {
|
|
43506
|
+
const seg = segments[i];
|
|
43507
|
+
const isLast = i === segments.length - 1;
|
|
43508
|
+
if (seg === "**") {
|
|
43509
|
+
src += isLast ? ".+" : "(?:[^/]+/)*";
|
|
43510
|
+
continue;
|
|
43511
|
+
}
|
|
43512
|
+
src += seg.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "*" ? "[^/]*" : "\\" + ch);
|
|
43513
|
+
if (!isLast) src += "/";
|
|
43514
|
+
}
|
|
43515
|
+
return new RegExp(src + "$", "i");
|
|
43516
|
+
}
|
|
43517
|
+
var NON_APP_MATCHERS = NON_APP_PATH_PATTERNS.map(globToRegExp);
|
|
43518
|
+
function normalizeScanPath(filePath) {
|
|
43519
|
+
const raw = typeof filePath === "string" ? filePath : "";
|
|
43520
|
+
const trimmed = raw.trim();
|
|
43521
|
+
const looksWindows = !trimmed.includes("/") && trimmed.includes("\\");
|
|
43522
|
+
const withForwardSlashes = (looksWindows ? trimmed.replace(/\\/g, "/") : trimmed).replace(/^[A-Za-z]:\//, "/");
|
|
43523
|
+
const out = [];
|
|
43524
|
+
for (const segment of withForwardSlashes.split("/")) {
|
|
43525
|
+
if (segment === "" || segment === ".") continue;
|
|
43526
|
+
if (segment === "..") {
|
|
43527
|
+
if (out.length > 0 && out[out.length - 1] !== "..") out.pop();
|
|
43528
|
+
else out.push("..");
|
|
43529
|
+
continue;
|
|
43530
|
+
}
|
|
43531
|
+
out.push(segment);
|
|
43532
|
+
}
|
|
43533
|
+
return out.join("/");
|
|
43534
|
+
}
|
|
43535
|
+
function isVendoredPath(filePath) {
|
|
43536
|
+
const normalized = normalizeScanPath(filePath);
|
|
43537
|
+
if (!normalized) return false;
|
|
43538
|
+
return NON_APP_MATCHERS.some((re) => re.test(normalized));
|
|
43539
|
+
}
|
|
43540
|
+
var HEURISTIC_SECRET_RULES = /* @__PURE__ */ new Set(["ENTROPY"]);
|
|
43541
|
+
var ALWAYS_GRADED_RULES = /* @__PURE__ */ new Set(["VC115", "VC062"]);
|
|
43542
|
+
function isAlwaysGraded(finding) {
|
|
43543
|
+
const rule = finding?.rule ?? "";
|
|
43544
|
+
if (ALWAYS_GRADED_RULES.has(rule)) return true;
|
|
43545
|
+
if (finding?.category !== "Secrets") return false;
|
|
43546
|
+
return !HEURISTIC_SECRET_RULES.has(rule);
|
|
43547
|
+
}
|
|
43548
|
+
function isVendoredFinding(finding) {
|
|
43549
|
+
if (isAlwaysGraded(finding)) return false;
|
|
43550
|
+
return isVendoredPath(finding?.file ?? "");
|
|
43551
|
+
}
|
|
43552
|
+
function vendoredReason(filePath) {
|
|
43553
|
+
const normalized = normalizeScanPath(filePath);
|
|
43554
|
+
if (!normalized) return null;
|
|
43555
|
+
if (/\.mdx?$/.test(normalized)) return "documentation";
|
|
43556
|
+
if (/(^|\/)(components\/ui|shadcn)(\/|$)/.test(normalized)) return "vendored UI component library";
|
|
43557
|
+
if (/(^|\/)\.(claude|agent|cursor|superpowers|windsurf)(\/|$)/.test(normalized)) return "AI-agent tooling artifact";
|
|
43558
|
+
if (isVendoredPath(normalized)) return "third-party or generated code";
|
|
43559
|
+
return null;
|
|
43560
|
+
}
|
|
43472
43561
|
var MAX_CACHE = 256;
|
|
43473
43562
|
var cache = /* @__PURE__ */ new Map();
|
|
43474
43563
|
function cacheKey(filename, contentHash) {
|
|
@@ -44174,8 +44263,8 @@ var hardcodedSecrets = {
|
|
|
44174
44263
|
if (valMatch && nameMatch) {
|
|
44175
44264
|
const value = valMatch[1];
|
|
44176
44265
|
const isKeySuffix = nameMatch[1].endsWith("_KEY");
|
|
44177
|
-
const
|
|
44178
|
-
if (isKeySuffix &&
|
|
44266
|
+
const isSlotIdentifier = value.length < 40 && /^[a-z0-9]+([_-][a-z0-9]+)+$/.test(value);
|
|
44267
|
+
if (isKeySuffix && isSlotIdentifier) continue;
|
|
44179
44268
|
}
|
|
44180
44269
|
}
|
|
44181
44270
|
matches.push(rm);
|
|
@@ -45209,12 +45298,20 @@ function detectPlatform(files) {
|
|
|
45209
45298
|
}
|
|
45210
45299
|
return [...platforms];
|
|
45211
45300
|
}
|
|
45212
|
-
function calculateGrade(findings, _totalFiles) {
|
|
45213
|
-
|
|
45214
|
-
|
|
45301
|
+
function calculateGrade(findings, _totalFiles, options = {}) {
|
|
45302
|
+
const scored = options.gradeVendored ? findings : findings.filter((f) => !isVendoredFinding(f));
|
|
45303
|
+
const ungraded = findings.length - scored.length;
|
|
45304
|
+
const holdout = ungraded > 0 ? ` ${ungraded} finding${ungraded === 1 ? "" : "s"} in vendored, generated, or documentation files ${ungraded === 1 ? "is" : "are"} reported but not graded.` : "";
|
|
45305
|
+
if (scored.length === 0) {
|
|
45306
|
+
return {
|
|
45307
|
+
grade: "A+",
|
|
45308
|
+
score: 100,
|
|
45309
|
+
summary: (ungraded > 0 ? "No security issues detected in your own code." : "No security issues detected. Excellent.") + holdout,
|
|
45310
|
+
ungraded
|
|
45311
|
+
};
|
|
45215
45312
|
}
|
|
45216
45313
|
let critical = 0, high = 0, medium = 0, low = 0;
|
|
45217
|
-
for (const f of
|
|
45314
|
+
for (const f of scored) {
|
|
45218
45315
|
if (f.severity === "critical") critical++;
|
|
45219
45316
|
else if (f.severity === "high") high++;
|
|
45220
45317
|
else if (f.severity === "medium") medium++;
|
|
@@ -45254,7 +45351,7 @@ function calculateGrade(findings, _totalFiles) {
|
|
|
45254
45351
|
} else {
|
|
45255
45352
|
summary = "No security issues detected.";
|
|
45256
45353
|
}
|
|
45257
|
-
return { grade, score: rawScore, summary };
|
|
45354
|
+
return { grade, score: rawScore, summary: summary + holdout, ungraded };
|
|
45258
45355
|
}
|
|
45259
45356
|
var complianceMap = {
|
|
45260
45357
|
VC001: { owasp: "A07:2021", cwe: "CWE-798" },
|
|
@@ -45684,13 +45781,16 @@ function runCustomRules(content, filePath, disabledRules = [], tier = "free", ex
|
|
|
45684
45781
|
}
|
|
45685
45782
|
if (maxLineLen > 5e4) return findings;
|
|
45686
45783
|
if (/onboarding|demo-data|example-vulnerable|code-sample/i.test(filePath)) return findings;
|
|
45784
|
+
const vendoredFile = isVendoredPath(filePath);
|
|
45687
45785
|
const ruleset = tier === "pro" && extraRules.length > 0 ? [...freeRules, ...extraRules] : freeRules;
|
|
45688
45786
|
for (const rule of ruleset) {
|
|
45689
45787
|
if (disabledRules.includes(rule.id)) continue;
|
|
45690
45788
|
const matches = rule.check(content, filePath);
|
|
45691
45789
|
const compliance = complianceMap[rule.id];
|
|
45790
|
+
const vendored = vendoredFile && isVendoredFinding({ file: filePath, rule: rule.id, category: rule.category });
|
|
45692
45791
|
for (const match of matches) {
|
|
45693
45792
|
findings.push({
|
|
45793
|
+
...vendored ? { vendored: true } : {},
|
|
45694
45794
|
id: `${match.rule}-${match.file}:${match.line}`,
|
|
45695
45795
|
rule: match.rule,
|
|
45696
45796
|
severity: match.severity,
|
|
@@ -45720,6 +45820,7 @@ Common false positive patterns you should catch:
|
|
|
45720
45820
|
- The comparison is a type check (typeof x === "string") not a secret comparison
|
|
45721
45821
|
- The file is a test, mock, or fixture file
|
|
45722
45822
|
- The "secret" is a publishable/public key (pk_test_, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY) designed to be client-side
|
|
45823
|
+
- The value is a slot/name identifier, not a credential: a SCREAMING_CASE *_KEY constant assigned a short lowercase word string (e.g. INDEX_KEY = "mc_saves_index", CACHE_KEY = "user-prefs-v2") is a localStorage/cookie/cache key NAME, not a secret. Real secrets are high-entropy and mixed-case (sk_live_aBcD\u2026), not dictionary words joined by - or _
|
|
45723
45824
|
- The innerHTML/dangerouslySetInnerHTML uses a static constant or JSON.stringify, not user input
|
|
45724
45825
|
- The redirect URL has already been validated (isAllowedRedirect, validateRedirect)
|
|
45725
45826
|
- The webhook endpoint is for a non-Stripe service but flagged as "Stripe webhook"
|
|
@@ -45738,6 +45839,14 @@ var MAX_TOTAL_FINDINGS = (() => {
|
|
|
45738
45839
|
if (!Number.isFinite(n) || n < 1) return DEFAULT_MAX_TOTAL_FINDINGS;
|
|
45739
45840
|
return Math.min(n, 1e3);
|
|
45740
45841
|
})();
|
|
45842
|
+
var DEFAULT_MAX_CONCURRENT = 6;
|
|
45843
|
+
var MAX_CONCURRENT = (() => {
|
|
45844
|
+
const raw = process.env.XPLOITSCAN_AI_CONCURRENCY;
|
|
45845
|
+
if (!raw) return DEFAULT_MAX_CONCURRENT;
|
|
45846
|
+
const n = parseInt(raw, 10);
|
|
45847
|
+
if (!Number.isFinite(n) || n < 1) return DEFAULT_MAX_CONCURRENT;
|
|
45848
|
+
return Math.min(n, 20);
|
|
45849
|
+
})();
|
|
45741
45850
|
var SEVERITY_PRIORITY = {
|
|
45742
45851
|
critical: 0,
|
|
45743
45852
|
high: 1,
|
|
@@ -45789,7 +45898,7 @@ function parseReviewResponse(text) {
|
|
|
45789
45898
|
}
|
|
45790
45899
|
}
|
|
45791
45900
|
async function filterFalsePositives(findings, fileContents) {
|
|
45792
|
-
const empty = { findings, filteredFindings: [], aiReviewed: false, removedCount: 0, totalBefore: findings.length };
|
|
45901
|
+
const empty = { findings, filteredFindings: [], aiReviewed: false, removedCount: 0, totalBefore: findings.length, unreviewedCount: 0 };
|
|
45793
45902
|
if (!process.env.ANTHROPIC_API_KEY) return empty;
|
|
45794
45903
|
if (findings.length === 0) return empty;
|
|
45795
45904
|
const prioritized = [...findings].sort((a, b) => {
|
|
@@ -45813,32 +45922,85 @@ async function filterFalsePositives(findings, fileContents) {
|
|
|
45813
45922
|
return empty;
|
|
45814
45923
|
}
|
|
45815
45924
|
const fpMap = /* @__PURE__ */ new Map();
|
|
45925
|
+
let reviewedAny = false;
|
|
45926
|
+
let firstError = null;
|
|
45927
|
+
let failedBatches = 0;
|
|
45928
|
+
let unreviewedCount = 0;
|
|
45929
|
+
const tasks = [];
|
|
45816
45930
|
for (const [file, fileFindings] of byFile) {
|
|
45817
45931
|
const content = fileContents.get(file);
|
|
45818
45932
|
if (!content) continue;
|
|
45819
45933
|
for (let i = 0; i < fileFindings.length; i += MAX_FINDINGS_PER_BATCH) {
|
|
45820
|
-
|
|
45934
|
+
tasks.push({ batch: fileFindings.slice(i, i + MAX_FINDINGS_PER_BATCH), content });
|
|
45935
|
+
}
|
|
45936
|
+
}
|
|
45937
|
+
const reviewBatch = async (task) => {
|
|
45938
|
+
const { batch, content } = task;
|
|
45939
|
+
try {
|
|
45821
45940
|
const prompt = buildReviewPrompt(batch, content);
|
|
45822
|
-
|
|
45823
|
-
|
|
45941
|
+
const response = await client.messages.create(
|
|
45942
|
+
{
|
|
45824
45943
|
model: "claude-haiku-4-5-20251001",
|
|
45825
45944
|
max_tokens: 1024,
|
|
45945
|
+
// Pinned, NOT left to the API default of 1.0. This is a binary
|
|
45946
|
+
// classification (real vs. false positive), not generation — there
|
|
45947
|
+
// is no upside to sampling variety and a real downside to it: a
|
|
45948
|
+
// filtered finding is a finding the user never sees, so drift here
|
|
45949
|
+
// means scanning the same unchanged code twice can yield different
|
|
45950
|
+
// results. Temperature 0 does not make the model bit-deterministic
|
|
45951
|
+
// (Anthropic makes no such guarantee), but it removes the one
|
|
45952
|
+
// variance source that was ours to control. See
|
|
45953
|
+
// scripts/fp-filter-repeatability.cjs for the measured flip rate,
|
|
45954
|
+
// and the assertion in ai-fp-filter.test.ts that keeps it pinned.
|
|
45955
|
+
temperature: 0,
|
|
45826
45956
|
system: REVIEW_SYSTEM_PROMPT,
|
|
45827
45957
|
messages: [{ role: "user", content: prompt }]
|
|
45828
|
-
}
|
|
45829
|
-
|
|
45830
|
-
|
|
45831
|
-
|
|
45832
|
-
|
|
45833
|
-
|
|
45834
|
-
|
|
45835
|
-
|
|
45836
|
-
|
|
45958
|
+
},
|
|
45959
|
+
// Cap a single call well under any serverless function timeout — the
|
|
45960
|
+
// SDK default is 10 minutes, long enough for one hung connection to
|
|
45961
|
+
// hold a worker until the platform SIGKILLs the whole scan request
|
|
45962
|
+
// (which would skip the graceful-degradation fallback). A timeout
|
|
45963
|
+
// turns a stuck batch into an unreviewed batch, handled below.
|
|
45964
|
+
{ timeout: 2e4 }
|
|
45965
|
+
);
|
|
45966
|
+
const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
45967
|
+
const results = parseReviewResponse(text);
|
|
45968
|
+
reviewedAny = true;
|
|
45969
|
+
for (const r of results) {
|
|
45970
|
+
if (r.verdict === "fp" && r.index >= 0 && r.index < batch.length) {
|
|
45971
|
+
const globalIndex = toReview.indexOf(batch[r.index]);
|
|
45972
|
+
if (globalIndex !== -1) {
|
|
45973
|
+
fpMap.set(globalIndex, r.reason);
|
|
45837
45974
|
}
|
|
45838
45975
|
}
|
|
45839
|
-
} catch {
|
|
45840
|
-
continue;
|
|
45841
45976
|
}
|
|
45977
|
+
} catch (err) {
|
|
45978
|
+
failedBatches++;
|
|
45979
|
+
unreviewedCount += batch.length;
|
|
45980
|
+
if (!firstError) firstError = err;
|
|
45981
|
+
}
|
|
45982
|
+
};
|
|
45983
|
+
let next = 0;
|
|
45984
|
+
const workerCount = Math.min(MAX_CONCURRENT, tasks.length);
|
|
45985
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
45986
|
+
while (next < tasks.length) {
|
|
45987
|
+
const cur = next++;
|
|
45988
|
+
await reviewBatch(tasks[cur]);
|
|
45989
|
+
}
|
|
45990
|
+
});
|
|
45991
|
+
await Promise.all(workers);
|
|
45992
|
+
if (firstError) {
|
|
45993
|
+
const msg = firstError instanceof Error ? firstError.message : String(firstError);
|
|
45994
|
+
if (!reviewedAny) {
|
|
45995
|
+
console.error(
|
|
45996
|
+
"[ai-fp-filter] AI review unavailable \u2014 every model call failed; returning unreviewed findings:",
|
|
45997
|
+
msg
|
|
45998
|
+
);
|
|
45999
|
+
} else {
|
|
46000
|
+
console.warn(
|
|
46001
|
+
`[ai-fp-filter] partial AI review \u2014 ${failedBatches} of ${tasks.length} batch(es) failed; those findings were not reviewed:`,
|
|
46002
|
+
msg
|
|
46003
|
+
);
|
|
45842
46004
|
}
|
|
45843
46005
|
}
|
|
45844
46006
|
const filtered = toReview.filter((_, i) => !fpMap.has(i));
|
|
@@ -45849,9 +46011,10 @@ async function filterFalsePositives(findings, fileContents) {
|
|
|
45849
46011
|
return {
|
|
45850
46012
|
findings: [...filtered, ...overflow],
|
|
45851
46013
|
filteredFindings,
|
|
45852
|
-
aiReviewed:
|
|
46014
|
+
aiReviewed: reviewedAny,
|
|
45853
46015
|
removedCount: fpMap.size,
|
|
45854
|
-
totalBefore
|
|
46016
|
+
totalBefore,
|
|
46017
|
+
unreviewedCount
|
|
45855
46018
|
};
|
|
45856
46019
|
}
|
|
45857
46020
|
function shannonEntropy(str) {
|
|
@@ -45970,6 +46133,7 @@ function scanEntropy(files) {
|
|
|
45970
46133
|
if (filePath.includes(".min.")) continue;
|
|
45971
46134
|
if (/pro-rules-bundle|\.bundle\.|\.chunk\./i.test(filePath)) continue;
|
|
45972
46135
|
if (/(?:\.test\.|\.spec\.|__tests__|__mocks__|fixtures?\/)/i.test(filePath)) continue;
|
|
46136
|
+
const vendored = isVendoredPath(filePath);
|
|
45973
46137
|
const lines = content.split("\n");
|
|
45974
46138
|
for (let i = 0; i < lines.length; i++) {
|
|
45975
46139
|
const line = lines[i];
|
|
@@ -46000,6 +46164,7 @@ function scanEntropy(files) {
|
|
|
46000
46164
|
if (entropy < 4.5 && !isLikelySecret) continue;
|
|
46001
46165
|
const masked = value.substring(0, 6) + "..." + value.substring(value.length - 4);
|
|
46002
46166
|
findings.push({
|
|
46167
|
+
...vendored ? { vendored: true } : {},
|
|
46003
46168
|
id: `ENTROPY-${filePath}:${i + 1}`,
|
|
46004
46169
|
rule: "ENTROPY",
|
|
46005
46170
|
severity: isLikelySecret ? "critical" : "high",
|
|
@@ -46110,8 +46275,11 @@ async function incrementUsage() {
|
|
|
46110
46275
|
async function uploadScanResults(result) {
|
|
46111
46276
|
const apiKey = getApiKey();
|
|
46112
46277
|
if (!apiKey) return;
|
|
46278
|
+
const gradedFindings = result.findings.filter(
|
|
46279
|
+
(f) => !isVendoredFinding(f)
|
|
46280
|
+
);
|
|
46113
46281
|
let critical = 0, high = 0, medium = 0, low = 0;
|
|
46114
|
-
for (const f of
|
|
46282
|
+
for (const f of gradedFindings) {
|
|
46115
46283
|
switch (f.severity) {
|
|
46116
46284
|
case "critical":
|
|
46117
46285
|
critical++;
|
|
@@ -46140,6 +46308,9 @@ async function uploadScanResults(result) {
|
|
|
46140
46308
|
highCount: high,
|
|
46141
46309
|
mediumCount: medium,
|
|
46142
46310
|
lowCount: low,
|
|
46311
|
+
// Held out of the grade, not out of the record: every finding is still in
|
|
46312
|
+
// `findings`, each flagged `vendored`.
|
|
46313
|
+
ungradedCount: result.findings.length - gradedFindings.length,
|
|
46143
46314
|
grade,
|
|
46144
46315
|
score,
|
|
46145
46316
|
duration: result.duration
|
|
@@ -46239,6 +46410,8 @@ function clearProRulesCache() {
|
|
|
46239
46410
|
}
|
|
46240
46411
|
|
|
46241
46412
|
export {
|
|
46413
|
+
isVendoredFinding,
|
|
46414
|
+
vendoredReason,
|
|
46242
46415
|
detectFramework,
|
|
46243
46416
|
detectPlatform,
|
|
46244
46417
|
calculateGrade,
|
|
@@ -46258,4 +46431,4 @@ export {
|
|
|
46258
46431
|
loadCachedProRules,
|
|
46259
46432
|
clearProRulesCache
|
|
46260
46433
|
};
|
|
46261
|
-
//# sourceMappingURL=chunk-
|
|
46434
|
+
//# sourceMappingURL=chunk-2VDAYFPC.js.map
|