diff --git a/README.md b/README.md index 2864e7b..88ad3c4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ While others try to _guess_ if a prompt is malicious (Semantic Security), Node9 **AIs are literal.** When you ask an agent to "Fix my disk space," it might decide to run `docker system prune -af`.

- +

**With Node9, the interaction looks like this:** diff --git a/examples/demo.ts b/examples/demo.ts index 8876a05..381d938 100644 --- a/examples/demo.ts +++ b/examples/demo.ts @@ -1,4 +1,21 @@ -import { protect } from '../src/index'; +/** + * Node9 SDK — protect() example + * + * There are two ways Node9 protects you: + * + * 1. CLI Proxy (automatic) — Node9 wraps Claude Code / Gemini CLI at the + * process level and intercepts every tool call automatically. No code needed. + * + * 2. SDK / protect() (manual) — for developers building their own Node.js apps + * with an AI SDK (Anthropic, LangChain, etc.). Wrap any dangerous function + * with `protect()` and Node9 will intercept it before execution, showing a + * native approval popup and applying your security policy. + * + * Usage: + * npm install @node9/proxy + * npx ts-node examples/demo.ts + */ +import { protect } from '@node9/proxy'; import chalk from 'chalk'; async function main() { @@ -6,16 +23,18 @@ async function main() { console.log(chalk.green(`✅ Success: Database ${name} has been deleted.`)); }; - // Wrap the dangerous function + // Wrap the dangerous function — Node9 will intercept it before it runs const secureDelete = protect('aws.rds.delete_database', deleteDatabase); console.log(chalk.cyan("🤖 AI Agent: 'I am going to clean up the production DB...'")); try { + // Node9 will show a native popup asking you to Allow / Block this action. + // If you click Block (or the policy denies it), an error is thrown. await secureDelete('production-db-v1'); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); - console.log(chalk.yellow(`\n🛡️ Node9 caught it: ${msg}`)); + console.log(chalk.yellow(`\n🛡️ Node9 blocked it: ${msg}`)); } } diff --git a/examples/node9.config.json.example b/examples/node9.config.json.example index 12ea1f3..27dd30f 100644 --- a/examples/node9.config.json.example +++ b/examples/node9.config.json.example @@ -1,57 +1,96 @@ { - "version": "1.0", "settings": { "mode": "standard", - "failOpen": false + "environment": "production", + "autoStartDaemon": true, + "enableUndo": true, + "enableHookLogDebug": false, + "approvers": { + "native": true, + "browser": false, + "cloud": false, + "terminal": true + } }, "policy": { + "sandboxPaths": ["/tmp/**", "**/sandbox/**", "**/test-results/**"], + "dangerousWords": [ - "delete", "drop", - "remove", - "rm", - "rmdir", - "terminate", - "refund", - "write", - "update", - "destroy", + "truncate", "purge", - "revoke", "format", - "truncate" + "destroy", + "terminate", + "revoke", + "docker", + "psql", + "rmdir", + "delete", + "alter", + "grant", + "rm" ], + "ignoredTools": [ "list_*", "get_*", "read_*", "describe_*", "read", - "write", - "edit", - "multiedit", "glob", "grep", "ls", "notebookread", - "notebookedit", - "todoread", - "todowrite", "webfetch", "websearch", "exitplanmode", "askuserquestion", - "run_shell_command" + "agent", + "task*", + "toolsearch", + "mcp__ide__*", + "getDiagnostics" + ], + + "toolInspection": { + "bash": "command", + "shell": "command", + "run_shell_command": "command", + "terminal.execute": "command", + "postgres:query": "sql", + "mcp__github__*": "command", + "mcp__redis__*": "query" + }, + + "rules": [ + { + "action": "rm", + "allowPaths": [ + "**/node_modules/**", + "dist/**", + "build/**", + ".next/**", + ".nuxt/**", + "coverage/**", + ".cache/**", + "tmp/**", + "temp/**", + "**/__pycache__/**", + "**/.pytest_cache/**", + "**/*.log", + "**/*.tmp", + ".DS_Store", + "**/yarn.lock", + "**/package-lock.json", + "**/pnpm-lock.yaml" + ] + } ] }, + "environments": { - "production": { - "requireApproval": true, - "slackChannel": "#alerts-prod-security" - }, - "development": { - "requireApproval": true, - "slackChannel": "#alerts-dev-sandbox" - } + "production": { "requireApproval": true }, + "development": { "requireApproval": false } } } diff --git a/src/core.ts b/src/core.ts index 7cfbf50..c7e5568 100644 --- a/src/core.ts +++ b/src/core.ts @@ -296,7 +296,6 @@ export function redactSecrets(text: string): string { interface EnvironmentConfig { requireApproval?: boolean; - slackChannel?: string; } interface PolicyRule { @@ -312,6 +311,7 @@ interface Config { enableUndo?: boolean; enableHookLogDebug?: boolean; approvers: { native: boolean; browser: boolean; cloud: boolean; terminal: boolean }; + environment?: string; }; policy: { sandboxPaths: string[]; @@ -866,8 +866,7 @@ export async function authorizeHeadless( if (cloudEnforced) { try { - const envConfig = getActiveEnvironment(getConfig()); - const initResult = await initNode9SaaS(toolName, args, creds!, envConfig?.slackChannel, meta); + const initResult = await initNode9SaaS(toolName, args, creds!, meta); if (!initResult.pending) { return { @@ -1213,6 +1212,7 @@ export function getConfig(): Config { if (s.enableHookLogDebug !== undefined) mergedSettings.enableHookLogDebug = s.enableHookLogDebug; if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers }; + if (s.environment !== undefined) mergedSettings.environment = s.environment; if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths); if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools); @@ -1252,7 +1252,7 @@ function tryLoadConfig(filePath: string): Record | null { } function getActiveEnvironment(config: Config): EnvironmentConfig | null { - const env = process.env.NODE_ENV || 'development'; + const env = config.settings.environment || process.env.NODE_ENV || 'development'; return config.environments[env] ?? null; } @@ -1339,7 +1339,6 @@ async function initNode9SaaS( toolName: string, args: unknown, creds: { apiKey: string; apiUrl: string }, - slackChannel?: string, meta?: { agent?: string; mcpServer?: string } ): Promise<{ pending: boolean; @@ -1358,7 +1357,6 @@ async function initNode9SaaS( body: JSON.stringify({ toolName, args, - slackChannel, context: { agent: meta?.agent, mcpServer: meta?.mcpServer,