NGW開発BLOG
← 記事一覧へ
·#028

「見せない」を権限で設計する文書管理のプライバシー保護

機微な文書を「隠す」機能を作ると、UIで消すだけでは足りない。所属×役職以上の二軸判定と、取得段での秘匿という原則を、あらゆる参照経路の一貫性から考える。

問題設定:「フラグを立てて隠す」では足りない

行政の文書管理には、個人情報や人事に関わる機微な文書がある。これらに「プライバシー保護」を設定できる機能を作った。設定すると、その文書を分掌事務として管理する所属の中で、指定した担当者の職名(役職)以上の職員だけが閲覧できる。特別職は例外として常に閲覧可、という仕様だ。

素朴に実装するなら、文書に保護フラグをひとつ持たせ、表示のときにフラグが立っていたら画面から消す。これで一見動く。だが、この設計は二つの意味で足りない。

  1. 「誰に見せるか」を単一のフラグでは表現できない。
  2. そもそも「消す」場所を間違えている。

順に見ていく。

なぜ二軸なのか:所属 × 役職以上

保護の対象は「機微かどうか」だけでは決まらない。誰から見て機微か、が本質だ。人事の文書は、それを扱う人事担当の所属では課長以上が扱ってよいが、無関係な所属の職員には課長であっても見せる理由がない。

だから判定は二軸になる。ひとつは所属軸で、その文書を分掌事務として管理する所属に属しているか。もうひとつは役職軸で、指定された担当者の職名「以上」の役職か。両方を満たして初めて閲覧できる。役職の「以上」は、職名に序列値を持たせて数値比較するのが素直だ。

-- 概念例:保護文書を閲覧できる条件
-- (管理所属に属する) かつ (基準役職以上) 、または 特別職
WHERE doc.protected = 0
   OR viewer.is_executive = 1
   OR ( viewer.division_cd = doc.manage_division_cd
        AND viewer.post_rank >= doc.required_post_rank )

ここで大事なのは、この条件を「フラグひとつ+UIの分岐」に畳み込めないことだ。閲覧可否はログインしている職員が誰かによって変わる。つまり可視性は文書の属性ではなく、文書と閲覧者の関係として初めて定まる。関係を評価するには、閲覧者の所属と役職を判定の場に持ち込む必要がある。

どこで消すか:UIではなく取得段で

より重要なのが「どこで消すか」だ。詳細画面で保護文書を弾いても、文書は一覧にも、検索結果にも、関連する決裁状況の一覧にも現れる。参照経路はひとつではない。ある経路で消し忘れれば、そこから存在が漏れる。

そして「存在が漏れる」ことそのものが情報だ。タイトルや本文を見せなくても、一覧に一行分の空きがある、検索の件数が期待よりひとつ多い、決裁状況にその起案が並んでいる——それだけで「ここに何か秘匿された文書がある」と推測できてしまう。秘匿すべきは中身だけでなく、存在そのものである。

この観点に立つと、正しい消し場所はUI層ではなくデータ取得の段、つまりクエリだと分かる。表示の直前で配列から除くのではなく、そもそも取得しない。可視性の条件を一覧・検索・決裁状況一覧など、文書に触れるすべてのクエリの WHERE に織り込み、ログイン職員の権限で絞る。実際、関連する決裁状況の一覧でも、プライバシー保護された起案や収受を権限判定で非表示にした。片方でも取りこぼせば、そこが推測の穴になるからだ。

取得段で絞ることには、もうひとつ効能がある。IDOR的な直接参照への耐性だ。UIで隠すだけの実装は、文書IDを直接URLに与えれば詳細を返してしまいがちだ。だが取得クエリ自体に可視性条件が入っていれば、IDを知っていても該当行は返らない。件数からの推測も、そもそも件数を数えるクエリが保護文書を除いていれば塞げる。消す場所をクエリに寄せることは、複数の攻撃面を一箇所で同時に閉じることでもある。

AIとの協働作業での学び:見せないとは「取得しない」こと

アクセス制御の初手はつい「表示を消す」になる。だが表示を消すのは、取得したものを後から隠す行為で、隠し忘れる経路が必ず残る。原則は逆で、見せてはいけないものは「そもそも取得しない」。

そして「見せない」が本当に成立するのは、中身に加えて存在まで秘匿できたときだ。見えない一行が、そこにあると推測できてしまうなら、まだ見せている。可視性は文書と閲覧者の関係として定義し、その条件をすべての取得経路のクエリに一貫して織り込む——この二点を守って初めて、「見せない」は設計として閉じる。