# セキュアコーディング実践ガイド【2026年版】NIST SSDF と OWASP ASVS で『安全に作る』エンジニアになる

> セキュアコーディングを“気合い”ではなく“仕組み”で実践するための完全ガイド。NISTの公式フレームワーク SSDF（SP 800-218）と OWASP ASVS 5.0 を地図に、信頼境界での検証・認可のサーバー強制・出力エンコード・秘密情報管理・依存対策を実コードで解説し、最後にこれらをCIで自動強制する方法までを示します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: セキュリティ, セキュアコーディング, NIST SSDF, OWASP ASVS, DevSecOps
- URL: https://tomodahinata.com/blog/secure-coding-practices-nist-ssdf-owasp-asvs-engineer-guide
- カテゴリ: セキュリティエンジニア・キャリア
- 総合ガイド: https://tomodahinata.com/blog/security-engineer-how-to-become-roadmap-skills-certification-guide

## 要点

- セキュアコーディングは個人の注意深さではなく、公式フレームワークに沿った“仕組み”で実現する。NISTの SSDF（SP 800-218 v1.1）は安全な開発を4つの実践群（PO/PS/PW/RV）に、OWASP ASVS 5.0 は検証要件を17章・約350項目・3レベルに体系化している
- 最重要の原則は『信頼境界で検証する』。クライアント・API応答・ファイル・環境変数——システムの外から来る値はすべて、境界でスキーマ検証してから内側に入れる。型安全な言語でも、境界での実行時検証は省略できない
- 認可はUIのif文ではなく、サーバー／DBで強制する。所有者条件をクエリに焼き込み、見つからなければ存在を隠して404に倒す。インジェクションはパラメータ化、XSSは出力エンコードで、原理的に断つ
- 防御をデフォルトにする。セキュリティヘッダー／CSP、秘密情報の型付き境界、最小権限、安全なエラー処理——『うっかり危険になる』のではなく『うっかりしても安全』な構造に倒す
- 最後はCIで自動強制する。SSDFの実践（依存スキャン・SAST・秘密情報スキャン）をGitHub Actionsの品質ゲートに焼き込めば、人間のレビュー漏れに依存せず、危険なコードがmainに入る前に止まる

---

セキュアコーディングがうまくいかないチームには、共通点があります。**「気をつける」「レビューで気づく」という、人間の注意深さに依存している**ことです。注意深さは疲れます。締め切り前に薄れます。新人には備わっていません。だから、**安全性を“個人の努力”ではなく“仕組み”に変える**——それが本記事のテーマです。

幸い、車輪を再発明する必要はありません。**NISTとOWASPが、安全な作り方を公式に体系化**してくれています。本記事は、この2つの一次情報を地図に、セキュアコーディングの原則を**コピーして使える実コード**で示し、最後にそれを**CIで自動強制する**ところまで通します。

> **この記事と姉妹記事の関係：** 「そもそもどんな職種を目指すか」は[セキュリティエンジニアになるには【完全ロードマップ】](/blog/security-engineer-how-to-become-roadmap-skills-certification-guide)に。本記事はその中核技能「安全に作る技術」を深掘りします。コード例はTypeScript/Next.js中心ですが、原則は言語非依存です。**特定スタック（Next.js × Supabase）での具体的な脆弱性検出・修正**は、各所で個別記事へリンクします。

---

## 0. 2つの公式フレームワーク — SSDF と ASVS

まず、これから何度も参照する2つの地図を押さえます。

### NIST SSDF（Secure Software Development Framework / SP 800-218）

[SSDF（SP 800-218 v1.1）](https://csrc.nist.gov/projects/ssdf)は、**安全なソフトウェアの“作り方”を4つの実践群に整理**した、開発プロセスのフレームワークです（v1.2が2025年12月にドラフト公開・意見募集中）。

| 実践群 | 何をするか |
|---|---|
| **PO（Prepare the Organization）** | 組織として安全に作る準備（方針・役割・ツールの整備） |
| **PS（Protect the Software）** | ソフトウェア自体とコードを保護（改ざん防止・完全性・秘密情報の保護） |
| **PW（Produce Well-Secured Software）** | よく守られたソフトウェアを生み出す（設計・セキュアコーディング・レビュー・テスト） |
| **RV（Respond to Vulnerabilities）** | 脆弱性に対応する（発見・修正・再発防止） |

本記事の§1〜§5は主に **PW**（安全に書く）、§6は **PS/PW** の自動化、§7は **RV** の入口にあたります。

### OWASP ASVS 5.0（Application Security Verification Standard）

[ASVS 5.0.0](https://owasp.org/www-project-application-security-verification-standard/)（2025年5月公開、専用サイト [asvs.dev](https://asvs.dev/)）は、**「安全なアプリが満たすべき要件」のチェックリスト**です。約350の検証要件を17章に整理し、**3段階のレベル**で「どこまで守るか」を選べます。

- **レベル1：** 最低限（多くのアプリの出発点）
- **レベル2：** 機密データを扱う大半のアプリの推奨水準
- **レベル3：** 医療・金融など、最高水準が必要なアプリ

> SSDFが「どう作るか（プロセス）」、ASVSが「何を満たすか（要件）」。**SSDFのプロセスで開発し、ASVSの要件で検証する**——この2つは補完関係にあります。

---

## 1. 大原則：信頼境界で検証する（ASVS V1/V2）

すべてのセキュアコーディングの出発点は、たった一つの問いです。**「この値は、信頼できる内側で生まれたか、信頼できない外側から来たか」**。外から来た値（ユーザー入力・API応答・ファイル・環境変数）は、**境界でスキーマ検証してから**内側に入れます。

TypeScriptのような型安全な言語でも、これは省略できません。**型はコンパイル時の約束にすぎず、実行時に外から来るデータが型どおりである保証はない**からです。境界では[Zod](https://zod.dev/)のような実行時バリデータで「検証して、初めて型を得る」。

```ts
// 境界バリデーション：外から来たJSONを、信じる前に検証する。
import { z } from "zod";

// ① 受け入れる形を、ホワイトリストとして厳密に宣言する。
const CreateUserSchema = z.object({
  email: z.string().email().max(254),
  displayName: z.string().min(1).max(50),
  age: z.number().int().min(0).max(150),
}).strict(); // ← strict(): 想定外のキーを拒否（マスアサインメント対策）

export async function POST(req: Request): Promise<Response> {
  const json: unknown = await req.json(); // ② 外から来た値は常に unknown 型で受ける

  const parsed = CreateUserSchema.safeParse(json); // ③ 検証してから内側へ
  if (!parsed.success) {
    // ④ エラー詳細は最小限に。内部構造を攻撃者に教えない。
    return Response.json({ error: "Invalid input" }, { status: 400 });
  }

  // ⑤ ここから先、parsed.data は「検証済みの型」として安全に使える。
  const user = await createUser(parsed.data);
  return Response.json({ id: user.id }, { status: 201 });
}
```

ポイントは `.strict()` です。スキーマにないキーを**拒否**することで、`isAdmin: true` のような想定外フィールドを紛れ込ませる**マスアサインメント攻撃**を構造的に断てます。「来た値をそのまま展開して保存」が最も危険なアンチパターンです。

> 型安全を“方針”で終わらせず、境界で実行時検証として強制する考え方は、[TypeScript 型安全の規律](/blog/typescript-type-safety-discipline-zod-nevererror-no-any)に体系化しています。

---

## 2. 認可はサーバー／DBで強制する（ASVS V4・OWASP Top 10 A01）

[OWASP Top 10:2025](https://owasp.org/Top10/2025/)で**第1位**が「アクセス制御の不備（Broken Access Control）」です。最も多く、最も被害が大きい。原則は2つです。

1. **認証（あなたは誰か）と認可（あなたに権限があるか）を分けて、両方を必ず行う。**
2. **認可をクライアント（UI）に置かない。** ボタンを隠すのは利便性のためで、防御ではありません。攻撃者はAPIを直接叩きます。

```ts
// ❌ 危険：URLのIDを信じている。他人のIDに変えれば他人のデータが読める（IDOR）。
const doc = await db.document.findUnique({ where: { id: params.id } });

// ✅ 安全：所有者条件をクエリに焼き込む。他人のものは「存在しない」ことにする。
const doc = await db.document.findFirst({
  where: { id: params.id, ownerId: session.userId },
});
if (!doc) return new Response("Not Found", { status: 404 }); // 403ではなく404で存在を隠す
```

「所有者条件をクエリに焼き込む」のが急所です。**「取得してから所有者を確認する」**のではなく、**「自分のものしか取得できないクエリにする」**。後者は、確認を書き忘れても安全側に倒れます。

データベース自身に認可を寄せる **行レベルセキュリティ（RLS）** はさらに強力です。アプリのバグがあってもDBが最後の砦になります。具体的な設計は[Supabase RLS 本番設計](/blog/supabase-rls-production-multi-tenancy-patterns)、検出は[認可不備・IDORの検出ガイド](/blog/nextjs-supabase-idor-broken-authorization-rls-detection-guide)に。

---

## 3. インジェクションと出力エンコード（ASVS V5・Top 10 A05）

インジェクション（SQLi・コマンドインジェクション・XSS）は、**「データ」を「コード」として解釈させる**攻撃です。防御の原則は普遍的——**データとコードを混ぜない**。

### SQLインジェクション：必ずパラメータ化する

```ts
// ❌ 危険：文字列連結。' OR '1'='1 のような入力でクエリ構造が壊れる。
const rows = await db.query(`SELECT * FROM users WHERE email = '${email}'`);

// ✅ 安全：パラメータ化クエリ。値は常に「データ」として扱われ、コードにならない。
const rows = await db.query("SELECT * FROM users WHERE email = $1", [email]);
```

ORM（Prisma / Drizzle 等）を使えば、通常はパラメータ化が自動です。生SQLやRPCを書くときだけ要注意——詳細は[SQLインジェクション対策](/blog/supabase-postgres-sql-injection-rpc-prevention-guide)。

### XSS：出力時にエンコードする

XSSは「ユーザー入力がHTMLとして実行される」攻撃です。**Reactは既定でJSX内の値をエスケープする**ため、`{userInput}` は安全です。危険なのは、その安全装置を自分で外すときだけ。

```tsx
// ❌ 危険：dangerouslySetInnerHTML に未検証のHTMLを渡す＝XSSの入口。
<div dangerouslySetInnerHTML={{ __html: userProvidedHtml }} />

// ✅ 安全：どうしてもHTMLを描画するなら、信頼できるサニタイザを通す。
import DOMPurify from "isomorphic-dompurify";
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userProvidedHtml) }} />

// ✅ 最も安全：そもそもHTMLとして描画せず、テキストとして渡す（Reactが自動エスケープ）。
<div>{userProvidedText}</div>
```

判断基準はシンプルです。**「`dangerouslySetInnerHTML` を書こうとしている自分」を疑う**こと。詳細は[XSS / DOM-based XSS 対策](/blog/nextjs-xss-dom-xss-dangerouslysetinnerhtml-prevention-guide)。

---

## 4. 防御をデフォルトに：セキュリティヘッダーとCSP（ASVS V13）

優れたセキュリティエンジニアは、**「うっかり危険になる」のではなく「うっかりしても安全」な構造**を作ります。その代表が、HTTPレスポンスヘッダーによる多層防御です。1枚のミドルウェアで、アプリ全体に防御の床を敷けます。

```ts
// middleware.ts — アプリ全体に「安全な既定値」を敷く（Next.js）。
import { NextResponse, type NextRequest } from "next/server";

export function middleware(_req: NextRequest): NextResponse {
  const res = NextResponse.next();

  // クリックジャッキング防止：フレーム埋め込みを禁止。
  res.headers.set("X-Frame-Options", "DENY");
  // MIMEスニッフィング防止：Content-Typeを尊重させる。
  res.headers.set("X-Content-Type-Options", "nosniff");
  // 常にHTTPSを強制（1年・サブドメイン込み）。
  res.headers.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
  // リファラからのパス・クエリ漏えいを抑える。
  res.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
  // 不要なブラウザ機能を既定で無効化（最小権限）。
  res.headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
  // CSP：許可したオリジン以外のスクリプト実行を禁止（XSSの被害を大幅に軽減）。
  res.headers.set(
    "Content-Security-Policy",
    "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'",
  );

  return res;
}

export const config = { matcher: "/:path*" }; // 全ルートに適用
```

CSP（Content-Security-Policy）は、XSSが万一すり抜けても**「許可していない場所のスクリプトは動かさない」**第二の壁になります。nonceを使った厳格なCSPの本番運用は[セキュリティヘッダー・CSPの実装](/blog/nextjs-security-headers-csp-nonce-middleware-guide)に。

---

## 5. 秘密情報と依存関係（SSDF PS・Top 10 A02/A03）

### 秘密情報：コードに書かない・型付き境界で扱う

APIキー・DB認証情報・署名鍵は、**絶対にコードやリポジトリに書かない**。環境変数で注入し、しかも**「サーバー専用の秘密」と「クライアントに出てよい公開値」を型で隔てる**のが堅牢です。

```ts
// env.ts — 環境変数も「外から来る値」。起動時に境界で検証する。
import { z } from "zod";

// サーバー専用（クライアントへ絶対に漏らさない）。
const ServerEnvSchema = z.object({
  DATABASE_URL: z.string().url(),
  STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
});

// クライアントへ露出してよい公開値だけを別スキーマに分離する。
const PublicEnvSchema = z.object({
  NEXT_PUBLIC_APP_URL: z.string().url(),
});

// 起動時に検証：設定ミスは「本番で漏れる」前に「起動で落として」気づく。
export const serverEnv = ServerEnvSchema.parse(process.env);
export const publicEnv = PublicEnvSchema.parse(process.env);
```

「`NEXT_PUBLIC_` を付けた秘密鍵がバンドルに焼き込まれて漏える」は頻発する事故です。型で隔てておけば、誤って公開側に秘密を置く設計を“しにくく”できます（→ [環境変数からの秘密漏えい防止](/blog/nextjs-env-secret-leak-prevention-public-vars-guide)）。

### 依存関係：自分で書いていないコードも“あなたの責任”

現代のアプリは、コードの大半が依存パッケージです。そこに既知の脆弱性があれば、あなたのアプリの脆弱性になります（Top 10 A03「ソフトウェアサプライチェーンの障害」）。**依存の脆弱性スキャン（SCA）を自動化**し、更新を継続的に当てる仕組みが必須です。GitHubなら[Dependabot](/blog/dependabot-production-guide)が標準解になります。

---

## 6. SSDFをCIに焼き込む — 危険なコードをmainに入れない

ここまでの原則を、**人間のレビューに依存せず自動で強制**するのが最後の仕上げです。SSDFのPW（よく守られたソフトウェアを生む）・PS（ソフトウェアの保護）を、GitHub Actionsの品質ゲートとして実装します。**PRごとに自動で回り、ひとつでも危険があればマージをブロック**します。

```yaml
# .github/workflows/security-gate.yml
# SSDFの実践（依存スキャン・SAST・秘密情報スキャン）をPRの必須ゲートにする。
name: security-gate

on:
  pull_request:
    branches: [main]

permissions:
  contents: read          # 最小権限：このジョブが必要なのは読み取りだけ
  security-events: write  # SAST結果(SARIF)をSecurityタブへ送るためだけに付与

jobs:
  secure-coding-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # ① 依存の脆弱性スキャン（SCA / SSDF: PW.4, RV）。既知CVEを持つ依存で落とす。
      - name: Audit dependencies
        run: npm audit --audit-level=high

      # ② 静的解析（SAST / SSDF: PW.7, PW.8）。インジェクション・認可漏れ等のパターンを検出。
      - name: Static analysis (Semgrep)
        uses: semgrep/semgrep-action@v1
        with:
          config: p/owasp-top-ten   # OWASP Top 10 のルールセット

      # ③ 秘密情報スキャン（SSDF: PS.3）。鍵やトークンの混入をマージ前に止める。
      - name: Secret scanning (Gitleaks)
        uses: gitleaks/gitleaks-action@v2

      # ④ 型・テスト（壊れた契約と退行を止める）。
      - name: Type-check and test
        run: |
          npm ci
          npm run type-check
          npm test
```

このゲートが入ると、セキュリティは「リリース直前にまとめてやる特別な工程」から、**「PRごとに自動で効く日常の制約」**に変わります。これがDevSecOpsの核心であり、SSDFが目指す姿です。

> **正直な線引き：** この自動ゲートが強いのは、**機械的に検出できる“横の統制”**——依存の脆弱性・既知の危険パターン・秘密情報の混入です。一方、**「この認可ロジックは事業ルールとして正しいか」「このテナント分離に抜けはないか」という“縦のリスク”は、ツールでは判定できません**。そこは人間の設計レビュー／監査の領域です。何が自動化でき、何ができないかの境界は[セキュリティ監査は何を見るのか](/blog/nextjs-supabase-security-audit-scope-when-needed-guide)に正直にまとめています。Next.js × Supabase構成のCIゲート実装（SARIF連携まで）は[セキュリティCI・SARIF](/blog/nextjs-supabase-security-ci-sarif-github-actions-guide)に。

---

## 7. ASVSをチェックリストとして使う

最後に、ここまでの実装が「十分か」を測る物差しが [ASVS 5.0](https://asvs.dev/) です。使い方はシンプルです。

1. **アプリの機密度からレベルを選ぶ。** 一般的なSaaSならレベル2が目安。
2. **該当章の要件を、PRやリリースのチェックリストにする。** 認証（V6）、セッション管理、アクセス制御（V4）、入力検証（V2）、暗号、ログ…と章ごとに「満たしているか」を確認する。
3. **満たせない項目は、リスクとして記録する。** すべてを完璧にする必要はありません。**「何を守れていて、何を守れていないかを把握している」**ことが、把握していないことより決定的に重要です。

ASVSは「全部やれ」という命令書ではなく、**「抜けを可視化する地図」**として使うのが正しい姿勢です。

---

## 8. よくある質問（FAQ）

**Q. 言語が違っても、この原則は通用しますか？**
A. はい。コード例はTypeScriptですが、**信頼境界での検証・認可のサーバー強制・パラメータ化・出力エンコード・秘密情報をコードに書かない**は、Go・Python・Java・Rubyすべてに共通する普遍原則です。SSDFもASVSも言語非依存です。

**Q. ORMを使えばSQLインジェクションは気にしなくていい？**
A. 通常のクエリは自動でパラメータ化されるので安全です。ただし**生SQL・ストアドプロシージャ・RPC・動的なORDER BY**などORMの外に出る箇所は要注意です。「ORMを使っているから安全」と思考停止せず、生SQLを書く箇所だけは必ずパラメータ化を確認してください。

**Q. SSDFとASVS、どちらから学ぶべきですか？**
A. 実装者なら**ASVSの要件（何を満たすか）から**入ると具体的で掴みやすいです。チームやプロセスを設計する立場なら**SSDF（どう作るか）**が効きます。両方を行き来するのが理想です。

**Q. CIゲートを入れると開発が遅くなりませんか？**
A. 短期的にはPRが少し止まります。しかし、**脆弱性は「後で見つかるほど修正コストが跳ね上がる」**ため、PR時点で止めるほうが圧倒的に安くつきます。最初はゲートを「警告のみ」で導入し、チームが慣れたら「ブロック」に上げる段階導入が現実的です。

**Q. 全部やる時間がありません。優先順位は？**
A. OWASP Top 10の順、つまり**①アクセス制御（認可）②セキュリティ設定（ヘッダー/秘密情報）③インジェクション**から。被害が大きく頻度も高い順です。完璧を目指さず、ここから着実に。

---

## 9. まとめ

セキュアコーディングは、才能でも気合いでもなく、**公式フレームワークに沿った“仕組み”**です。

- **SSDF（どう作るか）と ASVS（何を満たすか）**を地図にする。車輪を再発明しない。
- **信頼境界で検証する。** 外から来た値はすべてスキーマ検証してから内側へ（`.strict()` でマスアサインメントも断つ）。
- **認可はサーバー／DBで強制する。** 所有者条件をクエリに焼き込み、見つからなければ404で存在を隠す。
- **データとコードを混ぜない。** インジェクションはパラメータ化、XSSは出力エンコードで原理的に断つ。
- **防御をデフォルトに。** セキュリティヘッダー／CSP、秘密情報の型付き境界、最小権限。
- **CIで自動強制する。** 依存スキャン・SAST・秘密情報スキャンを品質ゲートにし、人間のレビュー漏れに依存しない。

そして覚えておいてください。**自動化で守れるのは“横の統制”まで**です。事業ルールに根ざした認可やテナント分離といった**“縦のリスク”は、人間の設計判断**が要ります。だからこそ、横は仕組みで一掃し、縦に人間の時間を集中させる——それが、限られたリソースで最大の安全を得る、セキュリティエンジニアの実践知です。

---

### 参考（公式一次情報）

- フレームワーク：[NIST SSDF（SP 800-218）](https://csrc.nist.gov/projects/ssdf)／[OWASP ASVS 5.0](https://owasp.org/www-project-application-security-verification-standard/)（[asvs.dev](https://asvs.dev/)）
- 標準：[OWASP Top 10:2025](https://owasp.org/Top10/2025/)／[OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- ツール：[Semgrep](https://semgrep.dev/)／[Gitleaks](https://github.com/gitleaks/gitleaks)／[Zod](https://zod.dev/)
- 関連：[セキュリティエンジニアになるには（ロードマップ）](/blog/security-engineer-how-to-become-roadmap-skills-certification-guide)
