ファイルパス完全ガイド:仕組み・OS差異・セキュリティと実務的ベストプラクティス

はじめに:ファイルパスとは何か

ファイルパス(file path)は、オペレーティングシステム上でファイルやディレクトリを一意に指定するための位置情報です。単なる文字列のように見えますが、実務・開発・セキュリティ観点では多くの落とし穴があります。本コラムでは、構成要素、OSごとの差異、予約語や禁止文字、長さ制限、URIとの関係、セキュリティリスクと対策、開発での取り扱い(ライブラリやAPI)まで詳しく解説します。

パスの基本構成

ファイルパスは一般に次の要素で構成されます。

  • 区切り文字(ディレクトリセパレータ):例:Unix系は「/」、Windowsは「\」
  • ルート:絶対パスの起点(Unixでは「/」、Windowsではドライブ文字とコロン"C:\"やUNCパス"\\server\share\")
  • パス要素(コンポーネント):ディレクトリやファイル名の断片
  • 特殊トークン:現在のディレクトリ「.」、親ディレクトリ「..」

これらの組み合わせで相対パスや絶対パスが構成されます。

絶対パスと相対パス

絶対パスはルートから対象までを完全に指定するパスです。相対パスは現在の作業ディレクトリ(カレントディレクトリ)を起点に相対的に指定します。プログラムでファイルを開く場合、相対パスは実行環境によって解釈が変わるため、意図しないファイルを操作するリスクがあります。実務では可能な限り絶対パスを使うか、アプリ起動時に基準ディレクトリを明示的に設定することが望ましいです。

OSによる差異と注意点

主要なOS(Windows、Linux/Unix、macOS)での重要差異は次のとおりです。

  • 区切り文字:Unix系は「/」のみ、Windowsは「\」が既定。ただしWindowsでは「/」も一部APIで受け付けられる場合がある。
  • 大文字小文字:Linux/Unixは通常ケースセンシティブ、Windowsはケースインセンシティブ(ただしケースは保持される)。macOSはデフォルトのHFS+やAPFSでケースインセンシティブだが、ケースセンシティブにフォーマットすることも可能。
  • 禁止文字:Windowsでは "\/:*?"<>|" といった文字がファイル名に使えない(NULL 文字も不可)。Unix系では"/"とNULL(\0)のみが禁止。
  • 予約語:Windowsには "CON, PRN, AUX, NUL, COM1..." などのデバイス名がありファイル名に使えない。
  • 長さの制限:Windowsの従来の MAX_PATH (260 文字) 問題、Unix系では一般に1コンポーネントあたり255バイトなど。Windowsでは "\\?\" プレフィックスやシステム設定で長いパスを許可できる。

特殊パス、UNC、ファイルURI

ネットワーク共有やURIなど、ファイルパスにはバリエーションがあります。

  • UNC(Universal Naming Convention):Windowsのネットワークパスは "\\server\share\path\to\file" の形式。
  • ファイルURI:RFC 8089 が定義する file: スキーム(例:"file:///home/user/file.txt"、Windowsでは "file:///C:/path/to/file")。URIではパス成分のエンコーディングが必要(空白や非ASCIIはパーセントエンコーディング)。
  • 仮想パス:Webアプリやアーカイブ内のパス(zip内部のパス等)は実際のファイルシステムのパス規則とは独立していることが多い。

Unicode と正規化(NFC/NFD)の問題

ファイル名にはUnicodeが使えますが、同じ見た目でも内部的には異なる正規化形式(NFC/NFD)になることがあります。特にmacOSのHFS+はNFD(分解)で保存する傾向があり、LinuxやWindowsとは正規化の扱いが異なるため、クロスプラットフォームで比較や一意性チェックを行うときは注意が必要です。実務では正規化(通常NFC)を明示的に行った上で比較することを推奨します。

パスの正規化と正当性チェック

アプリケーションが外部入力を受けてファイルパスを扱う場合、次の操作が必須です。

  • 正規化:相対トークン(.、..)や連続区切りを解決する(例:"/a/b/../c" → "/a/c")。
  • 実測(canonicalization):シンボリックリンクやマウント境界を考慮して実際の絶対パスを取得(例:realpath() や GetFullPathName())。
  • バリデーション:許可されたベースディレクトリの下にあるかどうかを確認する(典型的な防御は canonicalize した結果が許可ディレクトリで始まることを確認すること)。

セキュリティリスクと対策(パス・トラバーサル等)

代表的な問題はパス・トラバーサル(../ を使って上位ディレクトリに遡り、本来アクセスできないファイルに到達する攻撃)です。対策は次の通りです。

  • 文字列連結でパスを組み立てない。必ずプラットフォームの API(例:Path.join、os.path.join、Path.resolve)を使う。
  • 入力されたパスは必ず正規化/canonicalize してからアクセス許可のチェックを行う(順序逆では意味が無い)。
  • シンボリックリンクやリパースポイントの存在に注意。TOCTOU(Time Of Check To Time Of Use)問題を回避するために可能な限り原子的APIを使う。Unix系では openat, O_NOFOLLOW 等、Windowsでは CreateFile のフラグを検討する。
  • テンポラリファイルは安全な生成関数(mkstemp や SecureTemporaryFile など)を使い、予測可能なファイル名を避ける。
  • 最小権限でファイルを開く。読み取り専用で十分なら書き込み権限を与えない。

実務でよくある落とし穴

  • ユーザー入力でパスを受け取るWebアプリ:ディレクトリトラバーサル攻撃の主要対象。
  • ログやエラーメッセージに生パスを書き出す:情報漏洩リスク。相対パスやハッシュ化で記録するなど工夫する。
  • パス長制限の無視:長いパスで例外が発生すると処理が停止する。Windows の長いパス対応を検討する。
  • クロスプラットフォームの比較ミス:Windows の大文字小文字ルールを無視したパス比較でバグが生じる。

ライブラリ・API(言語別の推奨)

ほとんどの主要言語は安全にパスを扱うためのライブラリを提供しています。これらを利用することが最も簡単で効果的な対策です。

  • Python:pathlib.Path、os.path.join、os.path.realpath。Pathlib はオブジェクト指向で可読性が高い。
  • Java:java.nio.file.Path と Paths、Files API。toRealPath() で正規化。
  • Node.js:path.join、path.resolve、fs.realpath。セキュリティ関連では path.normalize を過信しない。
  • Go:path/filepath パッケージの Join、Abs、Clean など。
  • Windows API:GetFullPathName、CreateFile、PathCchCanonicalize など(Microsoft ドキュメントを参照)。

運用上のベストプラクティス(チェックリスト)

  • 入力チェックと正規化を必須化する。
  • パス結合は必ず OS 提供の関数を使う。
  • 外部に表示するパスは情報をマスクするか、相対パスで示す。
  • 長さ制限やUnicode正規化の方針をドキュメント化し、CI/テストで検証する。
  • ファイルアクセスは最小権限で行い、シンボリックリンクを扱う場合の挙動を明確にする。
  • サーバー側ではホワイトリスト方式で許可パスを限定する(例:ユーザーアップロードは /var/www/uploads 以下のみ)。

まとめ

ファイルパスは一見単純な文字列ですが、OSごとの差異、Unicode、長さ制限、予約語、セキュリティ問題(パス・トラバーサル、TOCTOU、シンボリックリンク)など、実務で注意すべき点が多くあります。安全に扱うには、言語やOSが用意する公式APIを使い、正規化→canonicalize→バリデーション→最小権限の順で処理することが重要です。テストやドキュメントで扱い方を明確にし、クロスプラットフォームでの挙動差を常に意識しましょう。

参考文献