activeloopai / activeloopai/hivemind

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

未关闭
#131 0 条评论 0 个 reaction 已指派 1 人 已被 @kaghni 认领 在 GitHub 查看
主要语言
TypeScript
星标
1.6k
派生
107
平均合并
17 小时 30 分钟
30 天内合并 PR
6

描述

## 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)

贡献指南

这个仓库没有索引到贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。