@node9/proxy 1.9.1 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ### The "Sudo" Command for AI Agents.
4
4
 
5
5
  [![NPM Version](https://img.shields.io/npm/v/@node9/proxy.svg)](https://www.npmjs.com/package/@node9/proxy)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
7
  [![Open in HF Spaces](https://huggingface.co/datasets/huggingface/badges/resolve/main/open-in-hf-spaces-sm.svg)](https://huggingface.co/spaces/Node9ai/node9-security-demo)
8
8
  [![Documentation](https://img.shields.io/badge/docs-node9.ai%2Fdocs-blue)](https://node9.ai/docs)
9
9
 
@@ -15,18 +15,18 @@
15
15
 
16
16
  ## The "Aha!" Moment
17
17
 
18
- **AIs are literal.** Ask an agent to "fix disk space" and it might run `docker system prune -af --volumes`.
18
+ **AIs move fast.** Ask an agent to "ship the fix" and it might push straight to git without asking you.
19
19
 
20
20
  <p align="center">
21
- <img src="https://github.com/user-attachments/assets/7b22e0fb-35ff-4088-8ee9-cc23216f362f" width="100%">
21
+ <img src="https://github.com/user-attachments/assets/4aa6e45b-9aba-4953-9ce3-548226622588" width="100%">
22
22
  </p>
23
23
 
24
24
  With Node9:
25
25
 
26
- 1. **AI attempts:** `Bash("docker system prune -af --volumes")`
26
+ 1. **AI attempts:** `Bash("git push origin main")`
27
27
  2. **Node9 intercepts:** OS-native popup appears instantly
28
28
  3. **You block it** — one click
29
- 4. **AI pivots:** _"I'll remove large log files instead"_
29
+ 4. **AI pivots:** _"I'll create a PR for review instead"_
30
30
 
31
31
  ---
32
32
 
@@ -128,6 +128,26 @@ configure(agent_name="my-agent", policy="require_approval")
128
128
 
129
129
  ---
130
130
 
131
+ ## Flight Recorder & HUD
132
+
133
+ Every tool call your AI agent makes is recorded — command, arguments, result, and cost estimate. Node9 wires a live statusline into Claude Code that shows you what's happening in real time:
134
+
135
+ ```
136
+ 🛡 node9 | standard | [bash-safe] | ✅ 12 allowed 🛑 2 blocked 🚨 0 dlp | ~$0.43 | ⚡ no-force-push
137
+ 📊 claude-opus-4-6 | ctx [████████░░░░░░░] 54% | 5h [██░░░░░░░░░░░░░] 12% | 7d [█░░░░░░░░░░░░░░] 7%
138
+ 🗂 2 CLAUDE.md | 8 rules | 3 MCPs | 4 hooks
139
+ ```
140
+
141
+ **Line 1 — Security state:** active mode, enabled shields, session totals (allowed / blocked / DLP hits), estimated cost, last rule that fired.
142
+
143
+ **Line 2 — Context & rate limits:** model name, context window usage, 5-hour and 7-day token rate-limit bars — so you can see when an agent is burning through quota.
144
+
145
+ **Line 3 — Environment:** how many CLAUDE.md files, rules, MCP servers, and hooks are active in the current project.
146
+
147
+ The HUD is wired automatically by `node9 setup`. Full session logs land in `~/.node9/audit.log`.
148
+
149
+ ---
150
+
131
151
  ## 📖 Full docs
132
152
 
133
153
  Everything else — config reference, smart rules, stateful rules, trusted hosts, approval modes, CLI reference — is at **[node9.ai/docs](https://node9.ai/docs)**.
package/dist/cli.js CHANGED
@@ -214,6 +214,7 @@ var init_config_schema = __esm({
214
214
  errorMap: () => ({ message: "verdict must be one of: allow, review, block" })
215
215
  }),
216
216
  reason: import_zod.z.string().optional(),
217
+ description: import_zod.z.string().optional(),
217
218
  // Unknown predicate names are filtered out rather than failing the whole rule.
218
219
  // Failing the whole z.array() would cause sanitizeConfig to drop the entire
219
220
  // `policy` top-level key, silently disabling ALL smart rules in the config.
@@ -791,7 +792,8 @@ var init_config = __esm({
791
792
  }
792
793
  ],
793
794
  verdict: "block",
794
- reason: "Recursive delete of home directory is irreversible"
795
+ reason: "Recursive delete of home directory is irreversible",
796
+ description: "The AI wants to recursively delete your home directory. This will permanently destroy all your personal files and cannot be undone."
795
797
  },
796
798
  // ── SQL safety ────────────────────────────────────────────────────────
797
799
  {
@@ -803,7 +805,8 @@ var init_config = __esm({
803
805
  ],
804
806
  conditionMode: "all",
805
807
  verdict: "review",
806
- reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table"
808
+ reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table",
809
+ description: "The AI is running a SQL statement that will modify every row in the table \u2014 no WHERE filter was found. This could wipe or corrupt all your data."
807
810
  },
808
811
  {
809
812
  name: "review-drop-truncate-shell",
@@ -818,7 +821,8 @@ var init_config = __esm({
818
821
  ],
819
822
  conditionMode: "all",
820
823
  verdict: "review",
821
- reason: "SQL DDL destructive statement inside a shell command"
824
+ reason: "SQL DDL destructive statement inside a shell command",
825
+ description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
822
826
  },
823
827
  // ── Git safety ────────────────────────────────────────────────────────
824
828
  {
@@ -834,7 +838,8 @@ var init_config = __esm({
834
838
  ],
835
839
  conditionMode: "all",
836
840
  verdict: "block",
837
- reason: "Force push overwrites remote history and cannot be undone"
841
+ reason: "Force push overwrites remote history and cannot be undone",
842
+ description: "The AI wants to force push to a remote git branch. This rewrites shared history and can permanently destroy commits that teammates have already pulled."
838
843
  },
839
844
  {
840
845
  name: "review-git-push",
@@ -849,7 +854,8 @@ var init_config = __esm({
849
854
  ],
850
855
  conditionMode: "all",
851
856
  verdict: "review",
852
- reason: "git push sends changes to a shared remote"
857
+ reason: "git push sends changes to a shared remote",
858
+ description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
853
859
  },
854
860
  {
855
861
  name: "review-git-destructive",
@@ -864,7 +870,8 @@ var init_config = __esm({
864
870
  ],
865
871
  conditionMode: "all",
866
872
  verdict: "review",
867
- reason: "Destructive git operation \u2014 discards history or working-tree changes"
873
+ reason: "Destructive git operation \u2014 discards history or working-tree changes",
874
+ description: "The AI wants to run a destructive git operation (reset, rebase, clean, or branch delete) that can permanently discard commits or uncommitted work."
868
875
  },
869
876
  // ── Shell safety ──────────────────────────────────────────────────────
870
877
  {
@@ -873,7 +880,8 @@ var init_config = __esm({
873
880
  conditions: [{ field: "command", op: "matches", value: "\\bsudo\\s", flags: "i" }],
874
881
  conditionMode: "all",
875
882
  verdict: "review",
876
- reason: "Command requires elevated privileges"
883
+ reason: "Command requires elevated privileges",
884
+ description: "The AI wants to run a command as root (sudo). Commands with root access can modify system files, install software, or change security settings."
877
885
  },
878
886
  {
879
887
  name: "review-curl-pipe-shell",
@@ -888,7 +896,8 @@ var init_config = __esm({
888
896
  ],
889
897
  conditionMode: "all",
890
898
  verdict: "block",
891
- reason: "Piping remote script into a shell is a supply-chain attack vector"
899
+ reason: "Piping remote script into a shell is a supply-chain attack vector",
900
+ description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
892
901
  }
893
902
  ],
894
903
  dlp: { enabled: true, scanIgnoredTools: true }
@@ -921,7 +930,8 @@ var init_config = __esm({
921
930
  tool: "*",
922
931
  conditions: [{ field: "command", op: "matches", value: "(^|&&|\\|\\||;)\\s*rm\\b" }],
923
932
  verdict: "review",
924
- reason: "rm can permanently delete files \u2014 confirm the target path"
933
+ reason: "rm can permanently delete files \u2014 confirm the target path",
934
+ description: "The AI wants to delete files. Unlike moving to trash, rm is permanent \u2014 the files cannot be recovered without a backup."
925
935
  },
926
936
  // ── SQL safety (Safe by Default) ──────────────────────────────────────────
927
937
  // These rules fire when an AI calls a database tool directly (e.g. MCP postgres,
@@ -933,14 +943,16 @@ var init_config = __esm({
933
943
  tool: "*",
934
944
  conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
935
945
  verdict: "review",
936
- reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead"
946
+ reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead",
947
+ description: "The AI wants to drop a database table. This permanently deletes the table and all its data \u2014 there is no undo."
937
948
  },
938
949
  {
939
950
  name: "review-truncate-sql",
940
951
  tool: "*",
941
952
  conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
942
953
  verdict: "review",
943
- reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead"
954
+ reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead",
955
+ description: "The AI wants to truncate a database table, which instantly deletes every row. The table structure remains but all data is gone."
944
956
  },
945
957
  {
946
958
  name: "review-drop-column-sql",
@@ -949,7 +961,8 @@ var init_config = __esm({
949
961
  { field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
950
962
  ],
951
963
  verdict: "review",
952
- reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead"
964
+ reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead",
965
+ description: "The AI wants to drop a column from a database table. This permanently removes the column and all its data from every row."
953
966
  }
954
967
  ];
955
968
  cachedConfig = null;
@@ -1844,6 +1857,9 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1844
1857
  reason: matchedRule.reason,
1845
1858
  tier: 2,
1846
1859
  ruleName: matchedRule.name ?? matchedRule.tool,
1860
+ ...(matchedRule.description ?? matchedRule.reason) && {
1861
+ ruleDescription: matchedRule.description ?? matchedRule.reason
1862
+ },
1847
1863
  ...matchedRule.verdict === "block" && matchedRule.dependsOnState?.length && {
1848
1864
  dependsOnStatePredicates: matchedRule.dependsOnState
1849
1865
  },
@@ -3289,7 +3305,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3289
3305
  blockedBy: "local-config",
3290
3306
  blockedByLabel: policyResult.blockedByLabel,
3291
3307
  ruleHit: policyResult.ruleName,
3292
- ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand }
3308
+ ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
3309
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
3293
3310
  };
3294
3311
  }
3295
3312
  }
@@ -9066,6 +9083,7 @@ RAW: ${raw}
9066
9083
  writeTty(import_chalk5.default.red(`
9067
9084
  \u{1F6D1} Node9 blocked "${toolName}"`));
9068
9085
  }
9086
+ if (result2?.ruleDescription) writeTty(import_chalk5.default.white(` ${result2.ruleDescription}`));
9069
9087
  writeTty(import_chalk5.default.gray(` Triggered by: ${blockedByContext}`));
9070
9088
  if (result2?.changeHint) writeTty(import_chalk5.default.cyan(` To change: ${result2.changeHint}`));
9071
9089
  if (result2?.recoveryCommand)
package/dist/cli.mjs CHANGED
@@ -192,6 +192,7 @@ var init_config_schema = __esm({
192
192
  errorMap: () => ({ message: "verdict must be one of: allow, review, block" })
193
193
  }),
194
194
  reason: z.string().optional(),
195
+ description: z.string().optional(),
195
196
  // Unknown predicate names are filtered out rather than failing the whole rule.
196
197
  // Failing the whole z.array() would cause sanitizeConfig to drop the entire
197
198
  // `policy` top-level key, silently disabling ALL smart rules in the config.
@@ -769,7 +770,8 @@ var init_config = __esm({
769
770
  }
770
771
  ],
771
772
  verdict: "block",
772
- reason: "Recursive delete of home directory is irreversible"
773
+ reason: "Recursive delete of home directory is irreversible",
774
+ description: "The AI wants to recursively delete your home directory. This will permanently destroy all your personal files and cannot be undone."
773
775
  },
774
776
  // ── SQL safety ────────────────────────────────────────────────────────
775
777
  {
@@ -781,7 +783,8 @@ var init_config = __esm({
781
783
  ],
782
784
  conditionMode: "all",
783
785
  verdict: "review",
784
- reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table"
786
+ reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table",
787
+ description: "The AI is running a SQL statement that will modify every row in the table \u2014 no WHERE filter was found. This could wipe or corrupt all your data."
785
788
  },
786
789
  {
787
790
  name: "review-drop-truncate-shell",
@@ -796,7 +799,8 @@ var init_config = __esm({
796
799
  ],
797
800
  conditionMode: "all",
798
801
  verdict: "review",
799
- reason: "SQL DDL destructive statement inside a shell command"
802
+ reason: "SQL DDL destructive statement inside a shell command",
803
+ description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
800
804
  },
801
805
  // ── Git safety ────────────────────────────────────────────────────────
802
806
  {
@@ -812,7 +816,8 @@ var init_config = __esm({
812
816
  ],
813
817
  conditionMode: "all",
814
818
  verdict: "block",
815
- reason: "Force push overwrites remote history and cannot be undone"
819
+ reason: "Force push overwrites remote history and cannot be undone",
820
+ description: "The AI wants to force push to a remote git branch. This rewrites shared history and can permanently destroy commits that teammates have already pulled."
816
821
  },
817
822
  {
818
823
  name: "review-git-push",
@@ -827,7 +832,8 @@ var init_config = __esm({
827
832
  ],
828
833
  conditionMode: "all",
829
834
  verdict: "review",
830
- reason: "git push sends changes to a shared remote"
835
+ reason: "git push sends changes to a shared remote",
836
+ description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
831
837
  },
832
838
  {
833
839
  name: "review-git-destructive",
@@ -842,7 +848,8 @@ var init_config = __esm({
842
848
  ],
843
849
  conditionMode: "all",
844
850
  verdict: "review",
845
- reason: "Destructive git operation \u2014 discards history or working-tree changes"
851
+ reason: "Destructive git operation \u2014 discards history or working-tree changes",
852
+ description: "The AI wants to run a destructive git operation (reset, rebase, clean, or branch delete) that can permanently discard commits or uncommitted work."
846
853
  },
847
854
  // ── Shell safety ──────────────────────────────────────────────────────
848
855
  {
@@ -851,7 +858,8 @@ var init_config = __esm({
851
858
  conditions: [{ field: "command", op: "matches", value: "\\bsudo\\s", flags: "i" }],
852
859
  conditionMode: "all",
853
860
  verdict: "review",
854
- reason: "Command requires elevated privileges"
861
+ reason: "Command requires elevated privileges",
862
+ description: "The AI wants to run a command as root (sudo). Commands with root access can modify system files, install software, or change security settings."
855
863
  },
856
864
  {
857
865
  name: "review-curl-pipe-shell",
@@ -866,7 +874,8 @@ var init_config = __esm({
866
874
  ],
867
875
  conditionMode: "all",
868
876
  verdict: "block",
869
- reason: "Piping remote script into a shell is a supply-chain attack vector"
877
+ reason: "Piping remote script into a shell is a supply-chain attack vector",
878
+ description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
870
879
  }
871
880
  ],
872
881
  dlp: { enabled: true, scanIgnoredTools: true }
@@ -899,7 +908,8 @@ var init_config = __esm({
899
908
  tool: "*",
900
909
  conditions: [{ field: "command", op: "matches", value: "(^|&&|\\|\\||;)\\s*rm\\b" }],
901
910
  verdict: "review",
902
- reason: "rm can permanently delete files \u2014 confirm the target path"
911
+ reason: "rm can permanently delete files \u2014 confirm the target path",
912
+ description: "The AI wants to delete files. Unlike moving to trash, rm is permanent \u2014 the files cannot be recovered without a backup."
903
913
  },
904
914
  // ── SQL safety (Safe by Default) ──────────────────────────────────────────
905
915
  // These rules fire when an AI calls a database tool directly (e.g. MCP postgres,
@@ -911,14 +921,16 @@ var init_config = __esm({
911
921
  tool: "*",
912
922
  conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
913
923
  verdict: "review",
914
- reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead"
924
+ reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead",
925
+ description: "The AI wants to drop a database table. This permanently deletes the table and all its data \u2014 there is no undo."
915
926
  },
916
927
  {
917
928
  name: "review-truncate-sql",
918
929
  tool: "*",
919
930
  conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
920
931
  verdict: "review",
921
- reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead"
932
+ reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead",
933
+ description: "The AI wants to truncate a database table, which instantly deletes every row. The table structure remains but all data is gone."
922
934
  },
923
935
  {
924
936
  name: "review-drop-column-sql",
@@ -927,7 +939,8 @@ var init_config = __esm({
927
939
  { field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
928
940
  ],
929
941
  verdict: "review",
930
- reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead"
942
+ reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead",
943
+ description: "The AI wants to drop a column from a database table. This permanently removes the column and all its data from every row."
931
944
  }
932
945
  ];
933
946
  cachedConfig = null;
@@ -1827,6 +1840,9 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1827
1840
  reason: matchedRule.reason,
1828
1841
  tier: 2,
1829
1842
  ruleName: matchedRule.name ?? matchedRule.tool,
1843
+ ...(matchedRule.description ?? matchedRule.reason) && {
1844
+ ruleDescription: matchedRule.description ?? matchedRule.reason
1845
+ },
1830
1846
  ...matchedRule.verdict === "block" && matchedRule.dependsOnState?.length && {
1831
1847
  dependsOnStatePredicates: matchedRule.dependsOnState
1832
1848
  },
@@ -3267,7 +3283,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3267
3283
  blockedBy: "local-config",
3268
3284
  blockedByLabel: policyResult.blockedByLabel,
3269
3285
  ruleHit: policyResult.ruleName,
3270
- ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand }
3286
+ ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
3287
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
3271
3288
  };
3272
3289
  }
3273
3290
  }
@@ -9041,6 +9058,7 @@ RAW: ${raw}
9041
9058
  writeTty(chalk5.red(`
9042
9059
  \u{1F6D1} Node9 blocked "${toolName}"`));
9043
9060
  }
9061
+ if (result2?.ruleDescription) writeTty(chalk5.white(` ${result2.ruleDescription}`));
9044
9062
  writeTty(chalk5.gray(` Triggered by: ${blockedByContext}`));
9045
9063
  if (result2?.changeHint) writeTty(chalk5.cyan(` To change: ${result2.changeHint}`));
9046
9064
  if (result2?.recoveryCommand)
package/dist/index.js CHANGED
@@ -199,6 +199,7 @@ var SmartRuleSchema = import_zod.z.object({
199
199
  errorMap: () => ({ message: "verdict must be one of: allow, review, block" })
200
200
  }),
201
201
  reason: import_zod.z.string().optional(),
202
+ description: import_zod.z.string().optional(),
202
203
  // Unknown predicate names are filtered out rather than failing the whole rule.
203
204
  // Failing the whole z.array() would cause sanitizeConfig to drop the entire
204
205
  // `policy` top-level key, silently disabling ALL smart rules in the config.
@@ -492,7 +493,8 @@ var DEFAULT_CONFIG = {
492
493
  }
493
494
  ],
494
495
  verdict: "block",
495
- reason: "Recursive delete of home directory is irreversible"
496
+ reason: "Recursive delete of home directory is irreversible",
497
+ description: "The AI wants to recursively delete your home directory. This will permanently destroy all your personal files and cannot be undone."
496
498
  },
497
499
  // ── SQL safety ────────────────────────────────────────────────────────
498
500
  {
@@ -504,7 +506,8 @@ var DEFAULT_CONFIG = {
504
506
  ],
505
507
  conditionMode: "all",
506
508
  verdict: "review",
507
- reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table"
509
+ reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table",
510
+ description: "The AI is running a SQL statement that will modify every row in the table \u2014 no WHERE filter was found. This could wipe or corrupt all your data."
508
511
  },
509
512
  {
510
513
  name: "review-drop-truncate-shell",
@@ -519,7 +522,8 @@ var DEFAULT_CONFIG = {
519
522
  ],
520
523
  conditionMode: "all",
521
524
  verdict: "review",
522
- reason: "SQL DDL destructive statement inside a shell command"
525
+ reason: "SQL DDL destructive statement inside a shell command",
526
+ description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
523
527
  },
524
528
  // ── Git safety ────────────────────────────────────────────────────────
525
529
  {
@@ -535,7 +539,8 @@ var DEFAULT_CONFIG = {
535
539
  ],
536
540
  conditionMode: "all",
537
541
  verdict: "block",
538
- reason: "Force push overwrites remote history and cannot be undone"
542
+ reason: "Force push overwrites remote history and cannot be undone",
543
+ description: "The AI wants to force push to a remote git branch. This rewrites shared history and can permanently destroy commits that teammates have already pulled."
539
544
  },
540
545
  {
541
546
  name: "review-git-push",
@@ -550,7 +555,8 @@ var DEFAULT_CONFIG = {
550
555
  ],
551
556
  conditionMode: "all",
552
557
  verdict: "review",
553
- reason: "git push sends changes to a shared remote"
558
+ reason: "git push sends changes to a shared remote",
559
+ description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
554
560
  },
555
561
  {
556
562
  name: "review-git-destructive",
@@ -565,7 +571,8 @@ var DEFAULT_CONFIG = {
565
571
  ],
566
572
  conditionMode: "all",
567
573
  verdict: "review",
568
- reason: "Destructive git operation \u2014 discards history or working-tree changes"
574
+ reason: "Destructive git operation \u2014 discards history or working-tree changes",
575
+ description: "The AI wants to run a destructive git operation (reset, rebase, clean, or branch delete) that can permanently discard commits or uncommitted work."
569
576
  },
570
577
  // ── Shell safety ──────────────────────────────────────────────────────
571
578
  {
@@ -574,7 +581,8 @@ var DEFAULT_CONFIG = {
574
581
  conditions: [{ field: "command", op: "matches", value: "\\bsudo\\s", flags: "i" }],
575
582
  conditionMode: "all",
576
583
  verdict: "review",
577
- reason: "Command requires elevated privileges"
584
+ reason: "Command requires elevated privileges",
585
+ description: "The AI wants to run a command as root (sudo). Commands with root access can modify system files, install software, or change security settings."
578
586
  },
579
587
  {
580
588
  name: "review-curl-pipe-shell",
@@ -589,7 +597,8 @@ var DEFAULT_CONFIG = {
589
597
  ],
590
598
  conditionMode: "all",
591
599
  verdict: "block",
592
- reason: "Piping remote script into a shell is a supply-chain attack vector"
600
+ reason: "Piping remote script into a shell is a supply-chain attack vector",
601
+ description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
593
602
  }
594
603
  ],
595
604
  dlp: { enabled: true, scanIgnoredTools: true }
@@ -622,7 +631,8 @@ var ADVISORY_SMART_RULES = [
622
631
  tool: "*",
623
632
  conditions: [{ field: "command", op: "matches", value: "(^|&&|\\|\\||;)\\s*rm\\b" }],
624
633
  verdict: "review",
625
- reason: "rm can permanently delete files \u2014 confirm the target path"
634
+ reason: "rm can permanently delete files \u2014 confirm the target path",
635
+ description: "The AI wants to delete files. Unlike moving to trash, rm is permanent \u2014 the files cannot be recovered without a backup."
626
636
  },
627
637
  // ── SQL safety (Safe by Default) ──────────────────────────────────────────
628
638
  // These rules fire when an AI calls a database tool directly (e.g. MCP postgres,
@@ -634,14 +644,16 @@ var ADVISORY_SMART_RULES = [
634
644
  tool: "*",
635
645
  conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
636
646
  verdict: "review",
637
- reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead"
647
+ reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead",
648
+ description: "The AI wants to drop a database table. This permanently deletes the table and all its data \u2014 there is no undo."
638
649
  },
639
650
  {
640
651
  name: "review-truncate-sql",
641
652
  tool: "*",
642
653
  conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
643
654
  verdict: "review",
644
- reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead"
655
+ reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead",
656
+ description: "The AI wants to truncate a database table, which instantly deletes every row. The table structure remains but all data is gone."
645
657
  },
646
658
  {
647
659
  name: "review-drop-column-sql",
@@ -650,7 +662,8 @@ var ADVISORY_SMART_RULES = [
650
662
  { field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
651
663
  ],
652
664
  verdict: "review",
653
- reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead"
665
+ reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead",
666
+ description: "The AI wants to drop a column from a database table. This permanently removes the column and all its data from every row."
654
667
  }
655
668
  ];
656
669
  var cachedConfig = null;
@@ -1665,6 +1678,9 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1665
1678
  reason: matchedRule.reason,
1666
1679
  tier: 2,
1667
1680
  ruleName: matchedRule.name ?? matchedRule.tool,
1681
+ ...(matchedRule.description ?? matchedRule.reason) && {
1682
+ ruleDescription: matchedRule.description ?? matchedRule.reason
1683
+ },
1668
1684
  ...matchedRule.verdict === "block" && matchedRule.dependsOnState?.length && {
1669
1685
  dependsOnStatePredicates: matchedRule.dependsOnState
1670
1686
  },
@@ -2795,7 +2811,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2795
2811
  blockedBy: "local-config",
2796
2812
  blockedByLabel: policyResult.blockedByLabel,
2797
2813
  ruleHit: policyResult.ruleName,
2798
- ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand }
2814
+ ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
2815
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
2799
2816
  };
2800
2817
  }
2801
2818
  }
package/dist/index.mjs CHANGED
@@ -169,6 +169,7 @@ var SmartRuleSchema = z.object({
169
169
  errorMap: () => ({ message: "verdict must be one of: allow, review, block" })
170
170
  }),
171
171
  reason: z.string().optional(),
172
+ description: z.string().optional(),
172
173
  // Unknown predicate names are filtered out rather than failing the whole rule.
173
174
  // Failing the whole z.array() would cause sanitizeConfig to drop the entire
174
175
  // `policy` top-level key, silently disabling ALL smart rules in the config.
@@ -462,7 +463,8 @@ var DEFAULT_CONFIG = {
462
463
  }
463
464
  ],
464
465
  verdict: "block",
465
- reason: "Recursive delete of home directory is irreversible"
466
+ reason: "Recursive delete of home directory is irreversible",
467
+ description: "The AI wants to recursively delete your home directory. This will permanently destroy all your personal files and cannot be undone."
466
468
  },
467
469
  // ── SQL safety ────────────────────────────────────────────────────────
468
470
  {
@@ -474,7 +476,8 @@ var DEFAULT_CONFIG = {
474
476
  ],
475
477
  conditionMode: "all",
476
478
  verdict: "review",
477
- reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table"
479
+ reason: "DELETE/UPDATE without WHERE clause \u2014 would affect every row in the table",
480
+ description: "The AI is running a SQL statement that will modify every row in the table \u2014 no WHERE filter was found. This could wipe or corrupt all your data."
478
481
  },
479
482
  {
480
483
  name: "review-drop-truncate-shell",
@@ -489,7 +492,8 @@ var DEFAULT_CONFIG = {
489
492
  ],
490
493
  conditionMode: "all",
491
494
  verdict: "review",
492
- reason: "SQL DDL destructive statement inside a shell command"
495
+ reason: "SQL DDL destructive statement inside a shell command",
496
+ description: "The AI wants to drop or truncate a database table via the shell. This permanently deletes the table structure or all its data."
493
497
  },
494
498
  // ── Git safety ────────────────────────────────────────────────────────
495
499
  {
@@ -505,7 +509,8 @@ var DEFAULT_CONFIG = {
505
509
  ],
506
510
  conditionMode: "all",
507
511
  verdict: "block",
508
- reason: "Force push overwrites remote history and cannot be undone"
512
+ reason: "Force push overwrites remote history and cannot be undone",
513
+ description: "The AI wants to force push to a remote git branch. This rewrites shared history and can permanently destroy commits that teammates have already pulled."
509
514
  },
510
515
  {
511
516
  name: "review-git-push",
@@ -520,7 +525,8 @@ var DEFAULT_CONFIG = {
520
525
  ],
521
526
  conditionMode: "all",
522
527
  verdict: "review",
523
- reason: "git push sends changes to a shared remote"
528
+ reason: "git push sends changes to a shared remote",
529
+ description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
524
530
  },
525
531
  {
526
532
  name: "review-git-destructive",
@@ -535,7 +541,8 @@ var DEFAULT_CONFIG = {
535
541
  ],
536
542
  conditionMode: "all",
537
543
  verdict: "review",
538
- reason: "Destructive git operation \u2014 discards history or working-tree changes"
544
+ reason: "Destructive git operation \u2014 discards history or working-tree changes",
545
+ description: "The AI wants to run a destructive git operation (reset, rebase, clean, or branch delete) that can permanently discard commits or uncommitted work."
539
546
  },
540
547
  // ── Shell safety ──────────────────────────────────────────────────────
541
548
  {
@@ -544,7 +551,8 @@ var DEFAULT_CONFIG = {
544
551
  conditions: [{ field: "command", op: "matches", value: "\\bsudo\\s", flags: "i" }],
545
552
  conditionMode: "all",
546
553
  verdict: "review",
547
- reason: "Command requires elevated privileges"
554
+ reason: "Command requires elevated privileges",
555
+ description: "The AI wants to run a command as root (sudo). Commands with root access can modify system files, install software, or change security settings."
548
556
  },
549
557
  {
550
558
  name: "review-curl-pipe-shell",
@@ -559,7 +567,8 @@ var DEFAULT_CONFIG = {
559
567
  ],
560
568
  conditionMode: "all",
561
569
  verdict: "block",
562
- reason: "Piping remote script into a shell is a supply-chain attack vector"
570
+ reason: "Piping remote script into a shell is a supply-chain attack vector",
571
+ description: "The AI wants to download a script from the internet and run it immediately, without you seeing what it contains. This is one of the most common ways malware gets installed."
563
572
  }
564
573
  ],
565
574
  dlp: { enabled: true, scanIgnoredTools: true }
@@ -592,7 +601,8 @@ var ADVISORY_SMART_RULES = [
592
601
  tool: "*",
593
602
  conditions: [{ field: "command", op: "matches", value: "(^|&&|\\|\\||;)\\s*rm\\b" }],
594
603
  verdict: "review",
595
- reason: "rm can permanently delete files \u2014 confirm the target path"
604
+ reason: "rm can permanently delete files \u2014 confirm the target path",
605
+ description: "The AI wants to delete files. Unlike moving to trash, rm is permanent \u2014 the files cannot be recovered without a backup."
596
606
  },
597
607
  // ── SQL safety (Safe by Default) ──────────────────────────────────────────
598
608
  // These rules fire when an AI calls a database tool directly (e.g. MCP postgres,
@@ -604,14 +614,16 @@ var ADVISORY_SMART_RULES = [
604
614
  tool: "*",
605
615
  conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
606
616
  verdict: "review",
607
- reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead"
617
+ reason: "DROP TABLE is irreversible \u2014 enable the postgres shield to block instead",
618
+ description: "The AI wants to drop a database table. This permanently deletes the table and all its data \u2014 there is no undo."
608
619
  },
609
620
  {
610
621
  name: "review-truncate-sql",
611
622
  tool: "*",
612
623
  conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
613
624
  verdict: "review",
614
- reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead"
625
+ reason: "TRUNCATE removes all rows \u2014 enable the postgres shield to block instead",
626
+ description: "The AI wants to truncate a database table, which instantly deletes every row. The table structure remains but all data is gone."
615
627
  },
616
628
  {
617
629
  name: "review-drop-column-sql",
@@ -620,7 +632,8 @@ var ADVISORY_SMART_RULES = [
620
632
  { field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
621
633
  ],
622
634
  verdict: "review",
623
- reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead"
635
+ reason: "DROP COLUMN is irreversible \u2014 enable the postgres shield to block instead",
636
+ description: "The AI wants to drop a column from a database table. This permanently removes the column and all its data from every row."
624
637
  }
625
638
  ];
626
639
  var cachedConfig = null;
@@ -1635,6 +1648,9 @@ async function evaluatePolicy(toolName, args, agent, cwd) {
1635
1648
  reason: matchedRule.reason,
1636
1649
  tier: 2,
1637
1650
  ruleName: matchedRule.name ?? matchedRule.tool,
1651
+ ...(matchedRule.description ?? matchedRule.reason) && {
1652
+ ruleDescription: matchedRule.description ?? matchedRule.reason
1653
+ },
1638
1654
  ...matchedRule.verdict === "block" && matchedRule.dependsOnState?.length && {
1639
1655
  dependsOnStatePredicates: matchedRule.dependsOnState
1640
1656
  },
@@ -2765,7 +2781,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2765
2781
  blockedBy: "local-config",
2766
2782
  blockedByLabel: policyResult.blockedByLabel,
2767
2783
  ruleHit: policyResult.ruleName,
2768
- ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand }
2784
+ ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
2785
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
2769
2786
  };
2770
2787
  }
2771
2788
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code & MCP.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",