sproxy 0.1.0__py3-none-any.whl
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.
- sproxy/__init__.py +8 -0
- sproxy/__main__.py +4 -0
- sproxy/addon.py +468 -0
- sproxy/audit.py +88 -0
- sproxy/auth.py +34 -0
- sproxy/ca.py +69 -0
- sproxy/cli.py +293 -0
- sproxy/pg.py +637 -0
- sproxy/policy.py +274 -0
- sproxy/replacements.py +28 -0
- sproxy/runner.py +850 -0
- sproxy/scanner.py +109 -0
- sproxy/secrets_resolver.py +104 -0
- sproxy-0.1.0.dist-info/METADATA +197 -0
- sproxy-0.1.0.dist-info/RECORD +19 -0
- sproxy-0.1.0.dist-info/WHEEL +5 -0
- sproxy-0.1.0.dist-info/entry_points.txt +2 -0
- sproxy-0.1.0.dist-info/licenses/LICENSE +21 -0
- sproxy-0.1.0.dist-info/top_level.txt +1 -0
sproxy/scanner.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Outbound leak detection.
|
|
2
|
+
|
|
3
|
+
Two independent detectors:
|
|
4
|
+
|
|
5
|
+
* **Known values** - exact substring match against resolved secret values (and
|
|
6
|
+
any extra ``watch_values``). Zero false positives; this is the core guard.
|
|
7
|
+
* **Patterns** - regexes for well-known credential shapes (provider API keys,
|
|
8
|
+
cloud keys, tokens, JWTs, private keys). Higher recall, used to catch secrets
|
|
9
|
+
sproxy was never told about.
|
|
10
|
+
|
|
11
|
+
The scanner deliberately reports only *which rule* matched, never the matched
|
|
12
|
+
text, so findings are safe to write to the audit log.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
20
|
+
# Cap how much of a body we scan so a huge upload can't stall the proxy.
|
|
21
|
+
MAX_SCAN_BYTES = 2 * 1024 * 1024
|
|
22
|
+
|
|
23
|
+
# (name, regex). Patterns are chosen for high signal / low false-positive rate.
|
|
24
|
+
_PATTERN_SPECS: list[tuple[str, str]] = [
|
|
25
|
+
("anthropic_api_key", r"sk-ant-[A-Za-z0-9_-]{20,}"),
|
|
26
|
+
("openai_api_key", r"sk-(?:proj-)?[A-Za-z0-9_-]{20,}"),
|
|
27
|
+
("github_token", r"gh[pousr]_[A-Za-z0-9]{36,}"),
|
|
28
|
+
("github_fine_grained_pat", r"github_pat_[A-Za-z0-9_]{22,}"),
|
|
29
|
+
("aws_access_key_id", r"(?:AKIA|ASIA)[0-9A-Z]{16}"),
|
|
30
|
+
("google_api_key", r"AIza[0-9A-Za-z_-]{35}"),
|
|
31
|
+
("slack_token", r"xox[baprs]-[A-Za-z0-9-]{10,}"),
|
|
32
|
+
("stripe_secret_key", r"(?:sk|rk)_live_[0-9a-zA-Z]{24,}"),
|
|
33
|
+
("gitlab_pat", r"glpat-[A-Za-z0-9_-]{20,}"),
|
|
34
|
+
("jwt", r"eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}"),
|
|
35
|
+
("private_key", r"-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----"),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
PATTERNS: list[tuple[str, re.Pattern]] = [
|
|
39
|
+
(name, re.compile(rx)) for name, rx in _PATTERN_SPECS
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class Finding:
|
|
45
|
+
kind: str # "secret" or "pattern"
|
|
46
|
+
name: str # secret name, or pattern name
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str: # pragma: no cover - display only
|
|
49
|
+
return f"{self.kind}:{self.name}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Scanner:
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
secret_values: dict[str, str] | None = None,
|
|
56
|
+
watch_values: list[str] | None = None,
|
|
57
|
+
detect_patterns: bool = True,
|
|
58
|
+
):
|
|
59
|
+
# name -> value, and value -> name for reverse lookup during redaction.
|
|
60
|
+
self.secret_values = dict(secret_values or {})
|
|
61
|
+
for i, v in enumerate(watch_values or []):
|
|
62
|
+
if v:
|
|
63
|
+
self.secret_values.setdefault(f"watch_{i}", v)
|
|
64
|
+
self._value_to_name = {v: n for n, v in self.secret_values.items() if v}
|
|
65
|
+
self.detect_patterns = detect_patterns
|
|
66
|
+
|
|
67
|
+
def scan(self, text: str) -> list[Finding]:
|
|
68
|
+
"""Return findings for a piece of outbound text (URL, header, body)."""
|
|
69
|
+
if not text:
|
|
70
|
+
return []
|
|
71
|
+
if len(text) > MAX_SCAN_BYTES:
|
|
72
|
+
text = text[:MAX_SCAN_BYTES]
|
|
73
|
+
findings: list[Finding] = []
|
|
74
|
+
seen: set[tuple[str, str]] = set()
|
|
75
|
+
|
|
76
|
+
for value, name in self._value_to_name.items():
|
|
77
|
+
if value in text and ("secret", name) not in seen:
|
|
78
|
+
findings.append(Finding("secret", name))
|
|
79
|
+
seen.add(("secret", name))
|
|
80
|
+
|
|
81
|
+
if self.detect_patterns:
|
|
82
|
+
for name, rx in PATTERNS:
|
|
83
|
+
if rx.search(text) and ("pattern", name) not in seen:
|
|
84
|
+
findings.append(Finding("pattern", name))
|
|
85
|
+
seen.add(("pattern", name))
|
|
86
|
+
|
|
87
|
+
return findings
|
|
88
|
+
|
|
89
|
+
def secret_names(self, findings: list[Finding]) -> set[str]:
|
|
90
|
+
return {f.name for f in findings if f.kind == "secret"}
|
|
91
|
+
|
|
92
|
+
def redact(self, text: str) -> tuple[str, list[Finding]]:
|
|
93
|
+
"""Replace any known secret values and pattern matches in ``text`` with a
|
|
94
|
+
marker. Returns the redacted text and the findings that were redacted."""
|
|
95
|
+
if not text:
|
|
96
|
+
return text, []
|
|
97
|
+
findings: list[Finding] = []
|
|
98
|
+
out = text
|
|
99
|
+
for value, name in self._value_to_name.items():
|
|
100
|
+
if value and value in out:
|
|
101
|
+
out = out.replace(value, f"[sproxy:redacted:{name}]")
|
|
102
|
+
findings.append(Finding("secret", name))
|
|
103
|
+
if self.detect_patterns:
|
|
104
|
+
for name, rx in PATTERNS:
|
|
105
|
+
new, n = rx.subn(f"[sproxy:redacted:{name}]", out)
|
|
106
|
+
if n:
|
|
107
|
+
out = new
|
|
108
|
+
findings.append(Finding("pattern", name))
|
|
109
|
+
return out, findings
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Resolve secret source specs to their real values.
|
|
2
|
+
|
|
3
|
+
Sources are resolved once, up front, by the trusted CLI process - never by the
|
|
4
|
+
agent and never by the addon. Supported backends:
|
|
5
|
+
|
|
6
|
+
gopass:PATH `gopass show -o PATH`
|
|
7
|
+
pass:PATH `pass show PATH` (first line)
|
|
8
|
+
vault:PATH#FIELD `vault kv get -field=FIELD PATH` (FIELD defaults to "value")
|
|
9
|
+
env:VAR environment variable VAR
|
|
10
|
+
file:PATH contents of a file (stripped)
|
|
11
|
+
literal:VALUE the value itself (handy for tests/demos)
|
|
12
|
+
|
|
13
|
+
A resolver is a callable ``(path: str) -> str``; the defaults shell out, but
|
|
14
|
+
tests inject fakes.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import subprocess
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SecretResolutionError(RuntimeError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _run(cmd: list[str]) -> str:
|
|
29
|
+
try:
|
|
30
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
31
|
+
except FileNotFoundError as exc:
|
|
32
|
+
raise SecretResolutionError(f"{cmd[0]} not found on PATH") from exc
|
|
33
|
+
except subprocess.CalledProcessError as exc:
|
|
34
|
+
stderr = (exc.stderr or "").strip()
|
|
35
|
+
raise SecretResolutionError(
|
|
36
|
+
f"{' '.join(cmd)} failed ({exc.returncode}): {stderr}"
|
|
37
|
+
) from exc
|
|
38
|
+
return result.stdout
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _gopass(path: str) -> str:
|
|
42
|
+
return _run(["gopass", "show", "-o", path]).strip()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _pass(path: str) -> str:
|
|
46
|
+
# `pass` prints the whole entry; the secret is conventionally the first line.
|
|
47
|
+
return _run(["pass", "show", path]).splitlines()[0] if path else ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _vault(spec: str) -> str:
|
|
51
|
+
# spec is "PATH#FIELD"; FIELD defaults to "value". The KV v2 CLI resolves
|
|
52
|
+
# the /data/ segment itself, so pass the logical path (e.g. secret/app).
|
|
53
|
+
# Auth is whatever the `vault` CLI already uses (VAULT_ADDR/VAULT_TOKEN/
|
|
54
|
+
# ~/.vault-token) - sproxy never handles Vault credentials itself.
|
|
55
|
+
path, _, field = spec.partition("#")
|
|
56
|
+
field = field or "value"
|
|
57
|
+
if not path:
|
|
58
|
+
raise SecretResolutionError("vault source needs a path, e.g. vault:secret/app#api_key")
|
|
59
|
+
return _run(["vault", "kv", "get", f"-field={field}", path]).strip()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _env(var: str) -> str:
|
|
63
|
+
if var not in os.environ:
|
|
64
|
+
raise SecretResolutionError(f"environment variable {var} is not set")
|
|
65
|
+
return os.environ[var]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _file(path: str) -> str:
|
|
69
|
+
try:
|
|
70
|
+
with open(os.path.expanduser(path), encoding="utf-8") as fh:
|
|
71
|
+
return fh.read().strip()
|
|
72
|
+
except OSError as exc:
|
|
73
|
+
raise SecretResolutionError(f"cannot read {path}: {exc}") from exc
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
DEFAULT_BACKENDS: dict[str, Callable[[str], str]] = {
|
|
77
|
+
"gopass": _gopass,
|
|
78
|
+
"pass": _pass,
|
|
79
|
+
"vault": _vault,
|
|
80
|
+
"env": _env,
|
|
81
|
+
"file": _file,
|
|
82
|
+
"literal": lambda v: v,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def resolve(source: str, backends: dict[str, Callable[[str], str]] | None = None) -> str:
|
|
87
|
+
"""Resolve a ``backend:path`` spec to its value."""
|
|
88
|
+
backends = backends or DEFAULT_BACKENDS
|
|
89
|
+
if ":" not in source:
|
|
90
|
+
raise SecretResolutionError(
|
|
91
|
+
f"secret source {source!r} must look like 'backend:path' "
|
|
92
|
+
f"(one of: {', '.join(sorted(backends))})"
|
|
93
|
+
)
|
|
94
|
+
backend, _, path = source.partition(":")
|
|
95
|
+
fn = backends.get(backend)
|
|
96
|
+
if fn is None:
|
|
97
|
+
raise SecretResolutionError(
|
|
98
|
+
f"unknown secret backend {backend!r} in {source!r}; "
|
|
99
|
+
f"known backends: {', '.join(sorted(backends))}"
|
|
100
|
+
)
|
|
101
|
+
value = fn(path)
|
|
102
|
+
if value == "":
|
|
103
|
+
raise SecretResolutionError(f"secret source {source!r} resolved to an empty value")
|
|
104
|
+
return value
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sproxy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local egress guard for coding agents: inject secrets, block secret leaks to hosts they may not reach, and keep a tamper-evident audit log.
|
|
5
|
+
Author-email: Igor Pejic <hi@igorpejic.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/igorpejic/sproxy
|
|
8
|
+
Project-URL: Issues, https://github.com/igorpejic/sproxy/issues
|
|
9
|
+
Keywords: mitmproxy,proxy,secrets,dlp,egress,ai-agents,claude-code,security
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Security
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: mitmproxy>=11
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# sproxy (secure proxy)
|
|
24
|
+
|
|
25
|
+
## Motivation
|
|
26
|
+
Do not send the secrets (API tokens, passwords) to LLM providers.
|
|
27
|
+
If the secrets are in the conversation context, you can consider them compromised.
|
|
28
|
+
So, don't let them ever enter the context.
|
|
29
|
+
sproxy helps with this.
|
|
30
|
+
|
|
31
|
+
## How it works?
|
|
32
|
+
|
|
33
|
+
At request time, sproxy detects the secrets placeholders and injects the real
|
|
34
|
+
secrets to the outbound requests.
|
|
35
|
+
The LLMs only see the placeholder values at all times.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
placeholder {{GITHUB_TOKEN}} real secret
|
|
39
|
+
┌────────┐ ───────────────────────► ┌────────┐ ───────────────► ┌──────────┐
|
|
40
|
+
│ LLM │ │ sproxy │ leak scan + │ approved │
|
|
41
|
+
│ agent │ ◄─────────────────────── │ │ audit │ host │
|
|
42
|
+
└────────┘ response └────────┘ └──────────┘
|
|
43
|
+
│
|
|
44
|
+
├─► ✗ block leak to attacker domain
|
|
45
|
+
└─► 🧾 .sproxy/audit.jsonl
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- provides a list of placeholder env values to be used by the LLM
|
|
51
|
+
- configure specific domains to which the secret should be ingested
|
|
52
|
+
- monitor and audit all traffic originating from LLM
|
|
53
|
+
- works with authorization Headers, Parameters and Postgres protocol
|
|
54
|
+
- local-first and fully open-source
|
|
55
|
+
- works with Codex, Claude Code and other harnesses
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Prerequisites
|
|
59
|
+
|
|
60
|
+
sproxy uses [mitmdump](https://docs.mitmproxy.org/stable/overview/installation/) and will
|
|
61
|
+
be pulled automatically by `pip install`:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install sproxy
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If you prefer to install mitmproxy separately, use `pip install --no-deps sproxy`
|
|
68
|
+
and make sure `mitmdump` is available on your `PATH` (or pass `--mitmdump` to
|
|
69
|
+
`sproxy run`).
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
## Quickstart
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 1. Write a starter policy and edit it.
|
|
76
|
+
sproxy init # creates ./sproxy.toml
|
|
77
|
+
|
|
78
|
+
# 2. Run your agent behind the guard.
|
|
79
|
+
|
|
80
|
+
# Start sproxy
|
|
81
|
+
sproxy run
|
|
82
|
+
# And load the placeholder Env variables
|
|
83
|
+
eval $(sproxy env) && codex
|
|
84
|
+
|
|
85
|
+
# Or run the process together with sproxy
|
|
86
|
+
sproxy run -- codex
|
|
87
|
+
|
|
88
|
+
# 3. After a session, check the audit log wasn't touched.
|
|
89
|
+
sproxy audit verify .sproxy/audit.jsonl
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
On first run, mitmproxy generates its CA at `~/.mitmproxy/mitmproxy-ca-cert.pem`.
|
|
93
|
+
sproxy points the agent's TLS trust env vars (`NODE_EXTRA_CA_CERTS`,
|
|
94
|
+
`REQUESTS_CA_BUNDLE`, `SSL_CERT_FILE`, …) at that file automatically.
|
|
95
|
+
Run `sproxy ca` to print the path.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
To observe secret injection on your local machine, follow [QUICKSTART.md](./QUICKSTART.md).
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### Placeholder environment variables
|
|
102
|
+
|
|
103
|
+
When a secret name is a valid environment-variable name, sproxy also exports
|
|
104
|
+
that name to the guarded process as an per-session secret placedholer
|
|
105
|
+
starting with `sproxy`.
|
|
106
|
+
For example, with a `GITHUB_API_KEY` secret, an SDK sees:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
GITHUB_API_KEY=sproxy_v1_<random-session-handle>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
sproxy replaces that handle with the real key only for the secret's allowed
|
|
113
|
+
hosts. The handle is different for every `sproxy run` session and stops working
|
|
114
|
+
when that session ends.
|
|
115
|
+
|
|
116
|
+
Secret backends supported:
|
|
117
|
+
|
|
118
|
+
- `gopass:PATH`,
|
|
119
|
+
- `pass:PATH`,
|
|
120
|
+
- `vault:PATH#FIELD` (HashiCorp Vault, via the `vault` CLI's existing auth)
|
|
121
|
+
- `env:VAR`
|
|
122
|
+
- `file:PATH`
|
|
123
|
+
- `literal:VALUE`
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
### Config file
|
|
127
|
+
|
|
128
|
+
The policy is loaded from `-c PATH` config file if given, else `./sproxy.toml`, else the
|
|
129
|
+
user-level `~/.config/sproxy/sproxy.toml`.
|
|
130
|
+
Relative paths inside a policy (`audit_log`, `sslrootcert`, `confdir`) resolve
|
|
131
|
+
against the config file's own directory.
|
|
132
|
+
|
|
133
|
+
Editing the policy while `sproxy run` hot-reloads sproxy.
|
|
134
|
+
(Note that you might have to re-authorize loading the secrets).
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
## PostgreSQL
|
|
138
|
+
|
|
139
|
+
The usual placeholder injection does not work for PostgreSQL like it does for http.
|
|
140
|
+
SCRAM authentication proves knowledge of the password inside the client,
|
|
141
|
+
so there is no request in flight to rewrite.
|
|
142
|
+
Instead, each `[postgres.NAME]` profile becomes a local endpoint that
|
|
143
|
+
terminates the wire protocol: the agent's client
|
|
144
|
+
authenticates to sproxy with a per-session token.
|
|
145
|
+
Sproxy opens the real connection to the host pinned in
|
|
146
|
+
policy. The real password never enters the agent's environment,
|
|
147
|
+
but it sees a fake placeholder which sproxy uses.
|
|
148
|
+
|
|
149
|
+
```toml
|
|
150
|
+
[postgres.develop]
|
|
151
|
+
host = "db.example.com" # the real server
|
|
152
|
+
port = 5432
|
|
153
|
+
listen = 6432 # local endpoint port; omit for auto-assigned
|
|
154
|
+
password_source = "gopass:work/postgres/develop/password"
|
|
155
|
+
user = "app_readonly" # optional: default role
|
|
156
|
+
database = "appdb" # optional: default database
|
|
157
|
+
sslmode = "verify-full" # upstream TLS (default); "disable" only for local dev servers
|
|
158
|
+
sslrootcert = "" # CA bundle (e.g. the AWS RDS bundle); "" = system trust store
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Each endpoint is advertised to the session through an environment variable
|
|
162
|
+
named after the profile which will render after `sproxy run` or `sproxy env`.
|
|
163
|
+
|
|
164
|
+
## The audit log
|
|
165
|
+
|
|
166
|
+
Stores one line per request captured.
|
|
167
|
+
Example:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{"seq":1,"ts":"...","host":"attacker.com","method":"POST","decision":"blocked","rules":["secret:GITHUB_TOKEN"], ...}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## TLS interception & certificate pinning
|
|
174
|
+
|
|
175
|
+
sproxy reads HTTPS by acting as a MITM with a trusted local CA. Clients that
|
|
176
|
+
pin certificates (require a specific cert, not merely a valid one) will
|
|
177
|
+
reject that CA and fail the handshake, and won't have their secrets replaced.
|
|
178
|
+
Use `passthrough_hosts` to whitelist such hosts:
|
|
179
|
+
```toml
|
|
180
|
+
passthrough_hosts = ["api.vendor.com"] # tunneled without interception
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Commands
|
|
184
|
+
|
|
185
|
+
| Command | What it does |
|
|
186
|
+
| ---------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
187
|
+
| `sproxy run [-c policy] [--port N] [--insecure] [--log-file path] [--no-reload] [-- <cmd…>]` | Run a command behind the guard; no command holds a session open. Policy edits apply live unless `--no-reload` |
|
|
188
|
+
| `sproxy init [-c path] [--force]` | Write a starter policy |
|
|
189
|
+
| `sproxy env` | Print exports that join this shell to the live session |
|
|
190
|
+
| `sproxy ca [-c policy]` | Print the mitmproxy CA cert path |
|
|
191
|
+
| `sproxy audit verify <log>` | Check the audit chain is intact |
|
|
192
|
+
| `sproxy version` | Print version |
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
sproxy/__init__.py,sha256=v5xSxVM3ALaddKBUapCk3jH2nbLNrTlLxPHtFirgcdA,283
|
|
2
|
+
sproxy/__main__.py,sha256=-qiWeoH9e6HFOq4x5ywHq5EWFMj6e6PaX4lXF0z0oWE,85
|
|
3
|
+
sproxy/addon.py,sha256=qAj1aOswGpmyfKzmv28Ekvxt_tpPpRq-nZ9r7zYGjbE,18686
|
|
4
|
+
sproxy/audit.py,sha256=hkrnpxBUM0yErv-dEZ4C_9wDW2uC7JlwDiK5YtVVsrw,3234
|
|
5
|
+
sproxy/auth.py,sha256=h2cZPYzNs35U8bNwsmexAprmmU4o-sJLSDuUxMtFO34,1234
|
|
6
|
+
sproxy/ca.py,sha256=EHtvWmjTqezQJYjPU2c4IyzD1nM1luFEDowLAA1sC54,2393
|
|
7
|
+
sproxy/cli.py,sha256=j41uRMgtv-_6GtUaoED-ll-HJi1S-YseJQJUC2AS7Vs,11178
|
|
8
|
+
sproxy/pg.py,sha256=LfTlVbQUiJVyFgTDpyvS0YaGyvwtpXonUm88Dui_uCU,25403
|
|
9
|
+
sproxy/policy.py,sha256=7q5rEnYAYqWE7Aopog9vK1-rXogkzxXeqKoqI5YZakw,10534
|
|
10
|
+
sproxy/replacements.py,sha256=SKA5Y0slNdVXu9Yhi8nJ_Havnk3l3EeMUrUWqbpEy14,1055
|
|
11
|
+
sproxy/runner.py,sha256=O2t0ofHvoomT3H8E0Iztlf6aikl1CWzwFjdqoMafknQ,33023
|
|
12
|
+
sproxy/scanner.py,sha256=n6x6G5T5GnjnwbtTsJtukDskMLT9wbFcqBuaYMGMd5c,4249
|
|
13
|
+
sproxy/secrets_resolver.py,sha256=0aYjC4dMqeOBVSONoQalkdvfRfPw57axdUQU9Lxt43A,3540
|
|
14
|
+
sproxy-0.1.0.dist-info/licenses/LICENSE,sha256=JoZ5Fxk5z-cnfUHtuYmWdF8nrZRaXneJQu6WBm8ycQY,1067
|
|
15
|
+
sproxy-0.1.0.dist-info/METADATA,sha256=WkFSuh_MwOvjOoX9gkv8VAGDFyms590YvBlI3eAKtpw,7844
|
|
16
|
+
sproxy-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
17
|
+
sproxy-0.1.0.dist-info/entry_points.txt,sha256=AV_wKPkzkSin3W5AAF-6LBfIIT6WhQBpFfu6LmRBDxw,43
|
|
18
|
+
sproxy-0.1.0.dist-info/top_level.txt,sha256=KOGn-X9-FP91vvInt0Y2s3IFIKarKtTpRAJuwoDRkbU,7
|
|
19
|
+
sproxy-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Igor Pejic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sproxy
|