Security
This page documents TameFlare's security model, including the proxy architecture, cryptographic design, data handling, and operational security guidance.
How HTTPS interception works
TameFlare's proxy intercepts HTTPS traffic using a technique called TLS termination with a local CA — the same approach used by corporate proxy tools (Zscaler, mitmproxy, Charles Proxy).
How it works
- On first run (
tf init), TameFlare generates an ECDSA P-256 CA certificate unique to your installation - The CA cert (
ca.crt) and private key (ca.key) are stored in your.TameFlare/directory - When
tf runwraps your agent, it setsHTTP_PROXYandHTTPS_PROXYenvironment variables pointing to the gateway - When the agent makes an HTTPS request, the gateway:
- Receives the
CONNECTrequest - Generates a per-domain certificate signed by your local CA
- Terminates TLS, inspects the request, applies permission checks
- If allowed, re-encrypts and forwards to the real destination with injected credentials
- Receives the
- The agent's TLS library trusts the local CA (configured via
SSL_CERT_FILEor system trust store)
Why this is safe for self-hosted
- Your CA, your machine. The CA key never leaves your infrastructure. No third party has it.
- Per-installation uniqueness. Every
tf initgenerates a fresh CA. There is no shared or hardcoded key. - No network exposure. The gateway listens on localhost by default. External traffic never touches it.
- Same model as corporate proxies. Organizations like banks and hospitals use identical TLS interception for DLP and compliance. TameFlare applies the same pattern to AI agent traffic.
ca.key to version control. The .TameFlare/ directory is in .gitignore by default. If you move the CA key, protect it with filesystem permissions (chmod 600).Trust store configuration
Most HTTP libraries respect HTTP_PROXY/HTTPS_PROXY and SSL_CERT_FILE automatically. tf run sets these for the child process. If your agent uses a custom TLS configuration, you may need to add the CA cert manually:
# Python (requests, httpx)
export SSL_CERT_FILE=.TameFlare/ca.crt
# Node.js
export NODE_EXTRA_CA_CERTS=.TameFlare/ca.crt
# Go
# Go respects SSL_CERT_FILE on Linux/macOS. On Windows, add to system trust store.
# System trust store (macOS)
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain .TameFlare/ca.crtProxy bypass analysis
What tf run enforces
tf run sets HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables for the child process. Most HTTP libraries (Python requests, Node.js axios/fetch, Go net/http) respect these automatically.
What it cannot prevent
TameFlare is honest about its limitations:
| Bypass method | Can TameFlare prevent it? | Mitigation |
|---|---|---|
| Agent ignores HTTP_PROXY env var | No | Use OS-level network isolation (firewall rules, network namespaces) |
| Agent uses raw TCP sockets | No | Proxy only intercepts HTTP/HTTPS |
| Agent hardcodes IP addresses | No | Firewall rules blocking direct outbound on ports 80/443 |
| Agent uses non-HTTP protocols (gRPC, WebSocket, UDP) | No | TameFlare is HTTP/HTTPS only |
| Agent reads ca.key from disk | Unlikely (file permissions) | Restrict .TameFlare/ca.key permissions to the gateway process user |
iptables or network namespaces to block direct outbound traffic from agent processes. On macOS, use pf firewall rules. This ensures the proxy is the only path to the internet.Threat model
TameFlare is designed to protect against:
| Threat | How TameFlare mitigates it |
|---|---|
| Agent calls unauthorized APIs | Deny-all default. No connector configured = no access. Agent cannot reach any domain you haven't set up. |
| Agent bypasses policy checks | Mode A: Agent doesn't hold external API keys — the Gateway does. Without permission, the request is blocked. |
| Agent forges a decision token | Tokens are ES256-signed (ECDSA P-256). The private key lives on the control plane. Agents never see it. |
| Agent replays a used token | Tokens include a nonce. The control plane marks nonces as used on first verification. Replay attempts are rejected. |
| Agent reuses a token for a different action | Tokens carry an action spec hash. The Gateway verifies the hash matches the action being executed. |
| Credential leakage | In proxy mode, agents never see real API keys. Credentials are injected by the gateway at request time. |
| Unauthorized dashboard access | RBAC with 4 roles (owner > admin > member > viewer). Session tokens stored in DB. Optional DASHBOARD_PASSWORD for shared access. |
| Secrets leaked from database | Slack tokens, GitHub PATs, and other secrets are encrypted at rest with AES-256-GCM when SETTINGS_ENCRYPTION_KEY is configured. |
| Runaway agent activity | Rate limiting (120 req/min per agent, configurable). Kill switch halts all traffic instantly. Scoped to all/connector/agent. |
What TameFlare does NOT protect against
- Prompt injection / hallucination content — TameFlare evaluates the action spec, not the LLM reasoning. If a hallucinated action has a valid type and parameters that pass policy checks, it will be allowed.
- Local file system / shell access — TameFlare operates at the network layer. It cannot prevent an agent from reading files, running shell commands, or accessing local resources. Use OS-level sandboxing (containers, seccomp) for that.
- Non-HTTP protocols — gRPC, WebSocket upgrades, raw TCP, and UDP are not intercepted by the proxy.
- Compromised host — If an attacker gains root access to the TameFlare server, they can read the credential vault, modify policies, or extract secrets. Secure your TameFlare deployment like any other production service.
- Third-party security audit — TameFlare has not yet been audited by a third party. We plan to commission an independent audit as the project matures. The full source code is available for your own review under the Elastic License v2.
Credential vault
The credential vault stores API keys for connectors (GitHub tokens, Stripe keys, OpenAI keys, etc.) encrypted on disk.
How it works
- Encryption: AES-256-GCM with a key derived from the gateway's master key
- Storage:
.TameFlare/credentials.enc— a single encrypted file - Access pattern: Gateway decrypts credentials in memory at startup, injects them into allowed requests at proxy time
- Agent isolation: Agents never see credentials. The proxy adds
Authorizationheaders (or equivalent) after permission checks pass.
Who can access credentials
| Actor | Can access? | How | |---|---|---| | Agent process | No | Agent only sees the proxy URL, never real API keys | | Gateway process | Yes | Decrypts vault at startup to inject credentials | | Root user on host | Yes | Can read the vault file and derive the key | | Dashboard user (admin) | Partial | Can add/remove credentials via wizard, but stored values are masked | | Dashboard user (viewer) | No | Cannot see or modify credentials |
Key loss recovery
If you lose the gateway's encryption key or the .TameFlare/credentials.enc file is corrupted:
- Credentials cannot be recovered
- Re-add credentials via
tf connector addor the dashboard wizard - The gateway will refuse to start if the vault file exists but cannot be decrypted
Control plane secrets
The control plane (Next.js) also encrypts integration secrets (Slack tokens, GitHub PATs configured via Settings) using AES-256-GCM when SETTINGS_ENCRYPTION_KEY is set:
- Each value encrypted with a unique IV
- Stored as
enc:<base64(iv + ciphertext + auth_tag)>in SQLite - If the key is lost, re-enter secrets in the dashboard
Data logging scope
What tf logs
| Data | Logged? | Where |
|---|---|---|
| Request URL and path | Yes | Traffic log (gateway SQLite) |
| HTTP method | Yes | Traffic log |
| Request headers | Selected only (Host, Content-Type, User-Agent) | Traffic log |
| Response status code | Yes | Traffic log |
| Latency (ms) | Yes | Traffic log |
| Parsed action type (e.g., github.pr.merge) | Yes | Traffic log + audit log |
| Decision (allow/deny/require_approval) | Yes | Traffic log + audit log |
| Agent name | Yes | Traffic log + audit log |
| Connector type | Yes | Traffic log |
| Approver identity and timestamp | Yes | Audit log |
What TameFlare does NOT log
| Data | Logged? | Reason |
|---|---|---|
| Request body | No | May contain PII, secrets, or sensitive business data |
| Response body | No | May contain PII or proprietary data |
| Authorization headers | No | Contains API keys (injected by proxy) |
| Cookie values | No | Session data |
| Query parameters | Partial (URL logged, but sensitive params like ?token= are not parsed) | Privacy |
Inter-component security
Localhost-only by default
Both the gateway and control plane listen on 127.0.0.1 by default. No traffic is exposed to the network unless you explicitly configure it.
Control plane ↔ Gateway communication
| Direction | Protocol | Auth |
|---|---|---|
| Gateway → Control plane (token verification) | HTTP | GATEWAY_SERVICE_TOKEN bearer token |
| Dashboard → Gateway (status, approvals) | HTTP | GATEWAY_SERVICE_TOKEN bearer token |
For remote deployments (gateway on a different machine), use HTTPS between components and set GATEWAY_URL to an HTTPS endpoint.
Agent isolation
- Each agent gets a dedicated proxy port (allocated by the gateway)
- An agent can only see its own traffic logs via the v1 API (scoped by API key)
- Agents cannot see other agents' actions, credentials, or permissions
- The dashboard shows all agents' activity (scoped by RBAC role)
Data residency
All data stays on your infrastructure. TameFlare self-hosted instances make zero outbound calls unless you explicitly configure integrations:
| Integration | What data is sent externally | When |
|---|---|---|
| None configured | Nothing. Zero outbound calls. | Always |
| Slack approvals | Action type, agent name, resource target, approval status | When an action requires approval |
| Webhook callbacks | Action type, decision, agent ID (configurable payload) | When webhook_url is set on an action request |
| Configured connectors | The proxied HTTP request itself (forwarded to the real API) | When an action is allowed |
Slack notifications do not include credentials, request/response bodies, or policy details.
SENTRY_DSN and POSTHOG_KEY env vars are disabled by default and only activate if you explicitly set them.Fail-closed model
TameFlare uses a fail-closed (deny-by-default) security model:
| Scenario | Behavior |
|---|---|
| No connector configured for a domain | Request blocked (403) |
| No permission rule matches | Request denied (default deny) |
| Policy engine encounters an error | Action denied |
| Gateway cannot reach control plane | Proxy mode: gateway operates independently using local permissions. SDK mode: action denied. |
| Approval timeout (5 minutes) | Request denied (408) |
| Kill switch active | All matching traffic blocked immediately |
| Rate limit exceeded | Request rejected (429) with Retry-After header |
| Invalid or expired decision token | Execution rejected |
| Nonce already used | Execution rejected (replay protection) |
There is no "fail-open" mode. If TameFlare cannot make a decision, the default is deny.
Cryptographic design
Decision tokens (ES256)
TameFlare uses ES256 (ECDSA with P-256 curve and SHA-256) for decision tokens, implemented via the jose library.
Token lifecycle:
- Agent requests an action via
POST /api/v1/actions - Policy engine evaluates and returns a decision
- If
alloworrequires_approval(after approval), the control plane signs a JWT containing:action_request_id— the specific action this token authorizesaction_spec_hash— SHA-256 hash of the canonical action spec JSONnonce— unique random value for replay protectioniat/exp— issued-at and expiration (5 minutes)
- Agent sends the token to
POST /api/v1/actions/:id/execute - Gateway forwards the token to the control plane for verification
- Control plane verifies signature, checks expiry, validates nonce (marks as used), and confirms action hash matches
Key management:
- ES256 key pair is generated on first boot and stored in the database
- The private key never leaves the control plane process
- There is currently no automatic key rotation — rotate manually by generating a new key pair and restarting
Key loss recovery
If the ES256 private key is lost:
- All outstanding (unused) decision tokens become unverifiable
- Generate a new key pair and restart the control plane
- Agents will need to re-request any pending actions to get new tokens
- Historical audit data is unaffected — tokens are verified at execution time, not retroactively
Encryption at rest
Settings encryption
When SETTINGS_ENCRYPTION_KEY is set, TameFlare encrypts sensitive settings (Slack tokens, GitHub PATs, signing secrets) using AES-256-GCM before storing them in the database.
Setup:
# Generate a 256-bit key
openssl rand -hex 32
# Set in your environment
SETTINGS_ENCRYPTION_KEY=your_64_char_hex_key_hereHow it works:
- Each value is encrypted with a unique IV (initialization vector)
- Encrypted values are stored as
enc:<base64(iv + ciphertext + auth_tag)> - Decryption happens in-memory when settings are read
- If the key is lost, encrypted settings cannot be recovered — re-enter them in the dashboard
Database
The SQLite database file is not encrypted by default. For production deployments:
- Use filesystem-level encryption (LUKS, FileVault, BitLocker)
- Or use Turso (hosted libSQL) which encrypts data at rest
- Restrict file permissions:
chmod 600 local.db
Authentication
Agent authentication
Agents authenticate via API keys in the Authorization: Bearer <key> header.
- Keys are generated with a cryptographically random value
- Only the bcrypt hash is stored in the database
- Keys are prefixed (
aaf_test_oraaf_live_) for easy identification - The prefix is stored separately for lookup without exposing the full key
- Keys are shown once at creation — they cannot be retrieved later
User authentication
Dashboard users authenticate via email/password:
- Passwords are hashed with bcrypt (cost factor 10)
- Sessions are stored in the database (not in-memory) and survive restarts
- Session tokens are cryptographically random, stored in HTTP-only cookies
- Legacy mode:
DASHBOARD_PASSWORDenvironment variable for shared access
Rate limiting
- 120 requests per minute per agent in proxy mode, 60 req/min in SDK mode (sliding window)
- Returns
429 Too Many RequestswithRetry-Afterheader - The Node.js SDK auto-retries on 429 with exponential backoff
- Rate limit state is in-memory (resets on restart)
Policy engine safety
The policy engine is not Turing-complete. It evaluates a fixed set of condition operators against action spec fields:
- No loops, recursion, or arbitrary code execution
- Maximum nesting depth for
any/allcombinators is bounded by YAML structure - Evaluation is O(policies × rules × conditions) — linear, not exponential
- A malformed policy cannot crash the engine — parse errors are caught and the policy is skipped
Operational security checklist
| Item | Status | Action |
|---|---|---|
| SETTINGS_ENCRYPTION_KEY set | Required for production | openssl rand -hex 32 |
| SIGNING_KEY_PRIVATE/PUBLIC set | Required for production | See Environment Variables |
| HTTPS enabled | Required for production | Use a reverse proxy (nginx, Caddy) with TLS |
| ca.key not in version control | Required | Already in .gitignore — verify with git status |
| Database file permissions | Recommended | chmod 600 local.db |
| Credential vault permissions | Recommended | chmod 600 .TameFlare/credentials.enc |
| Dashboard password or user auth | Required | Set DASHBOARD_PASSWORD or register users at /register |
| Rate limiting | Enabled by default | 120 req/min per agent (proxy), 60 req/min (SDK) |
| Kill switch tested | Recommended | Toggle on/off in Settings to verify it works |
| Audit log retention | Recommended | Set AUDIT_RETENTION_DAYS and run cleanup job |
| Backup strategy | Recommended | Copy .db file or use Turso |
Responsible disclosure
If you discover a security vulnerability in TameFlare, please report it responsibly:
- Email: security@tameflare.com
- Do not open a public GitHub issue for security vulnerabilities
- We will acknowledge receipt within 48 hours and provide a timeline for a fix
- See
SECURITY.mdfor the full disclosure policy, response timeline, and scope
Third-party security audit
TameFlare has not yet undergone a third-party security audit. This is planned and will be prioritized as the project matures.
| Item | Status | |---|---| | Third-party penetration test | Planned — not yet scheduled | | SOC 2 compliance | Not applicable (self-hosted, no cloud service) | | Code audit by external firm | Planned — seeking firms with Go + Node.js expertise | | Bug bounty program | Not yet — will consider after first audit |
What we do today
- Source-available code — the entire codebase is inspectable. You can audit it yourself or hire a firm to audit it.
- SECURITY.md — responsible disclosure process with 48h acknowledgment SLA
- Dependency scanning — GitHub Dependabot alerts enabled
- 51 unit tests for the policy engine (conditions, evaluator, nested combinators)
- Integration test scripts —
dogfood.js(99 tests),dogfood-gateway.js(81 tests),simulate-new-user.js(40 tests)
Test coverage
| Component | Tests | Type |
|---|---|---|
| Policy engine (conditions) | 14 tests | Unit (vitest) |
| Policy engine (evaluator) | 37 tests | Unit (vitest) |
| API routes | 99 tests | Integration (dogfood.js) |
| Gateway proxy | 81 tests | Integration (dogfood-gateway.js) |
| New-user flow | 40 tests | End-to-end (simulate-new-user.js) |
| Real agent (7 tasks) | 7 tests | Integration (test-TameFlare-agent/) |
Security-specific test coverage:
- Auth bypass — tested in dogfood (invalid keys, expired sessions, missing headers)
- RBAC enforcement — tested per role (owner, admin, member, viewer)
- Kill switch — tested activation, scoping, and deactivation
- Rate limiting — tested 429 responses and Retry-After headers
- Nonce replay — tested token reuse rejection
- Zod validation — tested malformed payloads, missing fields, extra fields
Next steps
- Deployment — production deployment guidance
- Core Concepts — understand tokens, enforcement levels, and the deny-wins model
- Troubleshooting — common issues and solutions
- Support — get help and report issues