Skip to content

feat(forward): per-program identities, Discord, wire record and an egress ceiling — plus two forward-auth fixes - #8

Merged
praxagent merged 14 commits into
mainfrom
feat/discord-gateway-injection
Oct 3, 2026
Merged

praxagent merged 14 commits into
mainfrom
feat/discord-gateway-injection

Conversation

@praxagent

@praxagent praxagent commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

This PR now carries all six open proxy PRs, because stacked PRs re-conflict after every squash merge. #3, #4, #5, #6 and #7 are folded in here and closed with a pointer. Merge this one.

Fixes

Features

Verified

  • Unit: 111 passed, ruff clean, docker compose config valid.

  • Live, in real mitmproxy/mitmproxy:latest with fake keys and fake upstreams, on this combined branch:

    Check Result
    main token over HTTPS 200, key injected
    callers-file token 200, logged as its identity
    no token 407 at CONNECT
    plain http no key upstream
    wire record lines carry the authenticated caller; verify: chain intact
  • Earlier live runs, per piece:

    • HTTPS auth: 407 before the fix, 200 after.
    • Per-program rules: allow/deny by program over HTTPS, a username spoof denied.
    • Discord: real discord.py against a fake Discord.
    • Cleartext: the key was injected before the fix and not after.
  • Not run against Discord itself.

Idea credit: NVIDIA's Open Agent Safety Platform — monitoring on the node's
only path to the model, out of the agent's reach.

The agent's audit log lives in the process it audits, so a compromised agent
can drop entries. With PROXY_WIRE_RECORD set, the forward proxy appends one
line per model response (OpenAI chat/Responses and Anthropic, JSON or SSE):
host, path, status, model, request hash, size, and the tool calls the model
returned as names + argument hashes — never text. Lines are hash-chained, so
an edit, deletion or reordering breaks the chain (python -m
secrets_proxy.wire_record verify). Tamper-evident, not tamper-proof: keep the
file writable only by the proxy and anchor the head hash off the box.
Recording never breaks a response.
Dev and prod can share the forward proxy; the caller label (the proxy
username) is captured before the credential is stripped and written on each
wire line, so a check runs against one instance's traces.
…licy change opens

Idea credit: NVIDIA OpenShell's policy prover.

- PROXY_EGRESS_CEILING: an outer boundary in the policy format, checked
  before the policy on every request. Outside it is denied outright and never
  asked about, so no person's answer, timed grant or policy edit can exceed
  it. Startup logs every policy rule that reaches past it.
- python -m secrets_proxy.egress_policy diff OLD NEW [--ceiling] [--forward-map]:
  every request shape the new policy treats more permissively, marking
  credential-bearing ones and ones beyond the ceiling; exits 1 when anything
  opened. An honest comparison over the rules' own vocabulary, not a proof.
… after stripping it

Every injection audit line said caller=-. Read the caller once, before the
strip, and use it for both the audit line and the wire record.
HTTPS sends the proxy credential on the CONNECT only; the tunnelled requests
carry none, and auth ran only in the request hook. So setting
PROXY_FORWARD_AUTH_TOKEN refused every HTTPS request, right token or not —
which is why no deployment runs with it. Authenticate at http_connect and
remember the caller per client connection (the shape of mitmproxy's own
proxyauth addon). Also: the audit line read the caller after the credential
was stripped, so it always said caller=-.
…hat name programs

Idea credit: NVIDIA OpenShell's per-program network policy.

- PROXY_FORWARD_CALLERS: a file of program name -> token hash. A request's
  identity is WHOSE TOKEN MATCHED, never the free-form Basic username. The
  main token identifies as PROXY_FORWARD_AUTH_NAME (default prax).
  python -m secrets_proxy.callers new NAME prints a token and its entry.
- Rules (policy and ceiling) take "callers": [...]. Answers are remembered
  per program, questions name the program, diff reports per program.
- Honest limit: separates components with separate environments, not
  processes sharing one; per-binary attribution is not built.
Plain http:// to an injection host got the key injected and sent unencrypted
(reproduced live with a fake key). Now it goes out without the credential,
as if none were configured, with a warning. Idea credit: Agent Substrate's
egress credential injection, which skips cleartext likewise.
…injection, one caller only

- header:<Name> rules take a prefix ("Bot " for Discord REST).
- ws-json:<path> rules set the credential in client->server WebSocket text
  messages (Discord IDENTIFY/RESUME at d.token); wss only.
- Rules can name callers; an exclusive rule must, or the proxy refuses to
  start — two instances holding a bot token both answer every message.
Verified with real discord.py through real mitmproxy against a fake Discord.
…ord-gateway-injection

# Conflicts:
#	README.md
#	docker-compose.yml
#	secrets_proxy/mitm_addon.py
#	tests/test_forward_auth.py
@praxagent praxagent changed the title feat(forward): Discord through the proxy — Bot prefix, gateway token injection, one caller only feat(forward): per-program identities, Discord, wire record and an egress ceiling — plus two forward-auth fixes Oct 2, 2026
@praxagent
praxagent merged commit 156ff36 into main Oct 3, 2026
@praxagent
praxagent deleted the feat/discord-gateway-injection branch October 3, 2026 00:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant