Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f04a4b7
chore: add semantic-release configuration
nadavis Mar 10, 2026
4526c7b
style: fix formatting in package.json
nadavis Mar 10, 2026
aad75a4
fix: expand codeKeys and increase fallback truncation in native popup
nadavis Mar 11, 2026
00e540a
feat: add local audit log and hook debug log to core
nadavis Mar 11, 2026
9915135
refactor: replace appendAuditModeEntry with appendLocalAudit
nadavis Mar 11, 2026
081a3a7
update readme
nadavis Mar 12, 2026
25955bf
feat: improve native popup with specific rule names and keyboard shor…
nadavis Mar 12, 2026
99520ae
docs: remove static image and expand video to full width in README
nadavis Mar 12, 2026
ced874c
docs: update demo video source in README
nadavis Mar 12, 2026
5e7b47a
docs: add .mp4 extension to demo video src
nadavis Mar 12, 2026
1359115
docs: replace video with gif for autoplay/loop support on GitHub
nadavis Mar 12, 2026
12a7372
docs: update demo gif asset URL
nadavis Mar 12, 2026
aaa8bab
docs: update demo gif asset URL
nadavis Mar 12, 2026
2b392c9
docs: update demo gif asset URL
nadavis Mar 12, 2026
30fb2eb
docs: update demo gif asset URL
nadavis Mar 12, 2026
5f2625b
Merge branch 'main' into dev
nadav-node9 Mar 12, 2026
a9e2069
ci: trigger fresh CI run to clear stale typecheck annotations
nadavis Mar 12, 2026
3f5f5b1
fix: restore missing return statement in buildPangoMessage lost durin…
nadavis Mar 12, 2026
eb4e63d
docs: update demo.ts with correct import and usage explanation
nadavis Mar 12, 2026
627c3db
fix: correct node9.config.json.example
nadavis Mar 12, 2026
a34ac4a
feat: add environment setting to config for explicit env selection
nadavis Mar 12, 2026
7a59bc7
style: fix prettier formatting in node9.config.json.example
nadavis Mar 12, 2026
2adcf0c
refactor: remove slackChannel from proxy config — managed via SaaS da…
nadavis Mar 12, 2026
38d2f6c
fix: remove unused envConfig variable left after slackChannel removal
nadavis Mar 12, 2026
7744f47
fix: add a new gif to readme
nadavis Mar 13, 2026
807aae7
Merge branch 'main' into dev
nadav-node9 Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

<p align="center">
<img src="https://github.com/user-attachments/assets/c3a8f3ae-f0aa-4c57-869a-5e1e2e356d35" width="100%">
<img src="https://github.com/user-attachments/assets/0e45e843-4cf7-408e-95ce-23fb09525ee4" width="100%">
</p>

**With Node9, the interaction looks like this:**
Expand Down
25 changes: 22 additions & 3 deletions examples/demo.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
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() {
const deleteDatabase = async (name: string) => {
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}`));
}
}

Expand Down
95 changes: 67 additions & 28 deletions examples/node9.config.json.example
Original file line number Diff line number Diff line change
@@ -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 }
}
}
10 changes: 4 additions & 6 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,6 @@ export function redactSecrets(text: string): string {

interface EnvironmentConfig {
requireApproval?: boolean;
slackChannel?: string;
}

interface PolicyRule {
Expand All @@ -312,6 +311,7 @@ interface Config {
enableUndo?: boolean;
enableHookLogDebug?: boolean;
approvers: { native: boolean; browser: boolean; cloud: boolean; terminal: boolean };
environment?: string;
};
policy: {
sandboxPaths: string[];
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1252,7 +1252,7 @@ function tryLoadConfig(filePath: string): Record<string, unknown> | 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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -1358,7 +1357,6 @@ async function initNode9SaaS(
body: JSON.stringify({
toolName,
args,
slackChannel,
context: {
agent: meta?.agent,
mcpServer: meta?.mcpServer,
Expand Down
Loading