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?

Claude Code投入前に機密ファイルを検知する

0
Posted at

Claude CodeやCodexにrepoを渡す前、「このファイルは読ませていい」とどこで判断してますか。

自分は最近ここがだいぶ気になっていて、.gitignore を見て安心するのは危ないな、と思うようになった。2026-06-29にThe Decoderが、MetaがClaude CodeとCodexの利用を制限し始めたと報じていた。理由は競合AIの学習データに自社コードが混ざる懸念、という話。

大企業の過敏な話に見えるけど、実装者目線ではもう少し地味な問題がある。AIコーディングエージェントにrepoを読ませる時、Gitに入っていないファイルまで作業ディレクトリに残っていることがある。.env、ローカルDB、サービスアカウントJSON、検証用トークン。.gitignore は「commitしない」ための設定であって、「モデルや外部ツールに読ませない」設定ではない。

なので、エージェントを起動する前にローカルで棚卸しする小さいスクリプトを書いた。目的は秘密情報スキャナの完成版ではなく、AIに渡す前のプリフライトチェック。ここをCIやpre-commandに挟むだけでも、事故の入口をかなり減らせる。

Claude Code投入前に見るべき範囲

自分が見たいのは2つ。

1つ目は、そもそも読ませないファイル。.env、秘密鍵、credentials系JSON、SQLite DB、.git 配下など。

2つ目は、読ませるつもりのソース内に混ざった秘密情報。これは .gitignore では拾えない。src/settings.py に一時的なAPIキーを直書きして、そのままAIに「この辺直して」と投げるケースが一番怖い。

下のコードはその2つを分けて出す。依存なし、Python 3.9以上で動く。

from __future__ import annotations

import argparse
import fnmatch
import re
from pathlib import Path

INCLUDE_EXT = {
    ".py", ".js", ".ts", ".tsx", ".go", ".rs", ".java",
    ".md", ".toml", ".yaml", ".yml", ".json", ".sql",
}
DENY_DIRS = {".git", "node_modules", ".venv", "venv", "dist", "build", "target", "__pycache__"}
DENY_GLOBS = [".env", ".env.*", "*.pem", "*.key", "*credential*.json", "*secret*.json", "*.sqlite", "*.db"]
SECRET_PATTERNS = [
    ("private key block", re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----")),
    ("OpenAI-style key", re.compile(r"sk-[A-Za-z0-9_-]{20,}")),
    ("GitHub token", re.compile(r"gh[pousr]_[A-Za-z0-9_]{20,}")),
    ("AWS access key id", re.compile(r"AKIA[0-9A-Z]{16}")),
    ("named secret", re.compile(r"(?i)(api[_-]?key|token|password|secret)\s*[:=]\s*['\"]?[^'\"\s]{12,}")),
]

def matches_any(path: Path, patterns: list[str]) -> str | None:
    posix = path.as_posix()
    for pat in patterns:
        if fnmatch.fnmatch(path.name, pat) or fnmatch.fnmatch(posix, pat):
            return pat
    return None

def should_scan(path: Path) -> bool:
    return path.suffix in INCLUDE_EXT and path.stat().st_size <= 256_000

def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("root", type=Path)
    args = parser.parse_args()

    root = args.root.resolve()
    included: list[Path] = []
    denied: list[tuple[Path, str]] = []
    leaks: list[tuple[Path, int, str]] = []

    for path in sorted(p for p in root.rglob("*") if p.is_file()):
        rel = path.relative_to(root)
        denied_dir = next((part for part in rel.parts if part in DENY_DIRS), None)
        denied_glob = matches_any(rel, DENY_GLOBS)
        if denied_dir or denied_glob:
            denied.append((rel, denied_dir or denied_glob or "deny rule"))
            continue
        if should_scan(path):
            included.append(rel)
            text = path.read_text(errors="ignore")
            for n, line in enumerate(text.splitlines(), 1):
                for label, rx in SECRET_PATTERNS:
                    if rx.search(line):
                        leaks.append((rel, n, label))
                        break

    print(f"SCAN_ROOT={root}")
    print(f"included={len(included)} denied={len(denied)} leaks={len(leaks)}")
    for rel, reason in denied:
        print(f"DENY {rel} ({reason})")
    for rel, line_no, label in leaks:
        print(f"LEAK {rel}:{line_no} ({label})")
    return 2 if leaks else 0

if __name__ == "__main__":
    raise SystemExit(main())

動作確認

一時ディレクトリに以下を置いて動かした。

src/main.py
src/settings.py              # API_KEYを直書き
docs/agent_task.md
.env
service-credentials.json
.git/config

実行結果。

$ python3 agent_scope_check.py /tmp/agent-scope-demo
SCAN_ROOT=/private/tmp/agent-scope-demo
included=3 denied=3 leaks=1
DENY .env (.env)
DENY .git/config (.git)
DENY service-credentials.json (*credential*.json)
LEAK src/settings.py:1 (OpenAI-style key)

終了コードは leaks=0 なら0、1件でも見つかれば2にしている。CIならこのまま落とせるし、ローカルならClaude CodeやCodexを起動する前のラッパーにしやすい。

python3 agent_scope_check.py . || exit 1
claude

もちろん、このままだと誤検知は出る。sk- っぽいダミー文字列、READMEに載せたサンプルキー、テストフィクスチャなど。そこは許可リストで潰すより、まず「AIに渡す前に人間が見る」運用にしたほうがいい。秘密情報スキャナを完璧にする話に寄せると、導入が重くなる。

なぜ .gitignore では足りないのか

.gitignore はGitの入力を絞る仕組みで、AIエージェントの入力を絞る仕組みではない。ここを混ぜると危ない。

たとえば .env はGitには入らない。でもローカルの作業ディレクトリにはある。エージェントがファイル検索を使う設計なら、実装途中でそれを読む可能性がある。読ませる側のCLIがどこまで送るか、どのツールがどこまで読めるか、ログがどこに残るか。この辺はツールごとに違う。

だから自分は、AIエージェント用に別の境界を置くべきだと思っている。Gitの境界、実行環境の境界、AIに読ませる境界は別。ひとまとめにすると、だいたい一番ゆるい境界に引きずられる。

今回のスクリプトは小さいけど、この分離をコードに落とせるのが良いところ。DENY_GLOBS はチームの事情に合わせて増やせる。たとえば自社なら customer_exports/**.harlocal_dump/* も入れると思う。HARファイルは地味に危ない。CookieやAuthorizationヘッダが普通に残るので。

運用に入れるならここまでやる

個人利用なら手動実行で十分。チームに入れるなら、次の3点は足したい。

まず、.agentignore ではなく .agentinclude に寄せる。デフォルト拒否にして、AIへ読ませるファイルだけ明示するほうが事故りにくい。最初は面倒だけど、AIに読ませるコンテキストも小さくなるので、コストと精度にも効く。

次に、ログを残す。どのファイルをAIに見せた前提で修正したのかが後から追えると、レビューが楽になる。生成されたコードの良し悪し以前に、入力が何だったかを再現できる。

最後に、ブロック対象を「秘密情報」だけにしない。顧客データ、未公開ロードマップ、評価データ、ライセンス上外に出せないコードも同じ棚に置く。APIキーだけ探して満足すると、むしろ見落とす。

おわりに

AIコーディングエージェントのリスクは、「賢いモデルが勝手に悪いことをする」より先に、「人間が読ませる範囲を雑に決める」ことで出ると思っている。Metaの件は派手な企業ニュースだけど、足元の対策はかなり地味だ。repoを渡す前に、何が読まれる前提なのかを一度ファイル一覧で見る。そこを自動化しておくだけで、Claude CodeやCodexの導入はだいぶ現実的になる。

Source: https://the-decoder.com/meta-restricts-use-of-claude-code-and-codex-to-keep-rival-ai-out-of-its-training-data/

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?