From 82700a8c24d79b67612e22397a7c157488094672 Mon Sep 17 00:00:00 2001 From: SentienceDEV Date: Thu, 26 Feb 2026 20:52:30 -0800 Subject: [PATCH] phase 3: policy & config --- policies/default.yaml | 143 ++++++++++++++ policies/examples/browser-agent.yaml | 156 +++++++++++++++ policies/examples/coding-agent.yaml | 244 ++++++++++++++++++++++++ secureclaw.mjs | 100 ++++++++++ src/plugins/secureclaw/auto-register.ts | 39 ++++ src/plugins/secureclaw/env.ts | 95 +++++++++ src/plugins/secureclaw/index.ts | 14 +- 7 files changed, 790 insertions(+), 1 deletion(-) create mode 100644 policies/default.yaml create mode 100644 policies/examples/browser-agent.yaml create mode 100644 policies/examples/coding-agent.yaml create mode 100644 secureclaw.mjs create mode 100644 src/plugins/secureclaw/auto-register.ts create mode 100644 src/plugins/secureclaw/env.ts diff --git a/policies/default.yaml b/policies/default.yaml new file mode 100644 index 000000000000..a542072dbca1 --- /dev/null +++ b/policies/default.yaml @@ -0,0 +1,143 @@ +# SecureClaw Default Policy +# Fail-closed: all actions require explicit allowlist +# +# This policy provides a secure baseline that blocks everything by default. +# Customize for your use case or use one of the examples in policies/examples/ + +version: "1.0" +name: "secureclaw-default" +description: "Fail-closed default policy - requires explicit allow rules" + +# Default behavior when no rule matches +default: deny + +# Principal configuration +principals: + - id: "agent:secureclaw" + description: "SecureClaw agent principal" + +# Resource patterns +resources: + # Safe read-only operations + safe_reads: + patterns: + - "*.md" + - "*.txt" + - "*.json" + - "*.yaml" + - "*.yml" + - "*.toml" + - "src/**" + - "lib/**" + - "docs/**" + - "README*" + - "LICENSE*" + - "package.json" + - "tsconfig.json" + - "*.config.js" + - "*.config.ts" + + # Sensitive paths - always deny + sensitive: + patterns: + - "**/.ssh/**" + - "**/.aws/**" + - "**/.gcp/**" + - "**/.azure/**" + - "**/id_rsa*" + - "**/id_ed25519*" + - "**/*.pem" + - "**/*.key" + - "**/.env*" + - "**/credentials*" + - "**/secrets*" + - "**/tokens*" + - "/etc/passwd" + - "/etc/shadow" + - "**/node_modules/**" + +# Authorization rules (evaluated in order) +rules: + # Block all access to sensitive resources + - id: "deny-sensitive" + effect: deny + actions: + - "*" + resources: + - "$sensitive" + reason: "Access to sensitive resources is blocked" + + # Allow reading safe file types + - id: "allow-safe-reads" + effect: allow + actions: + - "fs.read" + - "fs.list" + resources: + - "$safe_reads" + reason: "Safe read-only operations" + + # Allow search operations in project directories + - id: "allow-search" + effect: allow + actions: + - "fs.list" + resources: + - "**" + conditions: + - type: "path_prefix" + value: "./" + reason: "Search within project" + + # Block shell commands by default (very dangerous) + - id: "deny-shell" + effect: deny + actions: + - "shell.exec" + resources: + - "*" + reason: "Shell execution requires explicit policy override" + + # Block network requests by default + - id: "deny-network" + effect: deny + actions: + - "http.request" + resources: + - "*" + reason: "Network access requires explicit policy override" + + # Block browser automation by default + - id: "deny-browser" + effect: deny + actions: + - "browser.*" + resources: + - "*" + reason: "Browser automation requires explicit policy override" + + # Block agent spawning by default + - id: "deny-spawn" + effect: deny + actions: + - "agent.spawn" + resources: + - "*" + reason: "Agent spawning requires explicit policy override" + +# Audit configuration +audit: + enabled: true + log_allowed: true + log_denied: true + redact_sensitive: true + +# Rate limiting (optional) +rate_limits: + # Max tool calls per minute + global: 60 + # Per-action limits + actions: + "shell.exec": 10 + "http.request": 30 + "browser.*": 20 \ No newline at end of file diff --git a/policies/examples/browser-agent.yaml b/policies/examples/browser-agent.yaml new file mode 100644 index 000000000000..40c6c22ed2ee --- /dev/null +++ b/policies/examples/browser-agent.yaml @@ -0,0 +1,156 @@ +# SecureClaw Policy: Browser Agent +# Allows browser automation with domain restrictions +# +# Use case: Web scraping, form filling, testing automation +# Risk level: High - browser access can leak credentials + +version: "1.0" +name: "browser-agent" +description: "Policy for browser automation agents with domain allowlisting" + +default: deny + +principals: + - id: "agent:browser" + description: "Browser automation agent" + +resources: + # Allowed domains for navigation + allowed_domains: + patterns: + - "https://example.com/**" + - "https://*.example.com/**" + - "https://docs.example.com/**" + # Add your allowed domains here + # - "https://your-app.com/**" + # - "https://staging.your-app.com/**" + + # Blocked domains (even if in allowed patterns) + blocked_domains: + patterns: + - "**/login**" + - "**/signin**" + - "**/auth**" + - "**/oauth**" + - "**/password**" + - "**/credential**" + - "**/admin**" + - "**/settings/security**" + - "https://accounts.google.com/**" + - "https://login.microsoftonline.com/**" + - "https://github.com/login**" + - "https://github.com/settings/**" + + # Safe for screenshots (any visible page) + screenshot_safe: + patterns: + - "browser:current" + + # Sensitive file paths + sensitive_files: + patterns: + - "**/.ssh/**" + - "**/.aws/**" + - "**/credentials*" + - "**/.env*" + +rules: + # Block access to authentication pages + - id: "deny-auth-pages" + effect: deny + actions: + - "browser.navigate" + - "browser.interact" + resources: ["$blocked_domains"] + reason: "Authentication pages blocked for security" + + # Allow navigation to allowed domains + - id: "allow-navigation" + effect: allow + actions: ["browser.navigate"] + resources: ["$allowed_domains"] + reason: "Navigate to allowed domains" + + # Allow screenshots of current page + - id: "allow-screenshot" + effect: allow + actions: ["browser.screenshot"] + resources: ["$screenshot_safe"] + reason: "Capture screenshots" + + # Allow interactions on allowed domains + - id: "allow-interact" + effect: allow + actions: + - "browser.interact" + - "browser.click" + - "browser.type" + - "browser.scroll" + resources: ["$allowed_domains"] + conditions: + # Block typing in password fields + - type: "not_selector" + value: "input[type='password']" + reason: "Interact with allowed pages" + + # Block file system access + - id: "deny-fs" + effect: deny + actions: + - "fs.read" + - "fs.write" + - "fs.list" + resources: ["*"] + reason: "Browser agent has no file system access" + + # Block shell access + - id: "deny-shell" + effect: deny + actions: ["shell.exec"] + resources: ["*"] + reason: "Browser agent has no shell access" + + # Allow limited HTTP for API calls (same domain) + - id: "allow-api" + effect: allow + actions: ["http.request"] + resources: + - "https://api.example.com/**" + # Add your API endpoints here + reason: "API calls to allowed endpoints" + + # Block agent spawning + - id: "deny-spawn" + effect: deny + actions: ["agent.spawn"] + resources: ["*"] + reason: "Agent spawning blocked" + +audit: + enabled: true + log_allowed: true + log_denied: true + redact_sensitive: true + # Capture DOM snapshots for verification + capture_snapshots: true + snapshot_events: + - "browser.navigate" + - "browser.interact" + +rate_limits: + global: 60 + actions: + "browser.navigate": 10 + "browser.interact": 30 + "browser.screenshot": 20 + "http.request": 20 + +# Post-execution verification settings +verification: + enabled: true + # Verify DOM changes after interactions + dom_diff: true + # Alert on unexpected network requests + network_monitor: true + # Block if page navigates to blocked domain + navigation_guard: true \ No newline at end of file diff --git a/policies/examples/coding-agent.yaml b/policies/examples/coding-agent.yaml new file mode 100644 index 000000000000..f086ad7a8426 --- /dev/null +++ b/policies/examples/coding-agent.yaml @@ -0,0 +1,244 @@ +# SecureClaw Policy: Coding Agent +# Allows typical software development operations +# +# Use case: AI coding assistants (Claude Code, Cursor, Copilot, etc.) +# Risk level: Medium - allows file writes and controlled shell access + +version: "1.0" +name: "coding-agent" +description: "Policy for AI coding assistants with file and shell access" + +default: deny + +principals: + - id: "agent:coding" + description: "Coding assistant agent" + +resources: + # Source code files + source_code: + patterns: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" + - "**/*.py" + - "**/*.rs" + - "**/*.go" + - "**/*.java" + - "**/*.c" + - "**/*.cpp" + - "**/*.h" + - "**/*.hpp" + - "**/*.swift" + - "**/*.kt" + - "**/*.rb" + - "**/*.php" + - "**/*.cs" + - "**/*.vue" + - "**/*.svelte" + + # Config and docs + config_docs: + patterns: + - "**/*.json" + - "**/*.yaml" + - "**/*.yml" + - "**/*.toml" + - "**/*.md" + - "**/*.mdx" + - "**/*.txt" + - "**/*.xml" + - "**/*.html" + - "**/*.css" + - "**/*.scss" + - "**/*.less" + - "**/Dockerfile*" + - "**/.docker*" + - "**/Makefile" + - "**/.gitignore" + - "**/.gitattributes" + + # Test files + tests: + patterns: + - "**/*.test.*" + - "**/*.spec.*" + - "**/test/**" + - "**/tests/**" + - "**/__tests__/**" + + # Build artifacts - read only + build_output: + patterns: + - "dist/**" + - "build/**" + - "out/**" + - ".next/**" + - "target/**" + + # Sensitive - always block + sensitive: + patterns: + - "**/.ssh/**" + - "**/.aws/**" + - "**/.gcp/**" + - "**/.azure/**" + - "**/id_rsa*" + - "**/id_ed25519*" + - "**/*.pem" + - "**/*.key" + - "**/.env.local" + - "**/.env.production" + - "**/credentials*" + - "**/secrets*" + - "**/node_modules/**" + + # Safe shell commands + safe_commands: + patterns: + - "npm *" + - "pnpm *" + - "yarn *" + - "bun *" + - "npx *" + - "node *" + - "python *" + - "pip *" + - "cargo *" + - "go *" + - "make *" + - "git status*" + - "git log*" + - "git diff*" + - "git branch*" + - "git show*" + - "ls *" + - "cat *" + - "head *" + - "tail *" + - "wc *" + - "grep *" + - "find *" + - "tree *" + - "pwd" + - "echo *" + - "which *" + - "type *" + +rules: + # Block sensitive resources + - id: "deny-sensitive" + effect: deny + actions: ["*"] + resources: ["$sensitive"] + reason: "Sensitive resources blocked" + + # Allow reading all code and config + - id: "allow-read-code" + effect: allow + actions: ["fs.read", "fs.list"] + resources: + - "$source_code" + - "$config_docs" + - "$tests" + - "$build_output" + reason: "Read source code and config" + + # Allow writing source code and tests + - id: "allow-write-code" + effect: allow + actions: ["fs.write"] + resources: + - "$source_code" + - "$config_docs" + - "$tests" + reason: "Write source code" + + # Allow safe shell commands + - id: "allow-safe-shell" + effect: allow + actions: ["shell.exec"] + resources: ["$safe_commands"] + reason: "Safe development commands" + + # Allow git operations (except push/force) + - id: "allow-git-read" + effect: allow + actions: ["shell.exec"] + resources: + - "git status*" + - "git log*" + - "git diff*" + - "git branch*" + - "git show*" + - "git fetch*" + - "git pull*" + reason: "Git read operations" + + # Block dangerous git commands + - id: "deny-git-dangerous" + effect: deny + actions: ["shell.exec"] + resources: + - "git push --force*" + - "git reset --hard*" + - "git clean -fd*" + - "git rebase*" + reason: "Dangerous git operations require manual approval" + + # Allow running tests + - id: "allow-tests" + effect: allow + actions: ["shell.exec"] + resources: + - "npm test*" + - "npm run test*" + - "pnpm test*" + - "yarn test*" + - "pytest*" + - "cargo test*" + - "go test*" + - "jest*" + - "vitest*" + reason: "Run test suites" + + # Allow build commands + - id: "allow-build" + effect: allow + actions: ["shell.exec"] + resources: + - "npm run build*" + - "pnpm build*" + - "yarn build*" + - "cargo build*" + - "go build*" + - "make build*" + - "make all*" + reason: "Build project" + + # Block network access (no external API calls) + - id: "deny-network" + effect: deny + actions: ["http.request"] + resources: ["*"] + reason: "Network access blocked for coding agent" + + # Block browser automation + - id: "deny-browser" + effect: deny + actions: ["browser.*"] + resources: ["*"] + reason: "Browser access blocked for coding agent" + +audit: + enabled: true + log_allowed: true + log_denied: true + redact_sensitive: true + +rate_limits: + global: 120 + actions: + "shell.exec": 30 + "fs.write": 60 \ No newline at end of file diff --git a/secureclaw.mjs b/secureclaw.mjs new file mode 100644 index 000000000000..9ab1ba3999fe --- /dev/null +++ b/secureclaw.mjs @@ -0,0 +1,100 @@ +#!/usr/bin/env node + +/** + * SecureClaw CLI Entry Point + * + * This is the main entry point for SecureClaw - a zero-trust security + * fork of OpenClaw with pre-authorization and post-verification. + * + * SecureClaw automatically intercepts all tool calls and enforces + * authorization policies before execution. + */ + +import module from "node:module"; + +// Enable compile cache for faster startup +if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) { + try { + module.enableCompileCache(); + } catch { + // Ignore errors + } +} + +// Print SecureClaw banner +const showBanner = process.env.SECURECLAW_QUIET !== "true"; +if (showBanner) { + console.log("╔══════════════════════════════════════════════════════════════╗"); + console.log("║ ║"); + console.log("║ ███████╗███████╗ ██████╗██╗ ██╗██████╗ ███████╗ ║"); + console.log("║ ██╔════╝██╔════╝██╔════╝██║ ██║██╔══██╗██╔════╝ ║"); + console.log("║ ███████╗█████╗ ██║ ██║ ██║██████╔╝█████╗ ║"); + console.log("║ ╚════██║██╔══╝ ██║ ██║ ██║██╔══██╗██╔══╝ ║"); + console.log("║ ███████║███████╗╚██████╗╚██████╔╝██║ ██║███████╗ ║"); + console.log("║ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ║"); + console.log("║ ██████╗██╗ █████╗ ██╗ ██╗ ║"); + console.log("║ ██╔════╝██║ ██╔══██╗██║ ██║ ║"); + console.log("║ ██║ ██║ ███████║██║ █╗ ██║ ║"); + console.log("║ ██║ ██║ ██╔══██║██║███╗██║ ║"); + console.log("║ ╚██████╗███████╗██║ ██║╚███╔███╔╝ ║"); + console.log("║ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ║"); + console.log("║ ║"); + console.log("║ Zero-Trust Security for AI Agents ║"); + console.log("║ https://predicatesystems.ai/docs/secure-claw ║"); + console.log("║ ║"); + console.log("╚══════════════════════════════════════════════════════════════╝"); + console.log(""); +} + +// Check if SecureClaw is disabled +if (process.env.SECURECLAW_DISABLED === "true") { + console.warn("⚠️ SecureClaw security is DISABLED via SECURECLAW_DISABLED=true"); + console.warn("⚠️ Tool calls will NOT be authorized. This is NOT recommended."); + console.warn(""); +} + +const isModuleNotFoundError = (err) => + err && typeof err === "object" && "code" in err && err.code === "ERR_MODULE_NOT_FOUND"; + +const installProcessWarningFilter = async () => { + for (const specifier of ["./dist/warning-filter.js", "./dist/warning-filter.mjs"]) { + try { + const mod = await import(specifier); + if (typeof mod.installProcessWarningFilter === "function") { + mod.installProcessWarningFilter(); + return; + } + } catch (err) { + if (isModuleNotFoundError(err)) { + continue; + } + throw err; + } + } +}; + +await installProcessWarningFilter(); + +const tryImport = async (specifier) => { + try { + await import(specifier); + return true; + } catch (err) { + if (isModuleNotFoundError(err)) { + return false; + } + throw err; + } +}; + +// Import SecureClaw plugin and register it +// Note: The plugin is automatically registered via the bundled plugin system +// This import ensures the security hooks are active before any tool calls + +if (await tryImport("./dist/entry.js")) { + // OK +} else if (await tryImport("./dist/entry.mjs")) { + // OK +} else { + throw new Error("secureclaw: missing dist/entry.(m)js (build output)."); +} \ No newline at end of file diff --git a/src/plugins/secureclaw/auto-register.ts b/src/plugins/secureclaw/auto-register.ts new file mode 100644 index 000000000000..17b133ffea84 --- /dev/null +++ b/src/plugins/secureclaw/auto-register.ts @@ -0,0 +1,39 @@ +/** + * SecureClaw Auto-Registration + * + * This module is imported early in the OpenClaw boot sequence to + * auto-register the SecureClaw security plugin. + */ + +import { createSecureClawPlugin } from "./plugin.js"; +import { isSecureClawEnabled } from "./env.js"; + +/** + * Auto-register SecureClaw with the plugin system. + * Returns the plugin definition for manual registration if needed. + */ +export function autoRegisterSecureClaw(): ReturnType | null { + if (!isSecureClawEnabled()) { + console.log("[SecureClaw] Disabled via SECURECLAW_DISABLED=true"); + return null; + } + + const plugin = createSecureClawPlugin(); + + console.log("[SecureClaw] Security middleware initialized"); + console.log("[SecureClaw] All tool calls will be authorized before execution"); + + return plugin; +} + +/** + * Get the SecureClaw plugin without auto-registering. + * Use this for manual plugin registration. + */ +export function getSecureClawPlugin(): ReturnType { + return createSecureClawPlugin(); +} + +// Export for direct import +export { createSecureClawPlugin } from "./plugin.js"; +export { isSecureClawEnabled } from "./env.js"; diff --git a/src/plugins/secureclaw/env.ts b/src/plugins/secureclaw/env.ts new file mode 100644 index 000000000000..2017a3d1ec62 --- /dev/null +++ b/src/plugins/secureclaw/env.ts @@ -0,0 +1,95 @@ +/** + * SecureClaw Environment Configuration + * + * All SecureClaw settings can be configured via environment variables. + * This file documents and validates all supported environment variables. + */ + +export interface SecureClawEnvConfig { + /** Agent principal identifier (default: "agent:secureclaw") */ + SECURECLAW_PRINCIPAL?: string; + + /** Path to YAML policy file (default: "./policies/default.yaml") */ + SECURECLAW_POLICY?: string; + + /** Predicate Authority sidecar URL (default: "http://127.0.0.1:9120") */ + PREDICATE_SIDECAR_URL?: string; + + /** Set to "true" to fail-open when sidecar is unavailable (default: false) */ + SECURECLAW_FAIL_OPEN?: string; + + /** Set to "false" to disable post-execution verification (default: true) */ + SECURECLAW_VERIFY?: string; + + /** Set to "true" for verbose logging (default: false) */ + SECURECLAW_VERBOSE?: string; + + /** Tenant ID for multi-tenant deployments */ + SECURECLAW_TENANT_ID?: string; + + /** User ID for audit attribution */ + SECURECLAW_USER_ID?: string; + + /** Set to "true" to completely disable SecureClaw */ + SECURECLAW_DISABLED?: string; +} + +/** + * Check if SecureClaw is enabled via environment. + */ +export function isSecureClawEnabled(): boolean { + return process.env.SECURECLAW_DISABLED !== "true"; +} + +/** + * Get all SecureClaw environment variables with their current values. + */ +export function getSecureClawEnv(): SecureClawEnvConfig { + return { + SECURECLAW_PRINCIPAL: process.env.SECURECLAW_PRINCIPAL, + SECURECLAW_POLICY: process.env.SECURECLAW_POLICY, + PREDICATE_SIDECAR_URL: process.env.PREDICATE_SIDECAR_URL, + SECURECLAW_FAIL_OPEN: process.env.SECURECLAW_FAIL_OPEN, + SECURECLAW_VERIFY: process.env.SECURECLAW_VERIFY, + SECURECLAW_VERBOSE: process.env.SECURECLAW_VERBOSE, + SECURECLAW_TENANT_ID: process.env.SECURECLAW_TENANT_ID, + SECURECLAW_USER_ID: process.env.SECURECLAW_USER_ID, + SECURECLAW_DISABLED: process.env.SECURECLAW_DISABLED, + }; +} + +/** + * Print SecureClaw configuration for debugging. + */ +export function printSecureClawConfig(): void { + const env = getSecureClawEnv(); + console.log("SecureClaw Configuration:"); + console.log(" SECURECLAW_PRINCIPAL:", env.SECURECLAW_PRINCIPAL ?? "(default: agent:secureclaw)"); + console.log(" SECURECLAW_POLICY:", env.SECURECLAW_POLICY ?? "(default: ./policies/default.yaml)"); + console.log(" PREDICATE_SIDECAR_URL:", env.PREDICATE_SIDECAR_URL ?? "(default: http://127.0.0.1:9120)"); + console.log(" SECURECLAW_FAIL_OPEN:", env.SECURECLAW_FAIL_OPEN ?? "(default: false)"); + console.log(" SECURECLAW_VERIFY:", env.SECURECLAW_VERIFY ?? "(default: true)"); + console.log(" SECURECLAW_VERBOSE:", env.SECURECLAW_VERBOSE ?? "(default: false)"); + console.log(" SECURECLAW_TENANT_ID:", env.SECURECLAW_TENANT_ID ?? "(not set)"); + console.log(" SECURECLAW_USER_ID:", env.SECURECLAW_USER_ID ?? "(not set)"); + console.log(" SECURECLAW_DISABLED:", env.SECURECLAW_DISABLED ?? "(default: false)"); +} + +/** + * Environment variable documentation for README. + */ +export const ENV_DOCS = ` +## SecureClaw Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| \`SECURECLAW_PRINCIPAL\` | \`agent:secureclaw\` | Agent identity for authorization requests | +| \`SECURECLAW_POLICY\` | \`./policies/default.yaml\` | Path to YAML policy file | +| \`PREDICATE_SIDECAR_URL\` | \`http://127.0.0.1:9120\` | Predicate Authority sidecar endpoint | +| \`SECURECLAW_FAIL_OPEN\` | \`false\` | Set to \`true\` to allow actions when sidecar is unavailable | +| \`SECURECLAW_VERIFY\` | \`true\` | Set to \`false\` to disable post-execution verification | +| \`SECURECLAW_VERBOSE\` | \`false\` | Set to \`true\` for detailed logging | +| \`SECURECLAW_TENANT_ID\` | *(none)* | Tenant ID for multi-tenant deployments | +| \`SECURECLAW_USER_ID\` | *(none)* | User ID for audit attribution | +| \`SECURECLAW_DISABLED\` | \`false\` | Set to \`true\` to completely disable SecureClaw | +`; diff --git a/src/plugins/secureclaw/index.ts b/src/plugins/secureclaw/index.ts index a3e915609684..41268dbcfe42 100644 --- a/src/plugins/secureclaw/index.ts +++ b/src/plugins/secureclaw/index.ts @@ -5,6 +5,18 @@ * Intercepts all tool calls with pre-authorization and post-verification. */ +// Core plugin export { createSecureClawPlugin, type SecureClawPluginOptions } from "./plugin.js"; -export { extractResource, extractAction } from "./resource-extractor.js"; + +// Resource extraction utilities +export { extractResource, extractAction, redactResource, isSensitiveResource } from "./resource-extractor.js"; + +// Configuration export type { SecureClawConfig } from "./config.js"; +export { defaultConfig, loadConfigFromEnv, mergeConfig } from "./config.js"; + +// Environment variables +export { isSecureClawEnabled, getSecureClawEnv, printSecureClawConfig, ENV_DOCS } from "./env.js"; + +// Auto-registration +export { autoRegisterSecureClaw, getSecureClawPlugin } from "./auto-register.js";