mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* feat(engine): add cap parameter to clampSearchLimit (H6) clampSearchLimit(limit, defaultLimit, cap = MAX_SEARCH_LIMIT) — third arg is a caller-specified cap so operation handlers can enforce limits below MAX_SEARCH_LIMIT. Backward compatible: existing two-arg callers still cap at MAX_SEARCH_LIMIT. This fixes a Codex-caught semantics bug: the prior signature took (limit, defaultLimit) where the second arg was misread as a cap. clampSearchLimit(x, 20) was actually allowing values up to 100, not 20. * feat(integrations): SSRF defense + recipe trust boundary (B1, B2, Fix 2, Fix 4, B3, B4) - B1: split loadAllRecipes into trusted (package-bundled) and untrusted (cwd/recipes, $GBRAIN_RECIPES_DIR) tiers. Only package-bundled recipes get embedded=true. Closes the fake trust boundary that let any cwd-local recipe bypass health-check gates. - B2: hard-block string health_checks for non-embedded recipes (was previously only blocked when isUnsafeHealthCheck regex matched, which the cwd recipe exploit bypassed). Embedded recipes still get the regex defense. - Fix 2: gate command DSL health_checks on isEmbedded. Non-embedded recipes cannot spawnSync. - Fix 4 + B3 + B4: gate http DSL health_checks on isEmbedded; for embedded recipes, validate URLs via new isInternalUrl() before fetch: - Scheme allowlist (http/https only): blocks file:, data:, blob:, ftp:, javascript: - IPv4 range check covering hex/octal/decimal/single-integer bypass forms - IPv6 loopback ::1 + IPv4-mapped ::ffff: (canonicalized hex hextets handled) - Metadata hostnames (AWS, GCP, instance-data) blocked - fetch with redirect: 'manual' + per-hop re-validation up to 3 hops Original PRs #105-109 by @garagon. Wave 3 collector branch reimplemented the fixes after Codex outside-voice review found that PRs #106/#108 alone did not actually gate cwd-local recipes (B1) and that PR #108 missed redirect-following SSRF (B3) and non-http schemes (B4). * feat(file_upload): path/slug/filename validation + remote-caller confinement (Fix 1, B5, H5, M4, Fix 5) - Fix 1 + B5 + H1: validateUploadPath uses realpathSync + path.relative to defeat symlink-parent traversal. lstatSync alone (the original PR #105 approach) only catches final-component symlinks; a symlinked parent dir still followed to /etc/passwd. Now the entire path chain is resolved. - H5: validatePageSlug uses an allowlist regex (alphanumeric + hyphens, slash-separated segments). Closes URL-encoded traversal (%2e%2e%2f), Unicode lookalikes, backslashes, control chars implicitly. - M4: validateFilename allowlist regex. Rejects control chars, backslash, RTL override (\u202E), leading dot/dash. Filename flows into storage_path so this matters for every storage backend. - Fix 5: clamp list_pages and get_ingest_log limits at the operation layer via new clampSearchLimit cap parameter (list_pages caps at 100, get_ingest_log at 50). Internal bulk commands bypass the operation layer and remain uncapped. - New OperationContext.remote flag distinguishes trusted local CLI from untrusted MCP callers. file_upload uses strict cwd confinement when remote=true (default), loose mode when remote=false (CLI). MCP stdio server sets remote=true; cli.ts and handleToolCall (gbrain call) set remote=false. Original PR #105 by @garagon. Issue #139 reported by @Hybirdss. * feat(search): query sanitization + structural prompt boundary (Fix 3, M1, M2, M3) - M1: restructure callHaikuForExpansion to use a system message that declares the user query as untrusted data, plus an XML-tagged <user_query> boundary in the user message. Layered defense with the existing tool_choice constraint (3 layers vs 1). - Fix 3 (regex sanitizer, defense-in-depth): sanitizeQueryForPrompt strips triple-backtick code fences, XML/HTML tags, leading injection prefixes, and caps at 500 chars. Original query is still used for downstream search; only the LLM-facing copy is sanitized. - M2: sanitizeExpansionOutput validates the model's alternative_queries array before it flows into search. Strips control chars, caps length, dedupes case-insensitively, drops empty/non-string items, caps to 2 items. - M3: console.warn on stripped content NEVER logs the query text — privacy-safe debug signal only. Original PR #107 by @garagon. M1/M2/M3 are wave 3 hardening per Codex review. * chore: bump version and changelog (v0.10.2) Security wave 3: 9 vulnerabilities closed across file_upload, recipe trust boundary, SSRF defense, prompt injection, and limit clamping. See CHANGELOG for full details. Contributors: - @garagon (PRs #105-109) - @Hybirdss (Issue #139) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: sync documentation with v0.10.2 security wave 3 - CLAUDE.md: document OperationContext.remote, new security helpers (validateUploadPath, validatePageSlug, validateFilename, isInternalUrl, parseOctet, hostnameToOctets, isPrivateIpv4, getRecipeDirs, sanitizeQueryForPrompt, sanitizeExpansionOutput), updated clampSearchLimit signature, recipe trust boundary, new test files - docs/integrations/README.md: replace string-form health_check example with typed DSL (string checks now hard-block for non-embedded recipes); add recipe trust boundary subsection - docs/mcp/DEPLOY.md: document file_upload remote-caller cwd confinement, symlink rejection, slug/filename allowlists Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
123 lines
3.7 KiB
Markdown
123 lines
3.7 KiB
Markdown
# Deploy GBrain Remote MCP Server
|
|
|
|
Access your brain from any device, any AI client. GBrain's MCP server runs locally
|
|
via `gbrain serve` (stdio). For remote access, wrap it in an HTTP server behind a
|
|
public tunnel.
|
|
|
|
## Two Paths
|
|
|
|
### Local (zero setup)
|
|
|
|
```bash
|
|
gbrain serve
|
|
```
|
|
|
|
Works with Claude Code, Cursor, Windsurf, and any MCP client that supports stdio.
|
|
No server, no tunnel, no token needed.
|
|
|
|
### Remote (any device, any AI client)
|
|
|
|
```
|
|
Your AI client (Claude Desktop, Perplexity, etc.)
|
|
→ ngrok tunnel (https://YOUR-DOMAIN.ngrok.app)
|
|
→ Your HTTP server (wraps gbrain serve)
|
|
→ Supabase Postgres (via pooler connection string)
|
|
```
|
|
|
|
This requires:
|
|
1. A machine running `gbrain serve` behind an HTTP wrapper
|
|
2. A public tunnel (ngrok, Tailscale, or cloud host)
|
|
3. Bearer token auth for security
|
|
|
|
## Remote Setup
|
|
|
|
### 1. Set up the tunnel
|
|
|
|
See the [ngrok-tunnel recipe](../../recipes/ngrok-tunnel.md) for full setup.
|
|
Quick version:
|
|
|
|
```bash
|
|
brew install ngrok
|
|
ngrok config add-authtoken YOUR_TOKEN
|
|
ngrok http 8787 --url your-brain.ngrok.app # Hobby tier for fixed domain
|
|
```
|
|
|
|
### 2. Create access tokens
|
|
|
|
```bash
|
|
# Create a token for each client
|
|
bun run src/commands/auth.ts create "claude-desktop"
|
|
|
|
# List all tokens
|
|
bun run src/commands/auth.ts list
|
|
|
|
# Revoke a token
|
|
bun run src/commands/auth.ts revoke "claude-desktop"
|
|
```
|
|
|
|
Tokens are per-client. Create one for each device/app. Revoke individually
|
|
if compromised. Tokens are stored SHA-256 hashed in your database.
|
|
|
|
### 3. Connect your AI client
|
|
|
|
- **Claude Code:** [setup guide](CLAUDE_CODE.md)
|
|
- **Claude Desktop:** [setup guide](CLAUDE_DESKTOP.md) (must use GUI, not JSON config)
|
|
- **Claude Cowork:** [setup guide](CLAUDE_COWORK.md)
|
|
- **Perplexity:** [setup guide](PERPLEXITY.md)
|
|
|
|
### 4. Verify
|
|
|
|
```bash
|
|
bun run src/commands/auth.ts test \
|
|
https://YOUR-DOMAIN.ngrok.app/mcp \
|
|
--token YOUR_TOKEN
|
|
```
|
|
|
|
## Operations
|
|
|
|
All 30 GBrain operations are available remotely, including `sync_brain` and
|
|
`file_upload` (no timeout limits with self-hosted server).
|
|
|
|
**Security note on `file_upload`:** remote MCP callers are confined to the working
|
|
directory where `gbrain serve` was launched. Symlinks, `..` traversal, and absolute
|
|
paths outside cwd are rejected. Page slugs and filenames are allowlist-validated
|
|
(alphanumeric + hyphens; no control chars, RTL overrides, or backslashes). Local
|
|
CLI callers (`gbrain file upload ...`) keep unrestricted filesystem access since
|
|
the user owns the machine.
|
|
|
|
## Deployment Options
|
|
|
|
See [ALTERNATIVES.md](ALTERNATIVES.md) for a comparison of ngrok, Tailscale
|
|
Funnel, and cloud hosts (Fly.io, Railway).
|
|
|
|
## Troubleshooting
|
|
|
|
**"missing_auth" error**
|
|
Include the Authorization header: `Authorization: Bearer YOUR_TOKEN`
|
|
|
|
**"invalid_token" error**
|
|
Run `bun run src/commands/auth.ts list` to see active tokens.
|
|
|
|
**"service_unavailable" error**
|
|
Database connection failed. Check your Supabase dashboard for outages.
|
|
|
|
**Claude Desktop doesn't connect**
|
|
Remote servers must be added via Settings > Integrations, NOT
|
|
`claude_desktop_config.json`. See [CLAUDE_DESKTOP.md](CLAUDE_DESKTOP.md).
|
|
|
|
## Expected Latencies
|
|
|
|
| Operation | Typical Latency | Notes |
|
|
|-----------|----------------|-------|
|
|
| get_page | < 100ms | Single DB query |
|
|
| list_pages | < 200ms | DB query with filters |
|
|
| search (keyword) | 100-300ms | Full-text search |
|
|
| query (hybrid) | 1-3s | Embedding + vector + keyword + RRF |
|
|
| put_page | 100-500ms | Write + trigger search_vector update |
|
|
| get_stats | < 100ms | Aggregate query |
|
|
|
|
**Note:** `gbrain serve --http` (built-in HTTP transport) is planned but not yet
|
|
implemented. Currently, remote MCP requires a custom HTTP wrapper. See the
|
|
production deployment pattern in the [voice recipe](../../recipes/twilio-voice-brain.md)
|
|
for a reference implementation.
|