saferelay 3.5.3__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.
- saferelay-3.5.3/PKG-INFO +149 -0
- saferelay-3.5.3/README.md +118 -0
- saferelay-3.5.3/saferelay/__init__.py +4 -0
- saferelay-3.5.3/saferelay/__main__.py +418 -0
- saferelay-3.5.3/saferelay/client.py +140 -0
- saferelay-3.5.3/saferelay.egg-info/PKG-INFO +149 -0
- saferelay-3.5.3/saferelay.egg-info/SOURCES.txt +11 -0
- saferelay-3.5.3/saferelay.egg-info/dependency_links.txt +1 -0
- saferelay-3.5.3/saferelay.egg-info/entry_points.txt +2 -0
- saferelay-3.5.3/saferelay.egg-info/requires.txt +3 -0
- saferelay-3.5.3/saferelay.egg-info/top_level.txt +1 -0
- saferelay-3.5.3/setup.cfg +4 -0
- saferelay-3.5.3/setup.py +25 -0
saferelay-3.5.3/PKG-INFO
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: saferelay
|
|
3
|
+
Version: 3.5.3
|
|
4
|
+
Summary: Zero-trust local DLP for AI-era workflows — 40+ threat patterns across 8+ countries
|
|
5
|
+
Home-page: https://saferelay.ai
|
|
6
|
+
Author: LogicGrid AI, LLC
|
|
7
|
+
Author-email: support@logicgrid.ai
|
|
8
|
+
License: Proprietary
|
|
9
|
+
Keywords: ai-safety api-keys cli dlp pii redaction security zero-trust saferelay
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Provides-Extra: redis
|
|
19
|
+
Requires-Dist: redis>=4.0; extra == "redis"
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: keywords
|
|
27
|
+
Dynamic: license
|
|
28
|
+
Dynamic: provides-extra
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
# SafePaste Enterprise CLI
|
|
33
|
+
|
|
34
|
+
> Zero-trust DLP for Linux pipelines — 35+ threat patterns across 8+ countries.
|
|
35
|
+
|
|
36
|
+
[](https://pypi.org/project/saferelay-enterprise/)
|
|
37
|
+
[](https://hub.docker.com/r/logicgridai/saferelay)
|
|
38
|
+
[](https://saferelay.ai)
|
|
39
|
+
|
|
40
|
+
SafePaste redacts sensitive data in your Linux pipelines before it reaches AI tools, log aggregators, or external services.
|
|
41
|
+
BEFORE AFTER (SafePaste)
|
|
42
|
+
───────────────────────────────── ─────────────────────────────────
|
|
43
|
+
OPENAI_API_KEY=sk-proj-abc...xyz
|
|
44
|
+
AWS_ACCESS_KEY_ID=AKIA1234ABCD
|
|
45
|
+
Authorization: Bearer eyJhb... Authorization: Bearer [BEARER_1]
|
|
46
|
+
Server: [IP_1] Server: [DEVSEC_1]
|
|
47
|
+
SSN: [US_SSN_1] SSN: [US_SSN_1]
|
|
48
|
+
|
|
49
|
+
## Get a License
|
|
50
|
+
|
|
51
|
+
| Tier | Price | Get it |
|
|
52
|
+
|------|-------|--------|
|
|
53
|
+
| Free | $0 | [Chrome Web Store](https://chromewebstore.google.com/detail/saferelay-enterprise/odeoilooelkodahbbdokbollgahdcaag) |
|
|
54
|
+
| Pro | $7.99/mo or $59/yr | [saferelay.ai/#pricing](https://saferelay.ai/#pricing) |
|
|
55
|
+
| SafeRelay Suite | $99 one-time | [saferelay.ai/saferelay](https://saferelay.ai/saferelay) |
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install saferelay-enterprise
|
|
61
|
+
|
|
62
|
+
# With Redis support (enterprise distributed vault)
|
|
63
|
+
pip install saferelay-enterprise[redis]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Requires Python 3.9+. No external dependencies for the base install.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Free tier — mask IPs and API keys
|
|
72
|
+
cat /var/log/app.log | saferelay --mask
|
|
73
|
+
|
|
74
|
+
# Pro tier — full vault with unmask
|
|
75
|
+
docker logs my-app | saferelay --mask > clean.log
|
|
76
|
+
cat ai_response.txt | saferelay --unmask
|
|
77
|
+
|
|
78
|
+
# Activate Pro
|
|
79
|
+
saferelay --unlock "YOUR-LICENSE-KEY"
|
|
80
|
+
|
|
81
|
+
# Status
|
|
82
|
+
saferelay --status
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## What gets redacted
|
|
86
|
+
|
|
87
|
+
| Pattern | Free | Pro |
|
|
88
|
+
|---------|------|-----|
|
|
89
|
+
| IPv4 addresses | ✓ | ✓ |
|
|
90
|
+
| API keys (OpenAI, Anthropic, AWS, GitHub, Slack, Gemini) | ✓ | ✓ |
|
|
91
|
+
| Bitcoin / Ethereum addresses | ✓ | ✓ |
|
|
92
|
+
| PEM private keys | ✓ | ✓ |
|
|
93
|
+
| .env file values | ✓ | ✓ |
|
|
94
|
+
| MAC addresses | — | ✓ |
|
|
95
|
+
| Credit cards (Luhn-validated) | — | ✓ |
|
|
96
|
+
| US SSN | — | ✓ |
|
|
97
|
+
| EU IBAN | — | ✓ |
|
|
98
|
+
| UK NINO | — | ✓ |
|
|
99
|
+
| Nigeria NIN / Bank / Phone | — | ✓ |
|
|
100
|
+
| Canada SIN | — | ✓ |
|
|
101
|
+
| India Aadhaar / PAN | — | ✓ |
|
|
102
|
+
| South Africa ID | — | ✓ |
|
|
103
|
+
| Australia TFN | — | ✓ |
|
|
104
|
+
| Brazil CPF | — | ✓ |
|
|
105
|
+
| Singapore NRIC | — | ✓ |
|
|
106
|
+
| Germany Tax ID | — | ✓ |
|
|
107
|
+
| Seed phrases (12/24 word) | — | ✓ |
|
|
108
|
+
| ETH private keys | — | ✓ |
|
|
109
|
+
| Custom NDA keywords | — | ✓ |
|
|
110
|
+
|
|
111
|
+
## Docker
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
docker pull logicgridai/saferelay:latest
|
|
115
|
+
|
|
116
|
+
cat /var/log/app.log | docker run --rm -i logicgridai/saferelay --mask
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Kubernetes sidecar
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
containers:
|
|
123
|
+
- name: saferelay
|
|
124
|
+
image: logicgridai/saferelay:latest
|
|
125
|
+
env:
|
|
126
|
+
- name: SAFEPASTE_LICENSE_KEY
|
|
127
|
+
valueFrom:
|
|
128
|
+
secretKeyRef:
|
|
129
|
+
name: saferelay-secret
|
|
130
|
+
key: license-key
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Pricing
|
|
134
|
+
|
|
135
|
+
| Tier | Price | Features |
|
|
136
|
+
|------|-------|---------|
|
|
137
|
+
| Free | $0 | IP + API key redaction |
|
|
138
|
+
| Pro | $7.99/month or $59/year | 35+ patterns, full vault |
|
|
139
|
+
| SafeRelay Suite | $99 one-time | SafePaste + SpeakPaste + Boomerang Snip |
|
|
140
|
+
|
|
141
|
+
→ [Get a license at saferelay.ai](https://saferelay.ai)
|
|
142
|
+
|
|
143
|
+
## Privacy
|
|
144
|
+
|
|
145
|
+
Clipboard and log content never leaves your machine. License activation sends only a hashed device fingerprint to `api.saferelay.ai` — no log data, ever.
|
|
146
|
+
|
|
147
|
+
Full privacy policy: [saferelay.ai/privacy](https://saferelay.ai/privacy)
|
|
148
|
+
|
|
149
|
+
**Built by [LogicGrid AI, LLC](https://logicgrid.ai)** — support@logicgrid.ai
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# SafePaste Enterprise CLI
|
|
2
|
+
|
|
3
|
+
> Zero-trust DLP for Linux pipelines — 35+ threat patterns across 8+ countries.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/saferelay-enterprise/)
|
|
6
|
+
[](https://hub.docker.com/r/logicgridai/saferelay)
|
|
7
|
+
[](https://saferelay.ai)
|
|
8
|
+
|
|
9
|
+
SafePaste redacts sensitive data in your Linux pipelines before it reaches AI tools, log aggregators, or external services.
|
|
10
|
+
BEFORE AFTER (SafePaste)
|
|
11
|
+
───────────────────────────────── ─────────────────────────────────
|
|
12
|
+
OPENAI_API_KEY=sk-proj-abc...xyz
|
|
13
|
+
AWS_ACCESS_KEY_ID=AKIA1234ABCD
|
|
14
|
+
Authorization: Bearer eyJhb... Authorization: Bearer [BEARER_1]
|
|
15
|
+
Server: [IP_1] Server: [DEVSEC_1]
|
|
16
|
+
SSN: [US_SSN_1] SSN: [US_SSN_1]
|
|
17
|
+
|
|
18
|
+
## Get a License
|
|
19
|
+
|
|
20
|
+
| Tier | Price | Get it |
|
|
21
|
+
|------|-------|--------|
|
|
22
|
+
| Free | $0 | [Chrome Web Store](https://chromewebstore.google.com/detail/saferelay-enterprise/odeoilooelkodahbbdokbollgahdcaag) |
|
|
23
|
+
| Pro | $7.99/mo or $59/yr | [saferelay.ai/#pricing](https://saferelay.ai/#pricing) |
|
|
24
|
+
| SafeRelay Suite | $99 one-time | [saferelay.ai/saferelay](https://saferelay.ai/saferelay) |
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install saferelay-enterprise
|
|
30
|
+
|
|
31
|
+
# With Redis support (enterprise distributed vault)
|
|
32
|
+
pip install saferelay-enterprise[redis]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python 3.9+. No external dependencies for the base install.
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Free tier — mask IPs and API keys
|
|
41
|
+
cat /var/log/app.log | saferelay --mask
|
|
42
|
+
|
|
43
|
+
# Pro tier — full vault with unmask
|
|
44
|
+
docker logs my-app | saferelay --mask > clean.log
|
|
45
|
+
cat ai_response.txt | saferelay --unmask
|
|
46
|
+
|
|
47
|
+
# Activate Pro
|
|
48
|
+
saferelay --unlock "YOUR-LICENSE-KEY"
|
|
49
|
+
|
|
50
|
+
# Status
|
|
51
|
+
saferelay --status
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## What gets redacted
|
|
55
|
+
|
|
56
|
+
| Pattern | Free | Pro |
|
|
57
|
+
|---------|------|-----|
|
|
58
|
+
| IPv4 addresses | ✓ | ✓ |
|
|
59
|
+
| API keys (OpenAI, Anthropic, AWS, GitHub, Slack, Gemini) | ✓ | ✓ |
|
|
60
|
+
| Bitcoin / Ethereum addresses | ✓ | ✓ |
|
|
61
|
+
| PEM private keys | ✓ | ✓ |
|
|
62
|
+
| .env file values | ✓ | ✓ |
|
|
63
|
+
| MAC addresses | — | ✓ |
|
|
64
|
+
| Credit cards (Luhn-validated) | — | ✓ |
|
|
65
|
+
| US SSN | — | ✓ |
|
|
66
|
+
| EU IBAN | — | ✓ |
|
|
67
|
+
| UK NINO | — | ✓ |
|
|
68
|
+
| Nigeria NIN / Bank / Phone | — | ✓ |
|
|
69
|
+
| Canada SIN | — | ✓ |
|
|
70
|
+
| India Aadhaar / PAN | — | ✓ |
|
|
71
|
+
| South Africa ID | — | ✓ |
|
|
72
|
+
| Australia TFN | — | ✓ |
|
|
73
|
+
| Brazil CPF | — | ✓ |
|
|
74
|
+
| Singapore NRIC | — | ✓ |
|
|
75
|
+
| Germany Tax ID | — | ✓ |
|
|
76
|
+
| Seed phrases (12/24 word) | — | ✓ |
|
|
77
|
+
| ETH private keys | — | ✓ |
|
|
78
|
+
| Custom NDA keywords | — | ✓ |
|
|
79
|
+
|
|
80
|
+
## Docker
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
docker pull logicgridai/saferelay:latest
|
|
84
|
+
|
|
85
|
+
cat /var/log/app.log | docker run --rm -i logicgridai/saferelay --mask
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Kubernetes sidecar
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
containers:
|
|
92
|
+
- name: saferelay
|
|
93
|
+
image: logicgridai/saferelay:latest
|
|
94
|
+
env:
|
|
95
|
+
- name: SAFEPASTE_LICENSE_KEY
|
|
96
|
+
valueFrom:
|
|
97
|
+
secretKeyRef:
|
|
98
|
+
name: saferelay-secret
|
|
99
|
+
key: license-key
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Pricing
|
|
103
|
+
|
|
104
|
+
| Tier | Price | Features |
|
|
105
|
+
|------|-------|---------|
|
|
106
|
+
| Free | $0 | IP + API key redaction |
|
|
107
|
+
| Pro | $7.99/month or $59/year | 35+ patterns, full vault |
|
|
108
|
+
| SafeRelay Suite | $99 one-time | SafePaste + SpeakPaste + Boomerang Snip |
|
|
109
|
+
|
|
110
|
+
→ [Get a license at saferelay.ai](https://saferelay.ai)
|
|
111
|
+
|
|
112
|
+
## Privacy
|
|
113
|
+
|
|
114
|
+
Clipboard and log content never leaves your machine. License activation sends only a hashed device fingerprint to `api.saferelay.ai` — no log data, ever.
|
|
115
|
+
|
|
116
|
+
Full privacy policy: [saferelay.ai/privacy](https://saferelay.ai/privacy)
|
|
117
|
+
|
|
118
|
+
**Built by [LogicGrid AI, LLC](https://logicgrid.ai)** — support@logicgrid.ai
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
saferelay — SafeRelay Enterprise CLI
|
|
4
|
+
Zero-trust local DLP for AI-era workflows.
|
|
5
|
+
35+ threat patterns across 8+ countries.
|
|
6
|
+
"""
|
|
7
|
+
__version__ = "3.5.3"
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
import urllib.error
|
|
16
|
+
import urllib.parse
|
|
17
|
+
import urllib.request
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Callable
|
|
20
|
+
try:
|
|
21
|
+
import redis # type: ignore[import-untyped]
|
|
22
|
+
HAS_REDIS = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
HAS_REDIS = False
|
|
25
|
+
|
|
26
|
+
VAULT_PATH = Path.home() / ".safepaste" / "vault.json"
|
|
27
|
+
CONFIG_PATH = Path.home() / ".safepaste" / "config.json"
|
|
28
|
+
REDIS_ENV_VAR = "SAFEPASTE_REDIS_URL"
|
|
29
|
+
REDIS_HASH_KEY = "safepaste:vault"
|
|
30
|
+
|
|
31
|
+
# ── License validation (SafePaste Worker) ───────────────────────────────────
|
|
32
|
+
LICENSE_VERIFY_URL = "https://logicgrid-commerce-worker.admin-thequanthub.workers.dev/license/validate"
|
|
33
|
+
|
|
34
|
+
DEFAULT_CONFIG: dict = {
|
|
35
|
+
"is_licensed": False,
|
|
36
|
+
"license_key": "",
|
|
37
|
+
"license_tier": "",
|
|
38
|
+
"devsec_mode": True,
|
|
39
|
+
"fintech_shield": True,
|
|
40
|
+
"corporate_shield": True,
|
|
41
|
+
"web3_shield": True,
|
|
42
|
+
"civic_shield": True,
|
|
43
|
+
"custom_keywords": [],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# ── Helpers ─────────────────────────────────────────────────────────────────
|
|
47
|
+
def _load_config() -> dict:
|
|
48
|
+
if CONFIG_PATH.exists():
|
|
49
|
+
try:
|
|
50
|
+
return {**DEFAULT_CONFIG, **json.loads(CONFIG_PATH.read_text())}
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
return dict(DEFAULT_CONFIG)
|
|
54
|
+
|
|
55
|
+
def _save_config(cfg: dict) -> None:
|
|
56
|
+
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
CONFIG_PATH.write_text(json.dumps(cfg, indent=2))
|
|
58
|
+
|
|
59
|
+
def _get_device_id() -> str:
|
|
60
|
+
import platform, hashlib
|
|
61
|
+
raw = "|".join([platform.node(), platform.machine(), platform.system()])
|
|
62
|
+
return hashlib.sha256(raw.encode()).hexdigest()[:32]
|
|
63
|
+
|
|
64
|
+
# ── License verification ─────────────────────────────────────────────────────
|
|
65
|
+
def verify_license(license_key: str, timeout: float = 30.0) -> tuple[bool, str]:
|
|
66
|
+
"""POST to SafePaste worker; returns (valid, tier)."""
|
|
67
|
+
key = license_key.strip()
|
|
68
|
+
if not key:
|
|
69
|
+
return False, ""
|
|
70
|
+
device_id = _get_device_id()
|
|
71
|
+
payload = json.dumps({"license_key": key, "device_id": device_id}).encode()
|
|
72
|
+
req = urllib.request.Request(
|
|
73
|
+
LICENSE_VERIFY_URL,
|
|
74
|
+
data=payload,
|
|
75
|
+
headers={"Content-Type": "application/json", "User-Agent": f"safepaste-cli/{__version__}"},
|
|
76
|
+
method="POST",
|
|
77
|
+
)
|
|
78
|
+
try:
|
|
79
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
80
|
+
data = json.loads(resp.read().decode())
|
|
81
|
+
if data.get("success") is True or data.get("valid") is True:
|
|
82
|
+
tier = str(data.get("tier", "pro")).lower().strip()
|
|
83
|
+
return True, tier
|
|
84
|
+
return False, ""
|
|
85
|
+
except Exception as exc:
|
|
86
|
+
print(f"safepaste: license check failed — {exc}", file=sys.stderr)
|
|
87
|
+
return False, ""
|
|
88
|
+
|
|
89
|
+
# ── Vault ────────────────────────────────────────────────────────────────────
|
|
90
|
+
def _get_redis() -> "redis.Redis | None":
|
|
91
|
+
url = os.environ.get(REDIS_ENV_VAR, "")
|
|
92
|
+
if not url or not HAS_REDIS:
|
|
93
|
+
return None
|
|
94
|
+
try:
|
|
95
|
+
r = redis.Redis.from_url(url, socket_timeout=2)
|
|
96
|
+
r.ping()
|
|
97
|
+
return r
|
|
98
|
+
except Exception:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
def _load_vault() -> dict:
|
|
102
|
+
r = _get_redis()
|
|
103
|
+
if r:
|
|
104
|
+
raw = r.hgetall(REDIS_HASH_KEY)
|
|
105
|
+
return {k.decode(): v.decode() for k, v in raw.items()} if raw else {}
|
|
106
|
+
if VAULT_PATH.exists():
|
|
107
|
+
try:
|
|
108
|
+
return json.loads(VAULT_PATH.read_text())
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
return {}
|
|
112
|
+
|
|
113
|
+
def _save_vault(vault: dict) -> None:
|
|
114
|
+
r = _get_redis()
|
|
115
|
+
if r:
|
|
116
|
+
if vault:
|
|
117
|
+
r.hset(REDIS_HASH_KEY, mapping=vault)
|
|
118
|
+
else:
|
|
119
|
+
r.delete(REDIS_HASH_KEY)
|
|
120
|
+
return
|
|
121
|
+
VAULT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
+
VAULT_PATH.write_text(json.dumps(vault, indent=2))
|
|
123
|
+
|
|
124
|
+
# ── Patterns (41 total) ──────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
# DevSec Shield
|
|
127
|
+
RE_IPV4 = re.compile(r"\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b")
|
|
128
|
+
RE_API_KEY = re.compile(r"\b(?:sk|pk|rk|key|token|secret|api[-_]?key)[-_]?[a-zA-Z0-9]{16,64}\b", re.IGNORECASE)
|
|
129
|
+
RE_AWS_KEY = re.compile(r"\bAKIA[0-9A-Z]{16}\b")
|
|
130
|
+
RE_GITHUB_TOKEN = re.compile(r"\bgh[pousr]_[A-Za-z0-9_]{36,255}\b")
|
|
131
|
+
RE_SLACK_TOKEN = re.compile(r"\bxox[bpaso]-[0-9A-Za-z\-]{10,72}\b")
|
|
132
|
+
RE_OPENAI_KEY = re.compile(r"\bsk-[A-Za-z0-9]{32,64}\b")
|
|
133
|
+
RE_BEARER = re.compile(r"(?i)\bBearer\s+[A-Za-z0-9\-._~+/]{20,512}\b")
|
|
134
|
+
RE_MAC_ADDR = re.compile(r"\b(?:[0-9A-Fa-f]{2}[:\-]){5}[0-9A-Fa-f]{2}\b")
|
|
135
|
+
RE_ENV_VALUE = re.compile(r"(?m)^[A-Z][A-Z0-9_]{2,}=\S{8,}$")
|
|
136
|
+
RE_INTERNAL_URL = re.compile(r"\bhttps?://(?:localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+)[^\s]*\b")
|
|
137
|
+
|
|
138
|
+
# FinTech Shield
|
|
139
|
+
RE_US_SSN = re.compile(r"\b(?!000|666|9\d{2})\d{3}[- ]?(?!00)\d{2}[- ]?(?!0000)\d{4}\b")
|
|
140
|
+
RE_EU_IBAN = re.compile(r"\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b")
|
|
141
|
+
RE_SWIFT_BIC = re.compile(r"\b[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}(?:[A-Z0-9]{3})?\b")
|
|
142
|
+
RE_CREDIT_CARD = re.compile(r"\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12})\b")
|
|
143
|
+
RE_UK_NINO = re.compile(r"\b[A-CEGHJ-PR-TW-Z]{2}\d{6}[A-D]\b", re.IGNORECASE)
|
|
144
|
+
RE_CA_SIN = re.compile(r"\b\d{3}[- ]?\d{3}[- ]?\d{3}\b")
|
|
145
|
+
RE_IN_AADHAAR = re.compile(r"\b[2-9]\d{3}[- ]?\d{4}[- ]?\d{4}\b")
|
|
146
|
+
RE_IN_PAN = re.compile(r"\b[A-Z]{5}\d{4}[A-Z]\b")
|
|
147
|
+
RE_NG_NIN = re.compile(r"\b\d{11}\b")
|
|
148
|
+
RE_ZA_ID = re.compile(r"\b\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{4}[01]\d{2}\b")
|
|
149
|
+
RE_AU_TFN = re.compile(r"\b\d{3}[- ]?\d{3}[- ]?\d{3}\b")
|
|
150
|
+
RE_BR_CPF = re.compile(r"\b\d{3}\.?\d{3}\.?\d{3}-?\d{2}\b")
|
|
151
|
+
RE_SG_NRIC = re.compile(r"\b[STFG]\d{7}[A-Z]\b", re.IGNORECASE)
|
|
152
|
+
RE_PASSPORT = re.compile(r"\b[A-Z]{1,2}[0-9]{6,9}\b")
|
|
153
|
+
|
|
154
|
+
# Corporate Identity Shield
|
|
155
|
+
RE_DUNS = re.compile(r"\b\d{2}-\d{3}-\d{4}\b|\b\d{9}\b")
|
|
156
|
+
RE_EIN = re.compile(r"\b\d{2}-\d{7}\b")
|
|
157
|
+
RE_VAT_EU = re.compile(r"\b(ATU\d{8}|BE0\d{9}|DE\d{9}|FR[A-Z0-9]{2}\d{9}|GB\d{9}|IT\d{11}|NL\d{9}B\d{2}|ES[A-Z0-9]\d{7}[A-Z0-9]|PL\d{10}|SE\d{12})\b")
|
|
158
|
+
RE_VENDOR_ID = re.compile(r"(?i)\b(?:VEND|PO|TENDER|CONTRACT|RFQ|INV)-[A-Z0-9]{4,20}\b")
|
|
159
|
+
RE_INVOICE = re.compile(r"(?i)\b(?:Invoice|INV|PO)[- ]?(?:No|#|Number)?[- ]?([A-Z]{0,4}\d{4,12})\b")
|
|
160
|
+
|
|
161
|
+
# Web3 Shield
|
|
162
|
+
RE_ETH_WALLET = re.compile(r"\b0x[a-fA-F0-9]{40}\b")
|
|
163
|
+
RE_BTC_WIF = re.compile(r"\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\b")
|
|
164
|
+
RE_SOL_ADDR = re.compile(r"\b[1-9A-HJ-NP-Za-km-z]{32,44}\b")
|
|
165
|
+
RE_SEED_PHRASE = re.compile(r"\b(?:[a-z]{3,8}\s+){11}[a-z]{3,8}\b|\b(?:[a-z]{3,8}\s+){23}[a-z]{3,8}\b")
|
|
166
|
+
RE_GEMINI_KEY = re.compile(r"\baccount-[A-Za-z0-9]{20,40}\b")
|
|
167
|
+
|
|
168
|
+
# Mission & Civic Shield
|
|
169
|
+
RE_GPS_COORDS = re.compile(r"\b-?[0-9]{1,3}\.[0-9]{4,}\s*,\s*-?[0-9]{1,3}\.[0-9]{4,}\b")
|
|
170
|
+
RE_UNHCR_ID = re.compile(r"\b[A-Z]{3}-\d{2}-\d{6,8}C?\d?\b")
|
|
171
|
+
RE_BENEFICIARY = re.compile(r"(?i)\b(?:BEN|DONOR|CASE|HH|PROG)-[A-Z0-9]{4,16}\b")
|
|
172
|
+
|
|
173
|
+
# Context-aware validators
|
|
174
|
+
_DUNS_CTX = re.compile(r"(?i)(DUNS|D-U-N-S|D&B|Dun\s*&\s*Bradstreet|business\s*credit)")
|
|
175
|
+
_EIN_CTX = re.compile(r"(?i)(EIN|Tax\s*ID|TIN|employer\s*identification|federal\s*tax)")
|
|
176
|
+
|
|
177
|
+
def _duns_validator(m: re.Match, text: str) -> bool:
|
|
178
|
+
start = max(0, m.start() - 80)
|
|
179
|
+
end = min(len(text), m.end() + 80)
|
|
180
|
+
return bool(_DUNS_CTX.search(text[start:end]))
|
|
181
|
+
|
|
182
|
+
def _ein_validator(m: re.Match, text: str) -> bool:
|
|
183
|
+
start = max(0, m.start() - 50)
|
|
184
|
+
end = min(len(text), m.end() + 50)
|
|
185
|
+
return bool(_EIN_CTX.search(text[start:end]))
|
|
186
|
+
|
|
187
|
+
# ── Threat table ─────────────────────────────────────────────────────────────
|
|
188
|
+
# (priority, label, pattern, shield, validator)
|
|
189
|
+
# shield: "free" | "devsec" | "fintech" | "corporate" | "web3" | "civic"
|
|
190
|
+
ThreatRow = tuple[int, str, re.Pattern, str, Callable]
|
|
191
|
+
|
|
192
|
+
def _build_threats(cfg: dict, is_pro: bool) -> list[ThreatRow]:
|
|
193
|
+
devsec = is_pro and cfg.get("devsec_mode", True)
|
|
194
|
+
fintech = is_pro and cfg.get("fintech_shield", True)
|
|
195
|
+
corporate = is_pro and cfg.get("corporate_shield", True)
|
|
196
|
+
web3 = is_pro and cfg.get("web3_shield", True)
|
|
197
|
+
civic = is_pro and cfg.get("civic_shield", True)
|
|
198
|
+
|
|
199
|
+
rows: list[ThreatRow] = [
|
|
200
|
+
# Free tier
|
|
201
|
+
(10, "IPV4", RE_IPV4, "free", lambda m, t: True),
|
|
202
|
+
(11, "DEVSEC", RE_API_KEY, "free", lambda m, t: True),
|
|
203
|
+
(12, "DEVSEC", RE_AWS_KEY, "free", lambda m, t: True),
|
|
204
|
+
(13, "DEVSEC", RE_OPENAI_KEY, "free", lambda m, t: True),
|
|
205
|
+
]
|
|
206
|
+
if devsec:
|
|
207
|
+
rows += [
|
|
208
|
+
(20, "DEVSEC", RE_GITHUB_TOKEN, "devsec", lambda m, t: True),
|
|
209
|
+
(21, "DEVSEC", RE_SLACK_TOKEN, "devsec", lambda m, t: True),
|
|
210
|
+
(22, "DEVSEC", RE_BEARER, "devsec", lambda m, t: True),
|
|
211
|
+
(23, "MAC_ADDR", RE_MAC_ADDR, "devsec", lambda m, t: True),
|
|
212
|
+
(24, "ENV_VALUE", RE_ENV_VALUE, "devsec", lambda m, t: True),
|
|
213
|
+
(25, "INT_URL", RE_INTERNAL_URL, "devsec", lambda m, t: True),
|
|
214
|
+
]
|
|
215
|
+
if fintech:
|
|
216
|
+
rows += [
|
|
217
|
+
(30, "US_SSN", RE_US_SSN, "fintech", lambda m, t: True),
|
|
218
|
+
(31, "EU_IBAN", RE_EU_IBAN, "fintech", lambda m, t: True),
|
|
219
|
+
(32, "SWIFT_BIC", RE_SWIFT_BIC, "fintech", lambda m, t: True),
|
|
220
|
+
(33, "CC", RE_CREDIT_CARD,"fintech", lambda m, t: True),
|
|
221
|
+
(34, "UK_NINO", RE_UK_NINO, "fintech", lambda m, t: True),
|
|
222
|
+
(35, "CA_SIN", RE_CA_SIN, "fintech", lambda m, t: True),
|
|
223
|
+
(36, "IN_AADHAAR",RE_IN_AADHAAR, "fintech", lambda m, t: True),
|
|
224
|
+
(37, "IN_PAN", RE_IN_PAN, "fintech", lambda m, t: True),
|
|
225
|
+
(38, "NG_NIN", RE_NG_NIN, "fintech", lambda m, t: True),
|
|
226
|
+
(39, "ZA_ID", RE_ZA_ID, "fintech", lambda m, t: True),
|
|
227
|
+
(40, "AU_TFN", RE_AU_TFN, "fintech", lambda m, t: True),
|
|
228
|
+
(41, "BR_CPF", RE_BR_CPF, "fintech", lambda m, t: True),
|
|
229
|
+
(42, "SG_NRIC", RE_SG_NRIC, "fintech", lambda m, t: True),
|
|
230
|
+
(43, "PASSPORT", RE_PASSPORT, "fintech", lambda m, t: True),
|
|
231
|
+
]
|
|
232
|
+
if corporate:
|
|
233
|
+
rows += [
|
|
234
|
+
(50, "DUNS", RE_DUNS, "corporate", _duns_validator),
|
|
235
|
+
(51, "EIN", RE_EIN, "corporate", _ein_validator),
|
|
236
|
+
(52, "VAT_EU", RE_VAT_EU, "corporate", lambda m, t: True),
|
|
237
|
+
(53, "VENDOR_ID", RE_VENDOR_ID, "corporate", lambda m, t: True),
|
|
238
|
+
(54, "INVOICE", RE_INVOICE, "corporate", lambda m, t: True),
|
|
239
|
+
]
|
|
240
|
+
if web3:
|
|
241
|
+
rows += [
|
|
242
|
+
(60, "ETH_WALLET", RE_ETH_WALLET, "web3", lambda m, t: True),
|
|
243
|
+
(61, "BTC_WIF", RE_BTC_WIF, "web3", lambda m, t: True),
|
|
244
|
+
(62, "SOL_ADDR", RE_SOL_ADDR, "web3", lambda m, t: True),
|
|
245
|
+
(63, "SEED_PHRASE", RE_SEED_PHRASE, "web3", lambda m, t: True),
|
|
246
|
+
(64, "GEMINI_KEY", RE_GEMINI_KEY, "web3", lambda m, t: True),
|
|
247
|
+
]
|
|
248
|
+
if civic:
|
|
249
|
+
rows += [
|
|
250
|
+
(70, "GPS_COORDS", RE_GPS_COORDS, "civic", lambda m, t: True),
|
|
251
|
+
(71, "UNHCR_ID", RE_UNHCR_ID, "civic", lambda m, t: True),
|
|
252
|
+
(72, "BENEFICIARY", RE_BENEFICIARY, "civic", lambda m, t: True),
|
|
253
|
+
]
|
|
254
|
+
return sorted(rows, key=lambda r: r[0])
|
|
255
|
+
|
|
256
|
+
# ── Redaction engine ─────────────────────────────────────────────────────────
|
|
257
|
+
def redact(text: str, threats: list[ThreatRow], vault: dict,
|
|
258
|
+
custom_keywords: list[str]) -> tuple[str, dict]:
|
|
259
|
+
counters: dict[str, int] = {}
|
|
260
|
+
result = text
|
|
261
|
+
|
|
262
|
+
# Custom NDA keywords first
|
|
263
|
+
for kw in custom_keywords:
|
|
264
|
+
escaped = re.escape(kw)
|
|
265
|
+
pat = re.compile(escaped, re.IGNORECASE)
|
|
266
|
+
for m in reversed(list(pat.finditer(result))):
|
|
267
|
+
counters["NDA"] = counters.get("NDA", 0) + 1
|
|
268
|
+
ph = f"[NDA_{counters['NDA']}]"
|
|
269
|
+
vault[ph] = m.group(0)
|
|
270
|
+
result = result[:m.start()] + ph + result[m.end():]
|
|
271
|
+
|
|
272
|
+
# Shield patterns
|
|
273
|
+
matches: list[tuple[int, int, str, str]] = []
|
|
274
|
+
for _, label, pattern, shield, validator in threats:
|
|
275
|
+
for m in pattern.finditer(result):
|
|
276
|
+
if validator(m, result):
|
|
277
|
+
matches.append((m.start(), m.end(), label, m.group(0)))
|
|
278
|
+
|
|
279
|
+
# Sort by position descending to replace without offset issues
|
|
280
|
+
matches.sort(key=lambda x: x[0], reverse=True)
|
|
281
|
+
seen_spans: set[tuple[int,int]] = set()
|
|
282
|
+
for start, end, label, value in matches:
|
|
283
|
+
if any(s <= start < e or s < end <= e for s, e in seen_spans):
|
|
284
|
+
continue
|
|
285
|
+
seen_spans.add((start, end))
|
|
286
|
+
counters[label] = counters.get(label, 0) + 1
|
|
287
|
+
ph = f"[{label}_{counters[label]}]"
|
|
288
|
+
vault[ph] = value
|
|
289
|
+
result = result[:start] + ph + result[end:]
|
|
290
|
+
|
|
291
|
+
return result, vault
|
|
292
|
+
|
|
293
|
+
# ── Main ──────────────────────────────────────────────────────────────────────
|
|
294
|
+
def main() -> None:
|
|
295
|
+
cfg = _load_config()
|
|
296
|
+
is_pro = bool(cfg.get("is_licensed"))
|
|
297
|
+
|
|
298
|
+
p = argparse.ArgumentParser(
|
|
299
|
+
prog="safepaste",
|
|
300
|
+
description=(
|
|
301
|
+
f"SafePaste Enterprise CLI v{__version__} — "
|
|
302
|
+
"41 global PII patterns across 12+ countries. "
|
|
303
|
+
"Zero-trust DLP for AI-assisted workflows. "
|
|
304
|
+
"Free: IP + API key redaction. Pro: all Shield Packs + vault."
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
p.add_argument("input", nargs="?", type=argparse.FileType("r"), default=sys.stdin,
|
|
308
|
+
help="Input file (default: stdin).")
|
|
309
|
+
p.add_argument("--unmask", metavar="PLACEHOLDER",
|
|
310
|
+
help="Reveal a vaulted placeholder, e.g. [US_SSN_1].")
|
|
311
|
+
p.add_argument("--unlock", metavar="LICENSE_KEY",
|
|
312
|
+
help="Verify license key and enable Pro Shield Packs.")
|
|
313
|
+
p.add_argument("--status", action="store_true",
|
|
314
|
+
help="Show current license tier and active shields.")
|
|
315
|
+
p.add_argument("--clear-vault", action="store_true",
|
|
316
|
+
help="Wipe all vaulted values.")
|
|
317
|
+
p.add_argument("--pro", action="store_true",
|
|
318
|
+
help="Force Pro-tier for this run (no license call).")
|
|
319
|
+
p.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
320
|
+
|
|
321
|
+
# Shield toggles
|
|
322
|
+
p.add_argument("--no-devsec", action="store_true", help="Disable DevSec Shield.")
|
|
323
|
+
p.add_argument("--no-fintech", action="store_true", help="Disable FinTech Shield.")
|
|
324
|
+
p.add_argument("--no-corporate", action="store_true", help="Disable Corporate Identity Shield.")
|
|
325
|
+
p.add_argument("--no-web3", action="store_true", help="Disable Web3 Shield.")
|
|
326
|
+
p.add_argument("--no-civic", action="store_true", help="Disable Mission & Civic Shield.")
|
|
327
|
+
p.add_argument("--mask", action="store_true", help="Alias for default redact mode.")
|
|
328
|
+
|
|
329
|
+
args = p.parse_args()
|
|
330
|
+
|
|
331
|
+
# ── --unlock ──────────────────────────────────────────────────────────────
|
|
332
|
+
if args.unlock:
|
|
333
|
+
key = args.unlock.strip() or os.environ.get("SAFEPASTE_LICENSE_KEY", "").strip()
|
|
334
|
+
if not key:
|
|
335
|
+
print("safepaste: pass LICENSE_KEY or set SAFEPASTE_LICENSE_KEY", file=sys.stderr)
|
|
336
|
+
sys.exit(1)
|
|
337
|
+
print(f"safepaste: verifying license…", file=sys.stderr)
|
|
338
|
+
ok, tier = verify_license(key)
|
|
339
|
+
if ok:
|
|
340
|
+
cfg["is_licensed"] = True
|
|
341
|
+
cfg["license_key"] = key
|
|
342
|
+
cfg["license_tier"] = tier
|
|
343
|
+
_save_config(cfg)
|
|
344
|
+
print(f"safepaste: ✓ License verified — tier={tier}. Pro Shield Packs enabled.")
|
|
345
|
+
else:
|
|
346
|
+
print("safepaste: ✗ Invalid license key. Check your key and try again.", file=sys.stderr)
|
|
347
|
+
sys.exit(1)
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
# ── --status ──────────────────────────────────────────────────────────────
|
|
351
|
+
if args.status:
|
|
352
|
+
on, off = "✓ on", "✗ off"
|
|
353
|
+
tier = cfg.get("license_tier", "free") if is_pro else "free"
|
|
354
|
+
print(f"safepaste v{__version__}")
|
|
355
|
+
print(f" License : {'Pro — ' + tier if is_pro else 'Free'}")
|
|
356
|
+
print(f" DevSec : {on if cfg.get('devsec_mode') else off}")
|
|
357
|
+
print(f" FinTech : {on if is_pro and cfg.get('fintech_shield') else off}")
|
|
358
|
+
print(f" Corporate : {on if is_pro and cfg.get('corporate_shield') else off}")
|
|
359
|
+
print(f" Web3 : {on if is_pro and cfg.get('web3_shield') else off}")
|
|
360
|
+
print(f" Civic : {on if is_pro and cfg.get('civic_shield') else off}")
|
|
361
|
+
kws = cfg.get("custom_keywords", [])
|
|
362
|
+
print(f" NDA terms : {len(kws)} ({', '.join(kws[:3])}{'…' if len(kws) > 3 else ''})")
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
# ── --clear-vault ─────────────────────────────────────────────────────────
|
|
366
|
+
if args.clear_vault:
|
|
367
|
+
_save_vault({})
|
|
368
|
+
print("safepaste: vault cleared.")
|
|
369
|
+
return
|
|
370
|
+
|
|
371
|
+
# ── --unmask ──────────────────────────────────────────────────────────────
|
|
372
|
+
if args.unmask:
|
|
373
|
+
vault = _load_vault()
|
|
374
|
+
ph = args.unmask.strip()
|
|
375
|
+
if not ph.startswith("["):
|
|
376
|
+
ph = f"[{ph}]"
|
|
377
|
+
val = vault.get(ph)
|
|
378
|
+
if val:
|
|
379
|
+
print(val)
|
|
380
|
+
else:
|
|
381
|
+
print(f"safepaste: {ph} not found in vault.", file=sys.stderr)
|
|
382
|
+
sys.exit(1)
|
|
383
|
+
return
|
|
384
|
+
|
|
385
|
+
# ── Apply CLI overrides to config ─────────────────────────────────────────
|
|
386
|
+
if args.pro:
|
|
387
|
+
is_pro = True
|
|
388
|
+
if args.no_devsec:
|
|
389
|
+
cfg["devsec_mode"] = False
|
|
390
|
+
if args.no_fintech:
|
|
391
|
+
cfg["fintech_shield"] = False
|
|
392
|
+
if args.no_corporate:
|
|
393
|
+
cfg["corporate_shield"] = False
|
|
394
|
+
if args.no_web3:
|
|
395
|
+
cfg["web3_shield"] = False
|
|
396
|
+
if args.no_civic:
|
|
397
|
+
cfg["civic_shield"] = False
|
|
398
|
+
|
|
399
|
+
# ── Read input ────────────────────────────────────────────────────────────
|
|
400
|
+
try:
|
|
401
|
+
text = args.input.read()
|
|
402
|
+
except KeyboardInterrupt:
|
|
403
|
+
sys.exit(0)
|
|
404
|
+
|
|
405
|
+
# ── Build threats & redact ────────────────────────────────────────────────
|
|
406
|
+
threats = _build_threats(cfg, is_pro)
|
|
407
|
+
vault = _load_vault() if is_pro else {}
|
|
408
|
+
custom = cfg.get("custom_keywords", []) if is_pro else []
|
|
409
|
+
|
|
410
|
+
redacted, vault = redact(text, threats, vault, custom)
|
|
411
|
+
|
|
412
|
+
if is_pro:
|
|
413
|
+
_save_vault(vault)
|
|
414
|
+
|
|
415
|
+
print(redacted, end="")
|
|
416
|
+
|
|
417
|
+
if __name__ == "__main__":
|
|
418
|
+
main()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SafeRelay Python SDK — SafeRelayClient
|
|
3
|
+
pip install saferelay-enterprise
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
from saferelay import SafeRelayClient
|
|
7
|
+
client = SafeRelayClient(mode="zero-trust")
|
|
8
|
+
safe = client.redact("token AKIAIOSFODNN7EXAMPLE at 10.1.2.3")
|
|
9
|
+
print(safe) # "token 🔒[AWS_KEY_1] at 🔒[INTERNAL_IP_1]"
|
|
10
|
+
print(client.last_detections) # {"AWS_KEY": 1, "INTERNAL_IP": 1}
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
import re
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# Pattern definitions — mirrors patterns.js and boomerang_snip.py
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
_PATTERNS: list[tuple[str, re.Pattern, callable | None]] = [
|
|
22
|
+
# ── Network ────────────────────────────────────────────────────────────
|
|
23
|
+
("INTERNAL_IP", re.compile(
|
|
24
|
+
r'\b(?!127\.|255\.|0\.0\.0\.0)(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b'
|
|
25
|
+
), lambda m: all(0 <= int(x) <= 255 for x in m.split('.'))),
|
|
26
|
+
|
|
27
|
+
# ── AWS ────────────────────────────────────────────────────────────────
|
|
28
|
+
("AWS_KEY", re.compile(r'\bAKIA[0-9A-Z]{16}\b'), None),
|
|
29
|
+
("AWS_KEY", re.compile(r'\bASIA[0-9A-Z]{16}\b'), None),
|
|
30
|
+
("AWS_SECRET", re.compile(
|
|
31
|
+
r'(?:AWS_SECRET_ACCESS_KEY|aws_secret_access_key|SecretAccessKey)'
|
|
32
|
+
r'\s*[=:"\'\s]+["\']?([A-Za-z0-9/+=]{40})["\']?'
|
|
33
|
+
), None),
|
|
34
|
+
("AWS_SECRET", re.compile(
|
|
35
|
+
r'(?<![A-Za-z0-9/+])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])'
|
|
36
|
+
), lambda m: not re.fullmatch(r'[0-9a-fA-F]{40}', m)),
|
|
37
|
+
|
|
38
|
+
# ── API Keys ───────────────────────────────────────────────────────────
|
|
39
|
+
("OPENAI_KEY", re.compile(r'\bsk-proj-[a-zA-Z0-9_-]{20,}\b', re.I), None),
|
|
40
|
+
("OPENAI_KEY", re.compile(r'\bsk-(?!proj-|ant-)[a-zA-Z0-9_-]{16,}\b', re.I), None),
|
|
41
|
+
("ANTHROPIC_KEY", re.compile(r'\bsk-ant-[a-zA-Z0-9_-]{10,}\b', re.I), None),
|
|
42
|
+
("GITHUB_TOKEN", re.compile(r'\bghp_[a-zA-Z0-9]{20,}\b'), None),
|
|
43
|
+
("GITHUB_TOKEN", re.compile(r'\bgithub_pat_[a-zA-Z0-9_]{20,}\b'), None),
|
|
44
|
+
("GITHUB_TOKEN", re.compile(r'\bgh[osux]_[a-zA-Z0-9]{20,}\b'), None),
|
|
45
|
+
("SLACK_TOKEN", re.compile(r'\bxox[bap]-[a-zA-Z0-9-]{10,}\b', re.I), None),
|
|
46
|
+
("SLACK_WEBHOOK", re.compile(
|
|
47
|
+
r'https://hooks\.slack\.com/services/[A-Z0-9]+/[A-Z0-9]+/[A-Za-z0-9]+'
|
|
48
|
+
), None),
|
|
49
|
+
("STRIPE_KEY", re.compile(r'\bsk_(?:live|test)_[a-zA-Z0-9]{24,}\b'), None),
|
|
50
|
+
("DOCKER_TOKEN", re.compile(r'\bdckr_pat_[a-zA-Z0-9_-]{20,}\b'), None),
|
|
51
|
+
("NPM_TOKEN", re.compile(r'\bnpm_[a-zA-Z0-9]{36,}\b'), None),
|
|
52
|
+
("TWILIO_KEY", re.compile(r'\bSK[a-f0-9]{32}\b'), None),
|
|
53
|
+
("SENDGRID_KEY", re.compile(r'\bSG\.[a-zA-Z0-9_-]{22,}\.[a-zA-Z0-9_-]{43,}\b'), None),
|
|
54
|
+
("GEMINI_KEY", re.compile(r'\bAIza[0-9A-Za-z\-_]{35}\b'), None),
|
|
55
|
+
|
|
56
|
+
# ── Google OAuth ──────────────────────────────────────────────────────
|
|
57
|
+
("GOOGLE_OAUTH", re.compile(r'\b\d{6,}-[a-z0-9]+\.apps\.googleusercontent\.com\b', re.I), None),
|
|
58
|
+
("GOOGLE_OAUTH", re.compile(r'\bGOCSPX-[A-Za-z0-9_-]{20,}\b'), None),
|
|
59
|
+
("GOOGLE_OAUTH", re.compile(r'\b1//[A-Za-z0-9_\-]{10,}\b'), None),
|
|
60
|
+
|
|
61
|
+
# ── Crypto ─────────────────────────────────────────────────────────────
|
|
62
|
+
("BTC_ADDRESS", re.compile(r'\b1[a-km-zA-HJ-NP-Z1-9]{25,34}\b'), None),
|
|
63
|
+
("BTC_ADDRESS", re.compile(r'\b3[a-km-zA-HJ-NP-Z1-9]{25,34}\b'), None),
|
|
64
|
+
("BTC_ADDRESS", re.compile(r'\bbc1[a-z0-9]{6,87}\b', re.I), None),
|
|
65
|
+
("ETH_ADDRESS", re.compile(r'\b0x[a-fA-F0-9]{40}\b'), None),
|
|
66
|
+
("SEED_PHRASE", re.compile(r'\b([a-z]+\s){11}[a-z]+\b'), lambda m: len(m.strip().split()) == 12),
|
|
67
|
+
("SEED_PHRASE", re.compile(r'\b([a-z]+\s){23}[a-z]+\b'), lambda m: len(m.strip().split()) == 24),
|
|
68
|
+
("PEM_KEY", re.compile(r'-----BEGIN\s(?:RSA\s|EC\s|OPENSSH\s)?PRIVATE KEY-----'), None),
|
|
69
|
+
|
|
70
|
+
# ── PII ────────────────────────────────────────────────────────────────
|
|
71
|
+
("US_SSN", re.compile(r'\b(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b'), None),
|
|
72
|
+
("CREDIT_CARD", re.compile(r'\b(?:\d{4}[-\s]?){3}\d{3,4}\b'), None),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SafeRelayClient:
|
|
77
|
+
"""
|
|
78
|
+
Local, zero-trust DLP client for Python.
|
|
79
|
+
|
|
80
|
+
Modes:
|
|
81
|
+
zero-trust — redacts in place, nothing leaves the process (default)
|
|
82
|
+
audit — redacts and records detections for logging/reporting
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
client = SafeRelayClient(mode="zero-trust")
|
|
86
|
+
safe = client.redact(raw_log)
|
|
87
|
+
print(client.last_detections)
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(self, mode: Literal["zero-trust", "audit"] = "zero-trust"):
|
|
91
|
+
self.mode = mode
|
|
92
|
+
self._detections: dict[str, int] = {}
|
|
93
|
+
|
|
94
|
+
def redact(self, text: str, emoji: bool = True) -> str:
|
|
95
|
+
"""Redact all detected secrets and PII from *text*.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
text: The raw string to scan.
|
|
99
|
+
emoji: If True (default) prefix tokens with 🔒. Set False for
|
|
100
|
+
plain tokens like [AWS_KEY_1] instead of 🔒[AWS_KEY_1].
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Sanitised string with secrets replaced by labelled tokens.
|
|
104
|
+
"""
|
|
105
|
+
if not text:
|
|
106
|
+
return text
|
|
107
|
+
|
|
108
|
+
result = text
|
|
109
|
+
self._detections = {}
|
|
110
|
+
prefix = "🔒" if emoji else ""
|
|
111
|
+
|
|
112
|
+
for label, pattern, validator in _PATTERNS:
|
|
113
|
+
counter = self._detections.get(label, 0)
|
|
114
|
+
|
|
115
|
+
def _replace(m, l=label, v=validator, c=[counter]):
|
|
116
|
+
match_str = m.group(0)
|
|
117
|
+
if v and not v(match_str):
|
|
118
|
+
return match_str
|
|
119
|
+
c[0] += 1
|
|
120
|
+
self._detections[l] = c[0]
|
|
121
|
+
return f"{prefix}[{l}_{c[0]}]"
|
|
122
|
+
|
|
123
|
+
result = pattern.sub(_replace, result)
|
|
124
|
+
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
def is_safe(self, text: str) -> bool:
|
|
128
|
+
"""Return True if no secrets are detected in *text*."""
|
|
129
|
+
probe = self.redact(text, emoji=False)
|
|
130
|
+
return probe == text
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def last_detections(self) -> dict[str, int]:
|
|
134
|
+
"""Dict of {PATTERN_TYPE: count} from the last redact() call."""
|
|
135
|
+
return dict(self._detections)
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def last_detection_count(self) -> int:
|
|
139
|
+
"""Total number of secrets found in the last redact() call."""
|
|
140
|
+
return sum(self._detections.values())
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: saferelay
|
|
3
|
+
Version: 3.5.3
|
|
4
|
+
Summary: Zero-trust local DLP for AI-era workflows — 40+ threat patterns across 8+ countries
|
|
5
|
+
Home-page: https://saferelay.ai
|
|
6
|
+
Author: LogicGrid AI, LLC
|
|
7
|
+
Author-email: support@logicgrid.ai
|
|
8
|
+
License: Proprietary
|
|
9
|
+
Keywords: ai-safety api-keys cli dlp pii redaction security zero-trust saferelay
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Provides-Extra: redis
|
|
19
|
+
Requires-Dist: redis>=4.0; extra == "redis"
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: description-content-type
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: keywords
|
|
27
|
+
Dynamic: license
|
|
28
|
+
Dynamic: provides-extra
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
# SafePaste Enterprise CLI
|
|
33
|
+
|
|
34
|
+
> Zero-trust DLP for Linux pipelines — 35+ threat patterns across 8+ countries.
|
|
35
|
+
|
|
36
|
+
[](https://pypi.org/project/saferelay-enterprise/)
|
|
37
|
+
[](https://hub.docker.com/r/logicgridai/saferelay)
|
|
38
|
+
[](https://saferelay.ai)
|
|
39
|
+
|
|
40
|
+
SafePaste redacts sensitive data in your Linux pipelines before it reaches AI tools, log aggregators, or external services.
|
|
41
|
+
BEFORE AFTER (SafePaste)
|
|
42
|
+
───────────────────────────────── ─────────────────────────────────
|
|
43
|
+
OPENAI_API_KEY=sk-proj-abc...xyz
|
|
44
|
+
AWS_ACCESS_KEY_ID=AKIA1234ABCD
|
|
45
|
+
Authorization: Bearer eyJhb... Authorization: Bearer [BEARER_1]
|
|
46
|
+
Server: [IP_1] Server: [DEVSEC_1]
|
|
47
|
+
SSN: [US_SSN_1] SSN: [US_SSN_1]
|
|
48
|
+
|
|
49
|
+
## Get a License
|
|
50
|
+
|
|
51
|
+
| Tier | Price | Get it |
|
|
52
|
+
|------|-------|--------|
|
|
53
|
+
| Free | $0 | [Chrome Web Store](https://chromewebstore.google.com/detail/saferelay-enterprise/odeoilooelkodahbbdokbollgahdcaag) |
|
|
54
|
+
| Pro | $7.99/mo or $59/yr | [saferelay.ai/#pricing](https://saferelay.ai/#pricing) |
|
|
55
|
+
| SafeRelay Suite | $99 one-time | [saferelay.ai/saferelay](https://saferelay.ai/saferelay) |
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install saferelay-enterprise
|
|
61
|
+
|
|
62
|
+
# With Redis support (enterprise distributed vault)
|
|
63
|
+
pip install saferelay-enterprise[redis]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Requires Python 3.9+. No external dependencies for the base install.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Free tier — mask IPs and API keys
|
|
72
|
+
cat /var/log/app.log | saferelay --mask
|
|
73
|
+
|
|
74
|
+
# Pro tier — full vault with unmask
|
|
75
|
+
docker logs my-app | saferelay --mask > clean.log
|
|
76
|
+
cat ai_response.txt | saferelay --unmask
|
|
77
|
+
|
|
78
|
+
# Activate Pro
|
|
79
|
+
saferelay --unlock "YOUR-LICENSE-KEY"
|
|
80
|
+
|
|
81
|
+
# Status
|
|
82
|
+
saferelay --status
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## What gets redacted
|
|
86
|
+
|
|
87
|
+
| Pattern | Free | Pro |
|
|
88
|
+
|---------|------|-----|
|
|
89
|
+
| IPv4 addresses | ✓ | ✓ |
|
|
90
|
+
| API keys (OpenAI, Anthropic, AWS, GitHub, Slack, Gemini) | ✓ | ✓ |
|
|
91
|
+
| Bitcoin / Ethereum addresses | ✓ | ✓ |
|
|
92
|
+
| PEM private keys | ✓ | ✓ |
|
|
93
|
+
| .env file values | ✓ | ✓ |
|
|
94
|
+
| MAC addresses | — | ✓ |
|
|
95
|
+
| Credit cards (Luhn-validated) | — | ✓ |
|
|
96
|
+
| US SSN | — | ✓ |
|
|
97
|
+
| EU IBAN | — | ✓ |
|
|
98
|
+
| UK NINO | — | ✓ |
|
|
99
|
+
| Nigeria NIN / Bank / Phone | — | ✓ |
|
|
100
|
+
| Canada SIN | — | ✓ |
|
|
101
|
+
| India Aadhaar / PAN | — | ✓ |
|
|
102
|
+
| South Africa ID | — | ✓ |
|
|
103
|
+
| Australia TFN | — | ✓ |
|
|
104
|
+
| Brazil CPF | — | ✓ |
|
|
105
|
+
| Singapore NRIC | — | ✓ |
|
|
106
|
+
| Germany Tax ID | — | ✓ |
|
|
107
|
+
| Seed phrases (12/24 word) | — | ✓ |
|
|
108
|
+
| ETH private keys | — | ✓ |
|
|
109
|
+
| Custom NDA keywords | — | ✓ |
|
|
110
|
+
|
|
111
|
+
## Docker
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
docker pull logicgridai/saferelay:latest
|
|
115
|
+
|
|
116
|
+
cat /var/log/app.log | docker run --rm -i logicgridai/saferelay --mask
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Kubernetes sidecar
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
containers:
|
|
123
|
+
- name: saferelay
|
|
124
|
+
image: logicgridai/saferelay:latest
|
|
125
|
+
env:
|
|
126
|
+
- name: SAFEPASTE_LICENSE_KEY
|
|
127
|
+
valueFrom:
|
|
128
|
+
secretKeyRef:
|
|
129
|
+
name: saferelay-secret
|
|
130
|
+
key: license-key
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Pricing
|
|
134
|
+
|
|
135
|
+
| Tier | Price | Features |
|
|
136
|
+
|------|-------|---------|
|
|
137
|
+
| Free | $0 | IP + API key redaction |
|
|
138
|
+
| Pro | $7.99/month or $59/year | 35+ patterns, full vault |
|
|
139
|
+
| SafeRelay Suite | $99 one-time | SafePaste + SpeakPaste + Boomerang Snip |
|
|
140
|
+
|
|
141
|
+
→ [Get a license at saferelay.ai](https://saferelay.ai)
|
|
142
|
+
|
|
143
|
+
## Privacy
|
|
144
|
+
|
|
145
|
+
Clipboard and log content never leaves your machine. License activation sends only a hashed device fingerprint to `api.saferelay.ai` — no log data, ever.
|
|
146
|
+
|
|
147
|
+
Full privacy policy: [saferelay.ai/privacy](https://saferelay.ai/privacy)
|
|
148
|
+
|
|
149
|
+
**Built by [LogicGrid AI, LLC](https://logicgrid.ai)** — support@logicgrid.ai
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
saferelay/__init__.py
|
|
4
|
+
saferelay/__main__.py
|
|
5
|
+
saferelay/client.py
|
|
6
|
+
saferelay.egg-info/PKG-INFO
|
|
7
|
+
saferelay.egg-info/SOURCES.txt
|
|
8
|
+
saferelay.egg-info/dependency_links.txt
|
|
9
|
+
saferelay.egg-info/entry_points.txt
|
|
10
|
+
saferelay.egg-info/requires.txt
|
|
11
|
+
saferelay.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
saferelay
|
saferelay-3.5.3/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
setup(
|
|
3
|
+
name="saferelay",
|
|
4
|
+
version="3.5.3",
|
|
5
|
+
description="Zero-trust local DLP for AI-era workflows — 40+ threat patterns across 8+ countries",
|
|
6
|
+
long_description=open("README.md", encoding="utf-8").read(),
|
|
7
|
+
long_description_content_type="text/markdown",
|
|
8
|
+
author="LogicGrid AI, LLC",
|
|
9
|
+
author_email="support@logicgrid.ai",
|
|
10
|
+
url="https://saferelay.ai",
|
|
11
|
+
packages=find_packages(),
|
|
12
|
+
python_requires=">=3.9",
|
|
13
|
+
extras_require={"redis": ["redis>=4.0"]},
|
|
14
|
+
entry_points={"console_scripts": ["saferelay=safepaste.__main__:main"]},
|
|
15
|
+
license="Proprietary",
|
|
16
|
+
keywords="ai-safety api-keys cli dlp pii redaction security zero-trust saferelay",
|
|
17
|
+
classifiers=[
|
|
18
|
+
"Development Status :: 5 - Production/Stable",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Topic :: Security",
|
|
24
|
+
],
|
|
25
|
+
)
|