unsafe-inline
、unsafe-eval
為了最佳化 CSP,我們設計一個 CSP Agent,自動完成以下工作:
先建立一個 Express 伺服器,並設定一個最簡單的 CSP:只允許本站資源。
import express from "express";
const app = express();
app.use(express.json());
let currentCSP = "default-src 'self';"; // 初始策略
middleware 會在每次回應時,自動加上最新的 CSP header
app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", currentCSP);
next();
});
頁面裡故意放了一個 inline script,用來測試 CSP 是否會阻擋它
app.get("/", (req, res) => {
res.send(`
<html>
<head><title>CSP Test</title></head>
<body>
<h1>Hello CSP</h1>
<script>
console.log("Inline script executed");
</script>
</body>
</html>
`);
});
提供一個 API,讓我們的 Agent 可以動態更新 CSP
app.post("/set-csp", (req, res) => {
currentCSP = req.body.csp || currentCSP;
res.json({ message: "CSP updated", csp: currentCSP });
});
app.listen(3000, () => {
console.log("Server running on http://localhost:3000");
});
先設定一個 非常嚴格的 CSP:
default-src 'self'
)script-src 'self'
)import puppeteer from "puppeteer";
import fetch from "node-fetch";
import { GoogleGenerativeAI } from "@google/generative-ai";
import 'dotenv/config';
let cspPolicy = "default-src 'self'; script-src 'self';";
透過 fetch 呼叫伺服器提供的 /set-csp API,把新的 CSP 策略傳送過去,讓 Express 伺服器即時套用。
async function applyCSP(csp) {
await fetch("http://localhost:3000/set-csp", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ csp }),
});
}
利用 Puppeteer 模擬瀏覽器行為,開啟測試頁面並收集 console log。透過檢查 log 訊息,我們可以判斷 CSP 是否阻擋了 inline script 的執行。
async function testCSP() {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const logs = [];
page.on("console", (msg) => logs.push(msg.text()));
await page.goto("http://localhost:3000");
await browser.close();
return logs;
}
將瀏覽器回傳的錯誤訊息丟給 Gemini 模型,請 AI 判斷問題並生成新的 CSP 策略。
程式會自動清理回傳內容,只保留實際可套用的策略字串(例如 script-src 'self' 'nonce-xxxxxx';)。
async function analyzeErrorWithAI(logs) {
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
const prompt = `
以下是瀏覽器的 CSP 錯誤訊息:
${logs.join("\n")}
請判斷要怎麼修正策略,並輸出一份新的 Content-Security-Policy。
請只輸出一行 CSP,格式例如:
script-src 'self' 'nonce-xxxxxx';
`;
const result = await model.generateContent(prompt);
let policy = result.response.text().trim();
// 移除多餘的前綴
policy = policy.replace(/^Content-Security-Policy:\s*/i, "");
return policy;
}
(async () => {
console.log("[Agent] 套用初始 CSP:", cspPolicy);
await applyCSP(cspPolicy);
let logs = await testCSP();
console.log("[Browser Logs]", logs);
if (logs.some(log => log.includes("Refused to execute inline script"))) {
console.log("[Agent] 偵測到 CSP 問題,呼叫 Gemini AI 分析中...");
const newCsp = await analyzeErrorWithAI(logs);
console.log("[AI 建議的 CSP]:", newCsp);
await applyCSP(newCsp);
logs = await testCSP();
console.log("[Browser Logs after fix]", logs);
}
})();
[Agent] 套用初始 CSP: default-src 'self'; script-src 'self';
[Browser Logs] [
Refused to execute inline script because it violates the following Content Security Policy directive...
]
inline script 被阻擋,功能壞掉
[Agent] 偵測到 CSP 問題,呼叫 Gemini AI 分析中...
[AI 建議的 CSP]: script-src 'self' 'unsafe-inline';
[Browser Logs after fix] [ 'Inline script executed' ]
Gemini 生成的新策略允許了 unsafe-inline
,因此 inline script
成功執行。
不過要注意:unsafe-inline 雖然解決了錯誤,但安全性相對較低,實務上建議改用
nonce
或 hash
方式來允許特定 script。