0e4c2ebef4
Squash-merge the cloudflare-migration branch (7 commits by Kai ki) into staging with conflict resolution, feature integration, and bug fixes. Engine: - Paradigm D: single-stream Writer replacing dual-phase Plan/Beats - Delete Architect agent; story bible generated via Writer <plan> tag - Modular prompt architecture (segments/registry/builder) - StreamRouter for tagged stream splitting (<plan>/<story>/<choices>) Infrastructure: - Cloudflare Workers deployment (wrangler.jsonc, OpenNext adapter) - D1 database schema + Drizzle ORM (scaffolded, not yet active) - R2 storage helpers (scaffolded, not yet active) - Story persistence API routes + client-side persistence BYOK (Bring Your Own Key): - /api/llm/user-proxy with SSRF-protected LLM proxy (+ requireUser auth) - CORS-aware fetch in ai-client: auto-detect CORS failure, fallback to server proxy transparently via OpenAI SDK custom fetch - BYO config support added to classify-freeform and vision routes - SettingsModal CORS privacy notice (keys never logged/stored) SSE streaming: - engineClient.ts: fetchSSE helper for progressive scene events - startSession/requestScene accept optional emit callback - Fix SSE error event field name (error → message) in scene/start routes i18n integration: - Wire buildLanguageDirective into paradigm D's prompt builder - Update corsNotice i18n keys (zh-CN/en/ja) with CORS proxy privacy text - Preserve Session.language + LanguageSwitcher from i18n commit Co-authored-by: Kai ki <155355644+zbf1009@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
111 lines
2.8 KiB
JavaScript
111 lines
2.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Bundle Secret Scanner
|
|
* Scans Next.js production build artifacts for leaked prompt secrets.
|
|
* Usage: node scripts/scan-bundle-secrets.mjs
|
|
* Exit 0 if clean, exit 1 if secrets found (for CI).
|
|
*/
|
|
|
|
import { readFileSync, readdirSync, statSync } from "fs";
|
|
import { join } from "path";
|
|
|
|
// Critical prompt constant names that MUST NOT appear in client bundle
|
|
const SECRET_PATTERNS = [
|
|
"CHARACTER_WRITER_SYSTEM",
|
|
"CHARACTER_DESIGNER_SYSTEM",
|
|
"CINEMATOGRAPHER_SYSTEM",
|
|
"ARCHITECT_SYSTEM",
|
|
"WRITER_PLAN_SYSTEM",
|
|
"WRITER_BEATS_SYSTEM",
|
|
"VOICE_DESIGNER_SYSTEM",
|
|
"FREEFORM_CLASSIFY_SYSTEM",
|
|
"loadEngineConfig", // config.ts function should not leak
|
|
];
|
|
|
|
// Directories to scan (Next.js client bundle output)
|
|
const SCAN_DIRS = [
|
|
".next/static/chunks", // Client-side JS chunks
|
|
".next/static/css", // CSS bundles (shouldn't have JS, but scan anyway)
|
|
];
|
|
|
|
/**
|
|
* Recursively scan directory for files
|
|
*/
|
|
function* walkDir(dir) {
|
|
try {
|
|
const entries = readdirSync(dir);
|
|
for (const entry of entries) {
|
|
const fullPath = join(dir, entry);
|
|
const stat = statSync(fullPath);
|
|
if (stat.isDirectory()) {
|
|
yield* walkDir(fullPath);
|
|
} else if (stat.isFile() && /\.(js|css)$/i.test(entry)) {
|
|
yield fullPath;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
// Directory might not exist yet (e.g. fresh clone before build)
|
|
if (err.code !== "ENOENT") throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scan a single file for secret patterns
|
|
*/
|
|
function scanFile(filePath) {
|
|
const content = readFileSync(filePath, "utf8");
|
|
const found = [];
|
|
|
|
for (const pattern of SECRET_PATTERNS) {
|
|
if (content.includes(pattern)) {
|
|
found.push(pattern);
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* Main scanner
|
|
*/
|
|
function main() {
|
|
console.log("🔍 Scanning Next.js client bundles for leaked secrets...\n");
|
|
|
|
let totalFiles = 0;
|
|
let leaksFound = false;
|
|
const leakReport = [];
|
|
|
|
for (const dir of SCAN_DIRS) {
|
|
for (const filePath of walkDir(dir)) {
|
|
totalFiles++;
|
|
const secrets = scanFile(filePath);
|
|
if (secrets.length > 0) {
|
|
leaksFound = true;
|
|
leakReport.push({ file: filePath, secrets });
|
|
}
|
|
}
|
|
}
|
|
|
|
if (leaksFound) {
|
|
console.error("❌ SECRET LEAK DETECTED!\n");
|
|
for (const { file, secrets } of leakReport) {
|
|
console.error(` File: ${file}`);
|
|
console.error(` Leaked: ${secrets.join(", ")}\n`);
|
|
}
|
|
console.error(
|
|
"Fix: Ensure lib/engine/prompts.ts and lib/config.ts have 'import \"server-only\"' at the top."
|
|
);
|
|
console.error(
|
|
" Verify no client components import these modules (directly or transitively).\n"
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`✅ No secrets found in ${totalFiles} client bundle files.`);
|
|
console.log(" Prompt isolation is intact.\n");
|
|
process.exit(0);
|
|
}
|
|
|
|
main();
|