この記事は約 5 分で読めます。
筆者プロフィール: ソフトウェアエンジニア。「知った気にならない。いつまでも学び続ける」を信条に、業務と個人開発の両輪で技術を磨いています。AI 駆動開発で複数の個人開発アプリを構築・運用中。
👉 ポートフォリオ: 筆者ホームページ
個人開発の SaaS で 「セキュリティスコア 90/100」を CI で物理的に強制 する運用を整理します。運用中の SaaS 「たすきば Knowledge Relay」 で 6 ヶ月運用して、severity-1 リスクの再発をゼロに保っている仕組みです。
サービスの機能紹介・画面イメージ・コンセプトは公式プロダクトページをご覧ください。
👉 たすきば Knowledge Relay — 公式プロダクトページ
セキュリティスコアとは
たすきばの CI ワークフロー (.github/workflows/security.yml) が出力する 100 点満点の評価値。
| 採点項目 (例) | 配点 |
|---|---|
依存パッケージの脆弱性 (pnpm audit) |
-10 / Critical |
| Secret スキャン (TruffleHog / Gitleaks) | -20 / 検出 |
| Prisma クエリの tenant_id フィルタ漏れ | -10 / 件 |
| CSP / セキュリティヘッダ設定 | -5 / 不備 |
| 認証関連の単体テスト合格率 | -3 / 落ちたケース |
| HTML 安全性 (危険な API の利用検知) | -5 / 件 |
環境変数の漏洩 (.env.local の commit) |
-50 (即マージ禁止) |
合計が 100 点満点。各 PR で計算されます。
1. なぜ "スコア化" するのか
セキュリティチェックを個別に CI ジョブとして並べることもできますが、
| 個別ジョブ方式 | 弱点 |
|---|---|
| pass / fail の二択 | 警告レベル を表現できない |
| 優先度が見えない | どのジョブが致命的か分かりづらい |
| 全体感が見えない | 「全体としてどれだけ安全か」が一目で分からない |
スコア化することで、
- 「今 PR は 92 点なので、軽微な警告はあるがマージ可」
- 「今 PR は 87 点なので、深刻な問題があり修正必要」
のように、判断材料を 1 つの数字に集約 できます。
2. 90 点を下回ったらマージ禁止
GitHub の Branch protection rules で、security-score ジョブを required にしています。
# .github/workflows/security.yml
name: Security Score
on:
pull_request:
branches: [main]
jobs:
security-score:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Compute security score
run: pnpm run security-score
- name: Fail if below 90
run: |
score=$(cat .security-score)
if [ "$score" -lt 90 ]; then
echo "::error::Security score $score is below 90"
exit 1
fi
PR のチェックが赤になり、マージボタンが disable されます。
セキュリティを後回しにする選択肢が物理的に消える。
3. 採点ロジックの一部
スコア計算スクリプト (scripts/security-score.ts) の構造:
async function computeScore(): Promise<number> {
let score = 100;
const audit = await runAudit();
score -= audit.criticalCount * 10;
score -= audit.highCount * 3;
const secrets = await runSecretScan();
score -= secrets.length * 20;
const tenantFilter = await runTenantFilterCheck();
score -= tenantFilter.violations.length * 10;
return Math.max(0, score);
}
各チェックは独立した module で書かれており、新しいチェックを追加するのも容易。
4. 個人開発で 90 点強制は厳しすぎないか
率直に言うと、開発スピードは確実に落ちます。スコアを上げるために、PR ごとに pnpm audit の警告対応や設定見直しを強いられます。
それでも 90 点強制を選んだ理由:
| 理由 | 内容 |
|---|---|
| ユーザの情報を扱う以上、流出は許されない | マルチテナント SaaS の責任 |
| 個人開発だから仕組み化必須 | 人間のレビューに頼れない |
| 将来チーム化への投資 | 既に高基準が CI に焼かれている状態は強い |
「やりたいか / やりたくないか」ではなく「仕組み的にやらざるを得ない状態を作る」のが、個人開発で品質を守るコツです。
5. スコアを上げるためにすること
実際にスコア改善で取った対応:
pnpm audit の警告解消
□ Critical / High の脆弱性を抱えた依存は、即座にバージョンアップ
□ バージョンアップできない場合は、pnpm.overrides で代替
□ 利用していないパッケージは削除
TruffleHog / Gitleaks 通過
□ 過去にコミットした .env を git history から削除 (git filter-repo)
□ secrets はすべて process.env 経由
□ ハードコード禁止
Prisma tenant_id フィルタ
□ ESLint カスタムルールで PR の段階で警告
□ スコア計算では「警告残数 × 10」で減点
CSP ヘッダ
□ next.config.ts で Content-Security-Policy を厳格設定
□ インラインスクリプトは禁止、nonce ベース
6. セキュリティスコアを公開する
たすきばは、スコアを README に表示 しています。
[](docs/operations/SECURITY_OPS.md)
これは、ユーザ / 採用候補者 / 共同開発者に対する 透明性アピール でもあります。
「ちゃんと品質管理されたサービスです」を、口頭ではなく数字で示す。個人開発でも、これくらいの透明性は出せます。
7. 学んだこと — 品質は意思ではなく仕組み
「品質を守る」のは意思ではなく、仕組みで実現する。
意思に依存するセキュリティ管理は、個人開発では確実に破綻します。忙しい日、機能を急ぐ日、無理を効かせて品質を妥協してしまう日が必ず来る。
その「人間の妥協」を消すために、CI に焼き付ける。90 点未満ならマージできない、という 物理的制約があれば、妥協する余地がない。
個人開発を続けていくときの、私の防御線です。
おわりに
| 仕組み | 効果 |
|---|---|
| セキュリティスコア 100 点満点で評価 | 全体の安全度を 1 数字で把握 |
| GitHub Actions で自動計算 | 人間の判断を介さない |
| Branch protection で 90 点未満ならマージ禁止 | 妥協の余地を構造的に消す |
| README に Badge 表示 | 透明性アピール |
たすきばは、これを 6 ヶ月運用して、severity-1 リスクの再発をゼロに保っています。
セキュリティを「気を付ける」ではなく「CI に焼き付ける」 — 個人開発でも、ぜひ試してみてください。
本記事の運用は、運用中の SaaS 「たすきば Knowledge Relay」 で実装しています。
👉 たすきば Knowledge Relay — 公式プロダクトページ