4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

セキュリティテスト — シフトレフトでセキュリティを実装する

4
Last updated at Posted at 2026-05-24

はじめに

セキュリティの指摘がリリース直前に降ってきて予定が吹き飛ぶ
——多くの開発現場で経験のある景色だと思います。
AIコード生成が日常になり、既知の脆弱性パターンを含むコードが知らないうちに混入するリスクも上がりました。
そこで本稿では、最終確認に賭ける構造を、開発の各段階に分散する「4つのシフト先」として整理します。

TL;DR

  • セキュリティを「リリース直前のペネトレーションテスト一発勝負」から「開発の各段階で積み上げる層」に転換する
  • シフト先は4つ。設計時の脅威モデリング、実装時の SAST/SCA、コミット時のシークレットスキャン、統合時の DAST
  • ペネトレーションテストは積み上げの「最終確認」になり、開発者の日常活動で多くの脆弱性は事前に潰せる

1. なぜセキュリティは後付けになるのか

シフトレフトの話に入る前に、「現実にはなぜ後付けになるのか」を構造として理解しておきます。動機が腑に落ちないとシフト先の話が抽象論に聞こえるためです。

1.1 「セキュリティは専門家のもの」という暗黙の壁

セキュリティテストは伝統的に専門のペネトレーションテスター(pen テスター)が行ってきた歴史があります。
攻撃を考えるのは難しいという暗黙の感覚があり、開発者自身がセキュリティに踏み込むハードルを上げてきました。

結果として、「リリース直前に専門家に見てもらう」モデルが定着しました。日々の開発の中で何をすればよいかが、開発者の側から見えにくい状態です。

1.2 後付けが招く2つの問題

後付けモデルには大きく2つの問題があります。

  • 修正コストの爆発: 機能バグと同じく、セキュリティ問題も発見が遅いほど修正コストが上がります。実装後の指摘は「設計のやり直し」を意味することもあります
  • 検出漏れ: 最終チェック一発では、攻撃面の網羅性も限られます。リリースされた後で攻撃ベクトルが発覚することも珍しくありません

時間軸で見ると、後付け型では発見と修正コストの関係が次のようになっています。

矢印が右から左に大きく戻ってくる構造です。デプロイ直前の一発検査で見つかった問題が、設計や実装の手戻りとして跳ね返ってくる様子を表しています。

1.3 構造を変える発想

セキュリティ問題の修正コストが時間とともに急上昇するなら、発見を時間軸の左側に寄せれば全体コストは下がります。
これがシフトレフトの基本的な発想です。

2. シフトレフトを「4つのシフト先」で捉える

「シフトレフトしよう」という掛け声だけでは何も変わりません。「いつ・どこに・何を差すのか」という具体的な地図を持って初めて手が動きます。

2.1 シフトレフトとは何か

シフトレフトは「開発のタイムラインの左側(早期)に品質確保活動を寄せる」という考え方です。
セキュリティに当てはめると、リリース直前の検査ではなく、開発の各段階で少しずつセキュリティを積み上げることを意味します。

2.2 4つのシフト先

開発タイムライン上で、開発者が現実的にセキュリティ活動を差し込める起点は大きく4つに分けられます。

なお下記の図には STRIDE/SAST/SCA/DAST 等の略語が並びますが、各略語の正体は第3〜5章で順に説明します。
ここでは「開発タイムラインのどこに何が差さるか」の地図を一望することが目的です。

image.png

3. 設計時に差す: 15分の脅威モデリング

シフトレフトの最も左、「コードを書く前」に何ができるのかが直感的には見えにくい部分です。設計段階で攻撃面を洗い出すことが、後段すべての発見密度を上げます。

3.1 脅威モデリングとは

脅威モデリングは「このシステム・機能はどんな攻撃を受けうるか」を体系的に洗い出すための話し合い・整理の手法です。
仕様作成・設計レビューの一部として組み込み、攻撃者の視点をチームで共有します。

ポイントは「小さなスコープで・短時間で・繰り返す」ことです。
ユーザーストーリー1つあたり15分が現実的な目安になります。

3.2 15分の脅威モデリングの流れ

15分の中で踏む段階は次の5つです。

各ステップで起こることを補足します。

  • スコープ定義: 1スプリント中に複数の機能を一度に扱わない。「経費の承認フロー」のような単位で切る
  • アセットの洗い出し: 「対策コストは保護対象の価値を超えない」という観点でも使う。守るものが明確でないと過剰防御に向かう
  • ブラックハット思考: 「壊す側に回って考える」。観点リスト(後述する STRIDE)を使うと網羅性が上がる
  • 優先度付け: すべての脅威に対策を打つのは現実的でない。影響と発生確率で絞る
  • アビューザーストーリー化: 「悪意ある利用者は〜できないこと」という形式で要求に組み込み、テスト可能にする

3.3 STRIDE をブレストの観点として使う

STRIDE は脅威の6観点の頭文字を取った名前です。
「他の観点で見落としていないか」をブレスト中に確認するチェックリストとして使います。

経費精算 SaaS で各観点を「具体的な問い」に翻訳すると次のようになります。

観点 経費精算 SaaS で問うこと
なりすまし(Spoofing) 上司のセッションを乗っ取って自分の経費を承認できないか
改ざん(Tampering) 承認後の金額・領収書画像を後から書き換えられないか
否認(Repudiation) 承認・却下の操作を「やっていない」と言い逃れできないか(ログがあるか)
情報漏えい(Information disclosure) 他社員の経費明細・領収書 URL を URL 推測や API で取得できないか
サービス妨害(Denial of service) 大量の経費申請で月末締めの処理を止められないか
権限昇格(Elevation of privilege) 一般社員が経理担当の権限を取得する経路はないか

観点ごとに具体的な問いを立てると、設計上の漏れ(権限分離・ログ・URL 設計・レート制限)が自然と浮かび上がってきます。

3.4 アビューザーストーリーで要求に組み込む

脅威モデリングで挙がった懸念は、放置すると忘れられます。
そこで アビューザーストーリー(abuser story)に変換します。
悪意ある利用者の視点で書かれたユーザーストーリーのことで、通常のスプリント運用の中で対策が実装・テストされる形に乗せられます。

経費精算 SaaS の例で書くと、こんな形になります。

[アビューザーストーリー例]

悪意ある利用者として、私は
他の社員の領収書画像 URL を推測または列挙して
本人の承認なしに閲覧できる
ことができてはならない。

受け入れ基準:
- 領収書画像 URL は推測不能なトークンを含む
- アクセス時に閲覧者が承認チェーン上にいるか検証する
- 第三者の URL 直打ちで 403 が返ることをテストで保証

このストーリーは通常の機能ストーリーと同じバックログに並べます。
「受け入れ基準」に検証可能な条件が落とせると、後段のシフト先(SAST/DAST/機能テスト自動化)と接続します。

4. 実装・コミット時に差す: SAST/SCA/シークレット

設計時の備えがあっても、コードを書く瞬間に古典的な脆弱性が混入します。
AI 生成コードを使う場面でも、生成物が安全とは限りません。
ここで扱う脆弱性カテゴリの多くは、業界で広く参照される OWASP Top 10 にも代表例として含まれます。
実装中の手元で発見できる仕組みを差しておくことが、シフトレフトの2番目の柱になります。

4.1 実装中に発見すべき3つの脆弱性カテゴリ

実装〜コミット段階で扱うカテゴリは大きく3つに分けられます。

カテゴリ 何が問題か どこで発見するか
自分が書いたコード上の脆弱性 SQL インジェクション(SQLi)、XSS(クロスサイトスクリプティング)など SAST(コードを実行せず静的に解析)
依存ライブラリの既知脆弱性 利用しているライブラリのバージョンに脆弱性が公表されている SCA(依存ツリーを照合)
コミットに混ざるシークレット API キー・パスワード・トークンをうっかりリポジトリに含めてしまう シークレットスキャン(pre-commit hook)

各用語の中身は、続くサブセクションで順に見ていきます。

4.2 SAST: コードの中の典型的な脆弱性パターン

SAST(Static Application Security Testing)は、IDE プラグインや CI ジョブとして組み込んで使います。
書きながら指摘を受けられる位置に差すのがポイントで、手元での発見がもっとも安価になります。

典型例は SQL インジェクション(SQLi)です。
経費 ID から明細を引く処理で見てみます。

# SAST が警告する典型パターン: 文字列連結で SQL を組み立てている
def get_expense(expense_id):
    query = f"SELECT * FROM expenses WHERE id = '{expense_id}'"
    return db.execute(query)

# 修正後: パラメータ化クエリ(DB 側でクエリ構造とパラメータが分離して処理される)
def get_expense(expense_id):
    query = "SELECT * FROM expenses WHERE id = %s"
    return db.execute(query, (expense_id,))

「動くから OK」で済ませてしまうコードを、SAST は実装中に立ち止まらせてくれます。
AI 生成コードが古いパターンや危険なパターンを含むこともあるため、生成物の安全網としても機能します。

4.3 SCA: 依存ライブラリの既知脆弱性

SCA(Software Composition Analysis)が照合先にするのは脆弱性 DB です。
自分のコードが完璧でも、依存ライブラリの古いバージョンに脆弱性があればそこが穴になります。
「依存を更新するだけ」で済むケースが多く、
自動更新ボットと組み合わせると運用負荷が低く抑えられます。

SCA の延長として、近年は SBOM(Software Bill of Materials: ソフトウェアの部品表)で依存関係を可視化する運用が広がっています。
サプライチェーン全体を継続スキャンする土台になります。

4.4 シークレットスキャン: pre-commit で秘密の流出を止める

AWS のアクセスキー、本番 DB のパスワード、API トークン——うっかりコミットされた瞬間にリポジトリの履歴に残ります。
公開リポジトリならボットが秒単位でスキャンしている世界です。

ここで効くのが pre-commit hook(git commit を実行する直前に自動で走るスクリプトの仕組み)への組み込みです。
コミット前にローカルで止められれば、リポジトリの履歴を汚さずに済みます。

実際に走らせると、こんな出力が出てきます。

$ git commit -m "fix: 経費明細取得APIの修正"

Pre-commit security scan:
  ✗ Detected potential secret in src/config/db_settings.py
      Line 12: AWS_SECRET_ACCESS_KEY="AKIA...XXXX"
      Pattern: ^AKIA[0-9A-Z]{16}$ (AWS Access Key ID)

  Commit aborted. Move secrets to environment variables
  or your secret manager and try again.

ここで止められれば被害ゼロです。CI でリポジトリへの push 後に検出すると、履歴クリーニングが必要になり、対応コストが跳ね上がります。

冒頭で触れた OWASP Top 10 は、Web アプリで頻発する脆弱性 Top 10 のリストで、Web セキュリティの非営利団体 OWASP が公開しています。
本章で扱った SAST/SCA/シークレットスキャンのチェック対象は、その代表例を多く含みます。チームの観点リストに加えると、3つの仕組みの精度がさらに上がります。

5. 統合・デプロイ前に差す: DAST と機能セキュリティテスト

静的解析だけでは捉えられない脆弱性があります。
実際にアプリを動かして攻撃リクエストを送ったときに初めて見える振る舞い、認証・認可の境界、設定ミスなどは、統合段階での発見が向きます。

5.1 DAST: 動いているアプリに攻撃リクエストを投げる

DAST(Dynamic Application Security Testing)は、デプロイ済みの実アプリに攻撃リクエストを送る手法です。
応答を観察して脆弱性を見つけます。
SAST と違ってコード内部を見ない「ブラックボックス」型になります。

強みは「実行時にしか現れない問題」を捉えられる点です。
たとえば設定ミスや認証境界の抜けなどが該当します。

動き方をシーケンス図で見ると次のようになります。

多くの DAST ツールは完了まで時間がかかります。CI のナイトリージョブ、またはステージング検証パイプラインに置くのが現実的です。

5.2 機能セキュリティテストを通常の自動テストとして書く

DAST がカバーするのは「一般的な攻撃パターン」です。
一方で、アプリ固有のセキュリティ要件——認可境界、業務上の権限分離——は機能テストとして書くのが向いています。

第3章のアビューザーストーリーから直接落ちてくるのがこの種のテストです。
pytest や統合テストフレームワークなど、通常のテストと同じ仕組みで実行できます。

経費承認の認可境界を pytest で書くとこんな形になります。

# 機能セキュリティテストの例: 認可境界の検証
def test_general_user_cannot_approve_expense(client, general_user_token,
                                              pending_expense):
    """一般社員は経費承認エンドポイントを呼べない(403が返る)"""
    response = client.post(
        f"/expenses/{pending_expense.id}/approve",
        headers={"Authorization": f"Bearer {general_user_token}"},
    )
    assert response.status_code == 403
    assert "expenses" not in response.json()  # データの断片も返ってはならない

このテストはアビューザーストーリー「権限のない利用者は他人の経費を承認できない」の受け入れ基準を機械的に検証しています。
通常の CI で毎コミット走るため、最も速いフィードバックが得られるセキュリティテストでもあります。

5.3 どう使い分けるか

DAST と機能セキュリティテストは役割が違います。

  • DAST: 一般的な攻撃パターン全般の網羅的検査(ナイトリー実行)
  • 機能セキュリティテスト: 自分のアプリ固有の認可・権限分離(毎コミット実行)

両方差すことで、汎用の脆弱性とアプリ固有の認可境界の両方をカバーできます。

6. それでも残る最終層と、セキュリティを習慣にする

シフトレフトを敷いても、すべての層を開発者だけでカバーするのは現実的ではありません。
残る最終層を正しく位置づけ、シフトレフトを「敷きっぱなしにして日常活動として積み上げる」姿勢で締めます。

6.1 ペネトレーションテストが「最終確認」になるという変化

ペネトレーションテストは消えるわけではなく、最終確認の専門家レビューとして残ります。重要なのは「役割の変化」です。

シフトレフトが効いている状態では、ペネトレーションテストで見つかる問題の量と深刻度が大きく下がります。
専門家の時間が「典型的な脆弱性の指摘」ではなく「本記事の道具では見つけられない深い探索」に使えるようになります。

本番運用後には RASP(実行中のアプリ自身が攻撃を検知して防ぐ仕組み)など、開発者の活動の右側にある層も続きます。本記事の主軸からは外れるため位置づけのみ。

6.2 シフトレフトを「敷きっぱなしにする」ことが習慣を作る

「開発者の日常活動で多くの脆弱性は事前に潰せる」状態を実現する鍵は、各シフト先の道具を 敷きっぱなしにする ことです。つまりは仕組み化ですね。

  • pre-commit hook: コミット時にシークレットスキャンが必ず走る
  • IDE プラグイン: コードを書きながら SAST の指摘が出る
  • CI ジョブ: push のたびに SCA・機能セキュリティテストが回る

これらが当たり前に走り続けることで、意識せずともセキュリティ判断が日常に組み込まれていきます。

自分も最初は「ツールを並べただけでは結局やらなくなる」と思っていました。
実際に敷いてみると、反対に「外す方が手間」になります。
これがシフトレフトを継続させる構造だと感じます。

セキュリティは設計と実装の最初から積み上げる層であり、最後に確認する層ではありません。
4つのシフト先に道具を敷き、それを敷きっぱなしにする
——シフトレフトの本質はそこにあります。

おわりに

セキュリティ問題は1発アウト案件が非常に多いです
特に最近はAIを使った実装が増えてきて、
知らないうちに顧客情報などをコミットしてしまったなどのニュースも耳にします
そういった起こらないように事前対策の仕組みかがより重要になるかと考えます。

4
4
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
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?