Metadata-Version: 2.4
Name: sproxy
Version: 0.1.1
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.
Author-email: Igor Pejic <hi@igorpejic.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/igorpejic/sproxy
Project-URL: Issues, https://github.com/igorpejic/sproxy/issues
Keywords: mitmproxy,proxy,secrets,dlp,egress,ai-agents,claude-code,security
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mitmproxy>=11
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# sproxy (secure proxy)

## Motivation
Do not send the secrets (API tokens, passwords) to LLM providers.
If the secrets are in the conversation context, you can consider them compromised.
So, don't let them ever enter the context.
sproxy helps with this.

## How it works?

At request time, sproxy detects the secrets placeholders and injects the real
secrets to the outbound requests.
The LLMs only see the placeholder values at all times.

```
             placeholder {{GITHUB_TOKEN}}                 real secret
   ┌────────┐  ───────────────────────►  ┌────────┐  ───────────────►   ┌──────────┐
   │  LLM   │                            │ sproxy │   leak scan +       │ approved │
   │ agent  │  ◄───────────────────────  │        │   audit             │  host    │
   └────────┘        response            └────────┘                     └──────────┘
                                              │
                                              ├─►  ✗ block leak to attacker domain
                                              └─►  🧾 .sproxy/audit.jsonl
```

## Features

- provides a list of placeholder env values to be used by the LLM
- configure specific domains to which the secret should be ingested
- monitor and audit all traffic originating from LLM
- works with authorization Headers, Parameters and Postgres protocol
- local-first and fully open-source
- works with Codex, Claude Code and other harnesses


## Prerequisites

sproxy uses [mitmdump](https://docs.mitmproxy.org/stable/overview/installation/) and will
be pulled automatically by `pip install`:

```bash
pip install sproxy
```

If you prefer to install mitmproxy separately, use `pip install --no-deps sproxy`
and make sure `mitmdump` is available on your `PATH` (or pass `--mitmdump` to
`sproxy run`).


## Quickstart

```bash
# 1. Write a starter policy and edit it.
sproxy init                        # creates ./sproxy.toml

# 2. Run your agent behind the guard.

# Start sproxy
sproxy run
# And load the placeholder Env variables
eval $(sproxy env) && codex

# Or run the process together with sproxy
sproxy run -- codex

# 3. After a session, check the audit log wasn't touched.
sproxy audit verify .sproxy/audit.jsonl
```

On first run, mitmproxy generates its CA at `~/.mitmproxy/mitmproxy-ca-cert.pem`.
sproxy points the agent's TLS trust env vars (`NODE_EXTRA_CA_CERTS`,
`REQUESTS_CA_BUNDLE`, `SSL_CERT_FILE`, …) at that file automatically.
Run `sproxy ca` to print the path.


To observe secret injection on your local machine, follow [QUICKSTART.md](./QUICKSTART.md).


### Placeholder environment variables

When a secret name is a valid environment-variable name, sproxy also exports
that name to the guarded process as an per-session secret placedholer
starting with `sproxy`.
For example, with a `GITHUB_API_KEY` secret, an SDK sees:

```text
GITHUB_API_KEY=sproxy_v1_<random-session-handle>
```

sproxy replaces that handle with the real key only for the secret's allowed
hosts. The handle is different for every `sproxy run` session and stops working
when that session ends.

Secret backends supported:

- `gopass:PATH`,
- `pass:PATH`,
- `vault:PATH#FIELD` (HashiCorp Vault, via the `vault` CLI's existing auth)
- `env:VAR`
- `file:PATH`
- `literal:VALUE`


### Config file

The policy is loaded from `-c PATH` config file if given, else `./sproxy.toml`, else the
user-level `~/.config/sproxy/sproxy.toml`.
Relative paths inside a policy (`audit_log`, `sslrootcert`, `confdir`) resolve
against the config file's own directory.

Editing the policy while `sproxy run` hot-reloads sproxy.
(Note that you might have to re-authorize loading the secrets).


## PostgreSQL

The usual placeholder injection does not work for PostgreSQL like it does for http.
SCRAM authentication proves knowledge of the password inside the client,
so there is no request in flight to rewrite.
Instead, each `[postgres.NAME]` profile becomes a local endpoint that
terminates the wire protocol: the agent's client
authenticates to sproxy with a per-session token.
Sproxy opens the real connection to the host pinned in
policy. The real password never enters the agent's environment,
but it sees a fake placeholder which sproxy uses.

```toml
[postgres.develop]
host = "db.example.com"          # the real server
port = 5432
listen = 6432                    # local endpoint port; omit for auto-assigned
password_source = "gopass:work/postgres/develop/password"
user = "app_readonly"            # optional: default role
database = "appdb"               # optional: default database
sslmode = "verify-full"          # upstream TLS (default); "disable" only for local dev servers
sslrootcert = ""                 # CA bundle (e.g. the AWS RDS bundle); "" = system trust store
```

Each endpoint is advertised to the session through an environment variable
named after the profile which will render after `sproxy run` or `sproxy env`.

## The audit log

Stores one line per request captured.
Example:

```json
{"seq":1,"ts":"...","host":"attacker.com","method":"POST","decision":"blocked","rules":["secret:GITHUB_TOKEN"], ...}
```

## TLS interception & certificate pinning

sproxy reads HTTPS by acting as a MITM with a trusted local CA. Clients that
pin certificates (require a specific cert, not merely a valid one) will
reject that CA and fail the handshake, and won't have their secrets replaced.
Use `passthrough_hosts` to whitelist such hosts:
```toml
passthrough_hosts = ["api.vendor.com"]     # tunneled without interception
```

## Commands

| Command                                                                      | What it does                                     |
| ---------------------------------------------------------------------------- | ------------------------------------------------ |
| `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` |
| `sproxy init [-c path] [--force]`                                            | Write a starter policy                           |
| `sproxy env`                                                                 | Print exports that join this shell to the live session |
| `sproxy ca [-c policy]`                                                      | Print the mitmproxy CA cert path                 |
| `sproxy audit verify <log>`                                                  | Check the audit chain is intact                  |
| `sproxy version`                                                             | Print version                                    |



## License
MIT
