セキュアコーディング:開発者のための安全なコードの指針

この業界に忍び込んでいる危うい考えがあります。セキュリティは誰か他の人の問題だ、という考えです。運用チームのもの、セキュリティエンジニアのもの、四半期に一度やってきてアプリケーションをつつく専門のペネトレーションテスターのものだ、と。この考えは間違っており、火曜日に読む侵害事例のほとんどの原因になっています。

現実には、セキュリティはセキュリティレビューではなく、エディタの中で決まります。あなたが下すどの判断も——SQL クエリの組み立て方、ユーザー入力の扱い方、セッショントークンの保存の仕方も——扉を開けるか、しっかり閉めるかのどちらかです。攻撃者は開いた扉が一つあれば足ります。あなたはすべての扉を閉めなければなりません。

本記事では、すべての開発者が知っておくべき安全なコーディングの作法を扱います。2026 年の OWASP Top 10 を下敷きに、実際のコードベースで日々目にする弱点を取り上げます。インジェクション、クロスサイトスクリプティング、クロスサイトリクエストフォージェリ、認証の不備、依存関係のリスク、秘密情報の漏えいです。それぞれの節には、今日すぐ適用できる具体的なパターンが含まれます。

2026 年の OWASP Top 10:何が変わり、なぜ変わったか

OWASP Top 10 は、ウェブアプリケーションの主要なリスクについて、この業界が持つ合意に最も近い一覧です。2026 年版では、脅威の情勢がどう変化したかを映すいくつかの注目すべき変更が入りました。それらを理解することで、本当に重いリスクに力を集中できます。

2026 年の最大の変化は、AI が生成したコードが独立したリスク区分として明示されたことです。OWASP は初めて、大規模言語モデルが生み出したコードが固有のセキュリティ上の課題をもたらすとはっきり認めました。これらのモデルは、よく保守されたライブラリから、理解しないまま貼り付けられた断片まで、あらゆる種類の公開コードの巨大な集積で訓練されています。AI は、危ういものも含めて、見たパターンを再生します。2025 年の Snyk の調査では、AI アシスタントはセキュリティ面が曖昧なプロンプトを与えられると、およそ 30% の確率で既知の弱点を含むコードを提案しました。含意は明快です。AI が生成したコードは、見知らぬ外注業者が書いたコードと同じ水準の検討を必要とします。

もう一つの重要な変化は、インジェクション区分の統合です。以前の版では、SQL インジェクション、NoSQL インジェクション、OS コマンドインジェクション、LDAP インジェクションが別々の項目として扱われていました。2026 年版はそれらを「インジェクション」という一つの区分にまとめています。これは、根底のパターン——信頼できないデータがインタプリタ向けの文字列に連結されること——が対象によらず同じだという現実を映しています。個別の変種ではなく、根本原因に注意を向けさせる、有用な言い換えです。

暗号まわりの失敗は一覧の中で順位を上げています。AI による暗号解析の広がりと、量子計算を見据えた移行計画の遅れによるものです。現代の認証付き暗号(AES-GCM、ChaCha20-Poly1305)を使っておらず、長期保管データのポスト量子移行について考え始めてすらいないなら、利息のついて返ってくる将来の負債を積み上げていることになります。

2026 年版はまた、セキュリティの設定ミスとソフトウェアの完全性の失敗を「設定と侵害」という広い区分へ統合しました。信頼できないベースイメージや署名のない依存関係を使うことなど、多くの完全性の失敗が、開発ライフサイクルの入口での判断であることを認めた形です。

セキュリティは製品でも機能でもありません。エンジニアリング文化の属性です。設計の段階でセキュリティに気を配らないなら、最後にどれだけスキャンをかけても、それを埋め合わせることはできません。

インジェクション攻撃:同じ古い話、それでも最大の問題

インジェクション攻撃が OWASP の一覧の頂点に居座り続けるのには理由があります。実行が容易で、影響が甚大で、そして本番コードに驚くほどよくあるからです。根本の問題は、プログラムがユーザーの制御する入力を含む文字列を連結して、コマンドやクエリを組み立てることです。攻撃者は、その入力が想定された文脈から抜け出し、自分の命令を差し込むように仕立てます。

SQL インジェクションは古典的な例で、いまだに効きます。十年前に大学で SQL インジェクションを習った開発者が、締め切りの圧力の下でいまも危ういコードを書きます。解決はよく知られています。プレースホルダを使うクエリ、プリペアドステートメント、あるいは正しくエスケープを扱う ORM です。

// Vulnerable: string concatenation with user input
const query = `SELECT * FROM users WHERE email = '${req.query.email}'`;
db.execute(query);

// Fixed: parameterized query
const query = 'SELECT * FROM users WHERE email = ?';
db.execute(query, [req.query.email]);

危ういパターンは、経験を積んだ開発者なら明らかに誤りだと見抜くはずのものですが、それでも毎週のようにコードレビューに現れます。理由はいつも同じです。時間がない、パラメータ化するまでもないほど単純に見えるクエリ、特定のドライバでは使えない ORM。これらの理由は、事故のふりかえりを生き延びません。

同じ原理は NoSQL データベースにも当てはまります。たとえば MongoDB は、ユーザー入力がそのままクエリオブジェクトへ渡されるとき、インジェクションに対して脆くなります。攻撃者がパスワードの値として { '$ne': '' } を渡すだけで、生の入力からクエリが組み立てられている場合は認証を丸ごと迂回できてしまいます。

OS コマンドインジェクションもまた、なかなか消えない変種です。ユーザーの制御する入力を伴って exec や spawn、子プロセスの生成を呼ぶのは最悪手です。外部コマンドを実行する必要があるなら、許可された値の厳格な一覧に対して入力を検証してください。ユーザー入力からコマンド文字列を組み立ててはいけません。

クロスサイトスクリプティングと SPA の課題

クロスサイトスクリプティング(XSS)は、データベースではなくブラウザの DOM を狙うインジェクション攻撃です。攻撃者が悪意ある JavaScript をページに差し込むと、そのスクリプトは被害者のセッションの文脈で実行され、クッキー、localStorage、セッションデータへの参照と、ユーザーに成り代わってリクエストを送る能力を得ます。

React、Vue、Svelte で作られたシングルページアプリケーションの登場は、XSS の情勢を大きく変えました。現代のフレームワークはテンプレート式の値を自動でエスケープし、最もよくある XSS の経路を塞ぎます。しかし開発者はいまだにその防御を迂回できます。最もよくある迂回は、React の dangerouslySetInnerHTML、Vue の v-html、Svelte の @html です。これらの API には正当な用途(CMS からのリッチテキストの描画など)がありますが、内容を先に無害化しなければ XSS に扉を開けることになります。

// Vulnerable: rendering unsanitized HTML from user input
function Comment({ body }) {
  return <div dangerouslySetInnerHTML={{ __html: body }} />;
}

// Fixed: sanitize before rendering
import DOMPurify from 'dompurify';

function Comment({ body }) {
  const clean = DOMPurify.sanitize(body);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

サーバーサイドレンダリングは、XSS 対策にもう一つの次元を加えます。アプリケーションがサーバー上でユーザー生成の内容を描画し、HTML をクライアントへ送るなら、その内容はサーバー側でエスケープされていなければなりません。多くのテンプレートエンジンは既定でエスケープ済みの出力を返しますが、開発者は三重括弧やパイプのヘルパーを使って、エスケープしない出力を選ぶこともできます。テンプレート内でエスケープを外しているすべての箇所を見直してください。

開発者が見落としがちな第三の XSS 経路は、データ属性と URL を経由した間接的な差し込みです。ユーザー入力を使って DOM 要素のデータ属性を設定する場合、入力が属性の文脈を壊す文字を含んでいれば危険になり得ます。同じように、ユーザー入力から URL を組み立ててアンカータグに差し込む場合、攻撃者は javascript: の URL を使って任意のコードを実行できます。

クロスサイトリクエストフォージェリと認証の堅牢化

クロスサイトリクエストフォージェリ(CSRF)は、ウェブアプリケーションが認証済みユーザーのブラウザに寄せている信頼を悪用します。攻撃者はあなたのアプリケーション宛のリクエストを組み立て、悪意あるページへの訪問、リンクのクリック、画像の読み込みを通じて、認証済みのユーザーにそれを実行させます。アプリケーションが認証をセッションクッキーだけに頼っているなら、ブラウザはあなたのドメイン宛のどのリクエストにも自動でそのクッキーを添え、CSRF を成立させてしまいます。

標準的な防御は CSRF トークンです。どのフォームにも埋め込まれ、サーバーで検証される、一意で当てにくい値です。ユーザーがフォームを送信すると、サーバーは CSRF トークンがセッションに保存したものと一致するかを確かめます。攻撃者の偽造リクエストはこのトークンを含められません。同一オリジンポリシーが、攻撃者のページからあなたのサーバーの応答を読むことを禁じているからです。

// Vulnerable: no CSRF protection
app.post('/api/transfer', (req, res) => {
  const { toAccount, amount } = req.body;
  transferFunds(req.session.userId, toAccount, amount);
  res.json({ ok: true });
});

// Fixed: validate CSRF token
app.post('/api/transfer', (req, res) => {
  const token = req.headers['x-csrf-token'];
  if (!validateCsrfToken(req.session, token)) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }
  const { toAccount, amount } = req.body;
  transferFunds(req.session.userId, toAccount, amount);
  res.json({ ok: true });
});

現代のフレームワークは CSRF 対策を最初から備えています。Express の CSRF ミドルウェア、Django の組み込み CSRF ミドルウェア、Rails の真正性トークンの仕組みは、いずれも同じパターンを実装しています。開発者がやりがちな間違いは、HTML フォームを提供しない API エンドポイントでだけ CSRF 対策を切ってしまうことです。適切な CORS 設定がなければ、そうしたエンドポイントもクロスオリジンのリクエストの標的になり得ることを忘れています。

認証の堅牢化は CSRF だけの話ではありません。どのセッションにも期限があるべきです。パスワード再設定のトークンは一回限りで、時間制限があるべきです。どのログインのエンドポイントも、総当たり攻撃を防ぐためにレート制限を実装すべきです。多要素認証はすべてのユーザーが使えるようにし、推奨し、管理者アカウントでは必須にすべきです。

レート制限は、実装できるセキュリティ対策の中で最も費用対効果の高いものの一つです。ごくわずかなコードで済み、継続的な保守の負担がなく、ひとそろいの攻撃(クレデンシャルスタッフィング、総当たり、列挙、各種の API 乱用)を防ぎます。アプリケーションの層(ユーザーごと)、ネットワークの層(IP ごと)、エンドポイントの層(機微な操作ごと)で実装してください。ログインのエンドポイントなら、ユーザーあたり毎分 5 回程度が妥当な出発点です。

依存関係の管理と秘密情報の取り扱い

現代のアプリケーションは、オープンソースの依存関係という土台の上に築かれています。典型的な Node.js のプロジェクトは、数千の推移的依存を持ちます。その一つ一つが攻撃の入口になり得ます。2024 年の event-stream の事件(悪意ある人物が保守権限を得て、人気の npm パッケージに暗号資産を盗むコードを差し込んだもの)は、例外ではなく、オープンソースのサプライチェーンに構造的なリスクがあることへの警告です。

  • 完全性の検証を備えたパッケージマネージャを使ってください。npm shrinkwrap や yarn.lock は、どの導入でも正確に同じ依存ツリーを使うことを保証します。依存関係は固定し、範囲指定は使わないでください。
  • 依存関係の脆弱性スキャンを CI パイプラインの一部として走らせてください。npm audit、Snyk、Dependabot、Trivy などの道具は、依存グラフの既知の弱点を自動で検出し、重大なものがあればビルドを止めます。
  • 使っていない依存関係は取り除いてください。実際に使っていないパッケージは、利益のない負債です。depcheck のような道具で package.json の遊んでいる依存を見つけてください。
  • 各依存関係が要求する権限を監査してください。環境変数を読み、ファイルシステムに触れ、ネットワークへ接続するパッケージはリスクです。どの権限にも根拠を求めてください。
  • レビュー工程を伴う依存関係の自動更新を設定してください。Dependabot や Renovate は、新しいバージョンが出たときにプルリクエストを開きます。変更履歴を確認し、破壊的変更を見極め、素早く取り込んでください——とりわけセキュリティ修正については。

秘密情報の管理は、この式のもう一方の辺です。ソースコードに直書きされた API キー、データベースのパスワード、暗号鍵は、ペネトレーションテストで最もよく見つかる指摘の一つであり、そして完全に防げるものです。

// Vulnerable: hardcoded secrets in source code
const DB_PASSWORD = 'sup3r-s3cr3t-passw0rd!';
const API_KEY = 'sk-live-abc123def456';

// Fixed: use environment variables with validation
function getEnv(name: string): string {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }
  return value;
}

const dbPassword = getEnv('DB_PASSWORD');
const apiKey = getEnv('API_KEY');

// Also: use .env files locally, never commit them
// Add .env to .gitignore from day one

Git リポジトリに秘密情報をコミットしたことがあるなら、それはすでに漏えいしたものと考えるべきです。最新のコミットから取り除くだけでは足りません。まだ履歴の中に残っています。リポジトリに触れられる人なら誰でも見つけられます。唯一安全な対応は、その秘密情報をすぐに更新し、リポジトリが公開または共有されているなら、git-filter-repo や BFG Repo-Cleaner のような道具で履歴から消し去ることです。

本番では専用の秘密情報管理を使ってください。HashiCorp Vault、AWS Secrets Manager、Azure Key Vault、Google Secret Manager は、安全な保管、参照の監査、自動更新、そして細かな権限制御を提供します。ローカル開発では環境変数で十分ですが、本番の秘密情報は、環境設定に焼き込むのではなく、アプリケーションの起動時に秘密情報ストアから取得すべきです。

道具:SAST、DAST、セキュリティヘッダー

安全なコーディングは、何を書くかだけの話ではありません。何を確認し、どうデプロイするかも含みます。最も規律のある開発チームは、静的解析、動的解析、インフラの堅牢化を組み合わせ、防御の層を重ねます。

SAST(静的アプリケーションセキュリティテスト)の道具は、実行せずにソースコードを解析します。弱点を示すパターンを探します。SQL インジェクション、XSS、安全でないデシリアライズ、直書きされた秘密情報などです。SAST は開発のワークフローに組み込まれ、コードが書かれているその瞬間——最も修正が安いとき——に指摘を返します。よく使われるものには Semgrep、SonarQube、CodeQL、セキュリティ用プラグインを入れた ESLint があります。導入を成功させる鍵は、雑音の水準を抑えることです。規則を調整し、どの警告も行動につながるようにしてください。そうしなければ、開発者はただ無視するようになります。

DAST(動的アプリケーションセキュリティテスト)の道具は、作り込んだリクエストを送って応答を観察することで、動作中のアプリケーションを解析します。SAST では見つからない弱点(CSRF、認証の迂回、設定の問題)を検出します。OWASP ZAP、Burp Suite、Acunetix、StackHawk などがあります。DAST は CI/CD パイプラインに組み込み、デプロイ後のステージング環境で実行すると最もよく効きます。

第三の層は、セキュリティヘッダーによるインフラの堅牢化です。HTTP の応答ヘッダーは、アプリケーションを描画するときのブラウザの振る舞いを指示します。正しく設定すれば、実行時のコストなしにクライアント側の攻撃を丸ごと一区画防げます。

  • Content-Security-Policy:ブラウザが資源を読み込める出所を制限します。攻撃者がスクリプトタグを差し込めたとしても XSS を防ぎます。
  • Strict-Transport-Security:あなたのドメインへのすべての接続で HTTPS を使うようブラウザに強制します。SSL 剥ぎ取り攻撃を防ぎます。
  • X-Frame-Options:あなたのアプリケーションが他のドメインの iframe に埋め込まれるのを防ぎます。クリックジャッキングを抑えます。
  • X-Content-Type-Options:ブラウザによる応答型の MIME 推測を止めます。誤った content type に由来するスクリプト差し込みのリスクを減らします。
  • Secure、HttpOnly、SameSite を付けた Set-Cookie:クッキーが HTTPS でのみ送られ、JavaScript から参照できず、クロスオリジンでは送られないようにします。SameSite=Lax または Strict は、最も手軽で効果の高い CSRF 対策です。

セキュリティヘッダーは設定が簡単で、すぐ効きます。securityheaders.com のような道具でアプリケーションを走査し、欠けているヘッダーを見つけてください。多くのフレームワークは、ミドルウェアを通じてこれらの設定に対応しています。Express の Helmet、Django のセキュリティミドルウェア、Rails の Rack::Protection は、よく保守され、広く使われています。

開発時の SAST、デプロイ時の DAST、そしてインフラ層のセキュリティヘッダーの組み合わせは、重なり合う網を作ります。ある層が弱点を見逃しても、別の層が捕まえるかもしれません。これが、ソフトウェア開発ライフサイクルに置いた多層防御です。

セキュリティの文化を築く

どんな道具も、チェックリストも、枠組みも、セキュリティを設計上の制約として内面化した開発チームの代わりにはなりません。安全なコードを書くチームとそうでないチームの差は、静的解析ツールの質ではありません。日々のワークフローに埋め込まれた習慣と前提の集まりです。

最も重要な習慣は、実装前の脅威モデリングです。新しい機能のために一行でもコードを書く前に、問うてください。攻撃者は何を狙うだろうか。この機能が触れる最も価値あるデータは何か。攻撃者が入力を握ったらどうなるか。データベースが奪われたらどうなるか。これらの問いに早い段階で答えることで、コードレビューでは捕まえられない設計水準の弱点が表に出ます。弱点が実装ではなく、設計の側にあるからです。

二つ目の習慣は、セキュリティを第一級の関心事として扱うピアレビューです。スタイル、正しさ、性能だけに注目したコードレビューは、セキュリティの問題を見落とします。プルリクエストのテンプレートにセキュリティの確認項目を足してください。SQL インジェクションは。XSS は。CSRF は。差分に秘密情報は入っていないか。認可の確認は。レート制限は。セキュリティをレビュー工程の一部にすることで、責任がチーム全体に分散し、問題が本番に届く前に見つかります。

三つ目の習慣は、事故から学ぶことです。セキュリティ上の弱点が見つかったとき——自分のコードベースでも、他の誰かのコードベースでも——それを学びの機会として扱ってください。構造的な原因に焦点を当てたふりかえりを開いてください。なぜその弱点が入り込んだのか。なぜレビューで捕まらなかったのか。どの工程を変えれば、この種の弱点を将来防げるのか。責めを問わないふりかえりはセキュリティの文化を築き、責めを問うふりかえりはそれを壊します。

安全なコーディングは、一度学んで終わりの技能ではありません。脅威の情勢は変わります。道具も変わります。あなた自身の理解も変わります。どの開発者も、半期ごとに、新しい攻撃経路、新しい防御手法、新しい道具を学ぶ時間に投資すべきです。OWASP のチートシート集を読んでください。信頼できる場でセキュリティ研究者を追ってください。自分のアプリケーションに対してペネトレーションテストを走らせてください。目標は完全なセキュリティを達成することではありません。それは不可能です。目標は、あなたのアプリケーションを次の標的よりも硬い的にし、攻撃者を先へ行かせることです。

結局のところ、そういうことなのです。セキュリティとは、破られないシステムを作ることではありません。破る価値がないと思わせるシステムを作ることです。あなたが塞ぐ一つ一つの弱点、直書きをやめる一つ一つの秘密情報、実装する一つ一つのレート制限が、ほかの誰かをより魅力的な的にします。いちばん手近な果実にならないでください。

See what your own repository can account for.

Thirty minutes on a repository you choose, including the part the record cannot attribute.