Skip to content

mehanig/yourbro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

162 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

yourbro

Platform for AI-published web pages with zero-trust agent storage. Your AI agent publishes pages; your data lives on your machine — never on our server.

Live at yourbro.ai | How to Use

How It Works

There are two separate systems working together:

1. Page Publishing (OpenClaw → Filesystem)

OpenClaw writes page directories directly to /data/yourbro/pages/{slug}/. Each page is a directory with index.html plus any assets (JS, CSS, etc.). No registration API needed — the filesystem IS the database. Edits are live immediately.

You (human)                     OpenClaw                                           Your Agent
    │                               │                                                   │
    ├── Create API token ──────────>│                                                   │
    │   (dashboard)                 │                                                   │
    │                               ├── mkdir /data/yourbro/pages/my-page/ ────────────>│ (shared filesystem)
    │                               ├── Write index.html, app.js, style.css ───────────>│
    │                               ├── Write page.json (optional title) ──────────────>│
    │                               │                                                   │

2. Page Viewing & Data Storage (Browser → Agent via E2E Encrypted Relay)

All page traffic is E2E encrypted — the server is zero-knowledge. The same flow serves both public and private pages. The agent decides access based on key_id.

PAIRING (one-time):

Browser                              Agent (via relay)
┌──────────────────┐                ┌──────────────────┐
│ Generate X25519  │                │ Print pairing    │
│ keypair          │                │ code in logs     │
│ (WebCrypto)      │                │                  │
│ Store in         │                │                  │
│ IndexedDB        │                │                  │
│                  │                │                  │
│ Enter code in    │                │                  │
│ dashboard ───────┼── POST /pair ─>│ Verify code      │
│                  │   (via relay)  │ Store X25519 key │
│                  │<── agent key ──│ Return X25519 key│
└──────────────────┘                └──────────────────┘

PAGE VIEWING (unified E2E flow for all pages):

Browser (yourbro.ai)     Cloudflare R2          api.yourbro.ai         Your Agent
   │                        │                       │                       │
   │── GET /p/user/slug ───>│                       │                       │
   │<── shell.html ─────────│                       │                       │
   │                                                │                       │
   │  Generate/load X25519 keypair from IndexedDB   │                       │
   │  (all visitors — paired and anonymous)         │                       │
   │                                                │                       │
   │── GET /api/public-page/{user}/{slug} ─────────>│  (discovery only)     │
   │<── { agent_uuid, x25519_public } ─────────────│                       │
   │                                                │                       │
   │  Derive AES key: ECDH(viewer_priv, agent_pub) + HKDF-SHA256           │
   │  Encrypt inner request with AES-256-GCM        │                       │
   │                                                │                       │
   │── POST /api/public-page/{uuid}/{slug} ────────>│── WebSocket msg ─────>│
   │   { encrypted: true, key_id, payload }         │   (opaque blob)       │
   │                                                │                       │── Decrypt
   │                                                │                       │── Check key_id:
   │                                                │                       │   paired → any page
   │                                                │                       │   anon → public only
   │                                                │                       │── Encrypt response
   │<── { encrypted: true, payload } ───────────────│<── WebSocket resp ────│
   │                                                │                       │
   │  Decrypt response                              │                       │
   │  Render in sandboxed iframe                    │                       │
   │                                                                        │
   │   Rendering: shell converts all files to data URIs, rewrites HTML       │
   │   src/href via DOMParser, injects fetch/XHR override + property        │
   │   setter patches, loads via iframe srcdoc. sandbox="allow-scripts"     │
   │   (no allow-same-origin) gives iframe an opaque origin, preventing     │
   │   access to shell's IndexedDB keys. Works in all browsers.             │

STORAGE (same E2E relay, no cookies needed):

Browser              api.yourbro.ai          Your Agent
   │                    │                       │
   │── POST /public-page/{uuid}/{slug} ───────>│
   │   (E2E encrypted storage request)         │── Decrypt (auth = decryption success)
   │                    │                       │── Read/write SQLite
   │                    │<── WebSocket resp ────│── Encrypt response
   │<── E2E response ──│                       │

   Server is a relay pipe — it never sees plaintext (E2E encrypted).

Shared Pages & Custom Domain Authentication

Shared pages require a verified Google identity. On yourbro.ai, the shell fetches an identity token directly (session cookie is same-origin). On custom domains, the session cookie belongs to .yourbro.ai and isn't available, so the shell uses an auth bridge redirect to propagate the session:

FIRST VISIT (custom domain, not authenticated on this domain):

Browser                     api.yourbro.ai                        Custom Domain
   │                            │                                       │
   │── GET clicktof.art/slug ──>│ (custom domain middleware)            │
   │<── shell.html ─────────────│                                       │
   │                            │                                       │
   │── GET /api/identity-token ─┼─ 401 (no cookie on custom domain)     │
   │── E2E relay ──────────────>│──> agent returns login_required       │
   │                            │                                       │
   │── redirect to ────────────>│                                       │
   │   /auth/bridge?return=     │── has .yourbro.ai session cookie      │
   │   clicktof.art/slug        │── verifies custom domain is legit     │
   │                            │── generates one-time session code     │
   │<── redirect ───────────────│                                       │
   │                            │                                       │
   │── GET /auth/session?code=..┼──────────────────────────────────────>│
   │                            │   validates code                      │
   │                            │   sets yb_session cookie on           │
   │                            │   clicktof.art (7 days)               │
   │<── redirect to /slug ──────┼───────────────────────────────────────│
   │                            │                                       │
   │── GET /api/identity-token ─┼─ 200 (cookie now present)             │
   │── E2E relay with token ───>│──> agent verifies email + code ──> OK │

SUBSEQUENT VISITS (cookie set, valid for 7 days):

Browser                     Custom Domain
   │── GET clicktof.art/slug ──>│
   │<── shell.html ─────────────│
   │── GET /api/identity-token ─│ 200 (cookie present)
   │── E2E relay ──────────────>│ agent verifies → page loads

Custom domains pass through /api/ routes to the API server (they CNAME to it). The auth bridge verifies the return URL is a verified custom domain before issuing a session code.

If the user is not logged in to yourbro.ai at all, the auth bridge redirects to Google OAuth first, then completes the bridge flow — seamless single sign-on.

Zero-Trust Guarantees

  • Server DB dump → only public keys + page metadata, zero user data
  • Server admin snoops → E2E encryption means relay traffic is opaque to the server
  • Agent A compromised → cannot read Agent B (separate SQLite, separate keys)
  • Server JS injection → WebCrypto non-extractable keys prevent key theft; malicious JS can use the key while tab is open (full protection requires native app)
  • Stolen pairing code → useless to other users. The relay enforces ownership: only the user whose API token registered the agent can send requests to it (POST /api/relay/{agent_id} checks agent.UserID == session.UserID). The code itself is also one-time use, expires in 5 minutes, and rate-limited to 5 attempts
  • Stolen key_id → harmless. The key_id is the sender's X25519 public key (public by definition). Forging requests requires the matching private key — without it, ECDH produces a different shared secret and decryption fails

Quick Start (Local Docker)

Prerequisites

1. Configure

cp .env.example .env
# Edit .env — set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET
# Set GOOGLE_REDIRECT_URL=http://localhost/auth/google/callback

2. Build & Start

# Build everything
docker compose -f docker-compose.prod.yml -f docker-compose.local.yml --profile agent build

# Start Postgres first, run migrations, then start all services
docker compose -f docker-compose.prod.yml -f docker-compose.local.yml up -d postgres
docker compose -f docker-compose.prod.yml -f docker-compose.local.yml run --rm api ./server migrate
docker compose -f docker-compose.prod.yml -f docker-compose.local.yml --profile agent up -d

3. Login & Create API Token

  1. Visit http://localhost
  2. Login with Google
  3. In the dashboard, click "+ New Token" — copy the token (shown once)

4. Configure the Agent

The agent runs as a OpenClaw (OpenClaw) skill. In OpenClaw, set the YOURBRO_TOKEN environment variable to your API token — OpenClaw handles the rest.

For the local Docker setup, the agent container reads from agent/.env:

YOURBRO_TOKEN=yb_your_token_here
YOURBRO_SERVER_URL=http://nginx
YOURBRO_SQLITE_PATH=/data/agent.db

The agent connects to the server via WebSocket automatically. No ports to open, no domain needed. During connection, the agent sends its X25519 public key as a query parameter — the API stores it for the discovery endpoint.

5. Pair Your Browser with the Agent

The agent prints a pairing code on startup:

docker compose -f docker-compose.prod.yml -f docker-compose.local.yml logs agent-server | grep PAIRING
# === PAIRING CODE: A7X3KP9M (expires in 5 minutes) ===

In the dashboard, your agent appears under "Available Agents" as online. Enter the pairing code and click "Pair".

This exchanges X25519 keys between your browser and the agent:

  • Browser generates an X25519 keypair, stores it in IndexedDB, sends the public key to the agent
  • Agent stores the browser's public key in authorized_keys (SQLite) and returns its own public key
  • Both sides can now derive a shared AES-256-GCM key via ECDH + HKDF-SHA256

One-time setup. The keys persist across sessions.

6. Publish a Page

Pages are directory-based. OpenClaw just writes files — no API calls needed:

# Create the page directory
mkdir -p /data/yourbro/pages/hello/

# Write the HTML file
cat > /data/yourbro/pages/hello/index.html << 'EOF'
<html><body><h1>Hello from yourbro!</h1></body></html>
EOF

# Optional: set a custom title and make it public
echo '{"title": "Hello World", "public": true}' > /data/yourbro/pages/hello/page.json

Pages have three access levels:

  • Private (default): Only paired users can view
  • Shared: Specific Google accounts + access code. Set "allowed_emails" in page.json:
    echo '{"title": "For Team", "allowed_emails": ["alice@company.com"]}' > /data/yourbro/pages/hello/page.json
    The agent auto-generates an access_code and logs it. Share the URL and code with invitees. Two factors are required: verified email (API-signed Ed25519 token) + access code (travels only inside E2E encrypted channel, server never sees it).
  • Public: Anyone with the link. Set "public": true in page.json.

Page files live on your machine. To update, just edit the files — changes are live immediately. To delete:

rm -rf /data/yourbro/pages/hello/

7. Visit Your Page

Go to http://localhost/p/YOUR_USERNAME/hello

The page loads in an iframe. All traffic is E2E encrypted — the shell generates X25519 keys, discovers the agent via the API, derives an AES key, and fetches the page bundle through an encrypted relay. The server never sees the page content.

SDK API

// Available as window.clawdStorage inside pages with an agent endpoint
const storage = window.clawdStorage;

await storage.set("key", { any: "json value" });
const val = await storage.get("key");       // { any: "json value" }
const keys = await storage.list();           // ["key"]
const keys2 = await storage.list("prefix");  // keys starting with "prefix"
await storage.delete("key");

Local Development (without Docker)

Prerequisites: Go 1.23+, Node.js 22+, Docker (for Postgres only)

make install        # install frontend + SDK deps
make db             # start Postgres
make migrate        # apply migrations
make dev            # start API + frontend dev server

API on http://localhost:8080, frontend on http://localhost:5173 (Vite proxies API calls).

Rebuild After Code Changes

# API or frontend changes
docker compose -f docker-compose.prod.yml -f docker-compose.local.yml build api
docker compose -f docker-compose.prod.yml -f docker-compose.local.yml up -d api

# Agent changes
docker compose -f docker-compose.prod.yml -f docker-compose.local.yml --profile agent build agent-server
docker compose -f docker-compose.prod.yml -f docker-compose.local.yml --profile agent up -d agent-server

# Agent generates a new pairing code on each restart — re-pair from dashboard

Tear Down

docker compose -f docker-compose.prod.yml -f docker-compose.local.yml --profile agent down -v

Agent Setup

The yourbro agent runs as a OpenClaw (OpenClaw) skill. Install it from the OpenClaw skill registry:

  1. Set the YOURBRO_TOKEN environment variable in OpenClaw to your API token
  2. OpenClaw downloads the yourbro-agent binary and manages it automatically

The agent connects outbound via WebSocket — no exposed ports, no DNS, no TLS certificates needed. Works behind NAT/firewalls.

See skill/SKILL.md for full setup instructions.

Standalone Docker (without OpenClaw)

If running the agent outside OpenClaw:

docker compose -f docker-compose.agent.yml up -d

Configure via environment variables: YOURBRO_TOKEN and YOURBRO_SERVER_URL.

Production Deployment (yourbro server)

Target: single VPS with Docker Compose (nginx + TLS, API, Postgres).

Environment Variables

Variable Description
DATABASE_URL PostgreSQL connection string
JWT_SECRET Signs JWT session tokens
GOOGLE_CLIENT_ID Google OAuth app ID
GOOGLE_CLIENT_SECRET Google OAuth app secret
GOOGLE_REDIRECT_URL OAuth callback URL
FRONTEND_URL CORS allowed origin
IDENTITY_SIGNING_KEY Ed25519 private key for signing identity tokens (shared pages). Generate with go run ./api/cmd/genkey

Deploy

git clone <repo-url> /opt/yourbro && cd /opt/yourbro
cp .env.example .env  # fill in variables
bash deploy/setup.sh
bash deploy/deploy.sh

API Reference

yourbro Server

Method Path Auth Description
GET /health Health check
GET /api/public-page/{username}/{slug} Discovery: returns { agent_uuid, x25519_public } for online agent
POST /api/public-page/{agent_uuid}/{slug} Blind E2E encrypted relay to agent by UUID (no auth)
GET /api/me Cookie Current user
GET /api/agents Cookie List agents (with online status)
GET /api/agents/stream Cookie SSE stream for real-time agent status
DELETE /api/agents/{id} Cookie Remove agent
POST /api/relay/{agent_id} Cookie + E2E E2E encrypted relay to agent (all dashboard operations). Requires encrypted, key_id, payload.
GET /ws/agent Bearer WebSocket endpoint for agent connection (sends x25519_pub)
POST /api/tokens Cookie Create API token
GET /api/tokens Cookie List API tokens
DELETE /api/tokens/{id} Cookie Revoke API token
GET /api/page-analytics Cookie Page view analytics
GET /api/identity-token Cookie Short-lived Ed25519-signed JWT with user's email (for shared pages)
GET /.well-known/jwks.json Ed25519 public key for identity token verification (JWKS)
GET /auth/bridge Cookie Propagate session to custom domain (redirects to custom domain /auth/session)

Agent (reached via relay)

All agent endpoints are accessed through E2E encrypted relay only. There is no cleartext relay path. The relay envelope wraps the encrypted payload containing method, path, headers, and body.

Method Path Access Description
POST /api/pair E2E + Pairing code Register browser's X25519 public key
POST /api/auth-check Paired Check if browser is paired (decryption = auth)
POST /api/revoke-key Paired Revoke browser's encryption key
GET /api/pages Paired List all pages (with shared flag and allowed_emails)
GET /api/page/{slug} Paired, shared, or public Get page bundle. Returns specific errors: login_required, email_not_allowed, access_code_required, invalid_access_code
POST /api/page-storage/get E2E relay Get storage value
POST /api/page-storage/set E2E relay Set storage value
POST /api/page-storage/delete E2E relay Delete storage value
POST /api/page-storage/list E2E relay List storage keys

Project Structure

api/           Go backend (chi router, pgx) deployed to api.yourbro.ai
agent/         Agent data server (Go, SQLite, relay WebSocket client, E2E encryption)
web/           Vite + TypeScript SPA (dashboard, login, pairing UI) + static page shell, deployed to Cloudflare R2 at yourbro.ai
sdk/           ClawdStorage SDK (WebCrypto X25519, E2E encryption, relay transport)
migrations/    PostgreSQL schema migrations
nginx/         Nginx configs (prod TLS + local dev)
deploy/        Deployment scripts
skill/         OpenClaw skill definition (SKILL.md)

About

AI-published pages with zero-trust agent-backed storage

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors