Secrets firewall for LLM agents - an MCP server that calls your APIs without exposing credentials
1.7K
A secrets firewall for LLM agents.
aegis is an MCPโ server that lets a model make HTTP requests to your APIs โ without ever letting it see the credentials those requests are authenticated with.
To let an agent use an API, you normally hand it the API key โ in an environment
variable, an MCP server config, a .env file. From that moment the key is one
prompt injection, one careless log line or one screenshot away from leaking.
The model does not need the key. It needs the result.
aegis sits in between. The model asks for a request; aegis decides whether it is allowed, adds the credentials on the way out, and strips them out of whatever comes back.
โโโโโโโโโโโ MCP (OAuth 2.1) โโโโโโโโโโโโโ HTTPS + secret โโโโโโโโโโโโ
โ LLM โ โโโโโโโโโโโโโโโโโโโบ โ aegis โ โโโโโโโโโโโโโโโโโโบ โ API โ
โ agent โ โโโโโโโโโโโโโโโโโโโ โ :2019 โ โโโโโโโโโโโโโโโโโโ โ โ
โโโโโโโโโโโ redacted result โโโโโโโโโโโโโ response โโโโโโโโโโโโ
โ
config.yaml
users ยท targets ยท secrets
# 1. A password hash for the login screen
docker run --rm -it andreaskasper/aegis hashpw
# 2. Write a config.yaml (see below), then check it before it ever runs
docker run --rm -v "$PWD/config.yaml:/etc/aegis/config.yaml:ro" \
andreaskasper/aegis validate /etc/aegis/config.yaml
# 3. Run it
docker run -d --name aegis -p 2019:2019 \
-v "$PWD/config.yaml:/etc/aegis/config.yaml:ro" \
-e AEGIS_PUBLIC_URL=https://aegis.example.com \
--read-only --cap-drop ALL \
andreaskasper/aegis:latest
aegis writes nothing to disk, so --read-only is not a precaution you are taking
on its behalf โ it is how the container is meant to run.
With Compose:
services:
aegis:
image: andreaskasper/aegis:latest
ports: ["2019:2019"]
volumes:
- ./config.yaml:/etc/aegis/config.yaml:ro
environment:
AEGIS_PUBLIC_URL: https://aegis.example.com
read_only: true
cap_drop: [ALL]
restart: unless-stopped
Then point an MCP client at https://aegis.example.com/mcp. The client discovers
the OAuth endpoints, registers itself, opens the login page in a browser, and you
sign in with a user from your config.yaml.
aegis speaks plain HTTP and does not terminate TLS. Put it behind a reverse proxy (Traefik, Caddy, nginx, Cloudflare) in production. Ready-made Compose setups for Traefik and Cloudflare Tunnel are in the repositoryโ .
One YAML file. Users are the top-level unit: each brings their own secrets and their own targets.
server:
public_url: "https://aegis.example.com" # or AEGIS_PUBLIC_URL
max_response_bytes: 1048576 # 1 MiB
token_ttl: "12h"
users:
- name: andreas
password: "bcrypt:$2a$12$Xk8f...redacted..."
secrets:
github_pat: "file:/run/secrets/github_pat"
targets:
- id: github
description: "GitHub REST API, read-only"
base_url: "https://api.github.com"
methods: [GET]
paths: ["/repos/andreaskasper/**", "/user"]
rate_limit: "120/m"
inject:
headers:
Authorization: "Bearer ${github_pat}"
Accept: "application/vnd.github+json"
Secrets and passwords may be written literally or as a reference: env:NAME,
file:/path (for Docker secrets), or bcrypt:$2a$12$โฆ for passwords.
The file is watched and hot-reloaded. If an edit does not validate, the previous configuration stays live and the error is logged โ a typo cannot take the server down.
| Variable | Default | Notes |
|---|---|---|
AEGIS_CONFIG | /etc/aegis/config.yaml | mount your file here |
AEGIS_LISTEN | :2019 | |
AEGIS_PUBLIC_URL | โ | the URL clients reach it on |
Exactly two tools โ a small surface is the point.
list_targets โ which APIs this user may reach, with methods and path
patterns. Secrets and injection rules are not part of the answer.http_request โ url, method, headers, query, body. Returns status, headers
and body, after redaction and truncation.[REDACTED:name].Authorization, Cookie, Host
or X-Forwarded-*.aegis reduces the blast radius of a compromised or manipulated agent. It does not
make one safe: a POST-enabled target can still be used to do damage within
what you allowed. Scope targets to what the agent actually needs.
FROM gcr.io/distroless/static-debian12:nonroot โ no shell, no package
manager, a single static Go binary.nonroot (UID 65532). Works with --read-only and --cap-drop ALL.linux/amd64 and linux/arm64.| Tag | Points at |
|---|---|
latest | the most recent release |
0.1.6 | that exact release |
0.1 | the newest patch of that minor |
edge | current main, rebuilt on every commit |
sha-1a2b3c4 | one exact commit |
The identical image โ same digest, same build:
docker pull ghcr.io/andreaskasper/aegis:latest
Made with โค๏ธ by Andreas Kasperโ
Content type
Image
Digest
sha256:4b96f5548โฆ
Size
4.5 MB
Last updated
10 days ago
docker pull andreaskasper/aegis