@methiu/raigal 0.2.6 → 0.2.7
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/LICENSE +6 -1
- package/README.md +18 -23
- package/THIRD_PARTY_NOTICES.md +70 -0
- package/dist/cli.js +169 -70
- package/dist/index.js +168 -69
- package/dist/{json-DxIA7CCt.js → json-B2Kc7_rw.js} +1 -1
- package/dist/mcp.js +150 -53
- package/dist/{sarif-Cu7Rm1Ma.js → sarif-CpMse0iL.js} +1 -1
- package/dist/{version-DtOeK-2T.js → version-CXMYu17z.js} +1 -1
- package/package.json +7 -4
- package/scripts/check-tarball-keys.mjs +26 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as APP_VERSION } from "./version-
|
|
1
|
+
import { t as APP_VERSION } from "./version-CXMYu17z.js";
|
|
2
2
|
import { a as getEngineLabel, i as ENGINE_INFO, r as scoreImpactForRule, t as summarizeFindingAssessments } from "./finding-assessment-CGpt5YUR.js";
|
|
3
3
|
import { n as isSecretClassRule, r as maskSecrets, t as FIXED_SECRET_DIAGNOSTIC_MESSAGE } from "./mask-secrets-BXn-ZSEh.js";
|
|
4
4
|
import { createRequire, isBuiltin } from "node:module";
|
|
@@ -1357,7 +1357,28 @@ const isSafeRegularProjectFile = (rootDirectory, absolutePath) => {
|
|
|
1357
1357
|
return false;
|
|
1358
1358
|
}
|
|
1359
1359
|
};
|
|
1360
|
-
const
|
|
1360
|
+
const SECRET_CONFIG_EXTENSIONS = [
|
|
1361
|
+
".env",
|
|
1362
|
+
".yaml",
|
|
1363
|
+
".yml",
|
|
1364
|
+
".json",
|
|
1365
|
+
".toml",
|
|
1366
|
+
".ini",
|
|
1367
|
+
".properties",
|
|
1368
|
+
".tfvars"
|
|
1369
|
+
];
|
|
1370
|
+
const LOCKFILE_RE = /(?:^|\/)(?:package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|packages\.lock\.json|composer\.lock|bun\.lock)$/i;
|
|
1371
|
+
const isDotenvFile = (filePath) => {
|
|
1372
|
+
const base = path.basename(filePath).toLowerCase();
|
|
1373
|
+
return base === ".env" || base.startsWith(".env.");
|
|
1374
|
+
};
|
|
1375
|
+
const hasAllowedSourceExtension = (filePath, extraExtensions) => {
|
|
1376
|
+
const extension = path.extname(filePath);
|
|
1377
|
+
if (SOURCE_EXTENSIONS$1.has(extension)) return true;
|
|
1378
|
+
if (LOCKFILE_RE.test(filePath)) return false;
|
|
1379
|
+
if (extraExtensions.has(".env") && isDotenvFile(filePath)) return true;
|
|
1380
|
+
return extraExtensions.has(extension);
|
|
1381
|
+
};
|
|
1361
1382
|
const isInExcludedDirectory = (filePath, excludedDirectories) => {
|
|
1362
1383
|
const normalized = filePath.toLowerCase();
|
|
1363
1384
|
return excludedDirectories.some((directory) => normalized === directory || normalized.startsWith(`${directory}/`) || normalized.includes(`/${directory}/`));
|
|
@@ -2588,6 +2609,7 @@ const collectScanFileScope = (request) => {
|
|
|
2588
2609
|
projectFiles: [.../* @__PURE__ */ new Set([...projectSourceFiles, ...declarations])],
|
|
2589
2610
|
scoreFileCount: projectSourceFiles.length + projectTestFiles.length,
|
|
2590
2611
|
scopeLabel: candidates.label,
|
|
2612
|
+
secretFiles: filterEnumeratedProjectFiles(request.rootDirectory, candidates.files, [...SECRET_CONFIG_EXTENSIONS], request.excludePatterns, request.includePatterns),
|
|
2591
2613
|
testFiles
|
|
2592
2614
|
};
|
|
2593
2615
|
};
|
|
@@ -14807,13 +14829,19 @@ const SECRET_PATTERNS = [
|
|
|
14807
14829
|
},
|
|
14808
14830
|
{
|
|
14809
14831
|
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
14810
|
-
name: "AWS Access Key"
|
|
14832
|
+
name: "AWS Access Key",
|
|
14833
|
+
specific: true
|
|
14811
14834
|
},
|
|
14812
14835
|
{
|
|
14813
|
-
pattern: /(?:aws[_-]?secret|secret[_-]?key)\s*[:=]\s*["']([A-Za-z0-9/+=]{40})["']/gi,
|
|
14836
|
+
pattern: /(?:aws[_-]?secret(?:[_-]?access)?[_-]?key|secret[_-]?key)\s*[:=]\s*["']([A-Za-z0-9/+=]{40})["']/gi,
|
|
14814
14837
|
name: "AWS Secret Key",
|
|
14815
14838
|
keywordPrefixed: true
|
|
14816
14839
|
},
|
|
14840
|
+
{
|
|
14841
|
+
pattern: /\b(?:sk|rk)_live_[A-Za-z0-9]{20,}/g,
|
|
14842
|
+
name: "Stripe secret key",
|
|
14843
|
+
specific: true
|
|
14844
|
+
},
|
|
14817
14845
|
{
|
|
14818
14846
|
pattern: /(?:password|passwd|pwd|secret)\s*[:=]\s*["']([^"']{8,})["']/gi,
|
|
14819
14847
|
name: "Hardcoded password/secret",
|
|
@@ -14821,11 +14849,13 @@ const SECRET_PATTERNS = [
|
|
|
14821
14849
|
},
|
|
14822
14850
|
{
|
|
14823
14851
|
pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g,
|
|
14824
|
-
name: "Private key"
|
|
14852
|
+
name: "Private key",
|
|
14853
|
+
specific: true
|
|
14825
14854
|
},
|
|
14826
14855
|
{
|
|
14827
14856
|
pattern: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
|
|
14828
|
-
name: "JWT token"
|
|
14857
|
+
name: "JWT token",
|
|
14858
|
+
specific: true
|
|
14829
14859
|
},
|
|
14830
14860
|
{
|
|
14831
14861
|
pattern: /(?:token|bearer)\s*[:=]\s*["']([A-Za-z0-9_-]{20,})["']/gi,
|
|
@@ -14834,17 +14864,51 @@ const SECRET_PATTERNS = [
|
|
|
14834
14864
|
},
|
|
14835
14865
|
{
|
|
14836
14866
|
pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/g,
|
|
14837
|
-
name: "GitHub token"
|
|
14867
|
+
name: "GitHub token",
|
|
14868
|
+
specific: true
|
|
14838
14869
|
},
|
|
14839
14870
|
{
|
|
14840
14871
|
pattern: /xox[baprs]-[A-Za-z0-9-]+/g,
|
|
14841
|
-
name: "Slack token"
|
|
14872
|
+
name: "Slack token",
|
|
14873
|
+
specific: true
|
|
14842
14874
|
},
|
|
14843
14875
|
{
|
|
14844
14876
|
pattern: /(?:mongodb(?:\+srv)?|postgres|mysql|redis):\/\/[^:@/"'`\s]+:[^@"'`\s]+@[^"'`\s]+/gi,
|
|
14845
|
-
name: "Database connection string with credentials"
|
|
14877
|
+
name: "Database connection string with credentials",
|
|
14878
|
+
specific: true
|
|
14846
14879
|
}
|
|
14847
14880
|
];
|
|
14881
|
+
const FORMAT_PATTERNS = SECRET_PATTERNS.filter((p) => p.specific);
|
|
14882
|
+
const CONFIG_ASSIGNMENT_RE = /([A-Za-z0-9_.-]*(?:password|passwd|pwd|secret|token|api[_-]?key|apikey)[A-Za-z0-9_.-]*)["']?[ \t]*[:=][ \t]*["']?([^\s"'#,]+)/gi;
|
|
14883
|
+
const CONFIG_REFERENCE_KEY_RE = /(?:name|ref|path|file|arn|id|type|version|url|uri|length|count|env|header|field|prefix|policy|manager|store|provider)$/i;
|
|
14884
|
+
const CONFIG_REFERENCE_VALUE_RE = /^[<{$%]/;
|
|
14885
|
+
const CONFIG_PLACEHOLDER_VALUE_RE = /^(?:your|example|placeholder|redacted|dummy|xxx)/i;
|
|
14886
|
+
const VERSION_VALUE_RE = /^[~^<>=v]*\d+(?:\.\d+)+/;
|
|
14887
|
+
const CONFIG_LITERAL_VALUES = /* @__PURE__ */ new Set([
|
|
14888
|
+
"true",
|
|
14889
|
+
"false",
|
|
14890
|
+
"null",
|
|
14891
|
+
"none",
|
|
14892
|
+
"nil"
|
|
14893
|
+
]);
|
|
14894
|
+
const isConfigFile = (filePath) => isDotenvFile(filePath) || SECRET_CONFIG_EXTENSIONS.includes(path.extname(filePath).toLowerCase());
|
|
14895
|
+
const DOTENV_TEMPLATE_RE = /^\.env\.(?:example|sample|template|dist|defaults)$/i;
|
|
14896
|
+
const isLocalDatabaseUrl = (value) => {
|
|
14897
|
+
try {
|
|
14898
|
+
return LOCAL_DB_HOSTS.has(new URL(value).hostname.toLowerCase());
|
|
14899
|
+
} catch {
|
|
14900
|
+
return false;
|
|
14901
|
+
}
|
|
14902
|
+
};
|
|
14903
|
+
const configAssignmentName = (key) => {
|
|
14904
|
+
const lower = key.toLowerCase();
|
|
14905
|
+
if (/aws[_-]?secret/.test(lower)) return "AWS Secret Key";
|
|
14906
|
+
if (/api[_-]?key|apikey/.test(lower)) return "API key";
|
|
14907
|
+
if (/token/.test(lower) && !/password|passwd|pwd|secret/.test(lower)) return "Authentication token";
|
|
14908
|
+
return "Hardcoded password/secret";
|
|
14909
|
+
};
|
|
14910
|
+
const URL_VALUE_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
14911
|
+
const isConfigValueIgnorable = (key, value) => value.length < 8 || URL_VALUE_RE.test(value) || CONFIG_REFERENCE_KEY_RE.test(key) || CONFIG_REFERENCE_VALUE_RE.test(value) || CONFIG_PLACEHOLDER_VALUE_RE.test(value) || VERSION_VALUE_RE.test(value) || CONFIG_LITERAL_VALUES.has(value.toLowerCase()) || isNonProductionCredentialValue(value);
|
|
14848
14912
|
const isInsideStringLiteral = (content, matchIndex) => {
|
|
14849
14913
|
const lineStart = content.lastIndexOf("\n", matchIndex - 1) + 1;
|
|
14850
14914
|
const prefix = content.slice(lineStart, matchIndex);
|
|
@@ -14989,16 +15053,69 @@ const shouldSkipSecretFinding = (relativePath, name, lineText, matchedText) => {
|
|
|
14989
15053
|
if ((name === "Hardcoded password/secret" || name === "Authentication token") && isSymbolicConstantValue(matchedText)) return true;
|
|
14990
15054
|
return false;
|
|
14991
15055
|
};
|
|
15056
|
+
const collectSourceCandidates = (content, relativePath) => {
|
|
15057
|
+
const candidates = [];
|
|
15058
|
+
for (const { pattern, name, keywordPrefixed, specific } of SECRET_PATTERNS) for (const match of content.matchAll(new RegExp(pattern.source, pattern.flags))) {
|
|
15059
|
+
const matchedText = match[1] ?? match[0];
|
|
15060
|
+
const lineText = lineForMatch(content, match.index);
|
|
15061
|
+
if (isPlaceholderValue(matchedText)) continue;
|
|
15062
|
+
if (shouldSkipSecretFinding(relativePath, name, lineText, matchedText)) continue;
|
|
15063
|
+
if (keywordPrefixed && isInsideStringLiteral(content, match.index)) continue;
|
|
15064
|
+
candidates.push({
|
|
15065
|
+
name,
|
|
15066
|
+
start: match.index,
|
|
15067
|
+
end: match.index + match[0].length,
|
|
15068
|
+
specific: Boolean(specific)
|
|
15069
|
+
});
|
|
15070
|
+
}
|
|
15071
|
+
return candidates;
|
|
15072
|
+
};
|
|
15073
|
+
const collectConfigCandidates = (content, relativePath) => {
|
|
15074
|
+
const candidates = [];
|
|
15075
|
+
const template = DOTENV_TEMPLATE_RE.test(path.posix.basename(relativePath));
|
|
15076
|
+
for (const { pattern, name } of FORMAT_PATTERNS) for (const match of content.matchAll(new RegExp(pattern.source, pattern.flags))) {
|
|
15077
|
+
const lineText = lineForMatch(content, match.index);
|
|
15078
|
+
if (isPlaceholderValue(match[0])) continue;
|
|
15079
|
+
if (template && isLocalDatabaseUrl(match[0])) continue;
|
|
15080
|
+
if (shouldSkipSecretFinding(relativePath, name, lineText, match[0])) continue;
|
|
15081
|
+
candidates.push({
|
|
15082
|
+
name,
|
|
15083
|
+
start: match.index,
|
|
15084
|
+
end: match.index + match[0].length,
|
|
15085
|
+
specific: true
|
|
15086
|
+
});
|
|
15087
|
+
}
|
|
15088
|
+
for (const match of content.matchAll(new RegExp(CONFIG_ASSIGNMENT_RE.source, CONFIG_ASSIGNMENT_RE.flags))) {
|
|
15089
|
+
const [, key, value] = match;
|
|
15090
|
+
const name = configAssignmentName(key);
|
|
15091
|
+
const lineText = lineForMatch(content, match.index);
|
|
15092
|
+
if (isConfigValueIgnorable(key, value) || isPlaceholderValue(value)) continue;
|
|
15093
|
+
if (shouldSkipSecretFinding(relativePath, name, lineText, value)) continue;
|
|
15094
|
+
candidates.push({
|
|
15095
|
+
name,
|
|
15096
|
+
start: match.index,
|
|
15097
|
+
end: match.index + match[0].length,
|
|
15098
|
+
specific: false
|
|
15099
|
+
});
|
|
15100
|
+
}
|
|
15101
|
+
return candidates;
|
|
15102
|
+
};
|
|
15103
|
+
const dedupeCandidates = (candidates) => {
|
|
15104
|
+
const ordered = [...candidates].sort((a, b) => Number(b.specific) - Number(a.specific) || a.start - b.start);
|
|
15105
|
+
const kept = [];
|
|
15106
|
+
for (const candidate of ordered) {
|
|
15107
|
+
if (kept.some((k) => candidate.start < k.end && k.start < candidate.end)) continue;
|
|
15108
|
+
kept.push(candidate);
|
|
15109
|
+
}
|
|
15110
|
+
return kept.sort((a, b) => a.start - b.start);
|
|
15111
|
+
};
|
|
15112
|
+
const resolveSecretFiles = (context) => {
|
|
15113
|
+
const extras = [...SECRET_CONFIG_EXTENSIONS];
|
|
15114
|
+
return context.secretFiles ? filterExplicitFiles(context.rootDirectory, context.secretFiles, extras) : getSourceFilesWithExtras(context, extras);
|
|
15115
|
+
};
|
|
14992
15116
|
const scanSecrets = async (context) => {
|
|
14993
|
-
const files = getSourceFilesWithExtras(context, [
|
|
14994
|
-
".env",
|
|
14995
|
-
".yaml",
|
|
14996
|
-
".yml",
|
|
14997
|
-
".json",
|
|
14998
|
-
".toml"
|
|
14999
|
-
]);
|
|
15000
15117
|
const diagnostics = [];
|
|
15001
|
-
for (const filePath of
|
|
15118
|
+
for (const filePath of resolveSecretFiles(context)) {
|
|
15002
15119
|
let content;
|
|
15003
15120
|
try {
|
|
15004
15121
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -15007,30 +15124,20 @@ const scanSecrets = async (context) => {
|
|
|
15007
15124
|
}
|
|
15008
15125
|
content = maskComments(content, path.extname(filePath));
|
|
15009
15126
|
const relativePath = relativePosix(context.rootDirectory, filePath);
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
message: `Possible ${name} detected in source code`,
|
|
15025
|
-
help: "Move secrets to environment variables or a secrets manager",
|
|
15026
|
-
line,
|
|
15027
|
-
column: 0,
|
|
15028
|
-
category: "Security",
|
|
15029
|
-
fixable: false,
|
|
15030
|
-
redactSource: true
|
|
15031
|
-
});
|
|
15032
|
-
}
|
|
15033
|
-
}
|
|
15127
|
+
const candidates = isConfigFile(filePath) ? collectConfigCandidates(content, relativePath) : collectSourceCandidates(content, relativePath);
|
|
15128
|
+
for (const { name, start } of dedupeCandidates(candidates)) diagnostics.push({
|
|
15129
|
+
filePath: relativePath,
|
|
15130
|
+
engine: "security",
|
|
15131
|
+
rule: "security/hardcoded-secret",
|
|
15132
|
+
severity: "error",
|
|
15133
|
+
message: `Possible ${name} detected in source code`,
|
|
15134
|
+
help: "Move secrets to environment variables or a secrets manager",
|
|
15135
|
+
line: content.slice(0, start).split("\n").length,
|
|
15136
|
+
column: 0,
|
|
15137
|
+
category: "Security",
|
|
15138
|
+
fixable: false,
|
|
15139
|
+
redactSource: true
|
|
15140
|
+
});
|
|
15034
15141
|
}
|
|
15035
15142
|
return diagnostics;
|
|
15036
15143
|
};
|
|
@@ -16311,7 +16418,7 @@ const AllowedOwner = z.object({
|
|
|
16311
16418
|
login: z.string().max(100)
|
|
16312
16419
|
});
|
|
16313
16420
|
const EntitlementClaims = z.object({
|
|
16314
|
-
iss: z.literal(
|
|
16421
|
+
iss: z.literal("https://app.raigal.dev"),
|
|
16315
16422
|
sub: z.string(),
|
|
16316
16423
|
org_name: z.string(),
|
|
16317
16424
|
plan: z.enum(["trial", "active"]),
|
|
@@ -16419,14 +16526,6 @@ const fetchEntitlementFromApi = async (token, oidcToken, orgId) => {
|
|
|
16419
16526
|
const EMBEDDED_PUBLIC_KEYS = { rgl_key_2026_1: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA69B+1w8c4yJpEURW4Ps2b4BZcoIzeBg3jkCfIo2n2eM=\n-----END PUBLIC KEY-----" };
|
|
16420
16527
|
const keyObjectCache = /* @__PURE__ */ new Map();
|
|
16421
16528
|
const getPublicKey = (kid) => {
|
|
16422
|
-
if (process.env.RAIGAL_KEY_ID === kid && process.env.RAIGAL_PUBLIC_KEY) {
|
|
16423
|
-
const customKey = process.env.RAIGAL_PUBLIC_KEY.replace(/\\n/g, "\n");
|
|
16424
|
-
try {
|
|
16425
|
-
return createPublicKey(customKey);
|
|
16426
|
-
} catch {
|
|
16427
|
-
return null;
|
|
16428
|
-
}
|
|
16429
|
-
}
|
|
16430
16529
|
const cached = keyObjectCache.get(kid);
|
|
16431
16530
|
if (cached) return cached;
|
|
16432
16531
|
const pem = EMBEDDED_PUBLIC_KEYS[kid];
|
|
@@ -16486,9 +16585,8 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
16486
16585
|
const parsed = EntitlementClaims.safeParse(rawPayload);
|
|
16487
16586
|
if (!parsed.success) throw new JwtVerificationError(`Invalid entitlement claims: ${parsed.error.message}`, "invalid_claims");
|
|
16488
16587
|
const claims = parsed.data;
|
|
16489
|
-
|
|
16490
|
-
if (claims.
|
|
16491
|
-
if (!isVersionAtLeast("0.2.6", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
|
|
16588
|
+
if (claims.iss !== "https://app.raigal.dev") throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
|
|
16589
|
+
if (!isVersionAtLeast("0.2.7", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
|
|
16492
16590
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
16493
16591
|
const grace = options.allowGraceSeconds ?? 0;
|
|
16494
16592
|
if (now > claims.exp + grace) {
|
|
@@ -19829,22 +19927,10 @@ const printEngineStatus = (result) => {
|
|
|
19829
19927
|
}
|
|
19830
19928
|
};
|
|
19831
19929
|
//#endregion
|
|
19832
|
-
//#region src/commands/scan-hook-nudge.ts
|
|
19833
|
-
const shouldShowHookNudge = (ctx) => ctx.isTty && !ctx.isCi && ctx.installedAgentCount === 0;
|
|
19834
|
-
const buildHookNudge = (ctx) => {
|
|
19835
|
-
if (!shouldShowHookNudge(ctx)) return null;
|
|
19836
|
-
return [
|
|
19837
|
-
"",
|
|
19838
|
-
"Next: install the per-edit hook so your coding agent fixes slop before it lands.",
|
|
19839
|
-
ctx.invocation === "raigal" ? " raigal hook install" : " npm install -g @methiu/raigal && raigal hook install",
|
|
19840
|
-
""
|
|
19841
|
-
].join("\n");
|
|
19842
|
-
};
|
|
19843
|
-
//#endregion
|
|
19844
19930
|
//#region src/commands/scan-coverage.ts
|
|
19845
19931
|
const coverageReason = (c) => {
|
|
19846
19932
|
if (c.supportedFiles === 0 && c.dominantUnsupported) return `This repository is ${c.dominantUnsupported} (${c.unsupportedFiles} files), which Raigal does not analyze. No score — it would not reflect this code.`;
|
|
19847
|
-
if (c.supportedFiles === 0) return "No files in a language Raigal analyzes (TypeScript, JavaScript, Python, Go, Rust, Ruby, PHP,
|
|
19933
|
+
if (c.supportedFiles === 0) return "No files in a language Raigal analyzes (TypeScript, JavaScript, Python, Go, Rust, Ruby, PHP, C#, C/C++). Nothing to score.";
|
|
19848
19934
|
const lang = c.dominantUnsupported ?? "an unsupported language";
|
|
19849
19935
|
const files = `${c.supportedFiles} supported file${c.supportedFiles === 1 ? "" : "s"}`;
|
|
19850
19936
|
return `This repository is mostly ${lang} (${c.unsupportedFiles} files); Raigal analyzed only ${files}. Score withheld — it would represent a sliver of the codebase.`;
|
|
@@ -20045,6 +20131,18 @@ const computeScanExitCode = (opts) => {
|
|
|
20045
20131
|
*/
|
|
20046
20132
|
const computeCiExitCode = (opts) => opts.hasErrors || opts.scoreable && opts.score < opts.failBelow ? 1 : 0;
|
|
20047
20133
|
//#endregion
|
|
20134
|
+
//#region src/commands/scan-hook-nudge.ts
|
|
20135
|
+
const shouldShowHookNudge = (ctx) => ctx.isTty && !ctx.isCi && ctx.installedAgentCount === 0;
|
|
20136
|
+
const buildHookNudge = (ctx) => {
|
|
20137
|
+
if (!shouldShowHookNudge(ctx)) return null;
|
|
20138
|
+
return [
|
|
20139
|
+
"",
|
|
20140
|
+
"Next: install the per-edit hook so your coding agent fixes slop before it lands.",
|
|
20141
|
+
ctx.invocation === "raigal" ? " raigal hook install" : " npm install -g @methiu/raigal && raigal hook install",
|
|
20142
|
+
""
|
|
20143
|
+
].join("\n");
|
|
20144
|
+
};
|
|
20145
|
+
//#endregion
|
|
20048
20146
|
//#region src/output/rule-labels.ts
|
|
20049
20147
|
const RULE_LABELS = {
|
|
20050
20148
|
formatting: "Code not formatted",
|
|
@@ -20556,7 +20654,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
20556
20654
|
const projectName = projectInfo.projectName ?? "project";
|
|
20557
20655
|
const language = projectInfo.languages[0] ?? "unknown";
|
|
20558
20656
|
const printedHumanHeader = !machineOutput && showHeader;
|
|
20559
|
-
const { dependencyAuditFiles, dependencyAuditScope, files, projectFiles, scoreFileCount, scopeLabel, testFiles } = scanScope;
|
|
20657
|
+
const { dependencyAuditFiles, dependencyAuditScope, files, projectFiles, scoreFileCount, scopeLabel, secretFiles, testFiles } = scanScope;
|
|
20560
20658
|
const excludePatterns = [...config.exclude, ...readAislopIgnorePatterns(resolvedDir)];
|
|
20561
20659
|
const scanCoverage = deriveScanCoverage(projectInfo.coverage, scoreFileCount);
|
|
20562
20660
|
const reportProjectInfo = {
|
|
@@ -20597,6 +20695,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
20597
20695
|
excludePatterns,
|
|
20598
20696
|
testFiles,
|
|
20599
20697
|
projectFiles,
|
|
20698
|
+
secretFiles,
|
|
20600
20699
|
installedTools: projectInfo.installedTools,
|
|
20601
20700
|
config: engineConfig
|
|
20602
20701
|
}, config.engines, machineOutput)).map((result) => ({
|
|
@@ -20663,12 +20762,12 @@ const runScanBody = async (resolvedDir, config, options, projectInfo, scanScope,
|
|
|
20663
20762
|
scoreFileCount
|
|
20664
20763
|
});
|
|
20665
20764
|
if (options.sarif) {
|
|
20666
|
-
const { buildSarifLog } = await import("./sarif-
|
|
20765
|
+
const { buildSarifLog } = await import("./sarif-CpMse0iL.js");
|
|
20667
20766
|
console.log(JSON.stringify(buildSarifLog(results), null, 2));
|
|
20668
20767
|
return completion;
|
|
20669
20768
|
}
|
|
20670
20769
|
if (options.json) {
|
|
20671
|
-
const { buildJsonOutput } = await import("./json-
|
|
20770
|
+
const { buildJsonOutput } = await import("./json-B2Kc7_rw.js");
|
|
20672
20771
|
const jsonOut = buildJsonOutput(results, scoreResult, scoreFileCount, elapsedMs, scanCoverage);
|
|
20673
20772
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
20674
20773
|
return completion;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as APP_VERSION } from "./version-
|
|
1
|
+
import { t as APP_VERSION } from "./version-CXMYu17z.js";
|
|
2
2
|
import { i as ENGINE_INFO, n as withFindingAssessments, t as summarizeFindingAssessments } from "./finding-assessment-CGpt5YUR.js";
|
|
3
3
|
//#region src/output/json.ts
|
|
4
4
|
const buildJsonOutput = (results, scoreResult, fileCount, elapsedMs, coverage) => {
|
package/dist/mcp.js
CHANGED
|
@@ -84,7 +84,7 @@ const getActiveToken = () => {
|
|
|
84
84
|
};
|
|
85
85
|
//#endregion
|
|
86
86
|
//#region src/version.ts
|
|
87
|
-
const APP_VERSION = "0.2.
|
|
87
|
+
const APP_VERSION = "0.2.7";
|
|
88
88
|
//#endregion
|
|
89
89
|
//#region src/cloud/contract.ts
|
|
90
90
|
const Severity = z.enum(["error", "warning"]);
|
|
@@ -185,7 +185,7 @@ const AllowedOwner = z.object({
|
|
|
185
185
|
login: z.string().max(100)
|
|
186
186
|
});
|
|
187
187
|
const EntitlementClaims = z.object({
|
|
188
|
-
iss: z.literal(
|
|
188
|
+
iss: z.literal("https://app.raigal.dev"),
|
|
189
189
|
sub: z.string(),
|
|
190
190
|
org_name: z.string(),
|
|
191
191
|
plan: z.enum(["trial", "active"]),
|
|
@@ -293,14 +293,6 @@ const fetchEntitlementFromApi = async (token, oidcToken, orgId) => {
|
|
|
293
293
|
const EMBEDDED_PUBLIC_KEYS = { rgl_key_2026_1: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA69B+1w8c4yJpEURW4Ps2b4BZcoIzeBg3jkCfIo2n2eM=\n-----END PUBLIC KEY-----" };
|
|
294
294
|
const keyObjectCache = /* @__PURE__ */ new Map();
|
|
295
295
|
const getPublicKey = (kid) => {
|
|
296
|
-
if (process.env.RAIGAL_KEY_ID === kid && process.env.RAIGAL_PUBLIC_KEY) {
|
|
297
|
-
const customKey = process.env.RAIGAL_PUBLIC_KEY.replace(/\\n/g, "\n");
|
|
298
|
-
try {
|
|
299
|
-
return createPublicKey(customKey);
|
|
300
|
-
} catch {
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
296
|
const cached = keyObjectCache.get(kid);
|
|
305
297
|
if (cached) return cached;
|
|
306
298
|
const pem = EMBEDDED_PUBLIC_KEYS[kid];
|
|
@@ -360,9 +352,8 @@ const verifyEntitlementJwt = (jwt, options = {}) => {
|
|
|
360
352
|
const parsed = EntitlementClaims.safeParse(rawPayload);
|
|
361
353
|
if (!parsed.success) throw new JwtVerificationError(`Invalid entitlement claims: ${parsed.error.message}`, "invalid_claims");
|
|
362
354
|
const claims = parsed.data;
|
|
363
|
-
|
|
364
|
-
if (claims.
|
|
365
|
-
if (!isVersionAtLeast("0.2.6", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
|
|
355
|
+
if (claims.iss !== "https://app.raigal.dev") throw new JwtVerificationError(`Untrusted issuer: ${claims.iss}`, "invalid_issuer");
|
|
356
|
+
if (!isVersionAtLeast("0.2.7", claims.min_cli_version)) throw new JwtVerificationError(`CLI version ${APP_VERSION} is below required minimum ${claims.min_cli_version}. Run "raigal update" or "npm install -g @methiu/raigal@latest".`, "cli_version_unsupported");
|
|
366
357
|
const now = options.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
367
358
|
const grace = options.allowGraceSeconds ?? 0;
|
|
368
359
|
if (now > claims.exp + grace) {
|
|
@@ -1872,7 +1863,28 @@ const isSafeRegularProjectFile = (rootDirectory, absolutePath) => {
|
|
|
1872
1863
|
return false;
|
|
1873
1864
|
}
|
|
1874
1865
|
};
|
|
1875
|
-
const
|
|
1866
|
+
const SECRET_CONFIG_EXTENSIONS = [
|
|
1867
|
+
".env",
|
|
1868
|
+
".yaml",
|
|
1869
|
+
".yml",
|
|
1870
|
+
".json",
|
|
1871
|
+
".toml",
|
|
1872
|
+
".ini",
|
|
1873
|
+
".properties",
|
|
1874
|
+
".tfvars"
|
|
1875
|
+
];
|
|
1876
|
+
const LOCKFILE_RE = /(?:^|\/)(?:package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|packages\.lock\.json|composer\.lock|bun\.lock)$/i;
|
|
1877
|
+
const isDotenvFile = (filePath) => {
|
|
1878
|
+
const base = path.basename(filePath).toLowerCase();
|
|
1879
|
+
return base === ".env" || base.startsWith(".env.");
|
|
1880
|
+
};
|
|
1881
|
+
const hasAllowedSourceExtension = (filePath, extraExtensions) => {
|
|
1882
|
+
const extension = path.extname(filePath);
|
|
1883
|
+
if (SOURCE_EXTENSIONS$1.has(extension)) return true;
|
|
1884
|
+
if (LOCKFILE_RE.test(filePath)) return false;
|
|
1885
|
+
if (extraExtensions.has(".env") && isDotenvFile(filePath)) return true;
|
|
1886
|
+
return extraExtensions.has(extension);
|
|
1887
|
+
};
|
|
1876
1888
|
const isInExcludedDirectory = (filePath, excludedDirectories) => {
|
|
1877
1889
|
const normalized = filePath.toLowerCase();
|
|
1878
1890
|
return excludedDirectories.some((directory) => normalized === directory || normalized.startsWith(`${directory}/`) || normalized.includes(`/${directory}/`));
|
|
@@ -13796,13 +13808,19 @@ const SECRET_PATTERNS = [
|
|
|
13796
13808
|
},
|
|
13797
13809
|
{
|
|
13798
13810
|
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
13799
|
-
name: "AWS Access Key"
|
|
13811
|
+
name: "AWS Access Key",
|
|
13812
|
+
specific: true
|
|
13800
13813
|
},
|
|
13801
13814
|
{
|
|
13802
|
-
pattern: /(?:aws[_-]?secret|secret[_-]?key)\s*[:=]\s*["']([A-Za-z0-9/+=]{40})["']/gi,
|
|
13815
|
+
pattern: /(?:aws[_-]?secret(?:[_-]?access)?[_-]?key|secret[_-]?key)\s*[:=]\s*["']([A-Za-z0-9/+=]{40})["']/gi,
|
|
13803
13816
|
name: "AWS Secret Key",
|
|
13804
13817
|
keywordPrefixed: true
|
|
13805
13818
|
},
|
|
13819
|
+
{
|
|
13820
|
+
pattern: /\b(?:sk|rk)_live_[A-Za-z0-9]{20,}/g,
|
|
13821
|
+
name: "Stripe secret key",
|
|
13822
|
+
specific: true
|
|
13823
|
+
},
|
|
13806
13824
|
{
|
|
13807
13825
|
pattern: /(?:password|passwd|pwd|secret)\s*[:=]\s*["']([^"']{8,})["']/gi,
|
|
13808
13826
|
name: "Hardcoded password/secret",
|
|
@@ -13810,11 +13828,13 @@ const SECRET_PATTERNS = [
|
|
|
13810
13828
|
},
|
|
13811
13829
|
{
|
|
13812
13830
|
pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g,
|
|
13813
|
-
name: "Private key"
|
|
13831
|
+
name: "Private key",
|
|
13832
|
+
specific: true
|
|
13814
13833
|
},
|
|
13815
13834
|
{
|
|
13816
13835
|
pattern: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
|
|
13817
|
-
name: "JWT token"
|
|
13836
|
+
name: "JWT token",
|
|
13837
|
+
specific: true
|
|
13818
13838
|
},
|
|
13819
13839
|
{
|
|
13820
13840
|
pattern: /(?:token|bearer)\s*[:=]\s*["']([A-Za-z0-9_-]{20,})["']/gi,
|
|
@@ -13823,17 +13843,51 @@ const SECRET_PATTERNS = [
|
|
|
13823
13843
|
},
|
|
13824
13844
|
{
|
|
13825
13845
|
pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/g,
|
|
13826
|
-
name: "GitHub token"
|
|
13846
|
+
name: "GitHub token",
|
|
13847
|
+
specific: true
|
|
13827
13848
|
},
|
|
13828
13849
|
{
|
|
13829
13850
|
pattern: /xox[baprs]-[A-Za-z0-9-]+/g,
|
|
13830
|
-
name: "Slack token"
|
|
13851
|
+
name: "Slack token",
|
|
13852
|
+
specific: true
|
|
13831
13853
|
},
|
|
13832
13854
|
{
|
|
13833
13855
|
pattern: /(?:mongodb(?:\+srv)?|postgres|mysql|redis):\/\/[^:@/"'`\s]+:[^@"'`\s]+@[^"'`\s]+/gi,
|
|
13834
|
-
name: "Database connection string with credentials"
|
|
13856
|
+
name: "Database connection string with credentials",
|
|
13857
|
+
specific: true
|
|
13835
13858
|
}
|
|
13836
13859
|
];
|
|
13860
|
+
const FORMAT_PATTERNS = SECRET_PATTERNS.filter((p) => p.specific);
|
|
13861
|
+
const CONFIG_ASSIGNMENT_RE = /([A-Za-z0-9_.-]*(?:password|passwd|pwd|secret|token|api[_-]?key|apikey)[A-Za-z0-9_.-]*)["']?[ \t]*[:=][ \t]*["']?([^\s"'#,]+)/gi;
|
|
13862
|
+
const CONFIG_REFERENCE_KEY_RE = /(?:name|ref|path|file|arn|id|type|version|url|uri|length|count|env|header|field|prefix|policy|manager|store|provider)$/i;
|
|
13863
|
+
const CONFIG_REFERENCE_VALUE_RE = /^[<{$%]/;
|
|
13864
|
+
const CONFIG_PLACEHOLDER_VALUE_RE = /^(?:your|example|placeholder|redacted|dummy|xxx)/i;
|
|
13865
|
+
const VERSION_VALUE_RE = /^[~^<>=v]*\d+(?:\.\d+)+/;
|
|
13866
|
+
const CONFIG_LITERAL_VALUES = /* @__PURE__ */ new Set([
|
|
13867
|
+
"true",
|
|
13868
|
+
"false",
|
|
13869
|
+
"null",
|
|
13870
|
+
"none",
|
|
13871
|
+
"nil"
|
|
13872
|
+
]);
|
|
13873
|
+
const isConfigFile = (filePath) => isDotenvFile(filePath) || SECRET_CONFIG_EXTENSIONS.includes(path.extname(filePath).toLowerCase());
|
|
13874
|
+
const DOTENV_TEMPLATE_RE = /^\.env\.(?:example|sample|template|dist|defaults)$/i;
|
|
13875
|
+
const isLocalDatabaseUrl = (value) => {
|
|
13876
|
+
try {
|
|
13877
|
+
return LOCAL_DB_HOSTS.has(new URL(value).hostname.toLowerCase());
|
|
13878
|
+
} catch {
|
|
13879
|
+
return false;
|
|
13880
|
+
}
|
|
13881
|
+
};
|
|
13882
|
+
const configAssignmentName = (key) => {
|
|
13883
|
+
const lower = key.toLowerCase();
|
|
13884
|
+
if (/aws[_-]?secret/.test(lower)) return "AWS Secret Key";
|
|
13885
|
+
if (/api[_-]?key|apikey/.test(lower)) return "API key";
|
|
13886
|
+
if (/token/.test(lower) && !/password|passwd|pwd|secret/.test(lower)) return "Authentication token";
|
|
13887
|
+
return "Hardcoded password/secret";
|
|
13888
|
+
};
|
|
13889
|
+
const URL_VALUE_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
13890
|
+
const isConfigValueIgnorable = (key, value) => value.length < 8 || URL_VALUE_RE.test(value) || CONFIG_REFERENCE_KEY_RE.test(key) || CONFIG_REFERENCE_VALUE_RE.test(value) || CONFIG_PLACEHOLDER_VALUE_RE.test(value) || VERSION_VALUE_RE.test(value) || CONFIG_LITERAL_VALUES.has(value.toLowerCase()) || isNonProductionCredentialValue(value);
|
|
13837
13891
|
const isInsideStringLiteral = (content, matchIndex) => {
|
|
13838
13892
|
const lineStart = content.lastIndexOf("\n", matchIndex - 1) + 1;
|
|
13839
13893
|
const prefix = content.slice(lineStart, matchIndex);
|
|
@@ -13978,16 +14032,69 @@ const shouldSkipSecretFinding = (relativePath, name, lineText, matchedText) => {
|
|
|
13978
14032
|
if ((name === "Hardcoded password/secret" || name === "Authentication token") && isSymbolicConstantValue(matchedText)) return true;
|
|
13979
14033
|
return false;
|
|
13980
14034
|
};
|
|
14035
|
+
const collectSourceCandidates = (content, relativePath) => {
|
|
14036
|
+
const candidates = [];
|
|
14037
|
+
for (const { pattern, name, keywordPrefixed, specific } of SECRET_PATTERNS) for (const match of content.matchAll(new RegExp(pattern.source, pattern.flags))) {
|
|
14038
|
+
const matchedText = match[1] ?? match[0];
|
|
14039
|
+
const lineText = lineForMatch(content, match.index);
|
|
14040
|
+
if (isPlaceholderValue(matchedText)) continue;
|
|
14041
|
+
if (shouldSkipSecretFinding(relativePath, name, lineText, matchedText)) continue;
|
|
14042
|
+
if (keywordPrefixed && isInsideStringLiteral(content, match.index)) continue;
|
|
14043
|
+
candidates.push({
|
|
14044
|
+
name,
|
|
14045
|
+
start: match.index,
|
|
14046
|
+
end: match.index + match[0].length,
|
|
14047
|
+
specific: Boolean(specific)
|
|
14048
|
+
});
|
|
14049
|
+
}
|
|
14050
|
+
return candidates;
|
|
14051
|
+
};
|
|
14052
|
+
const collectConfigCandidates = (content, relativePath) => {
|
|
14053
|
+
const candidates = [];
|
|
14054
|
+
const template = DOTENV_TEMPLATE_RE.test(path.posix.basename(relativePath));
|
|
14055
|
+
for (const { pattern, name } of FORMAT_PATTERNS) for (const match of content.matchAll(new RegExp(pattern.source, pattern.flags))) {
|
|
14056
|
+
const lineText = lineForMatch(content, match.index);
|
|
14057
|
+
if (isPlaceholderValue(match[0])) continue;
|
|
14058
|
+
if (template && isLocalDatabaseUrl(match[0])) continue;
|
|
14059
|
+
if (shouldSkipSecretFinding(relativePath, name, lineText, match[0])) continue;
|
|
14060
|
+
candidates.push({
|
|
14061
|
+
name,
|
|
14062
|
+
start: match.index,
|
|
14063
|
+
end: match.index + match[0].length,
|
|
14064
|
+
specific: true
|
|
14065
|
+
});
|
|
14066
|
+
}
|
|
14067
|
+
for (const match of content.matchAll(new RegExp(CONFIG_ASSIGNMENT_RE.source, CONFIG_ASSIGNMENT_RE.flags))) {
|
|
14068
|
+
const [, key, value] = match;
|
|
14069
|
+
const name = configAssignmentName(key);
|
|
14070
|
+
const lineText = lineForMatch(content, match.index);
|
|
14071
|
+
if (isConfigValueIgnorable(key, value) || isPlaceholderValue(value)) continue;
|
|
14072
|
+
if (shouldSkipSecretFinding(relativePath, name, lineText, value)) continue;
|
|
14073
|
+
candidates.push({
|
|
14074
|
+
name,
|
|
14075
|
+
start: match.index,
|
|
14076
|
+
end: match.index + match[0].length,
|
|
14077
|
+
specific: false
|
|
14078
|
+
});
|
|
14079
|
+
}
|
|
14080
|
+
return candidates;
|
|
14081
|
+
};
|
|
14082
|
+
const dedupeCandidates = (candidates) => {
|
|
14083
|
+
const ordered = [...candidates].sort((a, b) => Number(b.specific) - Number(a.specific) || a.start - b.start);
|
|
14084
|
+
const kept = [];
|
|
14085
|
+
for (const candidate of ordered) {
|
|
14086
|
+
if (kept.some((k) => candidate.start < k.end && k.start < candidate.end)) continue;
|
|
14087
|
+
kept.push(candidate);
|
|
14088
|
+
}
|
|
14089
|
+
return kept.sort((a, b) => a.start - b.start);
|
|
14090
|
+
};
|
|
14091
|
+
const resolveSecretFiles = (context) => {
|
|
14092
|
+
const extras = [...SECRET_CONFIG_EXTENSIONS];
|
|
14093
|
+
return context.secretFiles ? filterExplicitFiles(context.rootDirectory, context.secretFiles, extras) : getSourceFilesWithExtras(context, extras);
|
|
14094
|
+
};
|
|
13981
14095
|
const scanSecrets = async (context) => {
|
|
13982
|
-
const files = getSourceFilesWithExtras(context, [
|
|
13983
|
-
".env",
|
|
13984
|
-
".yaml",
|
|
13985
|
-
".yml",
|
|
13986
|
-
".json",
|
|
13987
|
-
".toml"
|
|
13988
|
-
]);
|
|
13989
14096
|
const diagnostics = [];
|
|
13990
|
-
for (const filePath of
|
|
14097
|
+
for (const filePath of resolveSecretFiles(context)) {
|
|
13991
14098
|
let content;
|
|
13992
14099
|
try {
|
|
13993
14100
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -13996,30 +14103,20 @@ const scanSecrets = async (context) => {
|
|
|
13996
14103
|
}
|
|
13997
14104
|
content = maskComments(content, path.extname(filePath));
|
|
13998
14105
|
const relativePath = relativePosix(context.rootDirectory, filePath);
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14012
|
-
|
|
14013
|
-
message: `Possible ${name} detected in source code`,
|
|
14014
|
-
help: "Move secrets to environment variables or a secrets manager",
|
|
14015
|
-
line,
|
|
14016
|
-
column: 0,
|
|
14017
|
-
category: "Security",
|
|
14018
|
-
fixable: false,
|
|
14019
|
-
redactSource: true
|
|
14020
|
-
});
|
|
14021
|
-
}
|
|
14022
|
-
}
|
|
14106
|
+
const candidates = isConfigFile(filePath) ? collectConfigCandidates(content, relativePath) : collectSourceCandidates(content, relativePath);
|
|
14107
|
+
for (const { name, start } of dedupeCandidates(candidates)) diagnostics.push({
|
|
14108
|
+
filePath: relativePath,
|
|
14109
|
+
engine: "security",
|
|
14110
|
+
rule: "security/hardcoded-secret",
|
|
14111
|
+
severity: "error",
|
|
14112
|
+
message: `Possible ${name} detected in source code`,
|
|
14113
|
+
help: "Move secrets to environment variables or a secrets manager",
|
|
14114
|
+
line: content.slice(0, start).split("\n").length,
|
|
14115
|
+
column: 0,
|
|
14116
|
+
category: "Security",
|
|
14117
|
+
fixable: false,
|
|
14118
|
+
redactSource: true
|
|
14119
|
+
});
|
|
14023
14120
|
}
|
|
14024
14121
|
return diagnostics;
|
|
14025
14122
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as APP_VERSION } from "./version-
|
|
1
|
+
import { t as APP_VERSION } from "./version-CXMYu17z.js";
|
|
2
2
|
import { n as isSecretClassRule, t as FIXED_SECRET_DIAGNOSTIC_MESSAGE } from "./mask-secrets-BXn-ZSEh.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
//#region src/output/sarif.ts
|