Part 3 Webアプリケーション開発
第14章 セキュリティの基本
第13章では、支援ステータス機能をテストとコード品質の観点から確認した。 第14章では、そこにセキュリティの観点を重ねる。
セキュリティを学ぶと、攻撃名の多さに圧倒されることがある。 SQLインジェクション、XSS、CSRF、認証、認可、secret、依存関係、ログ。 初学者にとっては、どこから手をつければよいのか分かりにくい。
入口は、攻撃名を暗記することではない。 自分が作った機能について、何を守るのか、誰が何をしてよいのか、どこから情報が入り、どこへ出ていくのかを説明することである。 支援ステータス機能なら、支援メモ、支援ステータス、担当関係、Cookie、token、操作権限が守る対象になる。
この章では、ローカルの演習用アプリだけを対象にする。 実在する外部サービス、社内システム、本番環境、他者のアカウント、実データを対象にしない。 セキュリティ学習では、確認する範囲を守ること自体が重要な技術である。
この章でできるようになること
この章を終えると、次のことを自分の言葉で説明し、課題の成果物へ落とせるようになる。
- 自分の機能で守るべき資産を、データ、権限、可用性、信頼に分けて書ける。
- browser、API、DB、log、AI入力の間にある信頼境界を説明できる。
- OWASP Top 10やASVSを、暗記表ではなく見落とし防止の目次として使える。
- 認証と認可を分け、画面側の制御とAPI側の制御を別々に確認できる。
- IDを書き換えたときの担当外アクセスを、保存値が変わらないことまで含めて確認できる。
- 入力検証、パラメータ化クエリ、出力エンコードの役割の違いを説明できる。
- Cookie認証とAuthorization headerで、CSRFやtoken漏えいの見方が変わることを説明できる。
- エラーとログを、調査に役立つがsecretや個人情報を漏らさない形で設計できる。
- 依存関係の監査結果を、影響範囲、修正版、互換性、テスト結果で判断できる。
security-review.mdに、確認済みのリスク、未確認のリスク、修正、再発防止を書ける。
セキュリティは、後から貼るシールではない
セキュリティは、リリース直前にチェックリストだけで足すものではない。 要件を決めるとき、データを設計するとき、APIを作るとき、画面を作るとき、テストを書くときに少しずつ考える。
正しい使い方なら動く、だけでは足りない。 間違った入力が来たらどうなるか。 担当外のIDを指定されたらどうなるか。 ログインしていない利用者がAPIを呼んだらどうなるか。 支援メモに特殊な文字列が入ったら、画面でどう表示されるか。 ログやPR本文にsecretが混ざっていないか。 依存しているライブラリに既知の脆弱性がないか。
これらは、特別な専門家だけが見ることではない。 自分の変更をレビューへ出す開発者が、最初に見られる範囲で確認する。 深い診断や専門的な監査が必要な場面は別にあるが、日々の開発でも見落としを減らすことはできる。
安全な演習範囲を先に決める
この章の演習では、講師が用意したローカルの演習用アプリだけを使う。 これは形式的な注意ではない。 セキュリティの学習では、何を試してよいか、どこまで試してよいかを明確にする必要がある。
やってよいことは、ローカル演習用アプリで、用意された脆弱な実装を確認し、問題を再現し、修正し、再発防止を説明することである。 やってはいけないことは、実在する外部サービス、社内システム、本番環境、他者のアカウント、実データを対象にすることである。 許可されていないスキャンや攻撃も行わない。
提出物やAIに渡す内容にも注意する。 秘密情報、Cookie、token、個人情報、実際の支援メモを含めない。 画面キャプチャやログも、見られると困る情報が写り込むことがある。
セキュリティの演習は、うまく攻撃できるかを競う場ではない。 何が問題で、どう直し、どう再発防止するかを説明するための学習である。
守る資産から始める
資産とは、守るべきデータ、機能、権限、可用性、信頼である。 セキュリティを考えるときは、まず資産を書き出す。 守るものが曖昧なままでは、どのリスクが重要かを判断できない。
支援ステータス機能では、次のような資産がある。
- 支援メモ:受講者への対応内容や注意点が含まれる可能性がある。
- 支援ステータス:受講者の支援状況を示し、メンターの判断に使われる。
- 担当関係:誰がどの受講者を見てよいかを決める。
- 操作権限:誰が支援ステータスを更新できるかを決める。
- Cookie、session、token:ログイン状態やAPI利用の証明に使われる。
- ログと監査記録:問題発生時の調査や説明に使われる。
資産を書くと、次に脅威を書ける。 脅威とは、資産に対して起きてほしくない出来事である。
- 担当外メンターが支援メモを見る。
- 受講者ロールがメンター向けAPIを呼ぶ。
- 担当外の
learnerIdを指定して支援ステータスを更新する。 - 支援メモに入れた文字列が、画面でコードとして実行される。
- Authorization headerやCookieがログに出る。
- 古い依存関係の脆弱性から、意図しない操作が可能になる。
攻撃者という言葉も、強い悪意を持った外部者だけを意味しない。 権限を越えた利用者、誤操作をする利用者、壊れたクライアント、誤設定の運用、漏れたsecretを持つ第三者も、考える対象になる。
情報が渡る場所を線で見る
セキュリティ確認では、情報がどこから入り、どこへ渡るかを見る。 コードファイルだけを読んでも、リスクの位置は見えにくい。
支援ステータス機能では、少なくとも次の流れがある。
browser -> API -> DB
browser -> API -> server log
API -> browser
developer -> PR
developer -> AI
dependency registry -> application
ブラウザからAPIへは、支援ステータス、支援メモ、learnerId、Cookie、Authorization headerが渡る。 APIからDBへは、更新する値と対象IDが渡る。 APIからログへは、時刻、操作、対象、結果が渡る。 APIからブラウザへは、更新結果やエラーが返る。 開発者からPRやAIへは、説明、ログ、コード、スクリーンショットが渡る。
この線を見ると、確認すべき場所が分かる。 入力検証はbrowserからAPIへ入るところで見る。 認可はAPIがDBへ進む前に見る。 出力エンコードはAPIからbrowserへ戻って画面表示されるところで見る。 secretはログ、PR、AI、Network、環境変数の扱いで見る。 依存関係は、外部パッケージがアプリへ入るところで見る。
ここで、信頼境界という言葉を使う。 信頼境界は、信頼できる前提が変わる境目である。 ブラウザで選ばれた値は、APIに届いた時点では信頼できない入力として扱う。 APIがDBへ渡す値も、SQLの構造を壊さない形へ変換する。 開発者がAIへ貼るログも、社外サービスへ渡る前にsecretや個人情報を取り除く。
| 境界 | 信頼しないもの | 確認すること |
|---|---|---|
| browser -> API | request body、query、path params、header | 入力検証、認証、認可、body size |
| API -> DB | SQLへ渡る値、検索条件、更新値 | パラメータ化、DB制約、最小権限 |
| API -> browser | response body、error message | 出しすぎない情報、出力エンコード、status code |
| API -> log | request情報、error、actor、target | secretや本文を出さず、調査に必要なIDを残す |
| developer -> PR/AI | ログ、スクリーンショット、コード、差分 | Cookie、token、実名、支援メモを含めない |
OWASPは、暗記表ではなく見落とし防止の目次である
OWASP Top Tenは、Webアプリケーションでよく問題になるリスクを整理した資料である。 2026年6月時点では、OWASP Top Ten 2025が現行リリースである。 これは順位を暗記するための表ではなく、見落としを減らすための目次として使う。
OWASP Top 10 2025は、次のカテゴリを挙げている。 研修で全部を詳細に扱う必要はない。 自分の機能に関係するものを、確認観点へ変換する。
| OWASP Top 10 2025 | 支援ステータス機能での見方 |
|---|---|
| A01 Broken Access Control | 担当外メンターが支援メモや更新APIへ届かないか |
| A02 Security Misconfiguration | 開発用エラー、広すぎるCORS、不要なdebug設定が残っていないか |
| A03 Software Supply Chain Failures | 依存package、lock file、監査結果、更新判断を確認したか |
| A04 Cryptographic Failures | Cookie、token、secret、通信経路、保存時の扱いを確認したか |
| A05 Injection | SQLへ値を直接連結していないか。パラメータ化しているか |
| A06 Insecure Design | そもそも担当関係やロールを設計に入れているか |
| A07 Authentication Failures | ログイン状態やtokenの扱いを、画面任せにしていないか |
| A08 Software or Data Integrity Failures | 信頼できない更新経路や改ざんされた依存関係を想定しているか |
| A09 Security Logging & Alerting Failures | 調査に必要なログがあり、secretを漏らしていないか |
| A10 Mishandling of Exceptional Conditions | 例外時に500で内部情報を返したり、更新後にエラーへ進んだりしないか |
ASVS、Application Security Verification Standardは、アプリケーションのセキュリティ確認項目を体系化した標準である。 2026年6月時点では、ASVS 5.0.0がlatest stableとして参照できる。 研修ではすべてを読む必要はないが、認証、認可、入力、出力、エラー、ログ、構成といった観点が、確認項目として整理されていることを知っておくとよい。
NIST SSDF、Secure Software Development Frameworkは、セキュアなソフトウェア開発の高水準プラクティスをまとめた文書である。 最終版として参照されるSP 800-218はVersion 1.1である。 要件、設計、実装、検証、脆弱性対応を、開発ライフサイクルへ組み込む考え方を学ぶ補助になる。
これらの資料は、公式ページで最新状態を確認する。 セキュリティの参照情報は更新される。 本に書かれた版だけを固定的に信じるのではなく、必要なときに一次情報へ戻る習慣を持つ。
認証と認可を分ける
認証は、利用者が誰かを確認することである。 たとえば、ログイン中のユーザーがメンター本人かどうかを見る。
認可は、その利用者がその操作をしてよいかを確認することである。 ログインしていることと、対象データを操作してよいことは別である。 ここを混ぜると、ログイン済みなら何でもできる設計になりやすい。
支援ステータス機能では、ログイン中のメンターでも、すべての受講者を更新できるわけではない。 担当関係がある受講者だけを更新できる。 受講者ロールは、メンター向けAPIを使えない。 未ログイン利用者は、そもそもAPIを使えない。
認証と認可は、API側で必ず確認する。 画面でボタンを隠すことは、利用者に不要な操作を見せないためには有効である。 しかし、APIは画面以外から呼ばれる可能性がある。 最後の守りを画面だけに置いてはいけない。
learning-log-sample では、研修で確認しやすくするために x-mentor-id headerを使う箇所がある。
これは本物の認証ではない。
利用者が自由に書き換えられるheaderを、そのまま本人確認として信用してはいけない。
実務では、セッション、署名付きtoken、社内の認証基盤など、サーバー側で検証できる仕組みから現在の利用者を決める。
また、更新APIだけでなく一覧APIも確認対象である。 支援ステータスの一覧が担当メンターの担当範囲に絞られていなければ、更新できなくても支援メモや支援状況が見えてしまう。 「書けないから安全」ではなく、「見えてよいか」「更新してよいか」を分けて確認する。
| 確認対象 | 画面側で見ること | API側で見ること |
|---|---|---|
| 担当受講者一覧 | 担当範囲だけが表示されるか | responseに担当外受講者が含まれないか |
| 支援ステータス更新 | 担当外行に更新操作が出ないか | 担当外IDを指定しても403または方針に応じた404になるか |
| 受講者ロール | メンター向け画面へ導線がないか | メンター向けAPIが拒否するか |
| 未ログイン | ログイン画面へ誘導されるか | 401になるか |
ID直接指定のリスクを見る
URLやrequest bodyにIDが入るAPIでは、そのIDを書き換えたらどうなるかを見る。
たとえば、learnerId が1から2に変わったとき、対象が存在するかだけでなく、今のユーザーがその対象を見たり変更したりしてよいかを確認する。
これは、担当外データへのアクセス制御の問題である。 IDOR、Insecure Direct Object Reference、またはBOLA、Broken Object Level Authorizationと呼ばれることもある。 名前を覚えるより、「IDを変えたら他人の対象へ届かないか」を確認することが大切である。 対象が存在するかを見るだけでは不十分である。 存在するが担当外なら拒否する必要がある。
支援ステータス更新では、少なくとも次を確認する。
担当メンター:
PATCH /api/mentor/learners/{assignedLearnerId}/support-status -> 200
担当外メンター:
PATCH /api/mentor/learners/{assignedLearnerId}/support-status -> 403
未ログイン:
PATCH /api/mentor/learners/{assignedLearnerId}/support-status -> 401
受講者ロール:
PATCH /api/mentor/learners/{assignedLearnerId}/support-status -> 403
期待するstatus codeだけでなく、保存値が変わっていないことも確認する。 403が返っていても、内部で更新してからエラーになっているなら問題である。 APIテストやDB確認と組み合わせると、認可の説明が強くなる。
担当外の対象に対して 403 を返すか、存在自体を隠すために 404 を返すかは、チームの情報開示方針によって変わる。
どちらを選ぶ場合でも、同じ種類のrequestで一貫させる。
また、response bodyに「この受講者は別メンターの担当です」のような余計な情報を出しすぎない。
入力値は、画面から来ても信用しない
入力検証は、外から来た値の形式、範囲、業務ルールを確認することである。 画面でselectを使っていても、APIに来る値を信用してはいけない。 ブラウザの開発者ツールやcurlを使えば、画面にない値も送れる。
支援ステータスなら、許可する値を明確にする。
allowed:
- none
- needs_support
- in_progress
- resolved
rejected:
- 空文字
- null
- 許可されていない文字列
- 型が違う値
支援メモにも確認が必要である。 最大文字数、空欄の扱い、前後空白の扱い、保存してよい文字の範囲、ログに出すかどうかを見る。 入力検証は、利用者を困らせるためのものではない。 保存できるデータの意味をそろえ、後続の処理や表示を壊さないためのものでもある。
入力検証では、rejectするだけでなく、正規化するかどうかも決める。 たとえば支援メモの前後空白を削るのか、そのまま保存するのか。 大文字小文字を許すのか、許可値に完全一致させるのか。 余分な項目がbodyに入っていたとき、無視するのかエラーにするのか。 API契約と実装とテストで、同じ方針にする。
request bodyの大きさも確認する。 想定外に大きいbodyを受け続けると、メモリや処理時間を使い切る原因になる。 研修用アプリでも、body sizeの上限、JSON parse失敗時のstatus code、利用者向けmessageを確認観点に入れる。
SQLインジェクションは、入力がSQLの構造を変える問題である
SQLインジェクションは、信頼できない入力がSQLの構造を変えてしまう問題である。 初心者がまず見るべき合図は、ユーザー入力をSQL文字列に直接つないでいないかである。
危ない文字を頑張って消す、という発想だけでは不十分である。 大切なのは、入力をSQLの命令として解釈させないことである。 基本は、パラメータ化クエリやORMの安全なAPIを使うことである。
支援ステータス機能では、learnerId、supportStatus、検索条件、支援メモがSQLへ渡る可能性がある。
これらを文字列連結でqueryへ埋め込んでいないかを見る。
許可値検証とパラメータ化は役割が違う。
許可値検証は、業務上受け入れる値かを見る。
パラメータ化は、DBへ渡すときにSQL構造を壊さないようにする。
危ない例は、入力をSQL文字列に直接足している形である。
const sql = "select * from learners where id = '" + learnerId + "'";
安全な方向の例は、SQLの構造と値を分ける形である。 使っているDBライブラリによって書き方は違うが、考え方は同じである。
const sql = "select * from learners where id = ?";
const learner = db.get(sql, [learnerId]);
ORMを使っている場合でも、すべてが自動で安全になるわけではない。 ORMが提供する安全なwhere APIを使っているか、生SQLや文字列連結へ戻っていないかを見る。 検索条件、並び順、カラム名のようにプレースホルダで扱いにくい部分は、許可リストから選ぶ。
DBアカウントの権限も考える。 アプリが支援ステータス更新だけを行うなら、必要以上に広いDB権限を持たせない。 万一のときの被害を小さくするためである。
XSSは、入力がブラウザでコードになる問題である
XSSは Cross-Site Scripting の略である。 信頼できない入力が、ブラウザ上でコードとして実行される問題を指す。 ユーザーが入力した名前、メモ、コメント、検索語、エラーメッセージ、通知文などを表示するときに注意する。
入力された文字をどこに表示するかを見る。 HTML本文、HTML属性、URL、JavaScriptの中では、安全な出し方が違う。 出力エンコードは、表示する文脈に合わせて、値がコードとして解釈されないようにすることである。
支援メモを画面に表示するなら、その内容がHTMLとして解釈されず、テキストとして表示されるかを確認する。 フレームワークには、通常のテキスト表示で自動エスケープを行うものが多い。 ただし、HTMLを直接挿入するAPIや、エスケープを無効化する仕組みを使うと危険になる。
素のDOM操作なら、テキストとして出すときは textContent を使う。
innerHTML は、HTMLとして解釈してよい内容だけに限定する。
Reactなら通常の {value} 表示はエスケープされるが、dangerouslySetInnerHTML は名前の通り注意が必要である。
Vueの v-html など、フレームワークごとの「HTMLとして挿入する機能」も同じように扱う。
| 表示場所 | 基本方針 | 注意点 |
|---|---|---|
| HTML本文 | テキストとして表示する | textContent や通常のテンプレート表示を使う |
| HTML属性 | 属性値としてエンコードする | URLやイベント属性に混ぜない |
| URL | 許可するschemeやpathを決める | javascript: のような値を許さない |
| JavaScript内 | そもそも外部入力を直接埋め込まない | JSONとして安全に渡す |
エラー表示やトースト通知も確認対象である。 画面の中心に出る本文だけでなく、検索結果、空状態、エラー文、ログビュー、管理画面など、外部入力が表示される場所を探す。
CSRFは、ログイン中のブラウザから意図しない更新が送られる問題である
CSRFは Cross-Site Request Forgery の略である。 ログイン中のブラウザから、利用者が意図していない更新requestが送られる問題を指す。
Cookieは、条件が合うとブラウザが自動で送る。 そのため、Cookieでログイン状態を持つ更新APIでは、別のサイトから送られたrequestでも、ログイン済みとして扱われる場合がある。 更新APIでは、使っているフレームワークの推奨に従って、CSRF token、SameSite属性、送信元確認などを検討する。
基本として、GETで状態を変更しない。 GETは、ページやデータを取得するためのmethodとして扱う。 支援ステータスを変更する操作には、POST、PATCH、PUT、DELETEなど、変更の意図が分かるmethodを使う。 methodを分けるだけで完全に安全になるわけではないが、事故を減らす土台になる。
CSRF対策は、認証方式やフレームワークによって実装が異なる。 この章では仕組みの詳細を網羅しない。 自分のアプリがCookieで認証しているのか、Authorization headerを使っているのか、フレームワークが何を提供しているのかを確認する。
認証方式によって、気にするポイントが変わる。
| 認証情報の置き場所 | CSRFで見ること | 別に見ること |
|---|---|---|
| Cookie | ブラウザが自動送信するため、CSRF token、SameSite、Origin/Referer確認を検討する | Cookieに HttpOnly、Secure、SameSite を設定する |
| Authorization header | 別サイトから勝手に付けにくいため、典型的なCSRFの条件とは異なる | tokenをlocalStorageなどに置く場合、XSSで読まれるリスクを見る |
研修用の x-mentor-id |
本物の認証ではないため、CSRF以前に本人確認として信用しない | 実務へ持ち込まない |
SameSite属性だけで、すべてのCSRF対策が終わるわけではない。 古いブラウザ、外部連携、サブドメイン、method、フォーム送信、フレームワークの仕様によって考えることが変わる。 この章では、使っている認証方式を言葉にし、フレームワークの推奨対策を確認するところまでを目標にする。
エラーとログは、役に立って漏れない形にする
エラーとログは、守るためにも、漏らさないためにも重要である。 利用者に見せるエラーには、内部のSQL、stack trace、設定値、ファイルパス、secretを出しすぎない。 攻撃者に内部構造のヒントを渡してしまうことがある。
一方で、調査に必要な情報は残す。 時刻、操作、対象、結果、request id、認証済みユーザーの内部IDなどは、問題発生時の調査に役立つ。 ただし、Cookie、Authorization header、password、API key、支援メモ本文、個人情報をそのまま出してはいけない。
利用者向けエラーと開発者向けログを分ける。 利用者には、何が起きたかと次にできることを示す。 開発者には、原因調査に必要な手がかりを、安全な範囲で残す。
利用者向け:
この受講者の支援ステータスを変更する権限がありません。
ログ:
request_id=... actor_id=... action=update_support_status target_learner_id=... result=forbidden
ログは、あとで読む証拠である。 証拠として役に立つことと、漏れてはいけない情報を含まないことを両立させる。
ログに残す候補と、残さない候補を分ける。
| 種類 | ログに残すか | 理由 |
|---|---|---|
request_id |
残す | 問い合わせ、画面、サーバーログを結びつける |
actor_id |
残す。ただし内部IDなど最小限 | 誰の操作かを調査する |
target_learner_id |
残す。ただし必要最小限 | どの対象で失敗したかを見る |
result、error_code |
残す | 調査と集計に使う |
| Authorization header、Cookie | 残さない | secretそのものになり得る |
| password、API key、private key | 残さない | 漏えい時の影響が大きい |
| 支援メモ本文、個人情報 | 原則そのまま残さない | 必要ならマスク、要約、内部IDで扱う |
| stack trace | 利用者には出さない。サーバーログでは管理範囲を限定する | 調査に役立つが内部情報を含む |
研修用スターターでは、説明を簡単にするために、例外時の error.message をresponseへ返す実装が残っている場合がある。
実務のAPIでは、壊れたJSONやbody size超過は 400 系の入力エラーへ変換し、想定外の例外では一般的なmessageとrequest idだけを返す。
内部の詳細は、アクセス制御されたサーバーログで確認する。
secretは、コード以外からも漏れる
secretとは、API key、password、session ID、access token、private key、Cookie、Authorization headerなど、外へ出してはいけない値である。 ログインや外部接続に使われる鍵のようなものだと考えると分かりやすい。
secretは、ソースコードだけから漏れるわけではない。 次の場所を見る。
git diff.env.gitignore- server log
- browser Network
- PR本文
- issueやチャット
- スクリーンショット
- AIに渡す内容
もしsecretらしきものを見つけたら、消すだけでは足りない場合がある。 すでに共有されたsecretは、無効化や再発行が必要になることがある。 どこに出たか、誰が見られる状態だったか、どの環境に影響するかを確認する。
.env はローカル環境の値を置く場所として使われることがあるが、公開repositoryへ含めない。
共有するのは .env.example であり、そこには本物の値を入れない。
変数名、用途、形式だけを書く。
# .env.example
DATABASE_URL=
SESSION_SECRET=
secretが漏れた可能性があるときは、次を考える。
- そのsecretを無効化、再発行、rotationできるか。
- どの環境のsecretか。local、staging、productionのどれか。
- いつから、誰が見られる場所に出ていたか。
- ログ、issue、PR、チャット、AI入力、スクリーンショットにも残っていないか。
- 同じsecretを他の環境で使い回していないか。
個人情報や支援メモは、secretとは種類が違う。 しかし、見られると困る情報として慎重に扱う。 提出物やAIに渡す内容には、実名、メールアドレス、Cookie、token、実際の支援メモを含めない。
依存関係は、自分のコードの外にあるリスクである
アプリケーションは、自分で書いたコードだけでできているわけではない。 多くのライブラリ、フレームワーク、ツールに依存している。 古いライブラリや既知の脆弱性があるライブラリは、アプリ本体のコードがきれいでもリスクになる。
まず、依存関係を書くファイルを見る。
JavaScriptなら package.json やlock file、Pythonなら pyproject.toml、requirements.txt、lock fileが候補になる。
どのpackageを使い、どのversionが固定されているかを見る。
次に、監査コマンドを確認する。
プロジェクトによって、npm audit、pnpm audit、yarn audit、pip-audit、bundle audit などが使われる。
コマンド名を暗記するより、リポジトリのREADME、Makefile、CI設定にある標準コマンドを使うことが大切である。
監査結果は、出たらすぐ機械的にversionを上げればよいわけではない。 脆弱性の影響範囲、使っている機能、修正版、互換性、テスト結果を確認する。 修正するもの、今回は対応しないもの、次章以降で確認するものを分けて記録する。
依存関係の確認では、直接依存と間接依存を分ける。
直接依存は、自分の package.json などに書いたpackageである。
間接依存は、そのpackageがさらに使っているpackageである。
脆弱性は間接依存から出ることもある。
lock fileは、実際に入るversionを固定するための重要なファイルである。
package-lock.json、pnpm-lock.yaml、yarn.lock などを不用意に消したり、理由なく大きく変えたりしない。
依存関係を更新したら、なぜ更新したのか、どの脆弱性や互換性に関係するのか、どのテストを実行したのかを書く。
npm audit fix --force のような強い自動更新は、major versionの更新や破壊的変更を含むことがある。
実行前に差分、影響範囲、テスト計画を確認する。
研修では、標準コマンドの結果を読み、対応判断を記録することを優先する。
ローカル演習では、再現、原因、修正、再確認をそろえる
attack-and-fix-log.md では、ローカル演習用アプリで脆弱な実装を確認し、修正と再確認を記録する。
扱う候補は、SQLインジェクション、XSS、CSRFの基本例である。
ただし、実在環境では絶対に行わない。
記録することは、攻撃手順の自慢ではない。 次の順番で書く。
- 対象:SQL injection、XSS、CSRFのどれを扱ったか。
- 演習環境:local URL、branch、sample account。
- 脆弱な実装:どの実装が問題だったか。
- ローカルで再現したこと:用意された環境で何を確認したか。
- 原因:なぜ問題が起きたか。
- 修正方針:どう直すか。
- 実施した修正:実際に何を変えたか。
- 修正後の確認:何で直ったことを確認したか。
- 再発防止:テスト、レビュー観点、文書化。
セキュリティ修正は、直したつもりで終わらせない。 再発防止まで書く。 たとえば、認可漏れならAPIテストを追加する。 XSSなら危険な表示APIを使う箇所をレビュー観点に入れる。 secret漏れなら、ログ出力とPR前チェックを見直す。
AIは、攻撃手順ではなく確認観点に使う
AIは、脅威の洗い出し、認可確認ケースの候補、SQLインジェクション、XSS、CSRFの基本確認観点、秘密情報チェックリスト、依存関係確認の観点、修正案の比較、PRに書くレビュー文案に使える。
ただし、AIに渡す前に、秘密情報、Cookie、token、個人情報、実際の支援メモを外す。 実在環境への攻撃手順を生成させない。 出てきた提案は、公式資料、既存コード、ローカル実行結果で確認する。
依頼例は次のように、安全な範囲を明示する。
支援ステータス機能のセキュリティ確認観点を整理してください。
前提:
- メンターは担当受講者だけを変更できる
- 支援メモを画面に表示する
- APIはCookieまたはAuthorization headerで認証する
- ローカル演習環境だけを対象にする
- 実在サービスへの攻撃手順は不要
- 秘密情報や個人情報は含めない
出してほしいこと:
- 守るべき資産
- 認可確認ケース
- SQLインジェクション、XSS、CSRFの基本確認
- 秘密情報とログの確認
- 依存関係の確認
- PRに書くべきセキュリティ観点
AIに任せたままにしてはいけないこともある。 秘密情報を含むログの処理、安全であるという最終判断、認証認可方式の確定、依存関係更新の影響判断である。 これらは、開発者が文脈、公式資料、実行結果を見て判断する。
security-risk-note.mdに書くこと
security-risk-note.md は、守るものと確認すべきリスクを整理する文書である。
# Security Risk Note
## 対象機能
- メンターが担当受講者の支援ステータスを一覧で確認し、更新できる機能。
## 守るべき資産
| asset | why important |
| --- | --- |
| 支援メモ | 対応内容や注意点が含まれる可能性がある |
| 支援ステータス | 支援判断に使われる |
| 担当関係 | 見てよい範囲と更新できる範囲を決める |
| Cookie、token | 認証状態を示す |
## 脅威
| threat | impact | priority |
| --- | --- | --- |
| 担当外メンターが支援メモを見る | 個人情報や支援状況の漏えい | high |
| 受講者がメンター向けAPIを呼ぶ | 権限外更新 | high |
| 支援メモが画面でコードとして実行される | XSS | high |
| ログにAuthorization headerが出る | secret漏えい | high |
## 情報が渡る場所
| boundary | crossing data | risk | check |
| --- | --- | --- | --- |
| browser -> API | learnerId, status, note, Cookie/token | 不正入力、権限外操作 | 入力検証、認証、認可 |
| API -> DB | learnerId, status, note | SQL injection、不整合 | パラメータ化、DB制約 |
| API -> log | actor, target, result, error | secretや個人情報の漏えい | マスク、request id |
| API -> browser | learner, error message | 出しすぎ、XSS | response設計、出力エンコード |
| developer -> AI | ログ、差分、スクリーンショット | secret、個人情報の共有 | 入力前チェック |
## 優先して確認すること
-
## 今回対象外
-
この文書は、専門用語を並べる場所ではない。 自分の機能で何が困るのかを、読者が追える形で書く。
authorization-check.mdに書くこと
authorization-check.md では、認可と担当外アクセスを確認する。
ログイン済みかどうかだけではなく、ロールと担当関係を見る。
# Authorization Check
## ロールと操作
| actor | list learners | update support status | expected |
| --- | --- | --- | --- |
| assigned mentor | allowed | allowed | 200 |
| other mentor | denied or scoped | denied | 403または方針に応じた404 |
| learner | denied | denied | 403 |
| anonymous | denied | denied | 401 |
## IDを書き換えた確認
| case | 確認した操作 | expected status | actual status | note |
| --- | --- | --- | --- | --- |
| 担当外learnerIdを指定する | PATCH support-status | 403または404 | | 保存値も変わっていないか |
| 担当外learnerIdを一覧に含める | GET learners | 含まれない | | response bodyも確認 |
## 画面側の確認
-
## API側の確認
-
## 実行した確認
| command or operation | result | evidence |
| --- | --- | --- |
| npm test | | |
| curlで担当外IDを指定 | | |
## 修正が必要なこと
-
画面側の確認とAPI側の確認を分ける。 画面でボタンが出ないことと、APIが拒否することは別の確認である。
attack-and-fix-log.mdに書くこと
attack-and-fix-log.md は、ローカル演習での再現、原因、修正、再確認を残す文書である。
# Attack and Fix Log
## 対象
- SQL injection / XSS / CSRF:
## 演習環境
- local URL:
- branch:
- sample account:
## 脆弱な実装
-
## ローカルで再現したこと
-
## 原因
-
## 修正方針
-
## 実施した修正
-
## 修正後の確認
-
## 再発防止
- test:
- review point:
- documentation:
## 残るリスク
-
## 注意
- 実在環境では実施していない。
- 秘密情報、Cookie、token、個人情報、支援メモは提出物に含めていない。
この文書では、許可されたローカル演習であることを明記する。 実在環境での再現手順を残さない。
secrets-and-dependencies-check.mdに書くこと
secrets-and-dependencies-check.md では、秘密情報、見られると困る情報、依存関係を確認する。
# Secrets and Dependencies Check
## 秘密情報チェック
| location | checked | result | note |
| --- | --- | --- | --- |
| git diff | | | |
| .env | | | |
| .gitignore | | | |
| server log | | | |
| browser Network | | | |
| PR本文 | | | |
| AIに渡す内容 | | | |
## 見られると困る情報チェック
| location | checked | result | note |
| --- | --- | --- | --- |
| スクリーンショット | | | |
| 支援メモ | | | |
| エラー出力 | | | |
## 依存関係チェック
| item | checked | result | note |
| --- | --- | --- | --- |
| 依存関係を書くファイル | | | |
| バージョンを固定するファイル | | | |
| 監査コマンド | | | |
| 脆弱性が報告されたpackage | | | |
| 更新後のテスト | | | |
## 対応が必要なこと
-
## 対応しない判断をしたこと
-
対応しない判断も、理由を残す。 たとえば、研修環境では使っていない依存機能に関する警告だが、次章以降で影響確認する、という書き方ができる。
security-review.mdに書くこと
security-review.md は、PRや最終課題で説明するためのまとめである。
完璧に安全だと言い切る文書ではない。
どのリスクを確認し、何を修正し、どのリスクを残したかを、レビュアーが追える形にする。
# Security Review
## 対象変更
-
## 確認したリスク
| risk | checked | result |
| --- | --- | --- |
| authorization | | |
| list scoping | | |
| SQL injection | | |
| XSS | | |
| CSRF | | |
| secrets | | |
| dependencies | | |
| error and logs | | |
## 修正したこと
-
## 実行したテスト、手動確認
-
## 参照した資料
- OWASP Top Ten:
- OWASP ASVS:
- NIST SSDF:
## 残したリスク
-
## PRに書くこと
-
## レビュー文の下書き
セキュリティ観点では、認可、入力と表示、秘密情報、依存関係を確認しました。
担当外メンターによる更新はAPI側で403になることを確認しています。
残課題として、依存関係更新の影響確認を第15章以降で続けます。
## 将来確認すること
-
第13章の quality-review.md と同じく、確認済みの範囲と未確認の範囲を分ける。
実務で信頼されるのは、すべて安全ですと言い切ることではなく、証拠と残リスクを説明できることである。
セキュリティ確認で起きやすい誤解
- 攻撃名を覚えればセキュリティが分かると考える。まず、自分の機能で守る資産と情報の流れを書く。
- ログインしていれば何でも操作してよいと考える。認証と認可を分ける。
- 画面でボタンを隠せば認可できていると考える。API側で担当関係とロールを確認する。
- 研修用の
x-mentor-idを、本物の認証方式だと思う。利用者が書き換えられるheaderを本人確認として信用しない。 - 更新APIだけを守ればよいと考える。一覧や詳細で担当外データが見えるだけでも問題になる。
- IDが存在するかだけを見て、担当外かどうかを見ない。存在確認と権限確認は別である。
- 403が返っただけで安心する。保存値が変わっていないことも確認する。
- 画面のselectで選ばせているから、不正な値は来ないと考える。API側で入力検証する。
- SQLインジェクション対策を、危ない文字を消す作業だけで考える。パラメータ化クエリやORMの安全なAPIを使う。
- XSSを画面本文だけの問題だと考える。エラー、通知、検索語、管理画面も確認する。
- CORS設定をすればCSRF対策になると考える。CORSとCSRFは目的が違うため、認証方式とフレームワークの推奨を確認する。
- secretをソースコードだけで探す。ログ、Network、PR本文、AIに渡す内容、スクリーンショットも見る。
- 依存関係の警告を見なかったことにする。影響範囲、修正版、互換性、対応判断を記録する。
- AIにセキュリティ判断を丸投げする。公式資料、既存コード、ローカル実行結果で検証する。
- 演習用の再現を実在環境へ持ち出す。許可されたローカル環境だけで行う。
資産と攻撃再現で確認すること
この章では、security-risk-note.md、authorization-check.md、attack-and-fix-log.md、secrets-and-dependencies-check.md、security-review.md を作る。
最初に、第9章の domain-rules.md、第11章の validation-and-authorization.md、第13章の quality-review.md を読み直す。
支援ステータス機能で、守る資産、許可する操作、入力と表示、確認済みのテスト、残っているリスクを取り出す。
security-risk-note.md には、対象機能、守るべき資産、脅威、情報が渡る場所、優先して確認すること、今回対象外を書く。
browser、API、DB、log、AI入力の信頼境界を分け、どの境界で何を確認するかを書く。
authorization-check.md には、担当メンター、担当外メンター、受講者ロール、未ログインユーザーで、一覧表示と支援ステータス更新を確認した結果を書く。
IDを書き換えた場合の期待結果と実際の結果も記録する。
更新APIだけでなく、一覧responseに担当外データが含まれていないかも確認する。
403または404が返るだけでなく、保存値が変わっていないことを書く。
attack-and-fix-log.md には、ローカル演習用アプリで確認したSQLインジェクション、XSS、CSRFの基本例について、脆弱な実装、再現、原因、修正、修正後の確認、再発防止を書く。
実在環境を対象にしていないことも明記する。
secrets-and-dependencies-check.md には、git diff、.env、.gitignore、server log、browser Network、PR本文、AIに渡す内容、依存関係を書くファイル、lock file、監査コマンドの結果を書く。
security-review.md には、対象変更、確認したリスク、修正したこと、実行したテストと手動確認、残したリスク、PRに書くこと、将来確認することを書く。
提出物には、秘密情報、Cookie、token、個人情報、実際の支援メモを含めない。 AIを使った場合は、入力前、採用前、共有前、PR前に確認したことを書く。
セキュリティの基本で持ち帰ること
第14章で身につけるべきことは、セキュリティを自分の機能の言葉で説明することである。 守る資産を決める。 情報が渡る場所を見る。 信頼境界を見つける。 認証と認可を分ける。 画面側の制御とAPI側の制御を分ける。 入力を信用せず、出力する文脈を見る。 secretと依存関係をコードの外にあるリスクとして扱う。 ログは、役に立って漏れない形にする。
SQLインジェクション、XSS、CSRFは、攻撃名を暗記するためではなく、入力、出力、認証状態、状態変更を確認する入口として扱う。 OWASP Top 10やASVSは、全部暗記する表ではなく、見落としを減らすための目次として使う。 ローカル演習では、再現、原因、修正、再確認、再発防止をそろえる。
最終的な目的は、完璧に安全だと言い切ることではない。 どこまで確認し、何を修正し、何を残リスクとして扱っているかを、証拠とともに説明できるようにすることである。
コンテナと実行環境の章へ
次章では、アプリを他の環境でも再現しやすくするために、コンテナと実行環境を扱う。 第14章で見たsecret、依存関係、実行ユーザー、ログ、設定は、コンテナでも重要になる。 アプリをどこで、どんな設定で、誰の権限で動かすかを、次章で整理する。