トップ›基礎編›第7章
基礎編第7章

読んで理解するための、計算機科学のさわり

読む目安:約40分変数・関数・条件分岐。守りの有無を読み取る最低限

コードを書けるようになる必要はありません。しかし、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件の分かれ目でした。読むべき場所は、データを取り出した直後、書き換える直前です。

補足:読み取り練習の限界 この2つは、分かりやすいように簡略化しています。実際のコードでは、確認が別の関数の中に隠れていたり、データベース側のRLSで守られていたりします。「拒否の行が見当たらない」は、すぐに「穴がある」を意味しません。「どこで確認しているかをAIに聞く」の合図です。次の定型文を使います。

読めないコードは、AIに説明させる

自分で読み切れない関数は、AIに日本語で説明させます。ただし「このコードを説明して」とだけ頼むと、処理の流れを順に読み上げるだけになりがちです。確かめたいのは守りの有無なので、問いを3つに固定します。

AIへの頼み方(関数の説明)
次の関数について、日本語で説明してください。コードの修正はしないでください。

[ここに関数を貼る。または、ファイル名と関数名を書く]

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に日本語で説明させる。

出典