Skip to content

Add Moltbook/Supabase frontend key exposure check (CHK-SEC-013) #14

@MikeeBuilds

Description

@MikeeBuilds

Summary

The Moltbook breach was caused by a Supabase API key exposed on the frontend, granting unauthenticated read/write access to the entire production database. This exposed 1.5M API tokens and 35K user emails.

This pattern -- embedding database credentials in client-side code -- is common in "vibe-coded" projects and should be detectable by ClawPinch when scanning OpenClaw skill codebases.

The Vulnerability Pattern

┌──────────────────────────────────────────────────┐
│  Frontend JavaScript (publicly accessible)        │
│                                                   │
│  const supabase = createClient(                  │
│    "https://xxxx.supabase.co",                   │
│    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."    │  <-- anon key
│  );                                               │       (leaked)
│                                                   │
│  // This key allows:                              │
│  // - Reading ALL tables (no RLS)                 │
│  // - Writing to ALL tables                       │
│  // - Bypassing auth entirely                     │
└──────────────────────────────────────────────────┘
         │
         ▼
┌──────────────────────────────────────────────────┐
│  Supabase Database                                │
│                                                   │
│  ┌─────────────────┐  ┌─────────────────┐       │
│  │  auth_tokens     │  │  user_emails    │       │
│  │  (1.5M records)  │  │  (35K records)  │       │
│  └─────────────────┘  └─────────────────┘       │
│  ┌─────────────────┐  ┌─────────────────┐       │
│  │  agent_memories  │  │  private_msgs   │       │
│  │  (full access)   │  │  (full access)  │       │
│  └─────────────────┘  └─────────────────┘       │
└──────────────────────────────────────────────────┘

Proposed Check: CHK-SEC-013

Location: Add to scan_secrets.py (extends the secret detection scanner)

Detection Patterns

SUPABASE_PATTERNS = [
    # Supabase URL
    (
        "Supabase project URL",
        re.compile(r"https://[a-z0-9]+\.supabase\.co"),
    ),
    # Supabase anon/service key (JWT format starting with eyJ)
    (
        "Supabase API key",
        re.compile(r"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+"),
    ),
]

FRONTEND_FILE_PATTERNS = [
    "*.js", "*.jsx", "*.ts", "*.tsx",
    "*.html", "*.vue", "*.svelte",
]

Scan Logic

def check_supabase_keys(config_dir: str, findings: Findings):
    """CHK-SEC-013: Supabase keys in frontend/skill code."""
    
    # Scan skill directories for frontend code with embedded Supabase keys
    skills_dir = os.path.join(config_dir, "skills")
    if not os.path.isdir(skills_dir):
        return
    
    for root, dirs, files in os.walk(skills_dir):
        dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
        for fname in files:
            if not any(fname.endswith(ext) for ext in (".js", ".jsx", ".ts", ".tsx", ".html")):
                continue
            fpath = os.path.join(root, fname)
            try:
                with open(fpath, "r", errors="ignore") as f:
                    content = f.read(8192)
                
                has_url = SUPABASE_PATTERNS[0][1].search(content)
                has_key = SUPABASE_PATTERNS[1][1].search(content)
                
                if has_url and has_key:
                    findings.add(
                        "CHK-SEC-013", "critical",
                        f"Supabase credentials in frontend code: {fname}",
                        f"File {fpath} contains both a Supabase URL and API key. "
                        "This was the exact vulnerability pattern that caused the "
                        "Moltbook breach (1.5M tokens leaked).",
                        f"file={fpath} url={redact(has_url.group())} key={redact(has_key.group())}",
                        "Move Supabase credentials to server-side code or environment variables. "
                        "Enable Row Level Security (RLS) on all tables. "
                        "Rotate the exposed key immediately.",
                    )
                elif has_key:
                    findings.add(
                        "CHK-SEC-013", "warn",
                        f"Possible Supabase key in: {fname}",
                        f"File {fpath} contains what appears to be a Supabase JWT key.",
                        f"file={fpath} key={redact(has_key.group())}",
                        "Verify this is not an anon key exposed in client-side code.",
                    )
            except OSError:
                pass

Also Add to SECRET_VALUE_PATTERNS

Extend the existing patterns list in scan_secrets.py:

SECRET_VALUE_PATTERNS = [
    # ... existing patterns ...
    ("Supabase API key", re.compile(r"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}")),
    ("Firebase API key", re.compile(r"AIza[0-9A-Za-z_-]{35}")),
]

The "Glass Box Paradox"

This check addresses what the forensic analysis calls the "Glass Box Paradox":

Sophisticated reasoning engines deployed within transparent, unauthenticated containers, making their internal logic accessible to the public internet.

Skills that embed database keys in frontend code are "glass boxes" -- powerful functionality with zero access control.

References

  • Forensic analysis: "The Supabase Database Leak" -- Wiz researcher Gal Nagli
  • Forensic analysis: "The Glass Box Paradox"
  • 1.5M API tokens and 35K emails exposed
  • Pattern also applies to Firebase, PlanetScale, and other BaaS providers

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsecuritySecurity-related issue

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions