0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

問題点

0
Posted at

問題点

フロントエンドのビルド成果物(JavaScript バンドル、CSS、HTML など)は、リポジトリのソースコードとは別にデプロイされます。
ビルド時に環境変数や設定がインライン化されるため、意図しない API キーやシークレットがクライアント側に流出するケースが増えています。
ソースコードだけをスキャンしても、次のような漏洩は検出できません。

  • NEXT_PUBLIC_、REACT_APP_ などのフレームワークが自動的に公開するプレフィックス付き環境変数
  • ビルドツールが生成した設定ファイル(例: config.js、env.js)
  • ライブラリが内部で埋め込むデフォルトキー(例: デモ用公開鍵)

これらは「公開しても問題ない」ものと「実際に漏洩すべきシークレット」を区別しなければ、誤検知や見逃しが発生します。


メカニズム

  1. 成果物の取得

    • ビルド後のディレクトリ(例: dist/、build/)を対象に、すべてのテキストベースファイルを走査します。
    • バイナリは除外し、文字コードは UTF‑8 を前提にします。
  2. パターンマッチング

    • 正規表現ベースのシークレットパターン(API キー、トークン、証明書のシリアル等)を適用します。
    • フレームワーク固有の公開プレフィックス(NEXT_PUBLIC_、REACT_APP_、VITE_ など)を除外リストに追加し、意図的に公開された変数はスキップします。
  3. コンテキスト分析

    • 発見した文字列の前後 20 行を取得し、以下のヒューリスティックで判定します。
      • publicKey, publishableKey などのキーワードが同行にあるか
      • BEGIN PUBLIC KEY / END PUBLIC KEY の PEM ラベルが存在するか
    • これにより、公開鍵 と 秘密鍵 を区別します。
  4. レポート生成

    • 発見されたシークレットは、ファイル名、行番号、マッチした文字列、判定結果(公開/疑わしい)を JSON または Markdown で出力します。
    • CI/CD パイプラインに組み込めるよう、非ゼロ終了コードで「疑わしいシークレットが検出された」ことを示します。

実装例

# 依存パッケージ
pip install regex tqdm

# scan_artifacts.py
import pathlib, json, sys, re
from tqdm import tqdm

# 1. ビルド成果物ディレクトリ
ROOT = pathlib.Path(sys.argv[1] or "dist")

# 2. 公開プレフィックス除外リスト
PUBLIC_PREFIXES = [
    r"NEXT_PUBLIC_",
    r"REACT_APP_",
    r"VITE_",
]

# 3. シークレットパターン(例: Stripe の公開可能キー、AWS のシークレットアクセスキー)
PATTERNS = {
    "stripe_publishable": re.compile(r"pk_live_[0-9a-zA-Z]{24}"),
    "aws_secret": re.compile(r"AKIA[0-9A-Z]{16}"),
    "generic_secret": re.compile(r"[A-Za-z0-9]{40,}"),
}

# 4. 公開鍵判定ヘルパー
def is_public_key(snippet: str) -> bool:
    return bool(re.search(r"BEGIN PUBLIC KEY|END PUBLIC KEY|publicKey", snippet, re.I))

# 5. スキャン本体
def scan_file(path: pathlib.Path):
    try:
        text = path.read_text(encoding="utf-8")
    except UnicodeDecodeError:
        return []  # バイナリはスキップ

    findings = []
    for name, pat in PATTERNS.items():
        for m in pat.finditer(text):
            # 除外チェック
            if any(re.search(pref, text[:m.start()]) for pref in PUBLIC_PREFIXES):
                continue

            # 前後コンテキスト取得
            start = max(0, m.start() - 200)
            end = min(len(text), m.end() + 200)
            snippet = text[start:end]

            # 公開鍵かどうか判定
            public = is_public_key(snippet)
            findings.append({
                "file": str(path),
                "line": text[:m.start()].count("\n") + 1,
                "type": name,
                "value": m.group(0),
                "public_key": public,
                "snippet": snippet.strip().replace("\n", " "),
            })
    return findings

def main():
    all_findings = []
    for path in tqdm(list(ROOT.rglob("*.*"))):
        all_findings.extend(scan_file(path))

    # レポート出力
    print(json.dumps(all_findings, indent=2, ensure_ascii=False))
    # 疑わしいシークレットがあれば非ゼロ終了
    sys.exit(1 if any(not f["public_key"] for f in all_findings) else 0)

if __name__ == "__main__":
    main()

CI での利用例(GitHub Actions)

name: Secret Scan

on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.11"
      - name: Install deps
        run: pip install regex tqdm
      - name: Build
        run: npm ci && npm run build
      - name: Run scanner
        run: python scan_artifacts.py ./dist

注意点

項目 説明
誤検知 公開プレフィックスの除外リストが不完全だと、実際に公開すべきキーまでフラグが立つ可能性があります。プロジェクト固有のプレフィックスは必ず追加してください。
暗号化キー PEM 形式の公開鍵は BEGIN PUBLIC KEY で判定できますが、暗号化された秘密鍵は同様のラベルが無いことが多く、必ずしも検出できません。別途サーバー側のシークレット管理を併用してください。
パフォーマンス ビルド成果物が数千ファイルに及ぶ場合、正規表現の走査がボトルネックになることがあります。tqdm のようにプログレス表示しつつ、マルチプロセス (multiprocessing) で分割実行すると改善できます。
CI の失敗基準 本スクリプトは「公開鍵ではないシークレットが見つかった」場合に非ゼロで終了します。プロジェクトのリスク許容度に応じて、公開鍵だけでも警告 とする設定に変更可能です。
言語・フレームワーク依存 本例は JavaScript 系ビルドを想定していますが、同様のロジックは WebAssembly、React Native、Electron などのバンドルにも適用できます。対象ファイル拡張子を調整してください。

まとめ

  • ソースコードだけでなく、ビルド成果物を対象に
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?