Files
infiplot-web/scripts/scan-bundle-secrets.mjs
Zonghao Yuan 0e4c2ebef4 feat(engine): merge cloudflare-migration — paradigm D engine, BYOK proxy, story persistence (#95)
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>
2026-06-18 18:05:38 +08:00

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();