mimic-check 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mimic_check-0.1.0/PKG-INFO +111 -0
- mimic_check-0.1.0/README.md +98 -0
- mimic_check-0.1.0/pyproject.toml +31 -0
- mimic_check-0.1.0/setup.cfg +4 -0
- mimic_check-0.1.0/src/mimic/__init__.py +0 -0
- mimic_check-0.1.0/src/mimic/main.py +236 -0
- mimic_check-0.1.0/src/mimic_check.egg-info/PKG-INFO +111 -0
- mimic_check-0.1.0/src/mimic_check.egg-info/SOURCES.txt +10 -0
- mimic_check-0.1.0/src/mimic_check.egg-info/dependency_links.txt +1 -0
- mimic_check-0.1.0/src/mimic_check.egg-info/entry_points.txt +2 -0
- mimic_check-0.1.0/src/mimic_check.egg-info/requires.txt +2 -0
- mimic_check-0.1.0/src/mimic_check.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mimic-check
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A security tool to check for typosquatting and package reliability in requirements.txt
|
|
5
|
+
Author-email: Disnana <support@disnana.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests
|
|
12
|
+
Requires-Dist: toml
|
|
13
|
+
|
|
14
|
+
# mimic-check
|
|
15
|
+
|
|
16
|
+
`mimic-check` は、`requirements.txt` に記載された Python パッケージの安全性を検証するためのツールです。
|
|
17
|
+
タイポスクワッティング(有名なパッケージに似た名前の悪意あるパッケージ)の検出や、ダウンロード数・GitHub スター数に基づいた信頼性の評価を行います。
|
|
18
|
+
|
|
19
|
+
## 特徴
|
|
20
|
+
|
|
21
|
+
- **タイポ検出**: `requests` を `reqeusts` と書き間違えているようなケースを検出し警告します。
|
|
22
|
+
- **信頼性評価**: PyPI のダウンロード数(直近1ヶ月)と GitHub のスター数を確認し、閾値未満のパッケージを警告します。
|
|
23
|
+
- **効率的なキャッシュ**: 検証結果を当日中のみ有効なキャッシュとして保存し、不要な API リクエストを削減します。
|
|
24
|
+
- **PyPI 存在確認**: パッケージが PyPI に存在しない場合、即座に警告し後続のチェックをスキップします。
|
|
25
|
+
- **CI 対応**: `--ci` フラグを使用することで、問題検出時に非ゼロの終了コードで終了し、パイプラインを停止させることができます。
|
|
26
|
+
|
|
27
|
+
## セットアップ
|
|
28
|
+
|
|
29
|
+
### 必要条件
|
|
30
|
+
|
|
31
|
+
- Python 3.x
|
|
32
|
+
- `requests`, `toml` パッケージ
|
|
33
|
+
|
|
34
|
+
### インストール
|
|
35
|
+
|
|
36
|
+
PyPIからインストールする場合(公開後):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install mimic-check
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
ローカルで開発用にインストールする場合:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
これにより、`mimi` コマンドが使用可能になります。
|
|
49
|
+
|
|
50
|
+
## 使い方
|
|
51
|
+
|
|
52
|
+
### 基本的な実行
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mimi --file requirements.txt
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
実行すると、パッケージごとに以下のチェックが行われます:
|
|
59
|
+
1. 有名パッケージとの名前の類似性(タイポ)チェック
|
|
60
|
+
2. PyPI での存在確認
|
|
61
|
+
3. ダウンロード数とスター数の確認
|
|
62
|
+
|
|
63
|
+
問題が見つかった場合、警告が表示され、続行するかどうかを確認されます。`y` を入力すると、そのパッケージを含めた一時的な requirements ファイルで `pip install` が実行されます。
|
|
64
|
+
|
|
65
|
+
### CI モードでの実行
|
|
66
|
+
|
|
67
|
+
CI/CD パイプラインなどで、問題検出時に自動的にエラーとしたい場合は `--ci` フラグを使用します。
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
mimi --file requirements.txt --ci
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## PyPIへの配布方法
|
|
74
|
+
|
|
75
|
+
1. ビルドツールのインストール:
|
|
76
|
+
```bash
|
|
77
|
+
pip install build twine
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
2. パッケージのビルド:
|
|
81
|
+
```bash
|
|
82
|
+
python -m build
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
3. PyPIへのアップロード (テスト環境):
|
|
86
|
+
```bash
|
|
87
|
+
python -m twine upload --repository testpypi dist/*
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
4. PyPIへのアップロード (本番環境):
|
|
91
|
+
```bash
|
|
92
|
+
python -m twine upload dist/*
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 設定 (`config.toml`)
|
|
96
|
+
|
|
97
|
+
プロジェクトのルートにある `config.toml` で動作をカスタマイズできます。
|
|
98
|
+
|
|
99
|
+
```toml
|
|
100
|
+
[mimi]
|
|
101
|
+
min_downloads = 1000 # 信頼できるとみなす最小ダウンロード数
|
|
102
|
+
min_stars = 10 # 信頼できるとみなす最小 GitHub スター数
|
|
103
|
+
famous_packages = ["requests", "numpy", "pandas", ...] # タイポチェック対象の有名パッケージ
|
|
104
|
+
trusted_packages = [] # チェックをスキップする信頼済みパッケージ
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## キャッシュ
|
|
108
|
+
|
|
109
|
+
`package_cache.json` に検証結果がキャッシュされます。
|
|
110
|
+
- キャッシュは日付ごとに管理され、翌日には自動的に再検証されます。
|
|
111
|
+
- 信頼性の基準を満たしたパッケージのみがキャッシュされ、警告対象のパッケージは毎回チェックされます。
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# mimic-check
|
|
2
|
+
|
|
3
|
+
`mimic-check` は、`requirements.txt` に記載された Python パッケージの安全性を検証するためのツールです。
|
|
4
|
+
タイポスクワッティング(有名なパッケージに似た名前の悪意あるパッケージ)の検出や、ダウンロード数・GitHub スター数に基づいた信頼性の評価を行います。
|
|
5
|
+
|
|
6
|
+
## 特徴
|
|
7
|
+
|
|
8
|
+
- **タイポ検出**: `requests` を `reqeusts` と書き間違えているようなケースを検出し警告します。
|
|
9
|
+
- **信頼性評価**: PyPI のダウンロード数(直近1ヶ月)と GitHub のスター数を確認し、閾値未満のパッケージを警告します。
|
|
10
|
+
- **効率的なキャッシュ**: 検証結果を当日中のみ有効なキャッシュとして保存し、不要な API リクエストを削減します。
|
|
11
|
+
- **PyPI 存在確認**: パッケージが PyPI に存在しない場合、即座に警告し後続のチェックをスキップします。
|
|
12
|
+
- **CI 対応**: `--ci` フラグを使用することで、問題検出時に非ゼロの終了コードで終了し、パイプラインを停止させることができます。
|
|
13
|
+
|
|
14
|
+
## セットアップ
|
|
15
|
+
|
|
16
|
+
### 必要条件
|
|
17
|
+
|
|
18
|
+
- Python 3.x
|
|
19
|
+
- `requests`, `toml` パッケージ
|
|
20
|
+
|
|
21
|
+
### インストール
|
|
22
|
+
|
|
23
|
+
PyPIからインストールする場合(公開後):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install mimic-check
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
ローカルで開発用にインストールする場合:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
これにより、`mimi` コマンドが使用可能になります。
|
|
36
|
+
|
|
37
|
+
## 使い方
|
|
38
|
+
|
|
39
|
+
### 基本的な実行
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
mimi --file requirements.txt
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
実行すると、パッケージごとに以下のチェックが行われます:
|
|
46
|
+
1. 有名パッケージとの名前の類似性(タイポ)チェック
|
|
47
|
+
2. PyPI での存在確認
|
|
48
|
+
3. ダウンロード数とスター数の確認
|
|
49
|
+
|
|
50
|
+
問題が見つかった場合、警告が表示され、続行するかどうかを確認されます。`y` を入力すると、そのパッケージを含めた一時的な requirements ファイルで `pip install` が実行されます。
|
|
51
|
+
|
|
52
|
+
### CI モードでの実行
|
|
53
|
+
|
|
54
|
+
CI/CD パイプラインなどで、問題検出時に自動的にエラーとしたい場合は `--ci` フラグを使用します。
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
mimi --file requirements.txt --ci
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## PyPIへの配布方法
|
|
61
|
+
|
|
62
|
+
1. ビルドツールのインストール:
|
|
63
|
+
```bash
|
|
64
|
+
pip install build twine
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
2. パッケージのビルド:
|
|
68
|
+
```bash
|
|
69
|
+
python -m build
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
3. PyPIへのアップロード (テスト環境):
|
|
73
|
+
```bash
|
|
74
|
+
python -m twine upload --repository testpypi dist/*
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
4. PyPIへのアップロード (本番環境):
|
|
78
|
+
```bash
|
|
79
|
+
python -m twine upload dist/*
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 設定 (`config.toml`)
|
|
83
|
+
|
|
84
|
+
プロジェクトのルートにある `config.toml` で動作をカスタマイズできます。
|
|
85
|
+
|
|
86
|
+
```toml
|
|
87
|
+
[mimi]
|
|
88
|
+
min_downloads = 1000 # 信頼できるとみなす最小ダウンロード数
|
|
89
|
+
min_stars = 10 # 信頼できるとみなす最小 GitHub スター数
|
|
90
|
+
famous_packages = ["requests", "numpy", "pandas", ...] # タイポチェック対象の有名パッケージ
|
|
91
|
+
trusted_packages = [] # チェックをスキップする信頼済みパッケージ
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## キャッシュ
|
|
95
|
+
|
|
96
|
+
`package_cache.json` に検証結果がキャッシュされます。
|
|
97
|
+
- キャッシュは日付ごとに管理され、翌日には自動的に再検証されます。
|
|
98
|
+
- 信頼性の基準を満たしたパッケージのみがキャッシュされ、警告対象のパッケージは毎回チェックされます。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mimic-check"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Disnana", email = "support@disnana.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "A security tool to check for typosquatting and package reliability in requirements.txt"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.7"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"requests",
|
|
21
|
+
"toml",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
mimi = "mimic.main:main"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.package-data]
|
|
31
|
+
mimic = ["*.toml"]
|
|
File without changes
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import argparse
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import toml
|
|
6
|
+
import requests
|
|
7
|
+
import subprocess
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import json
|
|
11
|
+
import datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# キャッシュと設定の管理
|
|
15
|
+
DEFAULT_CONFIG_PATH = Path(__file__).parent / "config.toml"
|
|
16
|
+
CACHE_FILE = Path.home() / ".mimic_package_cache.json"
|
|
17
|
+
USER_CONFIG_FILE = Path.home() / ".mimic_config.toml"
|
|
18
|
+
REMOTE_CONFIG_URL = "https://raw.githubusercontent.com/disnana/mimic-check/main/config.toml"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_remote_config():
|
|
22
|
+
try:
|
|
23
|
+
res = requests.get(REMOTE_CONFIG_URL, timeout=5)
|
|
24
|
+
if res.status_code == 200:
|
|
25
|
+
return toml.loads(res.text)
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load_cache():
|
|
32
|
+
if CACHE_FILE.exists():
|
|
33
|
+
try:
|
|
34
|
+
with open(CACHE_FILE, "r") as f:
|
|
35
|
+
return json.load(f)
|
|
36
|
+
except Exception:
|
|
37
|
+
return {}
|
|
38
|
+
return {}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def save_cache(cache):
|
|
42
|
+
with open(CACHE_FILE, "w") as f:
|
|
43
|
+
json.dump(cache, f, indent=2)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_stats(pkg_name, config):
|
|
47
|
+
if pkg_name in config.get("trusted_packages", []):
|
|
48
|
+
return {"downloads": 999999, "stars": 999999, "date": "trusted"}
|
|
49
|
+
# キャッシュを読み込む(存在しない場合は空)
|
|
50
|
+
cache = load_cache()
|
|
51
|
+
today = str(datetime.date.today())
|
|
52
|
+
|
|
53
|
+
# キャッシュの有効性チェック(当日分のみ有効)
|
|
54
|
+
if pkg_name in cache:
|
|
55
|
+
cached_data = cache[pkg_name]
|
|
56
|
+
if cached_data.get("date") == today and cached_data.get("downloads", 0) > 0:
|
|
57
|
+
return cached_data
|
|
58
|
+
|
|
59
|
+
stats = {"downloads": 0, "stars": 0, "date": today}
|
|
60
|
+
|
|
61
|
+
# 1. Downloads (pypistats)
|
|
62
|
+
try:
|
|
63
|
+
url = f"https://pypistats.org/api/packages/{pkg_name}/recent"
|
|
64
|
+
for _ in range(3):
|
|
65
|
+
res = requests.get(url, headers={'User-Agent': 'Mimi-Security-Scanner'}, timeout=5)
|
|
66
|
+
if res.status_code == 200:
|
|
67
|
+
# 公式APIのレスポンス構造(辞書型)に合わせる
|
|
68
|
+
data = res.json().get("data", {})
|
|
69
|
+
stats["downloads"] = data.get("last_month", 0)
|
|
70
|
+
break
|
|
71
|
+
elif res.status_code == 404:
|
|
72
|
+
# 404の場合はPyPIに存在しないとみなし、チェックを即座に終了
|
|
73
|
+
stats["not_found"] = True
|
|
74
|
+
return stats
|
|
75
|
+
else:
|
|
76
|
+
time.sleep(1*_)
|
|
77
|
+
continue
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"DEBUG: Pypistats failed for {pkg_name}: {e}")
|
|
80
|
+
|
|
81
|
+
# 2. Stars (GitHub API)
|
|
82
|
+
try:
|
|
83
|
+
pypi_res = requests.get(f"https://pypi.org/pypi/{pkg_name}/json", timeout=5)
|
|
84
|
+
if pypi_res.status_code == 200:
|
|
85
|
+
pypi_res = pypi_res.json()
|
|
86
|
+
urls = pypi_res["info"].get("project_urls", {})
|
|
87
|
+
if urls:
|
|
88
|
+
candidates = [url for url in urls.values() if "github.com" in url]
|
|
89
|
+
repo_path = None
|
|
90
|
+
|
|
91
|
+
for url in candidates:
|
|
92
|
+
# 除外キーワード
|
|
93
|
+
if any(k in url for k in ["/sponsors/", "/issues/", "/pulls/"]):
|
|
94
|
+
continue
|
|
95
|
+
# 正確なキャプチャ
|
|
96
|
+
match = re.search(r"github\.com/([^/]+)/([^/]+)", url)
|
|
97
|
+
if match:
|
|
98
|
+
repo_path = f"{match.group(1)}/{match.group(2)}"
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
if repo_path:
|
|
102
|
+
api_url = f"https://api.github.com/repos/{repo_path}"
|
|
103
|
+
gh_res = requests.get(api_url, headers={'User-Agent': 'Mimi-Security-Scanner'}, timeout=5)
|
|
104
|
+
if gh_res.status_code == 200:
|
|
105
|
+
stats["stars"] = gh_res.json().get("stargazers_count", 0)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print(f"DEBUG: GitHub lookup failed for {pkg_name}: {e}")
|
|
108
|
+
|
|
109
|
+
# 基準を満たした場合のみキャッシュする(=未合格ならキャッシュされず、次回も再検証される)
|
|
110
|
+
min_dl = config.get("min_downloads", 1000)
|
|
111
|
+
min_st = config.get("min_stars", 10)
|
|
112
|
+
|
|
113
|
+
if stats["downloads"] >= min_dl or stats["stars"] >= min_st:
|
|
114
|
+
cache[pkg_name] = stats
|
|
115
|
+
save_cache(cache)
|
|
116
|
+
|
|
117
|
+
return stats
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def levenshtein_distance(s1, s2):
|
|
121
|
+
s1, s2 = s1.lower(), s2.lower()
|
|
122
|
+
if len(s1) < len(s2): s1, s2 = s2, s1
|
|
123
|
+
if len(s2) == 0: return len(s1)
|
|
124
|
+
prev = range(len(s2) + 1)
|
|
125
|
+
for i, c1 in enumerate(s1):
|
|
126
|
+
curr = [i + 1]
|
|
127
|
+
for j, c2 in enumerate(s2):
|
|
128
|
+
curr.append(min(prev[j + 1] + 1, curr[j] + 1, prev[j] + (c1 != c2)))
|
|
129
|
+
prev = curr
|
|
130
|
+
return prev[-1]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def analyze_package(pkg_name, config):
|
|
134
|
+
# タイポチェック
|
|
135
|
+
for famous in config.get("famous_packages", []):
|
|
136
|
+
dist = levenshtein_distance(pkg_name, famous)
|
|
137
|
+
if 0 < dist <= 2:
|
|
138
|
+
return f"🚨 TYPO ALERT: '{pkg_name}' is close to '{famous}'"
|
|
139
|
+
|
|
140
|
+
# 安全性評価 (configで閾値を可変に)
|
|
141
|
+
stats = get_stats(pkg_name, config)
|
|
142
|
+
|
|
143
|
+
if stats.get("not_found"):
|
|
144
|
+
return f"🚨 NOT FOUND: '{pkg_name}' was not found on PyPI"
|
|
145
|
+
|
|
146
|
+
downloads = stats["downloads"]
|
|
147
|
+
stars = stats["stars"]
|
|
148
|
+
|
|
149
|
+
min_dl = config.get("min_downloads", 1000)
|
|
150
|
+
min_st = config.get("min_stars", 10)
|
|
151
|
+
|
|
152
|
+
if downloads >= min_dl or stars >= min_st:
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
return f"⚠️ UNVERIFIED: '{pkg_name}' (Downloads: {downloads}, Stars: {stars})"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def main():
|
|
159
|
+
# WindowsのコンソールでUnicode出力をサポートするための設定
|
|
160
|
+
if sys.platform == "win32":
|
|
161
|
+
import io
|
|
162
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
163
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
|
164
|
+
|
|
165
|
+
parser = argparse.ArgumentParser()
|
|
166
|
+
parser.add_argument("--file", default="requirements.txt")
|
|
167
|
+
parser.add_argument("--config")
|
|
168
|
+
parser.add_argument("--ci", action="store_true")
|
|
169
|
+
args = parser.parse_args()
|
|
170
|
+
|
|
171
|
+
# 設定ファイルの読み込み優先順位:
|
|
172
|
+
# 1. 引数 --config
|
|
173
|
+
# 2. ユーザーホームディレクトリの .mimic_config.toml
|
|
174
|
+
# 3. カレントディレクトリの config.toml
|
|
175
|
+
# 4. パッケージ同梱のデフォルト config.toml
|
|
176
|
+
|
|
177
|
+
# 設定ファイルの読み出し
|
|
178
|
+
config = None
|
|
179
|
+
config_path = None
|
|
180
|
+
|
|
181
|
+
if args.config:
|
|
182
|
+
config_path = Path(args.config)
|
|
183
|
+
if config_path.exists():
|
|
184
|
+
config = toml.load(config_path)
|
|
185
|
+
elif USER_CONFIG_FILE.exists():
|
|
186
|
+
config_path = USER_CONFIG_FILE
|
|
187
|
+
config = toml.load(config_path)
|
|
188
|
+
elif Path("config.toml").exists():
|
|
189
|
+
config_path = Path("config.toml")
|
|
190
|
+
config = toml.load(config_path)
|
|
191
|
+
|
|
192
|
+
# ローカルに見つからない場合はリモート(GitHub)を試行
|
|
193
|
+
if config is None:
|
|
194
|
+
config = load_remote_config()
|
|
195
|
+
if config:
|
|
196
|
+
config_path = REMOTE_CONFIG_URL
|
|
197
|
+
|
|
198
|
+
# リモートもダメなら同梱のデフォルト
|
|
199
|
+
if config is None:
|
|
200
|
+
config_path = DEFAULT_CONFIG_PATH
|
|
201
|
+
if config_path.exists():
|
|
202
|
+
config = toml.load(config_path)
|
|
203
|
+
|
|
204
|
+
if config is None or "mimi" not in config:
|
|
205
|
+
print(f"Config load error: Could not find valid config")
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
config = config["mimi"]
|
|
209
|
+
|
|
210
|
+
with open(args.file, "r") as f:
|
|
211
|
+
lines = [line.strip() for line in f if line.strip() and not line.startswith("#")]
|
|
212
|
+
|
|
213
|
+
safe_lines = []
|
|
214
|
+
for line in lines:
|
|
215
|
+
match = re.match(r"^([a-zA-Z0-9\-_.]+)", line)
|
|
216
|
+
if not match: continue
|
|
217
|
+
pkg_name = match.group(1)
|
|
218
|
+
|
|
219
|
+
issue = analyze_package(pkg_name, config)
|
|
220
|
+
if issue:
|
|
221
|
+
print(issue)
|
|
222
|
+
if args.ci: sys.exit(1)
|
|
223
|
+
if input(f"Proceed with '{line}'? [y/N]: ").lower() != 'y': continue
|
|
224
|
+
|
|
225
|
+
safe_lines.append(line)
|
|
226
|
+
|
|
227
|
+
tmp_file = "requirements.tmp.txt"
|
|
228
|
+
with open(tmp_file, "w") as f:
|
|
229
|
+
f.write("\n".join(safe_lines))
|
|
230
|
+
|
|
231
|
+
subprocess.run([sys.executable, "-m", "pip", "install", "-r", tmp_file])
|
|
232
|
+
os.remove(tmp_file)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == "__main__":
|
|
236
|
+
main()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mimic-check
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A security tool to check for typosquatting and package reliability in requirements.txt
|
|
5
|
+
Author-email: Disnana <support@disnana.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests
|
|
12
|
+
Requires-Dist: toml
|
|
13
|
+
|
|
14
|
+
# mimic-check
|
|
15
|
+
|
|
16
|
+
`mimic-check` は、`requirements.txt` に記載された Python パッケージの安全性を検証するためのツールです。
|
|
17
|
+
タイポスクワッティング(有名なパッケージに似た名前の悪意あるパッケージ)の検出や、ダウンロード数・GitHub スター数に基づいた信頼性の評価を行います。
|
|
18
|
+
|
|
19
|
+
## 特徴
|
|
20
|
+
|
|
21
|
+
- **タイポ検出**: `requests` を `reqeusts` と書き間違えているようなケースを検出し警告します。
|
|
22
|
+
- **信頼性評価**: PyPI のダウンロード数(直近1ヶ月)と GitHub のスター数を確認し、閾値未満のパッケージを警告します。
|
|
23
|
+
- **効率的なキャッシュ**: 検証結果を当日中のみ有効なキャッシュとして保存し、不要な API リクエストを削減します。
|
|
24
|
+
- **PyPI 存在確認**: パッケージが PyPI に存在しない場合、即座に警告し後続のチェックをスキップします。
|
|
25
|
+
- **CI 対応**: `--ci` フラグを使用することで、問題検出時に非ゼロの終了コードで終了し、パイプラインを停止させることができます。
|
|
26
|
+
|
|
27
|
+
## セットアップ
|
|
28
|
+
|
|
29
|
+
### 必要条件
|
|
30
|
+
|
|
31
|
+
- Python 3.x
|
|
32
|
+
- `requests`, `toml` パッケージ
|
|
33
|
+
|
|
34
|
+
### インストール
|
|
35
|
+
|
|
36
|
+
PyPIからインストールする場合(公開後):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install mimic-check
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
ローカルで開発用にインストールする場合:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
これにより、`mimi` コマンドが使用可能になります。
|
|
49
|
+
|
|
50
|
+
## 使い方
|
|
51
|
+
|
|
52
|
+
### 基本的な実行
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mimi --file requirements.txt
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
実行すると、パッケージごとに以下のチェックが行われます:
|
|
59
|
+
1. 有名パッケージとの名前の類似性(タイポ)チェック
|
|
60
|
+
2. PyPI での存在確認
|
|
61
|
+
3. ダウンロード数とスター数の確認
|
|
62
|
+
|
|
63
|
+
問題が見つかった場合、警告が表示され、続行するかどうかを確認されます。`y` を入力すると、そのパッケージを含めた一時的な requirements ファイルで `pip install` が実行されます。
|
|
64
|
+
|
|
65
|
+
### CI モードでの実行
|
|
66
|
+
|
|
67
|
+
CI/CD パイプラインなどで、問題検出時に自動的にエラーとしたい場合は `--ci` フラグを使用します。
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
mimi --file requirements.txt --ci
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## PyPIへの配布方法
|
|
74
|
+
|
|
75
|
+
1. ビルドツールのインストール:
|
|
76
|
+
```bash
|
|
77
|
+
pip install build twine
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
2. パッケージのビルド:
|
|
81
|
+
```bash
|
|
82
|
+
python -m build
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
3. PyPIへのアップロード (テスト環境):
|
|
86
|
+
```bash
|
|
87
|
+
python -m twine upload --repository testpypi dist/*
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
4. PyPIへのアップロード (本番環境):
|
|
91
|
+
```bash
|
|
92
|
+
python -m twine upload dist/*
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 設定 (`config.toml`)
|
|
96
|
+
|
|
97
|
+
プロジェクトのルートにある `config.toml` で動作をカスタマイズできます。
|
|
98
|
+
|
|
99
|
+
```toml
|
|
100
|
+
[mimi]
|
|
101
|
+
min_downloads = 1000 # 信頼できるとみなす最小ダウンロード数
|
|
102
|
+
min_stars = 10 # 信頼できるとみなす最小 GitHub スター数
|
|
103
|
+
famous_packages = ["requests", "numpy", "pandas", ...] # タイポチェック対象の有名パッケージ
|
|
104
|
+
trusted_packages = [] # チェックをスキップする信頼済みパッケージ
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## キャッシュ
|
|
108
|
+
|
|
109
|
+
`package_cache.json` に検証結果がキャッシュされます。
|
|
110
|
+
- キャッシュは日付ごとに管理され、翌日には自動的に再検証されます。
|
|
111
|
+
- 信頼性の基準を満たしたパッケージのみがキャッシュされ、警告対象のパッケージは毎回チェックされます。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/mimic/__init__.py
|
|
4
|
+
src/mimic/main.py
|
|
5
|
+
src/mimic_check.egg-info/PKG-INFO
|
|
6
|
+
src/mimic_check.egg-info/SOURCES.txt
|
|
7
|
+
src/mimic_check.egg-info/dependency_links.txt
|
|
8
|
+
src/mimic_check.egg-info/entry_points.txt
|
|
9
|
+
src/mimic_check.egg-info/requires.txt
|
|
10
|
+
src/mimic_check.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mimic
|