one
Version:
One is a new React Framework that makes Vite serve both native and web.
130 lines (129 loc) • 4.63 kB
JavaScript
import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path";
const SECRET_PATTERNS = [
// api tokens / keys with known prefixes
["Anthropic API key", /sk-ant-[A-Za-z0-9_-]{20,}/g], ["OpenAI API key", /sk-proj-[A-Za-z0-9_-]{20,}/g], ["Stripe secret key", /sk_live_[A-Za-z0-9]{20,}/g], ["Stripe webhook secret", /whsec_[A-Za-z0-9]{20,}/g], ["GitHub token", /gh[ps]_[A-Za-z0-9]{36,}/g], ["GitHub PAT", /github_pat_[A-Za-z0-9_]{20,}/g], ["AWS access key", /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g], ["Postmark server token", /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g], ["Bearer token assignment", /["']Bearer\s+[A-Za-z0-9_.-]{20,}["']/g], ["Generic secret assignment", /(?:secret|password|api_key|apikey)\s*[:=]\s*["'][A-Za-z0-9_-]{16,}["']/gi],
// known env var names that should never be inlined
["BETTER_AUTH_SECRET value", /BETTER_AUTH_SECRET["']\s*[:=]\s*["'][^"']+["']/g], ["POSTMARK_SERVER_TOKEN value", /POSTMARK_SERVER_TOKEN["']\s*[:=]\s*["'][^"']+["']/g], ["ANTHROPIC_API_KEY value", /ANTHROPIC_API_KEY["']\s*[:=]\s*["'][^"']+["']/g]];
const SAFE_UUIDS = /* @__PURE__ */new Set(["00000000-0000-0000-0000-000000000000", "09259e3b-7be8-46f6-9801-106bf1866e1c",
// WebRTC SDP
"4ad15a19-80e2-4105-bf43-48039fd2963e"
// WebRTC SDP
]);
const BUILTIN_SAFE_PATTERNS = [/sk_live_your_/, /sk_live_your_key/, /rk_\w+_\w+/,
// tamagui theme tokens
/sk_all_element/,
// DOM property names
/sk_personal_data/
// analytics property names
];
function createSafeMatcher(userPatterns) {
const safeStrings = new Set(SAFE_UUIDS);
const safeRegexes = [...BUILTIN_SAFE_PATTERNS];
if (userPatterns) {
for (const p of userPatterns) {
if (typeof p === "string") {
safeStrings.add(p);
} else {
safeRegexes.push(p);
}
}
}
return match => {
if (safeStrings.has(match)) return true;
return safeRegexes.some(p => p.test(match));
};
}
async function collectJSFiles(dir) {
const files = [];
async function walk(currentDir) {
let entries;
try {
entries = await readdir(currentDir, {
withFileTypes: true
});
} catch {
return;
}
for (const entry of entries) {
const fullPath = join(currentDir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
} else if (entry.name.endsWith(".js")) {
files.push(fullPath);
}
}
}
await walk(dir);
return files;
}
async function scanBundleForSecrets(distDir, userSafePatterns) {
const isSafe = createSafeMatcher(userSafePatterns);
const files = await collectJSFiles(distDir);
const findings = [];
for (const fullPath of files) {
const relativePath = fullPath.slice(distDir.length + 1);
let content;
try {
content = await readFile(fullPath, "utf-8");
} catch {
continue;
}
for (const [label, pattern] of SECRET_PATTERNS) {
pattern.lastIndex = 0;
let match;
while ((match = pattern.exec(content)) !== null) {
const matched = match[0];
if (isSafe(matched)) continue;
const beforeMatch = content.slice(0, match.index);
const line = (beforeMatch.match(/\n/g)?.length ?? 0) + 1;
findings.push({
file: relativePath,
label,
match: matched.length > 40 ? `${matched.slice(0, 20)}...${matched.slice(-10)}` : matched,
line
});
}
}
}
return {
clean: findings.length === 0,
findings
};
}
async function runSecurityScan(clientDir, level, safePatterns) {
console.info(`
\u{1F512} scanning client bundles for leaked secrets...
`);
const {
clean,
findings
} = await scanBundleForSecrets(clientDir, safePatterns);
if (clean) {
console.info(`
\u{1F512} security scan passed \u2014 no secrets found
`);
return true;
}
const icon = level === "error" ? "\u{1F6A8}" : "\u26A0\uFE0F";
const header = level === "error" ? `${icon} ${findings.length} secret(s) leaked into client bundle:` : `${icon} ${findings.length} potential secret(s) found in client bundle:`;
console.error(`
${header}
`);
for (const f of findings) {
console.error(` ${f.label}`);
console.error(` file: ${f.file}:${f.line}`);
console.error(` match: ${f.match}
`);
}
if (level === "error") {
console.error(` Set build.securityScan to 'warn' to continue building despite findings.
`);
return false;
}
console.warn(` Set build.securityScan to 'error' to fail builds when secrets are detected.
`);
return true;
}
export { runSecurityScan, scanBundleForSecrets };
//# sourceMappingURL=securityScan.mjs.map