# Security Audit — Attack Vectors

Living document tracking external-facing attack surfaces in rivus.

## Telegram Bot (`lib/telegram/`)

**Added**: 2026-03-26
**Bot**: @rivus_official_bot
**Exposure**: Public — anyone with the bot username can message it

### Attack Surface

| Vector | Risk | Mitigation |
|--------|------|------------|
| **Bot token leak** | Full bot control — send messages as bot, read all incoming messages | Token in `~/.config/rivus/env` (not committed). Also in `~/.telegram.key`. Rotate via @BotFather `/revoke` if compromised |
| **Unsolicited messages** | Anyone can message the bot — spam, phishing, social engineering | Poller stores all messages but only notifies locally. No auto-reply. Future: allowlist by chat_id |
| **Message injection** | Malicious content in messages could be rendered unsafely in HTML reports or UIs | Messages stored as plain text. Escape before rendering in HTML contexts |
| **Impersonation** | Someone claiming to be a known contact | Telegram user IDs are immutable — verify by chat_id, not display name. Aliases bind to chat_id, not name |
| **Poller availability** | If poller is down, messages queue on Telegram side (24h retention) | Not critical — messages aren't lost, just delayed. Launchd daemon for persistence (future) |
| **SQLite DB exposure** | Conversation history in `lib/telegram/data/telegram.db` | Local file, not served. In `.gitignore`. Standard filesystem permissions |
| **Automated messaging abuse** | Code that calls `send_message` could spam contacts | Rate limiting not implemented. Telegram's own rate limits apply (30 msgs/sec to different chats). Future: add rate limiter |

### Identity Verification

Telegram provides per-message:
- `user.id` — immutable numeric ID, assigned at account creation
- `user.first_name`, `user.last_name` — set by user, can change
- `user.username` — optional, can change

**Trust**: `user.id` (chat_id) is the only reliable identifier. Display names and usernames can change. Alias system binds to chat_id.

### Credentials

| Credential | Location | Rotation |
|------------|----------|----------|
| `TELEGRAM_BOT_TOKEN` | `~/.config/rivus/env`, `~/.telegram.key` | @BotFather → `/revoke` → `/token` |
| `TELEGRAM_NOTIFY_CHAT_ID` | `~/.config/rivus/env` | N/A — Tim's own chat_id |

### Hardening TODO

- [ ] **Injection detection + silent quarantine**: On any detected injection attempt (prompt injection, command injection, suspicious payloads), immediately: (1) stop processing messages from that user, (2) silently mute the entire channel/chat for a configurable timeout (default 1h), (3) alert admin via Pushover/Telegram (separate from the quarantined channel), (4) log the raw payload for forensics (escaped/wrapped so it can't execute during review). Do NOT announce the quarantine to the attacker — silently drop messages and return no error. Applies to all inbound channels (Telegram, Slack, future).
- [ ] **Payload deobfuscation at boundary**: Before injection scanning, decode all common evasion layers — base64, gzip, URL encoding, unicode normalization, HTML entities, mixed-case tricks, zero-width characters, RTL overrides. Scan the decoded content, not the raw wire format. Attacker can chain encodings (base64-of-gzip, nested URL encoding), so decode iteratively until stable.
- [ ] **Inoculated storage for attack payloads**: NEVER store attack payloads raw. Raw storage is itself an attack vector — any system that later reads the forensic log (LLM, UI, admin tool, grep) becomes a secondary victim via the same injection. Payloads must be inoculated before storage: neutralized so they are readable by humans for forensic review but cannot execute in any downstream consumer. Design needed for the inoculation format (e.g., escaped + wrapped in inert container, or decomposed into safe metadata). This is a design task — do not implement naively.
- [ ] Allowlist: only accept messages from known chat_ids, ignore others
- [ ] Rate limit outbound messages per chat_id
- [ ] Auto-expire bot sessions (periodic token rotation)
- [ ] Audit log for all outbound messages (like push_audit.jsonl)
- [ ] Remove `~/.telegram.key` after token is in env (redundant copy)

---

## Slack (prepmind workspace)

**Added**: 2026-03-26
**Workspace**: prepmind (predmachine era)
**Credentials**: `~/.slack.yaml` — user tokens, bot tokens, webhooks
**Status**: Unknown — tokens may be expired. Need to test.

| Credential | Type | Notes |
|------------|------|-------|
| `tchklovski` user token | `xoxp-...` | Full user access to workspace |
| `vic_bot` bot token | `xoxb-...` | Bot access |
| `vic_bot` webhook | `hooks.slack.com/...` | One-way posting to channel |
| `live_trade_bot` | bot + webhook | Trading notifications |
| `paper_trade_bot` | bot + webhook | Paper trading notifications |
| `trade_notification_app` | OAuth app | Client ID + secret |

### TODO

- [ ] Test if tokens/webhooks still work
- [ ] If active, add webhook to APPRISE_URLS for one-way notifications
- [ ] Rotate or revoke any unused tokens

---

## Cloudflare Tunnels

**Added**: 2026-03-26 (placeholder)

Tunnels expose internal services externally. Protected by Cloudflare Access.

- See `infra/README.md` for tunnel configuration
- Access allowlist managed via `/access` skill

---

## Apprise / Pushover

**Added**: 2026-03-26 (placeholder)

| Credential | Location |
|------------|----------|
| `APPRISE_URLS` | `~/.config/rivus/env` |

Pushover token in URL grants send access to Tim's Pushover account. No inbound vector.

---

## Vario API (`vario-api.jott.ninja`)

**Added**: 2026-03-31
**Endpoint**: `https://vario-api.jott.ninja/v1/chat/completions`
**Exposure**: Public — OpenAI-compatible API, no CF Access gate

### Attack Surface

| Vector | Risk | Mitigation |
|--------|------|------------|
| **API key leak** | Full API access — caller can run any model at our expense | Keys are `sk-vario-{name}-{token}` format, SQLite-backed with per-key budget tracking. Revoke individual keys without affecting others |
| **Budget exhaustion** | Attacker or runaway client burns through API credits | Per-key budget limits enforced server-side. Budget tracking in SQLite |
| **Brute-force key guessing** | Enumerate valid API keys | Token portion is random — low probability. No rate limit on auth failures yet (TODO) |
| **CF Access bypass** | No OAuth gate — anyone with the URL can attempt requests | By design — API clients authenticate via Bearer token, not browser OAuth |
| **WAF skip** | Bot protection disabled for this subdomain — no Cloudflare bot/AI scraper filtering | Application-level Bearer auth is the primary defense. WAF skip is required because legitimate API clients (curl, SDKs) trigger bot detection |
| **Prompt injection via API** | Malicious prompts sent through the API | Same as any LLM API — vario passes prompts to upstream models. No special mitigation beyond upstream model guardrails |
| **Model enumeration** | Attacker discovers available models via `/v1/models` | Low risk — model list is not sensitive. Auth required for all endpoints |

### API Key Lifecycle

1. **Create**: `vario keys create --name <client> --budget <usd>` — generates `sk-vario-{name}-{token}`
2. **Budget**: Per-key spend tracked in SQLite. Requests rejected when budget exhausted
3. **Revoke**: `vario keys revoke <name>` — immediately invalidates the key
4. **Audit**: `vario keys list` — shows all keys with spend/budget status

### Credentials

| Credential | Location | Rotation |
|------------|----------|----------|
| Vario API keys | SQLite DB (`vario/api/data/keys.db`) | Revoke + recreate via CLI |
| CF Access bypass policy | CF Zero Trust dashboard | App ID: `17cff93b-451a-40c8-bda5-655517a1857c` |
| WAF skip rule | CF Firewall dashboard | Custom rule on `jott.ninja` zone |

### Hardening TODO

- [ ] Rate limit on failed auth attempts (prevent key brute-force)
- [ ] Alert on unusual spend patterns (sudden spike from a key)
- [ ] IP allowlist option per key (restrict key usage to known IPs)
- [ ] Audit log for all API requests (caller, model, tokens, cost)

---

*Add new sections as external integrations are added.*
