# Secrets & Security

> How Aerostack encrypts, stores, and injects MCP server secrets. AES-GCM encryption, per-server scoping, header injection, and key rotation.

Every MCP server connected to Aerostack — whether hosted, proxied, or installed from the Hub — can have secrets (API keys, tokens, credentials) that are needed to call external APIs. Aerostack encrypts these secrets at rest and injects them at request time. Team members who connect via workspace tokens never see the raw values.

---

## How Secrets Work

```mermaid
flowchart LR
    subgraph Store["At Rest"]
        A["You set a secretaerostack secrets set github GITHUB_TOKEN ghp_xxx"]
        B["AES-256-GCMEncrypted + stored inmcp_workspace_secrets table"]
    end

    subgraph Runtime["At Request Time"]
        C["Team member callsgithub__create_issue"]
        D["Gateway decrypts secret"]
        E["Injects as HTTP headerAuthorization: Bearer ghp_xxx"]
        F["Forwards to MCP server"]
    end

    A --> B
    C --> D --> E --> F
```

### Encryption Details

| Property | Value |
|----------|-------|
| **Algorithm** | AES-256-GCM |
| **Storage** | `mcp_workspace_secrets` table in the database |
| **Encryption key** | Per-project `WORKFLOW_SECRETS_KEY` (never stored in the database) |
| **IV** | Unique random IV per secret value |
| **Auth tag** | 128-bit GCM authentication tag (integrity + authenticity) |

Secrets are encrypted before they reach the database. The encryption key (`WORKFLOW_SECRETS_KEY`) is stored as a Cloudflare Worker secret, separate from the database.

---

## Per-Server Scoping

Secrets are scoped to a specific MCP server within your workspace. This means:

- Secrets set for `github-mcp` are **only** sent to the GitHub MCP server
- They are **never** sent to `slack-mcp`, `stripe-mcp`, or any other server in the same workspace
- Even if an MCP server is compromised, it cannot access secrets belonging to other servers

The scoping is enforced by the `inject_secrets` configuration, which maps secret names to specific MCP server slugs.

```bash
# These secrets are isolated from each other
aerostack secrets set github-mcp GITHUB_TOKEN "ghp_xxx"     # Only sent to github-mcp
aerostack secrets set stripe-mcp STRIPE_KEY "sk_live_xxx"    # Only sent to stripe-mcp
aerostack secrets set slack-mcp  SLACK_TOKEN "xoxb-xxx"      # Only sent to slack-mcp
```

---

## Setting Secrets

### Via CLI

```bash
# Basic usage
aerostack secrets set <mcp-slug>  "<value>"

# Examples
aerostack secrets set github-mcp GITHUB_TOKEN "ghp_xxxxxxxxxxxx"
aerostack secrets set postgres-mcp DATABASE_URL "postgres://user:pass@host:5432/db"
aerostack secrets set openai-mcp OPENAI_API_KEY "sk-proj-xxxxxxxxxxxx"
```

### Via Admin Dashboard

1. Navigate to **MCP Servers** > select your server > **Secrets**
2. Click **Add Secret**
3. Enter the name and value
4. Click **Save**

The value is encrypted immediately. After saving, the dashboard shows the secret name but never the raw value.

### Via API

```bash
curl -X POST https://api.aerostack.dev/api/v1/admin/mcp-servers/{serverSlug}/secrets \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "GITHUB_TOKEN", "value": "ghp_xxxxxxxxxxxx"}'
```

---

## Secret Injection

When a tool call reaches the gateway, secrets are decrypted and injected into the outbound request to the MCP server. You control how each secret is injected.

### As HTTP Headers (Default)

By default, each secret is injected as an HTTP header with the secret name as the header key:

```
Secret: GITHUB_TOKEN = "ghp_xxx"
Injected as: GITHUB_TOKEN: ghp_xxx
```

### As a Custom Header

Map a secret to a specific header name:

```bash
aerostack secrets inject github-mcp GITHUB_TOKEN --header "Authorization"
# Injected as: Authorization: ghp_xxx

aerostack secrets inject my-api API_KEY --header "X-API-Key"
# Injected as: X-API-Key: sk-xxx
```

### As Bearer Auth

A shorthand for injecting as `Authorization: Bearer <value>`:

```bash
aerostack secrets inject github-mcp GITHUB_TOKEN --bearer
# Injected as: Authorization: Bearer ghp_xxx
```

### Viewing Injection Rules

```bash
aerostack secrets list github-mcp
# WEBHOOK_SECRET   X-Webhook-Secret    2026-03-10
```

The `secrets list` command shows secret names and injection rules, but never the raw values. There is no command to retrieve a decrypted secret value. If you lose a key, set a new one.

---

## Key Rotation

Rotating a secret is a single command. Every team member gets the new key automatically on their next tool call — no notification, no `.env` file updates, no downtime.

1. **Update the secret**

   ```bash
   aerostack secrets set github-mcp GITHUB_TOKEN "ghp_new_token_here"
   # -> Secret updated. Takes effect on next tool call.
   ```

1. **Verify (optional)**

   Make a tool call through the gateway and confirm it works with the new key. The old key is overwritten and cannot be recovered.

That is the entire process. Compare this to rotating a key shared with 30 engineers via `.env` files.

---

## Deleting Secrets

```bash
# Delete a specific secret
aerostack secrets delete github-mcp GITHUB_TOKEN

# Delete all secrets for an MCP server (e.g., when removing the server)
aerostack secrets delete github-mcp --all
```

Deleting a secret removes it from the encrypted store. The MCP server will no longer receive it in subsequent requests.

---

## Security Model

### What team members can see

| Information | Visible to team members? |
|-------------|-------------------------|
| Workspace gateway URL | Yes |
| Their own `mwt_` token | Yes (shown once at creation) |
| Secret names (e.g., `GITHUB_TOKEN`) | No |
| Secret values (e.g., `ghp_xxx`) | No |
| Tool call results | Yes (their own calls only) |
| Analytics (who called what) | Workspace admins only |

### What workspace admins can see

| Information | Visible to admins? |
|-------------|-------------------|
| Secret names | Yes |
| Secret values | No (encrypted, no retrieval API) |
| All team members' analytics | Yes |
| Token list and status | Yes |

### Attack surface

| Threat | Mitigation |
|--------|-----------|
| Compromised developer laptop | Revoke their `mwt_` token. Production keys untouched. |
| Database breach | Secrets are AES-256-GCM encrypted. Attacker gets ciphertext only. |
| MCP server compromise | Per-server scoping. Compromised server only has its own secrets. |
| Stolen workspace token | Revoke the token. Issue a new one. No key rotation needed. |
| Insider threat (admin) | Admins cannot retrieve raw secret values. Only set/overwrite. |

---

## Best Practices

1. **Use per-server secrets.** Do not reuse the same API key across multiple MCP servers. If one server is compromised, only its secrets are exposed.

2. **Rotate keys periodically.** Aerostack makes rotation painless — update once, effective everywhere.

3. **Use least-privilege API keys.** When creating API keys for MCP servers, grant only the permissions the server needs. A GitHub MCP server does not need admin access — `repo` scope is usually sufficient.

4. **Monitor analytics.** Review per-user tool call logs regularly. Unusual patterns (e.g., a user making 10,000 API calls overnight) may indicate a compromised token.

5. **Revoke tokens promptly.** When a team member leaves or changes roles, revoke their workspace token immediately.

6. **Separate workspaces by trust level.** Create different workspaces for different access levels. Senior engineers get the workspace with deploy tools. Junior engineers get the read-only workspace.

---

## Next Steps

- [Team management and access control](/mcp/team-management)
- [Proxy your existing MCP server](/mcp/proxy-existing) with secret injection
- [Install from Hub](/mcp/install-from-hub) with automatic secret prompts
