コース目次 / 第3章

差分を読む — 直る前後の、形を覚える

これまでのコースで書いてきた修正前後のコードを、差分(diff)として並べて読み返します。パラメータ化・所有者チェック・許可リストなど、直しの「形」をパターンとして頭に入れます。

第3章 / 全6章目安 約10分この章のゴール: 修正前後の差分から、直しの共通パターンを読み取れるようになる

コードレビューでは、新しいコードだけでなく変更差分(diff)を読む機会が数多くあります。これまでのコースで見てきた「修正」を、diff の形で読み返し、パターンを頭に焼き付けます。

認可の穴(sec-authz)

  const order = ORDERS.find((o) => o.id === Number(m[1]));
  if (!order) { send(res, 404, { error: 'ありません' }); return; }
+ if (order.owner !== uid) { send(res, 403, { error: '権限がありません' }); return; }
  send(res, 200, order);

形:資源を返す直前に、+ で1行(所有者チェック)が足される。この形を見たら——「他の資源取得関数にも、同じ行があるか?」を次に確認します。1箇所直っていても、同じパターンの別関数が直っていないことは、実務でもよくあります。

SQLインジェクション(sec-injection)

- const sql = "SELECT * FROM users WHERE name='" + name + "' AND password='" + pw + "'";
- const row = db.prepare(sql).get();
+ const row = db.prepare('SELECT * FROM users WHERE name = ? AND password = ?').get(name, pw);

形:文字列連結(+)が丸ごと消え、プレースホルダ(?)と、値を渡す第2引数に置き換わる。「SQL文の中に変数が直接埋め込まれている行が、削除されているか」——diffでこの形を見たら、SQLi の修正だと即座に分かります。

オープンリダイレクト(sec-web-basics)

  const next = url.searchParams.get('next') || '/';
+ function safeNext(n) { return (n.startsWith('/') && !n.startsWith('//')) ? n : '/'; }
- res.writeHead(302, { Location: next });
+ res.writeHead(302, { Location: safeNext(next) });

形:生の入力(next)を直接使っていた箇所が、検証関数を通してから使う形に変わる。「入力を直接渡していた箇所に、検証・変換の関数呼び出しが挟まれたか」——これも、許可リスト化の典型的な diff の形です。

レースコンディション(sec-logic)

  if (stock > 0) {
-   await new Promise((r) => setTimeout(r, 50));
    stock -= 1;
+   await new Promise((r) => setTimeout(r, 50));
    res.end(...);
  }

形:コードの行数はほぼ変わらず、順序だけが入れ替わる。これは、diff の中でもとくに見落としやすい種類です。追加や削除ではなく並び替えだからこそ、「なぜ順番が変わったのか」を意識して読まないと、変更の意味を取り違えます。

パターンとして、頭に入れる

4つの diff に共通するのは——「危険な形が消え(または並び替わり)、安全な形に置き換わる」という構造です。この「形」をあらかじめ知っていると、自分がコードを書くときに、先回りして安全な形で書けるようになります。修正の diff を数多く読むことは、脆弱性を「見つける」訓練であると同時に、「最初から作らない」訓練でもあります。

持ち帰る一言

修正のdiffは、直しの形の教科書。 危険な連結が消える、検証関数が挟まる、順序が入れ替わる——これらの形をパターンとして持っていると、レビューで直感的に「ここが直った/直っていない」を判断できるようになります。次の章で、読むことの限界と、それを補う方法を見ます。

こうなっていればOK

卒業まであと2章です。

この章はまだ完了していません。