@node9/proxy 1.0.2 → 1.0.4

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/dist/index.js CHANGED
@@ -350,6 +350,23 @@ function extractShellCommand(toolName, args, toolInspection) {
350
350
  const value = getNestedValue(args, fieldPath);
351
351
  return typeof value === "string" ? value : null;
352
352
  }
353
+ function isSqlTool(toolName, toolInspection) {
354
+ const patterns = Object.keys(toolInspection);
355
+ const matchingPattern = patterns.find((p) => matchesPattern(toolName, p));
356
+ if (!matchingPattern) return false;
357
+ const fieldName = toolInspection[matchingPattern];
358
+ return fieldName === "sql" || fieldName === "query";
359
+ }
360
+ var SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
361
+ function checkDangerousSql(sql) {
362
+ const norm = sql.replace(/\s+/g, " ").trim().toLowerCase();
363
+ const hasWhere = /\bwhere\b/.test(norm);
364
+ if (/^delete\s+from\s+\S+/.test(norm) && !hasWhere)
365
+ return "DELETE without WHERE \u2014 full table wipe";
366
+ if (/^update\s+\S+\s+set\s+/.test(norm) && !hasWhere)
367
+ return "UPDATE without WHERE \u2014 updates every row";
368
+ return null;
369
+ }
353
370
  async function analyzeShellCommand(command) {
354
371
  const actions = [];
355
372
  const paths = [];
@@ -529,9 +546,20 @@ async function evaluatePolicy(toolName, args, agent) {
529
546
  if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
530
547
  return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)" };
531
548
  }
549
+ if (isSqlTool(toolName, config.policy.toolInspection)) {
550
+ const sqlDanger = checkDangerousSql(shellCommand);
551
+ if (sqlDanger) return { decision: "review", blockedByLabel: `SQL Safety: ${sqlDanger}` };
552
+ allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
553
+ actionTokens = actionTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
554
+ }
532
555
  } else {
533
556
  allTokens = tokenize(toolName);
534
557
  actionTokens = [toolName];
558
+ if (args && typeof args === "object") {
559
+ const flattenedArgs = JSON.stringify(args).toLowerCase();
560
+ const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
561
+ allTokens.push(...extraTokens);
562
+ }
535
563
  }
536
564
  const isManual = agent === "Terminal";
537
565
  if (isManual) {
@@ -760,8 +788,7 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
760
788
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
761
789
  if (cloudEnforced) {
762
790
  try {
763
- const envConfig = getActiveEnvironment(getConfig());
764
- const initResult = await initNode9SaaS(toolName, args, creds, envConfig?.slackChannel, meta);
791
+ const initResult = await initNode9SaaS(toolName, args, creds, meta);
765
792
  if (!initResult.pending) {
766
793
  return {
767
794
  approved: !!initResult.approved,
@@ -1015,6 +1042,7 @@ function getConfig() {
1015
1042
  if (s.enableHookLogDebug !== void 0)
1016
1043
  mergedSettings.enableHookLogDebug = s.enableHookLogDebug;
1017
1044
  if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
1045
+ if (s.environment !== void 0) mergedSettings.environment = s.environment;
1018
1046
  if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
1019
1047
  if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
1020
1048
  if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
@@ -1044,7 +1072,7 @@ function tryLoadConfig(filePath) {
1044
1072
  }
1045
1073
  }
1046
1074
  function getActiveEnvironment(config) {
1047
- const env = process.env.NODE_ENV || "development";
1075
+ const env = config.settings.environment || process.env.NODE_ENV || "development";
1048
1076
  return config.environments[env] ?? null;
1049
1077
  }
1050
1078
  function getCredentials() {
@@ -1104,7 +1132,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
1104
1132
  }).catch(() => {
1105
1133
  });
1106
1134
  }
1107
- async function initNode9SaaS(toolName, args, creds, slackChannel, meta) {
1135
+ async function initNode9SaaS(toolName, args, creds, meta) {
1108
1136
  const controller = new AbortController();
1109
1137
  const timeout = setTimeout(() => controller.abort(), 1e4);
1110
1138
  try {
@@ -1114,7 +1142,6 @@ async function initNode9SaaS(toolName, args, creds, slackChannel, meta) {
1114
1142
  body: JSON.stringify({
1115
1143
  toolName,
1116
1144
  args,
1117
- slackChannel,
1118
1145
  context: {
1119
1146
  agent: meta?.agent,
1120
1147
  mcpServer: meta?.mcpServer,
package/dist/index.mjs CHANGED
@@ -314,6 +314,23 @@ function extractShellCommand(toolName, args, toolInspection) {
314
314
  const value = getNestedValue(args, fieldPath);
315
315
  return typeof value === "string" ? value : null;
316
316
  }
317
+ function isSqlTool(toolName, toolInspection) {
318
+ const patterns = Object.keys(toolInspection);
319
+ const matchingPattern = patterns.find((p) => matchesPattern(toolName, p));
320
+ if (!matchingPattern) return false;
321
+ const fieldName = toolInspection[matchingPattern];
322
+ return fieldName === "sql" || fieldName === "query";
323
+ }
324
+ var SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
325
+ function checkDangerousSql(sql) {
326
+ const norm = sql.replace(/\s+/g, " ").trim().toLowerCase();
327
+ const hasWhere = /\bwhere\b/.test(norm);
328
+ if (/^delete\s+from\s+\S+/.test(norm) && !hasWhere)
329
+ return "DELETE without WHERE \u2014 full table wipe";
330
+ if (/^update\s+\S+\s+set\s+/.test(norm) && !hasWhere)
331
+ return "UPDATE without WHERE \u2014 updates every row";
332
+ return null;
333
+ }
317
334
  async function analyzeShellCommand(command) {
318
335
  const actions = [];
319
336
  const paths = [];
@@ -493,9 +510,20 @@ async function evaluatePolicy(toolName, args, agent) {
493
510
  if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
494
511
  return { decision: "review", blockedByLabel: "Node9 Standard (Inline Execution)" };
495
512
  }
513
+ if (isSqlTool(toolName, config.policy.toolInspection)) {
514
+ const sqlDanger = checkDangerousSql(shellCommand);
515
+ if (sqlDanger) return { decision: "review", blockedByLabel: `SQL Safety: ${sqlDanger}` };
516
+ allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
517
+ actionTokens = actionTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
518
+ }
496
519
  } else {
497
520
  allTokens = tokenize(toolName);
498
521
  actionTokens = [toolName];
522
+ if (args && typeof args === "object") {
523
+ const flattenedArgs = JSON.stringify(args).toLowerCase();
524
+ const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
525
+ allTokens.push(...extraTokens);
526
+ }
499
527
  }
500
528
  const isManual = agent === "Terminal";
501
529
  if (isManual) {
@@ -724,8 +752,7 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
724
752
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
725
753
  if (cloudEnforced) {
726
754
  try {
727
- const envConfig = getActiveEnvironment(getConfig());
728
- const initResult = await initNode9SaaS(toolName, args, creds, envConfig?.slackChannel, meta);
755
+ const initResult = await initNode9SaaS(toolName, args, creds, meta);
729
756
  if (!initResult.pending) {
730
757
  return {
731
758
  approved: !!initResult.approved,
@@ -979,6 +1006,7 @@ function getConfig() {
979
1006
  if (s.enableHookLogDebug !== void 0)
980
1007
  mergedSettings.enableHookLogDebug = s.enableHookLogDebug;
981
1008
  if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
1009
+ if (s.environment !== void 0) mergedSettings.environment = s.environment;
982
1010
  if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
983
1011
  if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
984
1012
  if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
@@ -1008,7 +1036,7 @@ function tryLoadConfig(filePath) {
1008
1036
  }
1009
1037
  }
1010
1038
  function getActiveEnvironment(config) {
1011
- const env = process.env.NODE_ENV || "development";
1039
+ const env = config.settings.environment || process.env.NODE_ENV || "development";
1012
1040
  return config.environments[env] ?? null;
1013
1041
  }
1014
1042
  function getCredentials() {
@@ -1068,7 +1096,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
1068
1096
  }).catch(() => {
1069
1097
  });
1070
1098
  }
1071
- async function initNode9SaaS(toolName, args, creds, slackChannel, meta) {
1099
+ async function initNode9SaaS(toolName, args, creds, meta) {
1072
1100
  const controller = new AbortController();
1073
1101
  const timeout = setTimeout(() => controller.abort(), 1e4);
1074
1102
  try {
@@ -1078,7 +1106,6 @@ async function initNode9SaaS(toolName, args, creds, slackChannel, meta) {
1078
1106
  body: JSON.stringify({
1079
1107
  toolName,
1080
1108
  args,
1081
- slackChannel,
1082
1109
  context: {
1083
1110
  agent: meta?.agent,
1084
1111
  mcpServer: meta?.mcpServer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",