stillrunning-pip 1.0.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.
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: stillrunning-pip
3
+ Version: 1.0.0
4
+ Summary: Secure pip wrapper with supply chain attack protection
5
+ Author-email: "stillrunning.io" <hello@stillrunning.io>
6
+ Project-URL: Homepage, https://stillrunning.io
7
+ Project-URL: Documentation, https://stillrunning.io/docs
8
+ Project-URL: Repository, https://github.com/johhnyg/stillrunning-pip
9
+ Project-URL: Issues, https://github.com/johhnyg/stillrunning-pip/issues
10
+ Keywords: security,supply-chain,pip,malware,protection
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Security
23
+ Classifier: Topic :: System :: Installation/Setup
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+
27
+ # stillrunning-pip
28
+
29
+ Secure pip wrapper that scans packages for supply chain attacks before installing.
30
+
31
+ [![PyPI version](https://badge.fury.io/py/stillrunning-pip.svg)](https://pypi.org/project/stillrunning-pip/)
32
+ [![stillrunning](https://stillrunning.io/badge/protected)](https://stillrunning.io)
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install stillrunning-pip
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ Use `stillrunning-pip` instead of `pip`:
43
+
44
+ ```bash
45
+ stillrunning-pip install requests flask
46
+ stillrunning-pip install -r requirements.txt
47
+ ```
48
+
49
+ Or create an alias:
50
+
51
+ ```bash
52
+ # Add to ~/.bashrc or ~/.zshrc
53
+ alias pip='stillrunning-pip'
54
+ ```
55
+
56
+ ## Setup
57
+
58
+ Configure your token and preferences:
59
+
60
+ ```bash
61
+ stillrunning-pip --setup
62
+ ```
63
+
64
+ Or create `~/.stillrunning/config.json` manually:
65
+
66
+ ```json
67
+ {
68
+ "token": "sr_your_token_here",
69
+ "block_dangerous": true,
70
+ "warn_suspicious": true,
71
+ "offline_mode": "warn"
72
+ }
73
+ ```
74
+
75
+ ## Example Output
76
+
77
+ ```
78
+ 🛡️ stillrunning security scan
79
+ Checking 5 package(s)...
80
+
81
+ ✅ CLEAN requests==2.31.0
82
+ ✅ CLEAN flask==2.3.0
83
+ ⚠️ WARNING sketchy-lib==1.0.0
84
+ → Obfuscated code patterns detected
85
+ 🚫 BLOCKED evil-pkg==0.1.0
86
+ → Known malicious package (reverse shell)
87
+
88
+ ❌ Installation blocked
89
+ 1 dangerous package(s) detected
90
+ ```
91
+
92
+ ## Configuration Options
93
+
94
+ | Option | Default | Description |
95
+ |--------|---------|-------------|
96
+ | `token` | `""` | stillrunning.io API token for AI scanning |
97
+ | `block_dangerous` | `true` | Block installs for dangerous packages |
98
+ | `warn_suspicious` | `true` | Show warnings for suspicious packages |
99
+ | `offline_mode` | `"warn"` | Behavior when API unreachable: `warn`, `block`, `allow` |
100
+ | `timeout` | `30` | API timeout in seconds |
101
+
102
+ ## Environment Variables
103
+
104
+ - `STILLRUNNING_TOKEN` — Override token from config
105
+
106
+ ## Free vs Paid
107
+
108
+ | Feature | Free | With Token |
109
+ |---------|------|------------|
110
+ | Known malicious packages | Blocked | Blocked |
111
+ | Threat feed database | Checked | Checked |
112
+ | AI analysis of unknown packages | - | Yes |
113
+ | Scans per day | Unlimited (cached) | 100-10000 |
114
+
115
+ Get a token at [stillrunning.io/pricing](https://stillrunning.io/pricing)
116
+
117
+ ## What It Detects
118
+
119
+ - **Known malicious packages** — Packages in our threat database (DPRK campaigns, typosquats, backdoors)
120
+ - **Typosquatting** — Packages with names similar to popular packages
121
+ - **AI-flagged packages** — Obfuscated code, credential harvesting, reverse shells
122
+
123
+ ## Bypass (Not Recommended)
124
+
125
+ To bypass scanning for a single install:
126
+
127
+ ```bash
128
+ pip install <package> # Use pip directly
129
+ ```
130
+
131
+ ## Uninstall
132
+
133
+ ```bash
134
+ pip uninstall stillrunning-pip
135
+ ```
136
+
137
+ ## License
138
+
139
+ MIT
@@ -0,0 +1,113 @@
1
+ # stillrunning-pip
2
+
3
+ Secure pip wrapper that scans packages for supply chain attacks before installing.
4
+
5
+ [![PyPI version](https://badge.fury.io/py/stillrunning-pip.svg)](https://pypi.org/project/stillrunning-pip/)
6
+ [![stillrunning](https://stillrunning.io/badge/protected)](https://stillrunning.io)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install stillrunning-pip
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ Use `stillrunning-pip` instead of `pip`:
17
+
18
+ ```bash
19
+ stillrunning-pip install requests flask
20
+ stillrunning-pip install -r requirements.txt
21
+ ```
22
+
23
+ Or create an alias:
24
+
25
+ ```bash
26
+ # Add to ~/.bashrc or ~/.zshrc
27
+ alias pip='stillrunning-pip'
28
+ ```
29
+
30
+ ## Setup
31
+
32
+ Configure your token and preferences:
33
+
34
+ ```bash
35
+ stillrunning-pip --setup
36
+ ```
37
+
38
+ Or create `~/.stillrunning/config.json` manually:
39
+
40
+ ```json
41
+ {
42
+ "token": "sr_your_token_here",
43
+ "block_dangerous": true,
44
+ "warn_suspicious": true,
45
+ "offline_mode": "warn"
46
+ }
47
+ ```
48
+
49
+ ## Example Output
50
+
51
+ ```
52
+ 🛡️ stillrunning security scan
53
+ Checking 5 package(s)...
54
+
55
+ ✅ CLEAN requests==2.31.0
56
+ ✅ CLEAN flask==2.3.0
57
+ ⚠️ WARNING sketchy-lib==1.0.0
58
+ → Obfuscated code patterns detected
59
+ 🚫 BLOCKED evil-pkg==0.1.0
60
+ → Known malicious package (reverse shell)
61
+
62
+ ❌ Installation blocked
63
+ 1 dangerous package(s) detected
64
+ ```
65
+
66
+ ## Configuration Options
67
+
68
+ | Option | Default | Description |
69
+ |--------|---------|-------------|
70
+ | `token` | `""` | stillrunning.io API token for AI scanning |
71
+ | `block_dangerous` | `true` | Block installs for dangerous packages |
72
+ | `warn_suspicious` | `true` | Show warnings for suspicious packages |
73
+ | `offline_mode` | `"warn"` | Behavior when API unreachable: `warn`, `block`, `allow` |
74
+ | `timeout` | `30` | API timeout in seconds |
75
+
76
+ ## Environment Variables
77
+
78
+ - `STILLRUNNING_TOKEN` — Override token from config
79
+
80
+ ## Free vs Paid
81
+
82
+ | Feature | Free | With Token |
83
+ |---------|------|------------|
84
+ | Known malicious packages | Blocked | Blocked |
85
+ | Threat feed database | Checked | Checked |
86
+ | AI analysis of unknown packages | - | Yes |
87
+ | Scans per day | Unlimited (cached) | 100-10000 |
88
+
89
+ Get a token at [stillrunning.io/pricing](https://stillrunning.io/pricing)
90
+
91
+ ## What It Detects
92
+
93
+ - **Known malicious packages** — Packages in our threat database (DPRK campaigns, typosquats, backdoors)
94
+ - **Typosquatting** — Packages with names similar to popular packages
95
+ - **AI-flagged packages** — Obfuscated code, credential harvesting, reverse shells
96
+
97
+ ## Bypass (Not Recommended)
98
+
99
+ To bypass scanning for a single install:
100
+
101
+ ```bash
102
+ pip install <package> # Use pip directly
103
+ ```
104
+
105
+ ## Uninstall
106
+
107
+ ```bash
108
+ pip uninstall stillrunning-pip
109
+ ```
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "stillrunning-pip"
7
+ version = "1.0.0"
8
+ description = "Secure pip wrapper with supply chain attack protection"
9
+ readme = "README.md"
10
+ authors = [
11
+ {name = "stillrunning.io", email = "hello@stillrunning.io"}
12
+ ]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Security",
26
+ "Topic :: System :: Installation/Setup",
27
+ ]
28
+ keywords = ["security", "supply-chain", "pip", "malware", "protection"]
29
+ requires-python = ">=3.8"
30
+
31
+ [project.urls]
32
+ Homepage = "https://stillrunning.io"
33
+ Documentation = "https://stillrunning.io/docs"
34
+ Repository = "https://github.com/johhnyg/stillrunning-pip"
35
+ Issues = "https://github.com/johhnyg/stillrunning-pip/issues"
36
+
37
+ [project.scripts]
38
+ stillrunning-pip = "stillrunning_pip.cli:main"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["."]
42
+ include = ["stillrunning_pip*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ """
2
+ stillrunning-pip — Supply chain attack protection for pip installs.
3
+
4
+ Usage:
5
+ stillrunning-pip install requests flask
6
+
7
+ Or alias it:
8
+ alias pip='stillrunning-pip'
9
+ """
10
+
11
+ __version__ = "1.0.0"
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ stillrunning-pip — Secure pip wrapper with supply chain attack protection.
4
+
5
+ Usage:
6
+ stillrunning-pip install requests flask
7
+ stillrunning-pip --setup
8
+ stillrunning-pip --version
9
+ """
10
+ import json
11
+ import os
12
+ import re
13
+ import subprocess
14
+ import sys
15
+ import urllib.request
16
+ from pathlib import Path
17
+
18
+ from .config import load_config, setup_config
19
+ from . import __version__
20
+
21
+ # Terminal colors
22
+ RED = "\033[91m"
23
+ YELLOW = "\033[93m"
24
+ GREEN = "\033[92m"
25
+ BOLD = "\033[1m"
26
+ DIM = "\033[2m"
27
+ RESET = "\033[0m"
28
+
29
+
30
+ def extract_packages(args: list) -> list:
31
+ """Extract package names from pip install arguments."""
32
+ packages = []
33
+ skip_next = False
34
+
35
+ for arg in args:
36
+ if skip_next:
37
+ skip_next = False
38
+ continue
39
+
40
+ if arg.startswith("-"):
41
+ # Some flags take values
42
+ if arg in ("-r", "--requirement", "-e", "--editable", "-t", "--target",
43
+ "-c", "--constraint", "-i", "--index-url", "--extra-index-url"):
44
+ skip_next = True
45
+ continue
46
+
47
+ if arg in ("install", "i"):
48
+ continue
49
+
50
+ # This is a package name (possibly with version specifier)
51
+ # Remove version specifiers for API
52
+ pkg = arg.strip()
53
+ if pkg:
54
+ packages.append(pkg)
55
+
56
+ return packages
57
+
58
+
59
+ def parse_requirements_file(filepath: str) -> list:
60
+ """Parse a requirements.txt file."""
61
+ packages = []
62
+ try:
63
+ with open(filepath) as f:
64
+ for line in f:
65
+ line = line.strip()
66
+ if not line or line.startswith("#") or line.startswith("-"):
67
+ continue
68
+ if "#" in line:
69
+ line = line.split("#")[0].strip()
70
+ if line:
71
+ packages.append(line)
72
+ except Exception:
73
+ pass
74
+ return packages
75
+
76
+
77
+ def call_api(packages: list, config: dict) -> dict:
78
+ """Call stillrunning.io API."""
79
+ # Format packages for API
80
+ package_list = []
81
+ for pkg in packages:
82
+ # Parse name and version
83
+ for sep in ["==", ">=", "<=", "~=", "!=", ">", "<", "@"]:
84
+ if sep in pkg:
85
+ parts = pkg.split(sep, 1)
86
+ package_list.append({
87
+ "name": parts[0].strip(),
88
+ "version": parts[1].strip() if len(parts) > 1 else "latest"
89
+ })
90
+ break
91
+ else:
92
+ # Remove extras like [security]
93
+ name = pkg.split("[")[0].strip() if "[" in pkg else pkg.strip()
94
+ package_list.append({"name": name, "version": "latest"})
95
+
96
+ payload = json.dumps({
97
+ "packages": package_list,
98
+ "token": config.get("token", "")
99
+ }).encode()
100
+
101
+ api_url = config.get("api_url", "https://stillrunning.io/api/pip-plugin/scan")
102
+ timeout = config.get("timeout", 30)
103
+
104
+ req = urllib.request.Request(
105
+ api_url,
106
+ data=payload,
107
+ headers={
108
+ "Content-Type": "application/json",
109
+ "User-Agent": f"stillrunning-pip/{__version__}"
110
+ }
111
+ )
112
+
113
+ try:
114
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
115
+ return json.loads(resp.read().decode())
116
+ except urllib.error.HTTPError as e:
117
+ error_body = e.read().decode() if e.fp else ""
118
+ return {"error": f"API error: {e.code}", "details": error_body}
119
+ except urllib.error.URLError as e:
120
+ return {"error": f"Network error: {e.reason}", "offline": True}
121
+ except Exception as e:
122
+ return {"error": str(e), "offline": True}
123
+
124
+
125
+ def print_result(result: dict):
126
+ """Print formatted result for a package."""
127
+ verdict = result.get("verdict", "UNKNOWN")
128
+ package = result.get("package", "unknown")
129
+ version = result.get("version", "")
130
+ score = result.get("score", 0)
131
+ reason = result.get("reason", "")
132
+
133
+ pkg_display = f"{package}=={version}" if version and version != "latest" else package
134
+
135
+ if verdict == "DANGEROUS":
136
+ print(f" {RED}{BOLD}🚫 BLOCKED{RESET} {pkg_display}")
137
+ if reason:
138
+ print(f" {DIM}→ {reason}{RESET}")
139
+ elif verdict == "SUSPICIOUS":
140
+ print(f" {YELLOW}⚠️ WARNING{RESET} {pkg_display}")
141
+ if reason:
142
+ print(f" {DIM}→ {reason}{RESET}")
143
+ elif verdict == "UNKNOWN":
144
+ print(f" {DIM}❓ UNKNOWN{RESET} {pkg_display}")
145
+ if reason:
146
+ print(f" {DIM}→ {reason}{RESET}")
147
+ else:
148
+ print(f" {GREEN}✅ CLEAN{RESET} {pkg_display}")
149
+
150
+
151
+ def main():
152
+ """Main entry point."""
153
+ args = sys.argv[1:]
154
+
155
+ # Handle special flags
156
+ if not args or "--help" in args or "-h" in args:
157
+ print(f"""
158
+ {BOLD}stillrunning-pip{RESET} v{__version__} — Secure pip wrapper
159
+
160
+ Usage:
161
+ stillrunning-pip install <packages> Scan and install packages
162
+ stillrunning-pip --setup Configure stillrunning-pip
163
+ stillrunning-pip --version Show version
164
+ stillrunning-pip <pip-command> Pass through to pip
165
+
166
+ Examples:
167
+ stillrunning-pip install requests flask
168
+ stillrunning-pip install -r requirements.txt
169
+ """)
170
+ if args and args[0] not in ("--help", "-h"):
171
+ # Pass through to pip
172
+ result = subprocess.run(["pip"] + args)
173
+ sys.exit(result.returncode)
174
+ sys.exit(0)
175
+
176
+ if "--version" in args:
177
+ print(f"stillrunning-pip {__version__}")
178
+ sys.exit(0)
179
+
180
+ if "--setup" in args:
181
+ setup_config()
182
+ sys.exit(0)
183
+
184
+ # Only intercept install commands
185
+ if args[0] not in ("install", "i"):
186
+ # Pass through non-install commands
187
+ result = subprocess.run(["pip"] + args)
188
+ sys.exit(result.returncode)
189
+
190
+ # Load config
191
+ config = load_config()
192
+
193
+ # Extract packages to check
194
+ packages = extract_packages(args)
195
+
196
+ # Check for -r flag and parse requirements file
197
+ for i, arg in enumerate(args):
198
+ if arg in ("-r", "--requirement") and i + 1 < len(args):
199
+ req_packages = parse_requirements_file(args[i + 1])
200
+ packages.extend(req_packages)
201
+
202
+ # Deduplicate
203
+ packages = list(set(packages))
204
+
205
+ if not packages:
206
+ # No packages to check, just pass through
207
+ result = subprocess.run(["pip"] + args)
208
+ sys.exit(result.returncode)
209
+
210
+ # Print header
211
+ print(f"\n{BOLD}🛡️ stillrunning security scan{RESET}")
212
+ print(f"{DIM} Checking {len(packages)} package(s)...{RESET}\n")
213
+
214
+ # Call API
215
+ api_result = call_api(packages, config)
216
+
217
+ # Handle errors
218
+ if "error" in api_result:
219
+ offline_mode = config.get("offline_mode", "warn")
220
+
221
+ if api_result.get("offline"):
222
+ if offline_mode == "block":
223
+ print(f"{RED}Error: API unreachable — install blocked{RESET}")
224
+ print(f"{DIM} {api_result['error']}{RESET}\n")
225
+ sys.exit(1)
226
+ elif offline_mode == "allow":
227
+ print(f"{DIM}API unavailable — proceeding without scan{RESET}\n")
228
+ result = subprocess.run(["pip"] + args)
229
+ sys.exit(result.returncode)
230
+ else: # warn
231
+ print(f"{YELLOW}⚠️ API unavailable — proceeding with caution{RESET}")
232
+ print(f"{DIM} {api_result['error']}{RESET}\n")
233
+ result = subprocess.run(["pip"] + args)
234
+ sys.exit(result.returncode)
235
+ else:
236
+ print(f"{RED}Error: {api_result['error']}{RESET}")
237
+ if api_result.get("details"):
238
+ print(f"{DIM} {api_result['details'][:200]}{RESET}")
239
+ print()
240
+ sys.exit(1)
241
+
242
+ # Process results
243
+ results = api_result.get("results", [])
244
+ blocked = []
245
+ warnings = []
246
+
247
+ for r in results:
248
+ verdict = r.get("verdict", "CLEAN")
249
+ if verdict == "DANGEROUS":
250
+ blocked.append(r)
251
+ print_result(r)
252
+ elif verdict == "SUSPICIOUS":
253
+ warnings.append(r)
254
+ if config.get("warn_suspicious", True):
255
+ print_result(r)
256
+ elif verdict == "UNKNOWN":
257
+ print_result(r)
258
+
259
+ # Handle blocked packages
260
+ if blocked and config.get("block_dangerous", True):
261
+ print(f"\n{RED}{BOLD}❌ Installation blocked{RESET}")
262
+ print(f"{RED} {len(blocked)} dangerous package(s) detected{RESET}")
263
+ print(f"\n{DIM}If you believe this is a false positive, report it at:{RESET}")
264
+ print(f"{DIM}https://stillrunning.io/report{RESET}\n")
265
+ sys.exit(1)
266
+
267
+ # Handle warnings
268
+ if warnings and config.get("warn_suspicious", True):
269
+ print(f"\n{YELLOW}⚠️ {len(warnings)} suspicious package(s) found{RESET}")
270
+
271
+ # Show upgrade prompt if unknown packages and no token
272
+ unknown = [r for r in results if r.get("verdict") == "UNKNOWN"]
273
+ if unknown and not config.get("token"):
274
+ print(f"\n{DIM}💡 {len(unknown)} packages not AI-scanned.{RESET}")
275
+ print(f"{DIM} Get a token at https://stillrunning.io/pricing{RESET}")
276
+
277
+ # All clear — proceed with install
278
+ clean_count = len([r for r in results if r.get("verdict") == "CLEAN"])
279
+ print(f"\n{GREEN}✅ {clean_count} package(s) verified{RESET}")
280
+ print(f"{DIM} Proceeding with pip install...{RESET}\n")
281
+
282
+ result = subprocess.run(["pip"] + args)
283
+ sys.exit(result.returncode)
284
+
285
+
286
+ if __name__ == "__main__":
287
+ main()
@@ -0,0 +1,79 @@
1
+ """Configuration loading for stillrunning-pip."""
2
+ import json
3
+ import os
4
+ from pathlib import Path
5
+
6
+ CONFIG_DIR = Path.home() / ".stillrunning"
7
+ CONFIG_FILE = CONFIG_DIR / "config.json"
8
+
9
+ DEFAULT_CONFIG = {
10
+ "token": "",
11
+ "block_dangerous": True,
12
+ "warn_suspicious": True,
13
+ "offline_mode": "warn", # "warn", "block", "allow"
14
+ "timeout": 30,
15
+ "api_url": "https://stillrunning.io/api/pip-plugin/scan"
16
+ }
17
+
18
+
19
+ def load_config() -> dict:
20
+ """Load config from ~/.stillrunning/config.json"""
21
+ config = DEFAULT_CONFIG.copy()
22
+
23
+ try:
24
+ if CONFIG_FILE.exists():
25
+ with open(CONFIG_FILE) as f:
26
+ user_config = json.load(f)
27
+ config.update(user_config)
28
+ except Exception:
29
+ pass
30
+
31
+ # Environment variable overrides
32
+ if os.environ.get("STILLRUNNING_TOKEN"):
33
+ config["token"] = os.environ["STILLRUNNING_TOKEN"]
34
+
35
+ return config
36
+
37
+
38
+ def save_config(config: dict):
39
+ """Save config to ~/.stillrunning/config.json"""
40
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
41
+
42
+ with open(CONFIG_FILE, "w") as f:
43
+ json.dump(config, f, indent=2)
44
+
45
+
46
+ def setup_config():
47
+ """Interactive config setup."""
48
+ print("\n" + "=" * 50)
49
+ print("stillrunning-pip Setup")
50
+ print("=" * 50 + "\n")
51
+
52
+ config = load_config()
53
+
54
+ # Token
55
+ print("Enter your stillrunning.io token (or press Enter to skip):")
56
+ print("Get a token at https://stillrunning.io/pricing")
57
+ token = input("> ").strip()
58
+ if token:
59
+ config["token"] = token
60
+
61
+ # Block dangerous
62
+ print("\nBlock installs for dangerous packages? [Y/n]")
63
+ response = input("> ").strip().lower()
64
+ config["block_dangerous"] = response != "n"
65
+
66
+ # Warn suspicious
67
+ print("\nWarn about suspicious packages? [Y/n]")
68
+ response = input("> ").strip().lower()
69
+ config["warn_suspicious"] = response != "n"
70
+
71
+ # Offline mode
72
+ print("\nBehavior when API is unreachable? [warn/block/allow] (default: warn)")
73
+ response = input("> ").strip().lower()
74
+ if response in ("warn", "block", "allow"):
75
+ config["offline_mode"] = response
76
+
77
+ save_config(config)
78
+ print(f"\nConfig saved to {CONFIG_FILE}")
79
+ print("You can now use: stillrunning-pip install <package>\n")
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: stillrunning-pip
3
+ Version: 1.0.0
4
+ Summary: Secure pip wrapper with supply chain attack protection
5
+ Author-email: "stillrunning.io" <hello@stillrunning.io>
6
+ Project-URL: Homepage, https://stillrunning.io
7
+ Project-URL: Documentation, https://stillrunning.io/docs
8
+ Project-URL: Repository, https://github.com/johhnyg/stillrunning-pip
9
+ Project-URL: Issues, https://github.com/johhnyg/stillrunning-pip/issues
10
+ Keywords: security,supply-chain,pip,malware,protection
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Security
23
+ Classifier: Topic :: System :: Installation/Setup
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+
27
+ # stillrunning-pip
28
+
29
+ Secure pip wrapper that scans packages for supply chain attacks before installing.
30
+
31
+ [![PyPI version](https://badge.fury.io/py/stillrunning-pip.svg)](https://pypi.org/project/stillrunning-pip/)
32
+ [![stillrunning](https://stillrunning.io/badge/protected)](https://stillrunning.io)
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install stillrunning-pip
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ Use `stillrunning-pip` instead of `pip`:
43
+
44
+ ```bash
45
+ stillrunning-pip install requests flask
46
+ stillrunning-pip install -r requirements.txt
47
+ ```
48
+
49
+ Or create an alias:
50
+
51
+ ```bash
52
+ # Add to ~/.bashrc or ~/.zshrc
53
+ alias pip='stillrunning-pip'
54
+ ```
55
+
56
+ ## Setup
57
+
58
+ Configure your token and preferences:
59
+
60
+ ```bash
61
+ stillrunning-pip --setup
62
+ ```
63
+
64
+ Or create `~/.stillrunning/config.json` manually:
65
+
66
+ ```json
67
+ {
68
+ "token": "sr_your_token_here",
69
+ "block_dangerous": true,
70
+ "warn_suspicious": true,
71
+ "offline_mode": "warn"
72
+ }
73
+ ```
74
+
75
+ ## Example Output
76
+
77
+ ```
78
+ 🛡️ stillrunning security scan
79
+ Checking 5 package(s)...
80
+
81
+ ✅ CLEAN requests==2.31.0
82
+ ✅ CLEAN flask==2.3.0
83
+ ⚠️ WARNING sketchy-lib==1.0.0
84
+ → Obfuscated code patterns detected
85
+ 🚫 BLOCKED evil-pkg==0.1.0
86
+ → Known malicious package (reverse shell)
87
+
88
+ ❌ Installation blocked
89
+ 1 dangerous package(s) detected
90
+ ```
91
+
92
+ ## Configuration Options
93
+
94
+ | Option | Default | Description |
95
+ |--------|---------|-------------|
96
+ | `token` | `""` | stillrunning.io API token for AI scanning |
97
+ | `block_dangerous` | `true` | Block installs for dangerous packages |
98
+ | `warn_suspicious` | `true` | Show warnings for suspicious packages |
99
+ | `offline_mode` | `"warn"` | Behavior when API unreachable: `warn`, `block`, `allow` |
100
+ | `timeout` | `30` | API timeout in seconds |
101
+
102
+ ## Environment Variables
103
+
104
+ - `STILLRUNNING_TOKEN` — Override token from config
105
+
106
+ ## Free vs Paid
107
+
108
+ | Feature | Free | With Token |
109
+ |---------|------|------------|
110
+ | Known malicious packages | Blocked | Blocked |
111
+ | Threat feed database | Checked | Checked |
112
+ | AI analysis of unknown packages | - | Yes |
113
+ | Scans per day | Unlimited (cached) | 100-10000 |
114
+
115
+ Get a token at [stillrunning.io/pricing](https://stillrunning.io/pricing)
116
+
117
+ ## What It Detects
118
+
119
+ - **Known malicious packages** — Packages in our threat database (DPRK campaigns, typosquats, backdoors)
120
+ - **Typosquatting** — Packages with names similar to popular packages
121
+ - **AI-flagged packages** — Obfuscated code, credential harvesting, reverse shells
122
+
123
+ ## Bypass (Not Recommended)
124
+
125
+ To bypass scanning for a single install:
126
+
127
+ ```bash
128
+ pip install <package> # Use pip directly
129
+ ```
130
+
131
+ ## Uninstall
132
+
133
+ ```bash
134
+ pip uninstall stillrunning-pip
135
+ ```
136
+
137
+ ## License
138
+
139
+ MIT
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ stillrunning_pip/__init__.py
4
+ stillrunning_pip/cli.py
5
+ stillrunning_pip/config.py
6
+ stillrunning_pip.egg-info/PKG-INFO
7
+ stillrunning_pip.egg-info/SOURCES.txt
8
+ stillrunning_pip.egg-info/dependency_links.txt
9
+ stillrunning_pip.egg-info/entry_points.txt
10
+ stillrunning_pip.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ stillrunning-pip = stillrunning_pip.cli:main
@@ -0,0 +1 @@
1
+ stillrunning_pip