6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「セキュリティスコア 90/100」を CI で仕組み的に強制する — 個人開発で品質を守る最終防衛線

6
Posted at

この記事は約 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 に表示 しています。

[![Security Score](https://img.shields.io/badge/security-93%2F100-brightgreen)](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 — 公式プロダクトページ

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?