コードを書けるようになる必要はありません。しかし、AIが書いたコードを「なんとなく読める」と、確認できることが一気に増えます。この章では、そのために必要な最低限の概念を7つに絞り、守りの設計で繰り返し出てくる4つの原則を内部統制と対応づけます。最後に、短いコードを2つ読んで、「ここで所有者を確認している/していない」を見分ける練習をします。
この章の学習目標
- 変数、関数、条件分岐、繰り返し、入力と出力、状態、エラー処理の7つの概念を、経理の言葉で説明できるようになる
- 信頼境界、最小権限、多層防御、フェイルセーフの4つの設計原則を、内部統制の対応する考え方とともに説明できるようになる
- 10行程度のコードを読んで、「所有者の確認があるか、ないか」を見分けられるようになる
コードを読むための7つの概念
プログラムは、どの言語で書かれていても、ほぼ次の7つの部品でできています。経理の業務マニュアルに置き換えると、見慣れたものばかりです。
| 概念 | 意味 | 経理でいえば | コードでの見た目 |
|---|---|---|---|
| 変数 | 値に名前を付けて、一時的に置いておく箱 | 伝票の各欄。「金額」「取引先」 | balance、user のような名前 |
| 関数 | ひとまとまりの処理に名前を付けたもの。入力を受け取り、結果を返す | 業務手順書の1項目。「振込を実行する」 | function、def で始まる塊 |
| 条件分岐 | 「もし〜なら、こうする。そうでなければ、こうする」 | 「50万円以上なら部長承認」 | if、else |
| 繰り返し | 同じ処理を、一覧の要素ごとに行う | 「全伝票について、証憑を照合する」 | for、while、map |
| 入力と出力 | 外から受け取るものと、外に返すもの | 受け取った申請書と、渡す控え | 関数の引数と、return で返す値 |
| 状態(state) | 処理をまたいで覚えておく値。ログイン中かどうか、残高 | 元帳の残高。前月から引き継ぐもの | データベースの値、セッション |
| エラー処理 | うまくいかなかったときに、どうするか | 証憑がないときは差し戻す | try、catch、return deny(...) |
セキュリティの観点で特に見るのは、条件分岐とエラー処理です。守りは、ほぼ必ず「もし本人でなければ、拒否する」という条件分岐の形で書かれます。その分岐が「ある」か「ない」か。あるなら、何を確かめているか。それだけ読めれば、第5章の8種類のうち半分は、自分の目で確かめられます。MDN Web Docsの「JavaScript とは」が、変数と関数の最初の一歩として読みやすい説明をしています。
もうひとつ、「状態」という言葉に慣れておいてください。ログイン中かどうか、残高がいくらか、取引が確定済みか。処理をまたいで覚えておく値のことで、第2章で見たセッションも状態のひとつです。守りの多くは「今の状態で、この操作をしてよいか」という判定なので、状態がどこに保存され、誰が書き換えられるかが分かると、読む場所が絞れます。元帳の残高を、起票者が勝手に書き換えられてはいけないのと同じ考え方です。
4つの設計原則と、内部統制
守りのあるコードには、繰り返し現れる4つの考え方があります。どれも、内部統制の基本原則と一対一で対応します。OWASPのSecure Product Design Cheat Sheetでも、最小権限と職務分掌、多層防御、安全側への失敗が設計の原則として挙げられています。
| 原則 | 意味 | 内部統制でいえば | あなたのアプリでは |
|---|---|---|---|
| 信頼境界 | ここから先は、相手が書き換えられる。境界をまたいで入ってくるものは、すべて確かめる | 外部から受け取った書類は、必ず照合してから使う | ブラウザから来たものは、サーバー側で確かめる(第2章) |
| 最小権限の原則 | 必要な最小限の権限だけを与える。初期状態は「許さない」 | 職務分掌。承認権限は役職と金額で限定する | 利用者は自分の行だけ。管理用の鍵はサーバー側だけ |
| 多層防御 | 一枚の守りに頼らず、複数の層で止める | 起票者の確認、承認者の確認、月次の照合。どれかが抜けても別の層で止まる | サーバー側の所有者確認と、データベース側のRLSの二重 |
| フェイルセーフ | 失敗したとき、確認できないときは、安全側(拒否)に倒す | 証憑が確認できない伝票は、通さずに差し戻す | ログイン情報が読めなければ拒否。エラー時に内部情報を出さない |
最小権限と多層防御は、コードそのものより「どこに何を置くか」に関わります。利用者が自分の行しか読めないようにする、管理用の鍵はサーバー側にしか置かない。これが最小権限です。そのうえで、サーバー側の確認とデータベース側のRLSを両方入れておけば、どちらかに抜けがあっても止まります。これが多層防御で、経理が起票者の確認・承認者の確認・月次の照合を重ねているのと同じ理由です。一枚の守りが完璧であることを前提にしないのが、内部統制の基本でした。
4つのうち、コードを読むときに最も効くのがフェイルセーフです。守りのあるコードは、関数の冒頭で「本人でなければ、ここで終わり」と早めに拒否する形になっています。確認が通った場合だけ、先に進む。この形が見えたら、守りがあると判断してよい第一歩です。
読み取り練習:所有者を確認しているか
ミニ銀行の処理を2つ、どちらも10行以内に簡略化したものです。細部は読めなくて構いません。「もし〜でなければ拒否する」という行が、いくつ、何を確かめているかだけを数えてください。
例A:取引の内容を返す処理(JavaScript)
// 例A:取引1件の内容を返す
export async function GET(request, { params }) {
const user = await getCurrentUser(request); // ログイン中の利用者を取り出す
if (!user) return deny(401); // 未ログインなら拒否
const tx = await db.transactions.find(params.id);
if (!tx) return deny(404); // 存在しなければ拒否
if (tx.ownerId !== user.id) return deny(403); // 持ち主でなければ拒否
return respond(tx);
}
拒否する行が3つあります。ログインしているか(401)、データが存在するか(404)、そしてデータの持ち主がログイン中の本人か(403)。3つめが所有者の確認です。すべて関数の冒頭にあり、確認が通ったときだけ最後の行で結果を返しています。フェイルセーフの形です。
例B:取引を取り消す処理(Python)
# 例B:取引を取り消す
@app.post("/transactions/<tx_id>/cancel")
def cancel(tx_id):
user = current_user() # ログイン中の利用者を取り出す
if user is None:
return deny(401) # 未ログインなら拒否
tx = db.transactions.get(tx_id)
tx.status = "cancelled" # ここで状態を書き換えている
db.save(tx)
return ok()
拒否する行は1つだけです。ログインしているかは見ていますが、取り出した取引(tx)の持ち主がログイン中の本人かを確かめる行がありません。そのまま状態を「取消」に書き換えて保存しています。ログインさえしていれば、他人の取引のIDを指定して取り消せる状態です。第5章の1番、アクセス制御の不備(IDOR)がここにあります。
直し方の方針は、例Aの3つめの行に当たる確認を、書き換えの前に入れることです。この2つの例の違いは、たった1行です。しかし、その1行があるかないかが、論文で1位だった339件の分かれ目でした。読むべき場所は、データを取り出した直後、書き換える直前です。
読めないコードは、AIに説明させる
自分で読み切れない関数は、AIに日本語で説明させます。ただし「このコードを説明して」とだけ頼むと、処理の流れを順に読み上げるだけになりがちです。確かめたいのは守りの有無なので、問いを3つに固定します。
次の関数について、日本語で説明してください。コードの修正はしないでください。 [ここに関数を貼る。または、ファイル名と関数名を書く] 1. この関数は、誰の入力を受け取りますか(ブラウザから来た値、URLに含まれる値、他の関数から渡された値、のどれですか) 2. この関数は、処理を進める前に何を確認していますか。ログイン済みか、データの所有者が本人か、管理者か、入力の形式、のそれぞれについて「確認している(該当行)/していない/別の場所で確認している(場所)」で答えてください 3. この関数は、何を返しますか。確認に失敗したとき、何を返しますか 分からないことは「分からない」と答えてください。推測で補わないでください。
2番の答えが「していない」で、かつ「別の場所で確認している」でもない項目が出たら、それが点検の対象です。この定型文は、第10章の権限の点検で、サーバー側処理を1つずつ読むときに使います。
用語の整理
- 引数と戻り値
- 関数が受け取る入力が引数(ひきすう)、返す結果が戻り値。例Aでは
requestとparamsが引数、respond(tx)やdeny(403)が戻り値。 - 真偽値
- 「はい」か「いいえ」の2つしかない値。条件分岐の判定に使う。
!userは「利用者が取れなかったら」の意味。 - null/None
- 「値がない」ことを表す特別な値。ログインしていないときの利用者、見つからなかったデータ。これを確かめずに使うと、エラーか、守りの抜けになる。
- ステータスコード
- レスポンスに付く3桁の番号。401は未ログイン、403は権限なし、404は見つからない、500はサーバー側のエラー。守りのあるコードは、401と403を返す行を持つ。
- ライブラリ/フレームワーク
- 他人が書いた、再利用できる部品の集まり。Next.js、Supabaseのクライアントがこれに当たる。多くの守り(エスケープ、プレースホルダー)はライブラリが標準で行う。
- リポジトリ
- コードと、その変更履歴の置き場所。GitHubに置かれているもの。第9章で、ここに秘密情報が紛れていないかを確かめる。
- 例外
- 処理の途中で起きた異常。捕まえて(
catch)安全側に倒すのがフェイルセーフ。捕まえずに内部情報を画面に出すと、設定の不備になる。
まとめ
- コードは、変数・関数・条件分岐・繰り返し・入力と出力・状態・エラー処理の7つの部品でできている。守りは「もし本人でなければ拒否する」という条件分岐の形で書かれるので、その有無を読む。
- 信頼境界、最小権限、多層防御、フェイルセーフの4原則は、外部書類の照合、職務分掌、複数の確認層、差し戻しという内部統制の原則と一対一で対応する。
- 読むべき場所は、データを取り出した直後、書き換える直前。所有者を確認する1行があるかないかが分かれ目。読み切れなければ、「誰の入力を受け取り、何を確認し、何を返すか」をAIに日本語で説明させる。
出典
- MDN Web Docs「JavaScript とは」(Mozilla)
- OWASP Cheat Sheet Series「Secure Product Design」(OWASP)
- OWASP Cheat Sheet Series「Authorization」(OWASP)
- Junquan Deng, Zhiyu Fan, Ruijie Meng「Understanding the (In)Security of Vibe-Coded Applications」(arXiv:2606.23130、2026年6月、v4は2026年9月14日)