[integrations] REST API gateway#201
Conversation
|
Conflicts with |
… variants
JavaScript Number loses precision beyond 2^53. All ID parsing now uses
string validation instead of Number(). Added extractThoughtId() to
handle all upsert_thought RPC response shapes (scalar, {id}, {thought_id}).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Why: === short-circuits at the first differing byte. For a public Edge Function deployed with --no-verify-jwt where MCP_ACCESS_KEY is the only auth layer, a remote attacker can use response-latency differences to byte-wise discover the key. Replace with XOR-accumulate over equal-length byte arrays so comparison time is independent of where inputs differ.
Why: The gateway is deployed with --no-verify-jwt so MCP_ACCESS_KEY is the only auth layer. Allow-Origin: * combined with write methods let any origin attempt a cross-origin call with a leaked key, and the complete absence of throttling meant a single leaked key could burn Supabase row quota and OpenRouter credits at fetch speed. - CORS_ALLOWED_ORIGINS env var (comma-separated) restricts to an explicit allowlist; unset keeps legacy * for backward compatibility. - RATE_LIMIT_PER_MIN env var (default 100) enforces a rolling 60s per-key cap with SHA-256 hashed bucket keys and 429 + Retry-After. - README Security section documents both settings and their defaults.
Why: /ingest and /ingestion-jobs/:id/execute forwarded request bodies to smart-ingest with no AbortSignal, no size limit, and a bare response.json() call. Three failure modes: hung upstream holds a worker slot until the 150s edge timeout kills it; an attacker can POST a 50 MB blob that the Edge Function will parse, stringify, and forward; an HTML/text error page from Supabase causes response.json() to throw a SyntaxError that the top-level catch mislabels as 'Invalid JSON in request body' — pointing the caller at their own payload instead of the real upstream failure. - readJsonWithCap() rejects bodies over 1 MB with 413 before parsing. - proxyFetchJson() uses AbortController with 60s timeout, distinguishes upstream_timeout (504), upstream_unreachable (502), upstream_invalid_json (502 + raw text snippet), and upstream_error (passes upstream status) from legitimate JSON responses.
Why: The sort query string was forwarded verbatim to PostgREST's order param. The service role can read every column on thoughts including embedding (1536-dim vector — heavy scan) and sensitivity_reasons (JSONB with PII-adjacent strings). An attacker could enumerate the schema, force unindexed scans, or partially exfiltrate metadata through sort order of returned rows. Allowlist: id, created_at, updated_at, importance, quality_score. Unknown sort values return 400 with the valid options.
Why: String(error) on a PostgrestError or an Error wrapped with a
PostgREST message leaked internal SQL text — table names, column names,
and constraint names — to the caller. Under service-role access,
constraint errors include data values. That is an info-leak vector for
any authenticated attacker.
Return a stable opaque payload: { error: 'internal_error', code:
'GENERIC', error_id: <uuid> }. Log the full error server-side tagged
with the same UUID so operators can correlate without exposing internals.
Why: PUT /thought/:id only refreshed content, embedding, type, and importance. A caller could rewrite a standard thought to include a credit-card number, SSN, or health identifier and the sensitivity_tier would stay at standard — so the thought would still be returned in exclude_restricted=true queries and in semantic search without filtering. Now runs detectSensitivity on the new content and applies resolveSensitivityTier against the existing tier (escalation-only). Writes sensitivity_tier + sensitivity_reasons into the update only when the tier actually changes. Adds an opt-in force_sensitivity flag that lets the caller bypass escalation-only semantics — but even with the flag the result is clamped to at least what detection returned, so force_sensitivity cannot hide detected PII.
8a3d698 to
a77389b
Compare
README: adds the "Gemini bulk history sync (Phase B/C)" section covering how the debugger-based capture works end-to-end, the "Debugging this browser" banner users will see while syncing, the anti-bot throttling strategy, and how to pause the flow (toggle Gemini off or dismiss the debugger banner). Updates the Supported Sites table row for Gemini, the Usage paragraph, and the Chrome Web Store permission justifications for `debugger` and `scripting`. metadata.json: bumps version 1.0.0 → 1.1.0, adds the `gemini-bulk-sync` tag, updates the `updated` date, and drops the `_todo` field so the current (stricter) metadata schema validates. The TODO it referenced (PR NateBJones-Projects#201 slug) is still explained in the README alongside the prerequisite link. License remains FSL-1.1-MIT. No new runtime dependencies, no binary blobs, no telemetry or third-party hosts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Rebased onto |
Accepting the access key via ?key= leaks it into CDN/proxy/Supabase access logs. Accept it only via the x-brain-key or Authorization: Bearer headers, matching enhanced-mcp. Updates README examples + auth docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
State that the service-role backend bypasses RLS (key = full-brain access, single-tenant by design) and that the key must be high-entropy since rate limiting is best-effort. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Both #201 and #202 are single-tenant, shared-key gateways over a service-role backend (RLS bypassed by design). Both now use constant-time key comparison, env-only secrets, and header-only auth — I dropped the |
justfinethanku
left a comment
There was a problem hiding this comment.
Audited for maintainer merge lane: branch is mergeable, Alan already documented the single-tenant trust model clearly, local passes, and I added the standard Community Contribution badge plus creator credit to the README so the contribution convention is preserved.
justfinethanku
left a comment
There was a problem hiding this comment.
Audited for the maintainer merge lane: the branch is mergeable, Alan already documented the single-tenant trust model clearly, local Deno type-checking for integrations/rest-api passes, and I added the standard Community Contribution badge plus creator credit to the README so the contribution convention is preserved.
Summary
Deno/Supabase Edge Function providing a REST API for non-MCP clients — 15 endpoints over thoughts, ingestion, and search.
Hardened per Wave 2.5 review:
Why
The stock MCP server is fine for AI clients, but dashboards, scripts, and webhooks need HTTP. This gateway mirrors the core MCP's capture/search/list/update surface over REST with the same auth model (`x-brain-key`) — so any client that can do HTTPS + headers can talk to an Open Brain.
Replaces the content from the closed PR #45 bundle with the Wave 2.5 security hardening pass incorporated.
Test plan