mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
Add WASM sandbox secure API extension
Extends the WASM sandbox with HTTP API capabilities, secrets management, tool aliasing, and leak detection. Key security principle: WASM never sees credentials, injection happens at host boundary. New modules: - secrets: AES-256-GCM encrypted storage with HKDF key derivation - leak_detector: Aho-Corasick + regex pattern matching for secret exfiltration - capabilities: Extended capability system (HTTP, ToolInvoke, Secrets) - allowlist: HTTP endpoint validation with glob patterns - credential_injector: Host-boundary credential injection - rate_limiter: Sliding window per-tool rate limiting - storage: WASM binary storage with BLAKE3 integrity verification Leak detection happens at two points: 1. Before HTTP request (prevents exfiltration via URL/headers/body) 2. After response (prevents exposure in outputs returned to WASM) Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
45bbfa026d
commit
32bfd24154
@@ -0,0 +1,322 @@
|
||||
-- WASM Secure API Extension
|
||||
-- V2: Secrets management, WASM tool storage, capabilities, and leak detection
|
||||
|
||||
-- ==================== Secrets ====================
|
||||
-- Encrypted secret storage for credential injection into WASM HTTP requests.
|
||||
-- WASM tools NEVER see plaintext secrets; injection happens at host boundary.
|
||||
|
||||
CREATE TABLE secrets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
|
||||
-- AES-256-GCM encrypted value (nonce || ciphertext || tag)
|
||||
encrypted_value BYTEA NOT NULL,
|
||||
-- Per-secret key derivation salt (for HKDF)
|
||||
key_salt BYTEA NOT NULL,
|
||||
|
||||
-- Optional metadata
|
||||
provider TEXT, -- e.g., "openai", "anthropic", "stripe"
|
||||
expires_at TIMESTAMPTZ,
|
||||
last_used_at TIMESTAMPTZ,
|
||||
usage_count BIGINT NOT NULL DEFAULT 0,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT unique_secret_per_user UNIQUE (user_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_secrets_user ON secrets(user_id);
|
||||
CREATE INDEX idx_secrets_provider ON secrets(provider) WHERE provider IS NOT NULL;
|
||||
CREATE INDEX idx_secrets_expires ON secrets(expires_at) WHERE expires_at IS NOT NULL;
|
||||
|
||||
-- Trigger to update updated_at
|
||||
CREATE TRIGGER update_secrets_updated_at
|
||||
BEFORE UPDATE ON secrets
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ==================== WASM Tools ====================
|
||||
-- Store compiled WASM binaries with integrity verification.
|
||||
|
||||
CREATE TABLE wasm_tools (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
version TEXT NOT NULL DEFAULT '1.0.0',
|
||||
|
||||
description TEXT NOT NULL,
|
||||
wasm_binary BYTEA NOT NULL,
|
||||
-- BLAKE3 hash for integrity verification on load
|
||||
binary_hash BYTEA NOT NULL,
|
||||
parameters_schema JSONB NOT NULL,
|
||||
|
||||
-- Provenance
|
||||
source_url TEXT,
|
||||
-- Trust levels: 'system' (built-in), 'verified' (audited), 'user' (untrusted)
|
||||
trust_level TEXT NOT NULL DEFAULT 'user',
|
||||
|
||||
-- Status: 'active', 'disabled', 'quarantined'
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT unique_wasm_tool_version UNIQUE (user_id, name, version)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_wasm_tools_user ON wasm_tools(user_id);
|
||||
CREATE INDEX idx_wasm_tools_name ON wasm_tools(user_id, name);
|
||||
CREATE INDEX idx_wasm_tools_status ON wasm_tools(status);
|
||||
CREATE INDEX idx_wasm_tools_trust ON wasm_tools(trust_level);
|
||||
|
||||
CREATE TRIGGER update_wasm_tools_updated_at
|
||||
BEFORE UPDATE ON wasm_tools
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ==================== Tool Capabilities ====================
|
||||
-- Fine-grained capability configuration per WASM tool.
|
||||
-- Follows principle of least privilege.
|
||||
|
||||
CREATE TABLE tool_capabilities (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
wasm_tool_id UUID NOT NULL REFERENCES wasm_tools(id) ON DELETE CASCADE,
|
||||
|
||||
-- HTTP capability: allowed endpoint patterns
|
||||
-- Each pattern is: {"host": "api.example.com", "path_prefix": "/v1/", "methods": ["GET", "POST"]}
|
||||
http_allowlist JSONB NOT NULL DEFAULT '[]',
|
||||
|
||||
-- Secrets this tool can use (injected at host boundary)
|
||||
-- Tool never sees the actual secret values
|
||||
allowed_secrets TEXT[] NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Tool invocation aliases (indirection layer)
|
||||
-- Maps alias name to real tool name, e.g., {"search": "brave_search"}
|
||||
tool_aliases JSONB NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Rate limiting
|
||||
requests_per_minute INT NOT NULL DEFAULT 60,
|
||||
requests_per_hour INT NOT NULL DEFAULT 1000,
|
||||
|
||||
-- Request/response size limits
|
||||
max_request_body_bytes BIGINT NOT NULL DEFAULT 1048576, -- 1 MB
|
||||
max_response_body_bytes BIGINT NOT NULL DEFAULT 10485760, -- 10 MB
|
||||
|
||||
-- Workspace access (path prefixes tool can read)
|
||||
workspace_read_prefixes TEXT[] NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Timeout for HTTP requests (seconds)
|
||||
http_timeout_secs INT NOT NULL DEFAULT 30,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT unique_capabilities_per_tool UNIQUE (wasm_tool_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tool_capabilities_tool ON tool_capabilities(wasm_tool_id);
|
||||
|
||||
CREATE TRIGGER update_tool_capabilities_updated_at
|
||||
BEFORE UPDATE ON tool_capabilities
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ==================== Leak Detection Patterns ====================
|
||||
-- Patterns for detecting secret leakage in tool outputs.
|
||||
-- Scanned before returning data to WASM or LLM.
|
||||
|
||||
CREATE TABLE leak_detection_patterns (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
|
||||
-- Regex pattern for detection
|
||||
pattern TEXT NOT NULL,
|
||||
|
||||
-- Severity: 'critical', 'high', 'medium', 'low'
|
||||
severity TEXT NOT NULL DEFAULT 'high',
|
||||
|
||||
-- Action: 'block' (fail request), 'redact' (mask secret), 'warn' (log only)
|
||||
action TEXT NOT NULL DEFAULT 'block',
|
||||
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_leak_patterns_enabled ON leak_detection_patterns(enabled) WHERE enabled = true;
|
||||
|
||||
-- Pre-populate with common API key patterns
|
||||
INSERT INTO leak_detection_patterns (name, pattern, severity, action) VALUES
|
||||
-- OpenAI (sk-proj-... or sk-... followed by alphanumeric)
|
||||
('openai_api_key', 'sk-(?:proj-)?[a-zA-Z0-9]{20,}(?:T3BlbkFJ[a-zA-Z0-9_-]*)?', 'critical', 'block'),
|
||||
|
||||
-- Anthropic (sk-ant-api followed by 90+ chars)
|
||||
('anthropic_api_key', 'sk-ant-api[a-zA-Z0-9_-]{90,}', 'critical', 'block'),
|
||||
|
||||
-- AWS Access Key ID (starts with AKIA)
|
||||
('aws_access_key', 'AKIA[0-9A-Z]{16}', 'critical', 'block'),
|
||||
|
||||
-- AWS Secret Access Key (40 char base64-ish)
|
||||
('aws_secret_key', '(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])', 'high', 'block'),
|
||||
|
||||
-- GitHub tokens (gh[pousr]_...)
|
||||
('github_token', 'gh[pousr]_[A-Za-z0-9_]{36,}', 'critical', 'block'),
|
||||
|
||||
-- GitHub fine-grained PAT
|
||||
('github_fine_grained_pat', 'github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}', 'critical', 'block'),
|
||||
|
||||
-- Stripe keys (sk_live_... or sk_test_...)
|
||||
('stripe_api_key', 'sk_(?:live|test)_[a-zA-Z0-9]{24,}', 'critical', 'block'),
|
||||
|
||||
-- NEAR AI session tokens
|
||||
('nearai_session', 'sess_[a-zA-Z0-9]{32,}', 'critical', 'block'),
|
||||
|
||||
-- Generic Bearer tokens in headers
|
||||
('bearer_token', 'Bearer\s+[a-zA-Z0-9_-]{20,}', 'high', 'redact'),
|
||||
|
||||
-- PEM private keys
|
||||
('pem_private_key', '-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----', 'critical', 'block'),
|
||||
|
||||
-- SSH private keys
|
||||
('ssh_private_key', '-----BEGIN\s+(?:OPENSSH|EC|DSA)\s+PRIVATE\s+KEY-----', 'critical', 'block'),
|
||||
|
||||
-- Google API keys
|
||||
('google_api_key', 'AIza[0-9A-Za-z_-]{35}', 'high', 'block'),
|
||||
|
||||
-- Slack tokens
|
||||
('slack_token', 'xox[baprs]-[0-9a-zA-Z-]{10,}', 'high', 'block'),
|
||||
|
||||
-- Discord tokens
|
||||
('discord_token', '[MN][A-Za-z\d]{23,}\.[\w-]{6}\.[\w-]{27}', 'high', 'block'),
|
||||
|
||||
-- Twilio (starts with SK)
|
||||
('twilio_api_key', 'SK[a-fA-F0-9]{32}', 'high', 'block'),
|
||||
|
||||
-- SendGrid
|
||||
('sendgrid_api_key', 'SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}', 'high', 'block'),
|
||||
|
||||
-- Mailchimp
|
||||
('mailchimp_api_key', '[a-f0-9]{32}-us[0-9]{1,2}', 'medium', 'block'),
|
||||
|
||||
-- Generic high-entropy strings (potential secrets) - careful with false positives
|
||||
('high_entropy_hex', '(?<![a-fA-F0-9])[a-fA-F0-9]{64}(?![a-fA-F0-9])', 'medium', 'warn');
|
||||
|
||||
-- ==================== Rate Limit State ====================
|
||||
-- Track rate limit consumption per tool per user.
|
||||
|
||||
CREATE TABLE tool_rate_limit_state (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
wasm_tool_id UUID NOT NULL REFERENCES wasm_tools(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL,
|
||||
|
||||
-- Sliding window counters
|
||||
minute_window_start TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
minute_count INT NOT NULL DEFAULT 0,
|
||||
hour_window_start TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
hour_count INT NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT unique_rate_limit_per_tool_user UNIQUE (wasm_tool_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_rate_limit_tool ON tool_rate_limit_state(wasm_tool_id);
|
||||
CREATE INDEX idx_rate_limit_user ON tool_rate_limit_state(user_id);
|
||||
|
||||
-- ==================== Secret Usage Audit Log ====================
|
||||
-- Audit trail for secret access (credential injection events).
|
||||
|
||||
CREATE TABLE secret_usage_log (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
secret_id UUID NOT NULL REFERENCES secrets(id) ON DELETE CASCADE,
|
||||
wasm_tool_id UUID REFERENCES wasm_tools(id) ON DELETE SET NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
|
||||
-- What endpoint was the secret injected for
|
||||
target_host TEXT NOT NULL,
|
||||
target_path TEXT,
|
||||
|
||||
-- Result of the operation
|
||||
success BOOLEAN NOT NULL,
|
||||
error_message TEXT,
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_secret_usage_secret ON secret_usage_log(secret_id);
|
||||
CREATE INDEX idx_secret_usage_tool ON secret_usage_log(wasm_tool_id);
|
||||
CREATE INDEX idx_secret_usage_user ON secret_usage_log(user_id);
|
||||
CREATE INDEX idx_secret_usage_created ON secret_usage_log(created_at DESC);
|
||||
|
||||
-- Partition by month for large deployments (optional, commented out)
|
||||
-- CREATE TABLE secret_usage_log_y2024m01 PARTITION OF secret_usage_log
|
||||
-- FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
|
||||
|
||||
-- ==================== Leak Detection Events ====================
|
||||
-- Log when potential secret leaks are detected and blocked.
|
||||
|
||||
CREATE TABLE leak_detection_events (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
pattern_id UUID REFERENCES leak_detection_patterns(id) ON DELETE SET NULL,
|
||||
wasm_tool_id UUID REFERENCES wasm_tools(id) ON DELETE SET NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
|
||||
-- Where the leak was detected
|
||||
source TEXT NOT NULL, -- 'http_response', 'tool_output', 'log_message'
|
||||
action_taken TEXT NOT NULL, -- 'blocked', 'redacted', 'warned'
|
||||
|
||||
-- Redacted context (no actual secrets stored)
|
||||
context_preview TEXT, -- First 100 chars with secret masked
|
||||
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_leak_events_pattern ON leak_detection_events(pattern_id);
|
||||
CREATE INDEX idx_leak_events_tool ON leak_detection_events(wasm_tool_id);
|
||||
CREATE INDEX idx_leak_events_user ON leak_detection_events(user_id);
|
||||
CREATE INDEX idx_leak_events_created ON leak_detection_events(created_at DESC);
|
||||
|
||||
-- ==================== Views ====================
|
||||
|
||||
-- View: Tools with their capabilities
|
||||
CREATE VIEW wasm_tools_with_capabilities AS
|
||||
SELECT
|
||||
t.id,
|
||||
t.user_id,
|
||||
t.name,
|
||||
t.version,
|
||||
t.description,
|
||||
t.trust_level,
|
||||
t.status,
|
||||
t.created_at,
|
||||
t.updated_at,
|
||||
c.http_allowlist,
|
||||
c.allowed_secrets,
|
||||
c.tool_aliases,
|
||||
c.requests_per_minute,
|
||||
c.requests_per_hour,
|
||||
c.workspace_read_prefixes
|
||||
FROM wasm_tools t
|
||||
LEFT JOIN tool_capabilities c ON c.wasm_tool_id = t.id;
|
||||
|
||||
-- View: Active leak detection patterns
|
||||
CREATE VIEW active_leak_patterns AS
|
||||
SELECT id, name, pattern, severity, action
|
||||
FROM leak_detection_patterns
|
||||
WHERE enabled = true;
|
||||
|
||||
-- View: Recent leak events summary
|
||||
CREATE VIEW recent_leak_events AS
|
||||
SELECT
|
||||
le.created_at,
|
||||
le.source,
|
||||
le.action_taken,
|
||||
lp.name as pattern_name,
|
||||
lp.severity,
|
||||
wt.name as tool_name,
|
||||
le.user_id
|
||||
FROM leak_detection_events le
|
||||
LEFT JOIN leak_detection_patterns lp ON lp.id = le.pattern_id
|
||||
LEFT JOIN wasm_tools wt ON wt.id = le.wasm_tool_id
|
||||
WHERE le.created_at > NOW() - INTERVAL '24 hours'
|
||||
ORDER BY le.created_at DESC;
|
||||
Reference in New Issue
Block a user