Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion STATUS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Infrastructure Status

Last Updated: 2026-05-30 16:34 UTC
Last Updated: 2026-05-30 16:40 UTC

## 🖥️ MCP Servers
| Server | Status | Latency |
Expand Down
1 change: 1 addition & 0 deletions replicas-matrix-bridge/src/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ function replicasHeaders(env: Env): HeadersInit {
return {
Authorization: `Bearer ${env.REPLICAS_API_KEY}`,
"Replicas-Org-Id": env.REPLICAS_ORG_ID,
"Content-Type": "application/json",
};
}

Expand Down
50 changes: 50 additions & 0 deletions replicas-matrix-bridge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,42 @@ export interface Env {
// wrangler.toml for the Claude Max $200 plan; override per-account.
USAGE_QUOTA_5H_TOK?: string;
USAGE_QUOTA_7D_TOK?: string;
// Bearer token gating /admin/*, /debug/*, and /dispatch. When unset the
// endpoints fall through with a warning log (migration mode) so existing
// operator curl flows keep working. Once set, every protected route
// requires `Authorization: Bearer ${ADMIN_TOKEN}`. Set via
// `wrangler secret put ADMIN_TOKEN`.
ADMIN_TOKEN?: string;
}

// Constant-time string compare to avoid leaking ADMIN_TOKEN length / prefix
// via response-time differences. Falls back to a length-mismatch fast path
// (both branches still walk both strings).
function timingSafeEqual(a: string, b: string): boolean {
const la = a.length;
const lb = b.length;
const len = Math.max(la, lb);
let diff = la ^ lb;
for (let i = 0; i < len; i++) {
const ca = i < la ? a.charCodeAt(i) : 0;
const cb = i < lb ? b.charCodeAt(i) : 0;
diff |= ca ^ cb;
}
return diff === 0;
}

function requireAuth(req: Request, env: Env, pathname: string): Response | null {
if (!env.ADMIN_TOKEN) {
console.log(`[auth] WARNING: ${pathname} called without ADMIN_TOKEN configured — allowing (migration mode). Set ADMIN_TOKEN secret to lock down.`);
return null;
}
const header = req.headers.get("Authorization") ?? "";
const match = /^Bearer\s+(.+)$/i.exec(header);
const presented = match?.[1]?.trim() ?? "";
if (!presented || !timingSafeEqual(presented, env.ADMIN_TOKEN)) {
return new Response("unauthorized", { status: 401 });
}
return null;
}

export { ReplicaPoller } from "./poller";
Expand All @@ -51,6 +87,20 @@ export default {
return new Response("ok");
}

// Gate every privileged path. /health stays public; everything else
// (admin/debug + dispatch + start-listener) requires ADMIN_TOKEN
// once it's set. While unset, the helper logs a warning and lets
// the request through so we don't break operator flows mid-migration.
const isProtected =
url.pathname.startsWith("/admin/") ||
url.pathname.startsWith("/debug/") ||
url.pathname === "/dispatch" ||
url.pathname === "/start-listener";
if (isProtected) {
const denied = requireAuth(req, env, url.pathname);
if (denied) return denied;
}

if (req.method === "POST" && url.pathname === "/start-listener") {
const stub = env.LISTENER.get(env.LISTENER.idFromName("global"));
await stub.fetch("https://listener/start", { method: "POST" });
Expand Down
33 changes: 29 additions & 4 deletions replicas-matrix-bridge/src/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,16 @@ export class MatrixListener {

if (ev.type === "m.room.message") {
const content = ev.content ?? {};
// Skip m.replace edits. When a user edits a prior message
// the homeserver emits a fresh m.room.message whose body
// is the fallback `* <new content>` string and whose
// m.relates_to.rel_type === "m.replace". Treating this
// like a new prompt would spawn a turn whose body starts
// with `* ` and waste credits. The user's intent was to
// amend, not re-ask. Future enhancement: cancel the
// in-flight turn + relaunch with the edited content.
const rel = (content as { "m.relates_to"?: { rel_type?: string } })["m.relates_to"];
if (rel?.rel_type === "m.replace") continue;
msgtype = content.msgtype as string | undefined;
body = content.body as string | undefined;
} else if (ev.type === "m.room.encrypted") {
Expand Down Expand Up @@ -370,9 +380,12 @@ export class MatrixListener {
const { plaintext } = await decryptMegolm(sessionKey, ciphertext);
const inner = JSON.parse(plaintext) as {
type?: string;
content?: { msgtype?: string; body?: string };
content?: { msgtype?: string; body?: string; "m.relates_to"?: { rel_type?: string } };
};
if (inner.type !== "m.room.message") return undefined;
// Skip m.replace edits in the encrypted path too — see the
// matching check in the unencrypted branch above.
if (inner.content?.["m.relates_to"]?.rel_type === "m.replace") return undefined;
return {
msgtype: inner.content?.msgtype ?? "",
body: inner.content?.body ?? "",
Expand Down Expand Up @@ -570,13 +583,25 @@ export class MatrixListener {
const r = await fetch(url, {
headers: { Authorization: `Bearer ${this.env.MATRIX_ACCESS_TOKEN}` },
});
if (!r.ok) return 2; // fail-open to "treat as DM" so we don't go silent
if (!r.ok) {
// Fail CLOSED on cold-cache lookup failure. Prior fail-open
// (return 2) treated unknown rooms as DMs, so a transient 401
// or rate-limit during the first message in a 50-person
// channel caused the bot to dispatch on every message until
// the next successful lookup — empirically observed as
// "bot exploded into a group chat" complaints. Returning a
// large sentinel keeps it quiet until mention rules pass or
// the count is genuinely known.
console.log(`[listener] joined_members ${r.status} for ${roomId} — fail-closed`);
return 100;
}
const j = (await r.json()) as { joined?: Record<string, unknown> };
const n = Object.keys(j.joined ?? {}).length;
await this.env.MAP.put(cacheKey, String(n), { expirationTtl: 3600 });
return n;
} catch {
return 2;
} catch (e) {
console.log(`[listener] joined_members threw for ${roomId}: ${e instanceof Error ? e.message : e} — fail-closed`);
return 100;
}
}

Expand Down
1 change: 1 addition & 0 deletions replicas-matrix-bridge/src/poller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,7 @@ function replicasHeaders(env: Env): HeadersInit {
return {
Authorization: `Bearer ${env.REPLICAS_API_KEY}`,
"Replicas-Org-Id": env.REPLICAS_ORG_ID,
"Content-Type": "application/json",
};
}

Expand Down