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?

pip install しただけで、import しなくても起動のたびに動く .pth:仕組みと入っていないか確かめる方法(Python 3.13で確認)

0
Posted at

結論

  • pip install したパッケージに .pth ファイルが入っていると、その中の import で始まる行は、パッケージを一度も import しなくても Python を起動するたびに実行されます(Python公式 site ドキュメント・筆者の環境で確認)。python -c pass でも、pip --version でも動きました。
  • 実行されるコードは、そのプロセスの環境変数(OPENAI_API_KEY など)をそのまま読めます。ダミーの DUMMY_API_KEY を置いた状態で、.pth から _KEY で終わる変数の数を数えられることを確かめました。
  • 確かめ方は2つ。入れる前は wheel の中に .pth があるかを python -m zipfile -l で見る。入れた後は site-packages の .pth から import 行を探す(スクリプトは下にあります)。setuptools のように正規のパッケージも import 行入りの .pth を持つので、見つかったら中身で判断します。

この記事は「AIエージェントと秘密情報」の検証シリーズです。新しい検証は X(@supportapp3_lab)でもお知らせしています。

X で「litellm の特定のバージョンを pip install した場合、マルウェア感染の可能性がある」という投稿が広く読まれていました。この件の中身はこの記事では扱いません。ここでは、一般論として「pip で入れただけのパッケージが、いつコードを動かせるのか」を、ダミーのパッケージで確かめます。AIエージェントに pip install を任せている人ほど、知っておくと点検しやすくなります。

検証環境と範囲

  • Debian 13.7 / Python 3.13.5 / pip 25.1.1
  • /tmp の一時ディレクトリに venv を作り、.pth を1つだけ含む wheel を手作りして入れました。.pth の中身は「起動したらマーカーファイルに1行書く」だけです。
  • キーはダミーの文字列で、値は表示も保存もしていません。Windows・macOS・conda、Python 3.15 以降は試していません。

使った .pth(1行):

demo_pth_hook.pth
import os; open(os.environ.get('MARK','/dev/null'),'a').write('pth ran: pid=%d keys_seen=%d\n' % (os.getpid(), sum(1 for k in os.environ if k.endswith('_KEY'))))

1. いつ動いたか

操作 .pth のコードが動いたか
pip install ./demo_pth-0.1-py3-none-any.whl(wheel を入れる処理そのもの) 動かなかった
python -c pass(何も import しない) 動いた
pip --version(pip も Python なので) 動いた
python -I -c pass(隔離モード) 動いた(venv の site-packages は対象のまま)
python -S -c pass(site を読み込まない) 動かなかった
pip uninstall の後に python -c pass 動かなかった(.pth も消えた)

マーカーファイルに書かれた行:

pth ran: pid=13777 keys_seen=1

keys_seen=1 は、ダミーの DUMMY_API_KEY を数えられたということです。数える代わりに値を読むことも、同じ1行で書けます。

Python公式の site ドキュメントには、.pth の行のうち import(とスペースかタブ)で始まるものは exec() で実行され、使うかどうかに関係なく Python の起動時に必ず動く(-S で site 自体を無効にした場合を除く)、と書かれています(site — Site-specific configuration hook)。

なお、この環境(Debian の python3.13 で作った venv)では、1回の起動でマーカーが2行ずつ増えました。python -v で見ると site-packages の処理が2回行われていましたが、理由は調べきれていません。システムの python3 とユーザーの site-packages の組み合わせでは1回でした。

2. wheel を入れる処理は動かないが、安心材料にはならない

表のとおり、wheel を入れる処理の間は .pth は動きませんでした。pip はすでに起動していて、.pth はその後に置かれたからです(起動時に読まれるという公式の説明からの解釈です)。ただし、次に Python が起動した瞬間に動くので、実質的な違いはほとんどありません。

wheel ではなくソースパッケージ(sdist)しかない場合は、入れるときにビルドのためのコードが動きます。pip の --only-binary :all: はソースパッケージを使わない指定です(pip install)。今回は sdist の動きは試していません。

3. 入れる前に、wheel の中に .pth があるか見る

pip download で wheel だけを取ってきて、中のファイル名を見ます。

pip download --no-deps --only-binary :all: パッケージ名==バージョン -d /tmp/dl
python -m zipfile -l /tmp/dl/*.whl | grep -E '\.pth|\.start|customize\.py'

手作りの wheel では次のように出ました。

demo_pth_hook.pth  (日時・サイズの列は省略)

何も出なければ、その wheel は .pth を持っていません。依存パッケージも見たいときは --no-deps を外します。

4. 入れた後に、起動時に動くものを一覧にする

今使っている Python(venv なら有効にした状態で)で実行します。読み取りだけで、中身は先頭80文字までしか出しません。

check_pth.py
#!/usr/bin/env python3
"""読み取り専用: いま使う Python が起動時に実行するもの(.pth の import 行、.start、sitecustomize/usercustomize)を一覧にする。
ファイルの中身は先頭80文字だけ表示(鍵が書かれていても全文は出さない)。"""
import site, sys, os, glob
dirs = list(dict.fromkeys(site.getsitepackages() + [site.getusersitepackages()]))
print("python:", sys.executable)
found = 0
for d in dirs:
    if not os.path.isdir(d): continue
    for p in sorted(glob.glob(os.path.join(d, "*.pth"))):
        with open(p, encoding="utf-8", errors="replace") as f:
            for i, line in enumerate(f, 1):
                if line.startswith(("import ", "import\t")):
                    found += 1; print(f"[pth-import] {p}:{i}: {line.strip()[:80]}")
    for p in sorted(glob.glob(os.path.join(d, "*.start"))):
        found += 1; print(f"[start] {p}")
    for n in ("sitecustomize.py", "usercustomize.py"):
        p = os.path.join(d, n)
        if os.path.exists(p): found += 1; print(f"[customize] {p}")
print(f"起動時に動くもの: {found} 件(見る場所 {len(dirs)} か所)")

ダミーのパッケージを入れた venv での出力(パスは一部省略):

python: /tmp/pthrepro.XXXX/v/bin/python
[pth-import] /tmp/pthrepro.XXXX/v/lib/python3.13/site-packages/demo_pth_hook.pth:1: import os; open(os.environ.get('MARK','/dev/null'),'a').write('pth ran: pid=%d k
起動時に動くもの: 1 件(見る場所 5 か所)

.start と sitecustomize.py・usercustomize.py も対象にしているのは、どれも起動時に実行されるからです(.start は Python 3.15 で追加された仕組みで、3.13 の今回の環境にはありません。公式ドキュメントによる)。

正規のパッケージでも出ます。 たとえば setuptools 84.2.1 の wheel には distutils-precedence.pth が入っていて、import os; var = 'SETUPTOOLS_USE_DISTUTILS'; ... で始まる行がありました。見つかったら「どのパッケージのファイルか」「中で何をしているか」を見て判断します。知らないパッケージの .pth が、ネットワークやファイルを扱うコードを読み込んでいたら要注意です。このスクリプトは怪しいかどうかまでは判定しません。

5. 見つかってしまったら

  • pip uninstall で .pth ごと消えました(今回の wheel では RECORD に載っていたため)。ただし、すでに動いたコードが何をしたかは消えません。
  • その Python を起動した環境にあった環境変数のキー、~/.ssh などのファイルは、読まれた前提で再発行・作り直しを考えます(動いたコードの内容が分からない場合の一般的な考え方で、何が読まれるかはコード次第です)。
  • AIエージェントに pip install を任せる場合は、キーを入れたシェルとは別の環境(使い捨ての venv やコンテナ)で動かすと、読まれる範囲を小さくできます(推測を含む一般論で、今回は試していません)。

まとめ

  • .pth の import 行は、import しなくても Python の起動のたびに動く(実機・Python公式)。pip --version でも動いた。
  • そのコードは環境変数のキーを読める(ダミーで実機確認)。
  • 入れる前は pip download + python -m zipfile -l で .pth を探す。入れた後は check_pth.py で一覧にする。
  • 正規のパッケージ(setuptools など)も .pth を持つので、中身を見て判断する。

AIエージェントの手元で .env やシェル履歴が読める状態かどうかは、ai-secret-check(npx github:supportapp3-lab/ai-secret-check scan、値は表示せず通信もしません)で点検できます。.pth はまだ点検対象ではありません。

関連記事:

参考(公式):

※ 仕様は 2026-10-24 時点の公式ドキュメントに基づきます。検証は Python 3.13.5 / pip 25.1.1 で、Linux の使い捨て環境でのみ行いました。

更新情報やちょっとしたTipsは X(@supportapp3_lab)で発信しています。

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?