activeloopai / activeloopai/hivemind

Security: harden admin notification endpoint before shipping (audit log, sanitation, fanout gating, signing)

Aberta
#131 0 comentários 0 reações 1 responsável Reivindicada por @kaghni Ver no GitHub
Linguagem predominante
TypeScript
Estrelas
1.6k
Forks
107
Merge médio
17h 30min
PRs com merge (30d)
6

Descrição

## Problem

When we ship the admin endpoint (`POST /admin/notifications` — currently deferred in #99 v1.5), it becomes the single most security-sensitive surface in hivemind: a successful broadcast lands in every user's Claude context simultaneously. Compromise of this endpoint = mass prompt-injection vector.

The companion issue (frame backend content as untrusted in `additionalContext`) raises the bar on what an attacker can DO with that vector. This issue covers everything that raises the bar on REACHING the vector in the first place + makes compromise traceable.

## Requirements before the admin endpoint ships

### 1. Separate admin-scoped auth

- Admin actions require an admin-scoped JWT (or distinct API token type), NOT a regular user token
- Tokens have explicit "notifications:write" scope; non-admin users' tokens lack it
- Token issuance is gated (manual review, not self-serve)

Compromise of any single regular user's credentials must not allow a broadcast.

### 2. Audit log

New table `notification_admin_log`:
```sql
CREATE TABLE notification_admin_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
notification_id UUID NOT NULL REFERENCES notifications(id),
actor_user_id UUID NOT NULL REFERENCES users(id),
target_spec JSONB NOT NULL, -- "all_users" | {"user_ids": [...]} | {"org_ids": [...]}
fanout_count INT NOT NULL,
body_full TEXT NOT NULL, -- full body for forensic review
dry_run BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

Every INSERT into `notifications` via the admin endpoint also writes here. Direct SQL INSERTs bypass this (see #5 below for the mitigation).

### 3. Mandatory dry-run for fanout > N

For any broadcast targeting `all_users` or any org with > N members (suggest N=10):
1. First request MUST be `dry_run=true`, returns `{fanout_count, sample_user_ids: [3]}`
2. Second request (within 5 min of dry-run) with `dry_run=false` actually inserts
3. The second request must include a `confirm_fanout_count` matching what dry-run returned

Stops the "fat-finger broadcast to all users by accident" failure mode AND forces an explicit pause before high-blast-radius operations.

### 4. Two-person approval for `all_users` broadcasts

`all_users` broadcasts require a separate approval token from a second admin. Implementation options:
- Slack approval workflow (admin1 posts the dry-run preview, admin2 reacts with ✅, generates approval token)
- Web UI approval queue
- For v1: simpler — require BOTH a "drafter" admin token AND an "approver" admin token in the request; they must be different users

### 5. Content sanitation at INSERT time

Rejected automatically (HTTP 400, audit log entry with rejection reason):
- Length: title > 180 chars OR body > 1000 chars
- Unicode: control chars (U+0000-U+001F except `\n`, `\t`), zero-width joiners, BiDi override chars
- Injection-trigger phrases (case-insensitive substring match): `ignore previous`, `disregard above`, `you are now`, `new instructions`, `system:`, `` (don't let attacker close our frame from the other issue)
- Markdown/HTML that could break the terminal renderer

Layered with the framing in the companion issue, this is meaningful defense in depth. Alone, it's whack-a-mole.

### 6. Rate limits

Per admin actor:
- Max 10 INSERTs per hour
- Max 3 `all_users` broadcasts per day
- Cooldown of 1h between `all_users` broadcasts

Doesn't stop a determined attacker but slows automated abuse + buys detection time.

### 7. Outbound notification signing (medium term — separate sub-task)

Backend signs every notification body with a key embedded in the hivemind client. Client refuses to render unsigned rows.

Defends against the threat model where someone gets direct SQL access to the `notifications` table but doesn't have the admin endpoint credentials. Doesn't help if the backend itself is fully compromised (signing key leaks too) — but raises cost significantly for the "exfil-then-INSERT" attacker.

Implementation: Ed25519 keypair, public key compiled into hivemind, private key in deeplake-api env. Signature column on `notifications`. Client-side check in `src/notifications/sources/backend.ts:toClient`.

### 8. Monitoring + alerts

- PostHog event on every admin notification INSERT (already easy given the existing PostHog integration)
- Alert on: any `all_users` broadcast, any broadcast outside business hours, any broadcast from a new admin actor, any broadcast > N chars
- Daily digest to `#hivemind-admin-audit` Slack channel summarizing broadcasts

## Why this is a hard blocker for the admin endpoint

Shipping `POST /admin/notifications` without these layers means:
- A leaked admin token = mass prompt injection on every hivemind user
- Direct SQL access from anyone with `notifications` write = same
- No audit trail = no forensic recovery
- No rate limits = automated abuse possible

The companion issue (frame untrusted content) constrains the attack EFFECT. This issue constrains the attack PATH. Both need to land before the endpoint goes live.

## Out of scope

- Capability gates on what Claude can DO when context contains untrusted notification content (refuse curl with credentials, refuse rm -rf, etc.). That's a separate, heavier workstream — closer to "CSP for AI agents". Filed under #99 hivemind v2 if appropriate, NOT a blocker for the admin endpoint.

## Related

- Companion: framing backend content as untrusted in additionalContext (separate issue)
- Tracker: hivemind#99 (admin endpoint deferred work)

Guia de contribuição

Nenhum guia de contribuição indexado para este repositório

Avaliação

Esta issue ainda não foi avaliada.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.