背景
Structiveでは <script type="importmap"> を使う構成を取っているが、本番環境で Content-Security-Policy (CSP) を適用する際に、インラインスクリプトがブロックされる可能性がある。
importmap は仕様上、外部ファイル(src指定)には対応しておらず、HTML内にインラインで埋め込む必要があるため、CSP制限との両立が課題となる。
方針(CSP対応方針)
- ✅
importmap は HTML内にインラインで記述する
- ✅ CSP制限を回避するために、
nonce を付与して許可する
- ✅
nonce はリクエストごとに ランダムに生成される一時トークン
- ✅ 同じ
nonce を HTMLスクリプトタグの nonce 属性と CSPヘッダー の両方に含める必要がある
実装例(Express.js + EJS)
✅ サーバー側:nonce を生成し、CSPに埋め込む
const crypto = require("crypto");
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString("base64");
res.locals.nonce = nonce;
res.setHeader("Content-Security-Policy",
`script-src 'self' 'nonce-${nonce}'`
);
next();
});
✅ テンプレート側(EJSなど)
<!-- importmapにnonceを適用 -->
<script type="importmap" nonce="<%= nonce %>">
{
"imports": {
"structive": "https://cdn.jsdelivr.net/npm/structive@0.1.0/dist/structive.min.js"
}
}
</script>
<!-- モジュール実行にも同じnonceを適用 -->
<script type="module" nonce="<%= nonce %>">
import { defineComponents } from "structive";
defineComponents({ "my-comp": "myComponent" });
</script>
注意点
- ❌ nonce を固定値にするのはNG(XSS耐性が失われる)
- ❌ script-src 'unsafe-inline' に頼る構成は避ける
- ✅ nonce はリクエストごとに動的に発行されるべき
- ✅ テンプレートとヘッダーの両方で同じnonceを使用すること
今後の検討事項
- Vite / Astro / 静的HTML生成環境での対応パターン
- importmapをテンプレートエンジンで自動埋め込みできる仕組み
- GitHub Pagesでの限定的代替(現状はCSP設定不可)
備考
このIssueはStructiveの本番運用に向けたセキュリティ強化対応の備忘録です。
必要に応じてWikiや docs/security.md に昇格予定。
背景
Structiveでは
<script type="importmap">を使う構成を取っているが、本番環境でContent-Security-Policy (CSP)を適用する際に、インラインスクリプトがブロックされる可能性がある。importmapは仕様上、外部ファイル(src指定)には対応しておらず、HTML内にインラインで埋め込む必要があるため、CSP制限との両立が課題となる。方針(CSP対応方針)
importmapは HTML内にインラインで記述するnonceを付与して許可するnonceはリクエストごとに ランダムに生成される一時トークンnonceを HTMLスクリプトタグのnonce属性とCSPヘッダーの両方に含める必要がある実装例(Express.js + EJS)
✅ サーバー側:
nonceを生成し、CSPに埋め込む✅ テンプレート側(EJSなど)
注意点
今後の検討事項
備考
このIssueはStructiveの本番運用に向けたセキュリティ強化対応の備忘録です。
必要に応じてWikiや docs/security.md に昇格予定。