【実務・中級編】【上級プロフェッショナル向け】ログ内の個人情報(マイナンバー・カード番号)をFSOで読み込みMasking(伏字化)するデータ匿名化パイプライン – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

ログの「毒」を無害化せよ:FSOと正規表現による堅牢なデータ匿名化パイプラインの設計術

本番環境のログをテスト環境へ持ち込む際、あなたは「ただコピーするだけ」で済ませていないか?

もし、そのログの中にマイナンバーやクレジットカード番号といった機密情報(PII: Personally Identifiable Information)が混入していたとしたら、その瞬間にあなたの設計は「セキュリティ上の脆弱性」へと変貌する。

業務自動化エンジニアが目指すべきは、単に「動くツール」を作ることではない。「例外を許さず、リソースを食い潰さず、データの一貫性を担保するパイプライン」を構築することだ。

本稿では、VBScriptの`FileSystemObject (FSO)`と`RegExp`エンジンを極限まで使いこなし、大規模なログファイルから機密情報を確実に検知・マスキング(伏字化)する、プロフェッショナル仕様の匿名化エンジンの設計思想を伝授する。

1. 初歩的な設計者が陥る「3つの致命的な罠」

実装に入る前に、現場でよく見られる「動くが、プロとは呼べない」コードの特徴を挙げておく。これらは、本番環境での運用において必ず破綻する。

① 全行一括読み込み(Memory Exhaustion)

`TextStream.ReadAll` を使ってファイル全体をメモリに載せようとする設計だ。数GBに及ぶログファイルを扱った瞬間、スクリプトはメモリ不足でクラッシュする。プロは「ストリーム処理(行単位の読み込み)」を選択する。

② 上書き保存の無謀さ(Data Loss Risk)

読み込み中のファイルをそのまま上書きしようとする設計だ。処理中にエラーが発生したり、ディスク容量が不足したりした場合、元のログファイルが破損し、復旧不能になる。プロは「一時ファイルへの書き出しと、最後にアトミックな入れ替え」を行う。

③ 正規表現の甘さ(False Negatives)

単なる数字の羅列を置換しようとして、IDや時刻まで壊してしまう、あるいはパターンを絞り込みすぎて機密情報を見逃す。プロは「境界条件(Word Boundary)」を意識した厳格なパターンを定義する。

2. アーキテクチャ設計:匿名化パイプラインの構造

堅牢なパイプラインは、以下の3つのレイヤーで構成されるべきだ。

1. I/Oレイヤー (FSO): ファイルの存在確認、ストリームのオープン、一時ファイルの生成・削除を管理する。
2. Detectionレイヤー (RegExp): 機密情報のパターンを定義し、高速にマッチングを行う。
3. Transformationレイヤー: マッチした文字列を、データの構造を壊さずに「MASKED」等の文字列へ置換する。

3. 【実践】プロフェッショナル・コード例

以下に、そのままプロダクション環境のベースとして使える、堅牢な設計思想に基づいたVBScriptコードを示す。

'***
' Script: DataAnonymizer.vbs
' Description: ログファイル内の機密情報(マイナンバー, カード番号)をマスキングする
' Author: Chief Architect
'***
Option Explicit

' --- 設定エリア ---
Const TARGET_DIR = "C:\Logs\Production\"  ' 処理対象ディレクトリ
Const OUTPUT_DIR = "C:\Logs\Anonymized\" ' 出力先ディレクトリ
Const TEMP_EXT = ".tmp"                  ' 一時ファイル拡張子

' マスキングパターンの定義 (RegExp)
' マイナンバー: 12桁の数字
' クレジットカード: 16桁の数字 (ハイフン区切り含む)
Dim regExMasking
Set regExMasking = New RegExp
regExMasking.Global = True
regExMasking.IgnoreCase = True
' パターンの結合: (12桁数字) OR (4桁-4桁-4桁-4桁)
regExMasking.Pattern = "\b\d{12}\b|\b\d{4}-\d{4}-\d{4}-\d{4}\b"

' --- メインルーチン ---
Call Main()

Sub Main()
    Dim fso, folder, file
    Set fso = CreateObject("Scripting.FileSystemObject")

    ' 1. 環境チェック
    If Not fso.FolderExists(OUTPUT_DIR) Then
        fso.CreateFolder(OUTPUT_DIR)
    End If

    If Not fso.FolderExists(TARGET_DIR) Then
        WScript.Echo "Error: Target directory not found."
        WScript.Quit 1
    End If

    Set folder = fso.GetFolder(TARGET_DIR)

    ' 2. ファイル走査
    For Each file In folder.Files
        ' ログファイルのみを対象 (拡張子によるフィルタリング)
        If LCase(fso.GetExtensionName(file.Name)) = "log" Then
            WScript.Echo "Processing: " & file.Name
            ProcessFile file.Path, fso
        End If
    Next

    WScript.Echo "All tasks completed successfully."
End Sub

' --- ファイル処理コアロジック ---
Sub ProcessFile(strFilePath, fso)
    Dim strFileName, strTempPath, strOutputPath
    Dim tsIn, tsOut, strLine
    Dim errStatus

    strFileName = fso.GetFileName(strFilePath)
    strTempPath = OUTPUT_DIR & strFileName & TEMP_EXT
    strOutputPath = OUTPUT_DIR & strFileName

    On Error Resume Next ' エラーハンドリング開始

    ' 3. ストリーム処理の実行 (メモリ節約のため1行ずつ読み込む)
    Set tsIn = fso.OpenTextFile(strFilePath, 1) ' ForReading
    If Err.Number <> 0 Then
        WScript.Echo "  [!] Failed to open source: " & strFileName
        Err.Clear
        Exit Sub
    End If

    Set tsOut = fso.CreateTextFile(strTempPath, True)
    If Err.Number <> 0 Then
        WScript.Echo "  [!] Failed to create temp file: " & strFileName
        tsIn.Close
        Err.Clear
        Exit Sub
    End If

    ' 行単位のループ(ここがパフォーマンスとメモリの要)
    Do Until tsIn.AtEndOfStream
        strLine = tsIn.ReadLine
        
        ' 正規表現による置換実行
        ' マッチした場合、"---" などの定数に置き換える
        strLine = regExMasking.Replace(strLine, "[CONFIDENTIAL_MASKED]")
        
        tsOut.WriteLine strLine
    Loop

    tsIn.Close
    tsOut.Close

    ' 4. アトミックなファイル入れ替え
    ' 一時ファイルが正常に作成できていれば、本番用ファイルとしてリネーム
    If fso.FileExists(strTempPath) Then
        If fso.FileExists(strOutputPath) Then fso.DeleteFile strOutputPath, True
        fso.MoveFile strTempPath, strOutputPath
    End If

    ' エラーチェック
    If Err.Number <> 0 Then
        WScript.Echo "  [!] Error during processing " & strFileName & ": " & Err.Description
        ' 不完全な一時ファイルを削除してクリーンアップ
        If fso.FileExists(strTempPath) Then fso.DeleteFile strTempPath
        Err.Clear
    Else
        WScript.Echo "  [OK] Anonymized: " & strFileName
    End If
    On Error GoTo 0
End Sub

4. 卓越した設計のための技術解説

正規表現の「境界(Boundary)」を制する

コード内で使用している `\b` というメタ文字に注目してほしい。これは「単語の境界」を意味する。
これがないと、例えば `1234567890123456`(16桁)という数字があった場合、その中の最初の12桁がマイナンバーとして誤検知され、中途半端にマスクされてしまう。`\b` を用いることで、「独立した12桁の数字」のみを正確に射抜くことができる。

ストリーム処理による「定数時間」のメモリ使用

`tsIn.ReadLine` を使用したループ構造は、ファイルサイズに関わらずメモリ消費量を一定に保つ。ログファイルが100MBでも10GBでも、このスクリプトが消費するメモリは「1行分の文字列サイズ」+「正規表現エンジンのバッファ」だけで済む。これが、大規模システムにおける「安定性」の正体だ。

失敗を前提とした「アトミック操作」

`MoveFile` を使う前に、必ず一時ファイル (`.tmp`) を完成させている。もし書き込み中にディスクフルが発生したり、プロセスが強制終了したりしても、「元のファイルには一切手を加えていない」し、「中途半端なファイルが本番名で残ることもない」。この「失敗しても元の状態を維持する(Rollback可能な設計)」こそが、ミッションクリティカルな現場で求められる作法だ。

5. さらに高みを目指すエンジニアへ

このスクリプトは基礎だが、実務では以下の要素を追加検討せよ。

1. 文字コードの壁: FSOはUTF-8の扱いに極めて弱い。もしログがUTF-8であるなら、`ADODB.Stream` オブジェクトへ切り替え、`Charset = “utf-8″` を明示的に指定せよ。
2. 並列化の検討: VBScript自体はシングルスレッドだが、大量のファイルを処理する場合、Windowsの「タスクスケジューラ」や「PowerShell」から複数のインスタンスを並列起動する構成を検討せよ。
3. ロギングのロギング: どのファイルで何件のマスキングが発生したかを、別途監査ログとして出力する機能を実装せよ。これはコンプライアンス対応において不可欠だ。

「コードは書くものではない。設計するものだ。」

この意識を持って、次なる自動化の壁に挑んでほしい。

タイトルとURLをコピーしました