@node9/proxy 1.7.1 → 1.8.2
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 +82 -21
- package/dist/cli.js +211 -265
- package/dist/cli.mjs +211 -265
- package/dist/index.js +89 -257
- package/dist/index.mjs +89 -257
- package/dist/shields/builtin/aws.json +59 -0
- package/dist/shields/builtin/bash-safe.json +78 -0
- package/dist/shields/builtin/filesystem.json +30 -0
- package/dist/shields/builtin/github.json +26 -0
- package/dist/shields/builtin/postgres.json +42 -0
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -250,251 +250,73 @@ ${lines.join("\n")}`
|
|
|
250
250
|
import fs2 from "fs";
|
|
251
251
|
import path2 from "path";
|
|
252
252
|
import os2 from "os";
|
|
253
|
-
var
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
name: "shield:postgres:block-drop-table",
|
|
261
|
-
tool: "*",
|
|
262
|
-
conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
|
|
263
|
-
verdict: "block",
|
|
264
|
-
reason: "DROP TABLE is irreversible \u2014 blocked by Postgres shield"
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
name: "shield:postgres:block-truncate",
|
|
268
|
-
tool: "*",
|
|
269
|
-
conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
|
|
270
|
-
verdict: "block",
|
|
271
|
-
reason: "TRUNCATE is irreversible \u2014 blocked by Postgres shield"
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
name: "shield:postgres:block-drop-column",
|
|
275
|
-
tool: "*",
|
|
276
|
-
conditions: [
|
|
277
|
-
{ field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
|
|
278
|
-
],
|
|
279
|
-
verdict: "block",
|
|
280
|
-
reason: "DROP COLUMN is irreversible \u2014 blocked by Postgres shield"
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
name: "shield:postgres:review-grant-revoke",
|
|
284
|
-
tool: "*",
|
|
285
|
-
conditions: [{ field: "sql", op: "matches", value: "\\b(GRANT|REVOKE)\\b", flags: "i" }],
|
|
286
|
-
verdict: "review",
|
|
287
|
-
reason: "Permission changes require human approval (Postgres shield)"
|
|
288
|
-
}
|
|
289
|
-
],
|
|
290
|
-
dangerousWords: ["dropdb", "pg_dropcluster"]
|
|
291
|
-
},
|
|
292
|
-
github: {
|
|
293
|
-
name: "github",
|
|
294
|
-
description: "Protects GitHub repositories from destructive AI operations",
|
|
295
|
-
aliases: ["git"],
|
|
296
|
-
smartRules: [
|
|
297
|
-
{
|
|
298
|
-
// Note: git branch -d/-D is already caught by the built-in review-git-destructive rule.
|
|
299
|
-
// This rule adds coverage for `git push --delete` which the built-in does not match.
|
|
300
|
-
name: "shield:github:review-delete-branch-remote",
|
|
301
|
-
tool: "bash",
|
|
302
|
-
conditions: [
|
|
303
|
-
{
|
|
304
|
-
field: "command",
|
|
305
|
-
op: "matches",
|
|
306
|
-
value: "git\\s+push\\s+.*--delete",
|
|
307
|
-
flags: "i"
|
|
308
|
-
}
|
|
309
|
-
],
|
|
310
|
-
verdict: "review",
|
|
311
|
-
reason: "Remote branch deletion requires human approval (GitHub shield)"
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
name: "shield:github:block-delete-repo",
|
|
315
|
-
tool: "*",
|
|
316
|
-
conditions: [
|
|
317
|
-
{ field: "command", op: "matches", value: "gh\\s+repo\\s+delete", flags: "i" }
|
|
318
|
-
],
|
|
319
|
-
verdict: "block",
|
|
320
|
-
reason: "Repository deletion is irreversible \u2014 blocked by GitHub shield"
|
|
321
|
-
}
|
|
322
|
-
],
|
|
323
|
-
dangerousWords: []
|
|
324
|
-
},
|
|
325
|
-
aws: {
|
|
326
|
-
name: "aws",
|
|
327
|
-
description: "Protects AWS infrastructure from destructive AI operations",
|
|
328
|
-
aliases: ["amazon"],
|
|
329
|
-
smartRules: [
|
|
330
|
-
{
|
|
331
|
-
name: "shield:aws:block-delete-s3-bucket",
|
|
332
|
-
tool: "*",
|
|
333
|
-
conditions: [
|
|
334
|
-
{
|
|
335
|
-
field: "command",
|
|
336
|
-
op: "matches",
|
|
337
|
-
value: "aws\\s+s3.*rb\\s|aws\\s+s3api\\s+delete-bucket",
|
|
338
|
-
flags: "i"
|
|
339
|
-
}
|
|
340
|
-
],
|
|
341
|
-
verdict: "block",
|
|
342
|
-
reason: "S3 bucket deletion is irreversible \u2014 blocked by AWS shield"
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
name: "shield:aws:review-iam-changes",
|
|
346
|
-
tool: "*",
|
|
347
|
-
conditions: [
|
|
348
|
-
{
|
|
349
|
-
field: "command",
|
|
350
|
-
op: "matches",
|
|
351
|
-
value: "aws\\s+iam\\s+(create|delete|attach|detach|put|remove)",
|
|
352
|
-
flags: "i"
|
|
353
|
-
}
|
|
354
|
-
],
|
|
355
|
-
verdict: "review",
|
|
356
|
-
reason: "IAM changes require human approval (AWS shield)"
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
name: "shield:aws:block-ec2-terminate",
|
|
360
|
-
tool: "*",
|
|
361
|
-
conditions: [
|
|
362
|
-
{
|
|
363
|
-
field: "command",
|
|
364
|
-
op: "matches",
|
|
365
|
-
value: "aws\\s+ec2\\s+terminate-instances",
|
|
366
|
-
flags: "i"
|
|
367
|
-
}
|
|
368
|
-
],
|
|
369
|
-
verdict: "block",
|
|
370
|
-
reason: "EC2 instance termination is irreversible \u2014 blocked by AWS shield"
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
name: "shield:aws:review-rds-delete",
|
|
374
|
-
tool: "*",
|
|
375
|
-
conditions: [
|
|
376
|
-
{ field: "command", op: "matches", value: "aws\\s+rds\\s+delete-", flags: "i" }
|
|
377
|
-
],
|
|
378
|
-
verdict: "review",
|
|
379
|
-
reason: "RDS deletion requires human approval (AWS shield)"
|
|
380
|
-
}
|
|
381
|
-
],
|
|
382
|
-
dangerousWords: []
|
|
383
|
-
},
|
|
384
|
-
"bash-safe": {
|
|
385
|
-
name: "bash-safe",
|
|
386
|
-
description: "Blocks high-risk bash patterns: pipe-to-shell, rm -rf /, disk overwrites, eval",
|
|
387
|
-
aliases: ["bash", "shell"],
|
|
388
|
-
smartRules: [
|
|
389
|
-
{
|
|
390
|
-
name: "shield:bash-safe:block-pipe-to-shell",
|
|
391
|
-
tool: "bash",
|
|
392
|
-
conditions: [
|
|
393
|
-
{
|
|
394
|
-
field: "command",
|
|
395
|
-
op: "matches",
|
|
396
|
-
value: "(curl|wget)\\s+[^|]*\\|\\s*(bash|sh|zsh|fish|python3?|ruby|perl|node)",
|
|
397
|
-
flags: "i"
|
|
398
|
-
}
|
|
399
|
-
],
|
|
400
|
-
verdict: "block",
|
|
401
|
-
reason: "Pipe-to-shell is a common supply-chain attack vector \u2014 blocked by bash-safe shield"
|
|
402
|
-
},
|
|
403
|
-
{
|
|
404
|
-
name: "shield:bash-safe:block-obfuscated-exec",
|
|
405
|
-
tool: "bash",
|
|
406
|
-
conditions: [
|
|
407
|
-
{
|
|
408
|
-
field: "command",
|
|
409
|
-
op: "matches",
|
|
410
|
-
value: "base64\\s+(-d|--decode).*\\|\\s*(bash|sh|zsh)",
|
|
411
|
-
flags: "i"
|
|
412
|
-
}
|
|
413
|
-
],
|
|
414
|
-
verdict: "block",
|
|
415
|
-
reason: "Obfuscated execution via base64 decode \u2014 blocked by bash-safe shield"
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
name: "shield:bash-safe:block-rm-root",
|
|
419
|
-
tool: "bash",
|
|
420
|
-
conditions: [
|
|
421
|
-
{
|
|
422
|
-
field: "command",
|
|
423
|
-
op: "matches",
|
|
424
|
-
value: "rm\\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)[a-zA-Z]*\\s+(\\/|~|\\$HOME|\\$\\{HOME\\})\\s*$",
|
|
425
|
-
flags: "i"
|
|
426
|
-
}
|
|
427
|
-
],
|
|
428
|
-
verdict: "block",
|
|
429
|
-
reason: "rm -rf of root or home directory is catastrophic \u2014 blocked by bash-safe shield"
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
name: "shield:bash-safe:block-disk-overwrite",
|
|
433
|
-
tool: "bash",
|
|
434
|
-
conditions: [
|
|
435
|
-
{
|
|
436
|
-
field: "command",
|
|
437
|
-
op: "matches",
|
|
438
|
-
value: "dd\\s+.*of=\\/dev\\/(sd|nvme|hd|vd|xvd)",
|
|
439
|
-
flags: "i"
|
|
440
|
-
}
|
|
441
|
-
],
|
|
442
|
-
verdict: "block",
|
|
443
|
-
reason: "Writing directly to a block device is irreversible \u2014 blocked by bash-safe shield"
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
name: "shield:bash-safe:review-eval",
|
|
447
|
-
tool: "bash",
|
|
448
|
-
conditions: [
|
|
449
|
-
{
|
|
450
|
-
field: "command",
|
|
451
|
-
op: "matches",
|
|
452
|
-
value: '\\beval\\s+[\\$`("]',
|
|
453
|
-
flags: "i"
|
|
454
|
-
}
|
|
455
|
-
],
|
|
456
|
-
verdict: "review",
|
|
457
|
-
reason: "eval of dynamic content requires human approval (bash-safe shield)"
|
|
458
|
-
}
|
|
459
|
-
],
|
|
460
|
-
dangerousWords: []
|
|
461
|
-
},
|
|
462
|
-
filesystem: {
|
|
463
|
-
name: "filesystem",
|
|
464
|
-
description: "Protects the local filesystem from dangerous AI operations",
|
|
465
|
-
aliases: ["fs"],
|
|
466
|
-
smartRules: [
|
|
467
|
-
{
|
|
468
|
-
name: "shield:filesystem:review-chmod-777",
|
|
469
|
-
tool: "bash",
|
|
470
|
-
conditions: [
|
|
471
|
-
{ field: "command", op: "matches", value: "chmod\\s+(777|a\\+rwx)", flags: "i" }
|
|
472
|
-
],
|
|
473
|
-
verdict: "review",
|
|
474
|
-
reason: "chmod 777 requires human approval (filesystem shield)"
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
name: "shield:filesystem:review-write-etc",
|
|
478
|
-
tool: "bash",
|
|
479
|
-
conditions: [
|
|
480
|
-
{
|
|
481
|
-
field: "command",
|
|
482
|
-
// Narrow to write-indicative operations to avoid approval fatigue on reads.
|
|
483
|
-
// Matches: tee /etc/*, cp .../etc/*, mv .../etc/*, > /etc/*, install .../etc/*
|
|
484
|
-
op: "matches",
|
|
485
|
-
value: "(tee|\\bcp\\b|\\bmv\\b|install|>+)\\s+.*\\/etc\\/"
|
|
486
|
-
}
|
|
487
|
-
],
|
|
488
|
-
verdict: "review",
|
|
489
|
-
reason: "Writing to /etc requires human approval (filesystem shield)"
|
|
490
|
-
}
|
|
491
|
-
],
|
|
492
|
-
// dd removed: too common as a legitimate tool (disk imaging, file ops).
|
|
493
|
-
// mkfs removed: already in the built-in DANGEROUS_WORDS baseline.
|
|
494
|
-
// wipefs retained: rarely legitimate in an agent context and not in built-ins.
|
|
495
|
-
dangerousWords: ["wipefs"]
|
|
253
|
+
var BUILTIN_DIR = path2.join(__dirname, "shields", "builtin");
|
|
254
|
+
var USER_SHIELDS_DIR = path2.join(os2.homedir(), ".node9", "shields");
|
|
255
|
+
function validateShieldDefinition(raw, filePath) {
|
|
256
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
257
|
+
process.stderr.write(`[node9] Shield file is not an object: ${filePath}
|
|
258
|
+
`);
|
|
259
|
+
return null;
|
|
496
260
|
}
|
|
497
|
-
|
|
261
|
+
const r = raw;
|
|
262
|
+
if (typeof r.name !== "string" || !r.name) {
|
|
263
|
+
process.stderr.write(`[node9] Shield file missing 'name': ${filePath}
|
|
264
|
+
`);
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
if (typeof r.description !== "string") {
|
|
268
|
+
process.stderr.write(`[node9] Shield file missing 'description': ${filePath}
|
|
269
|
+
`);
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
if (!Array.isArray(r.aliases)) {
|
|
273
|
+
process.stderr.write(`[node9] Shield file missing 'aliases' array: ${filePath}
|
|
274
|
+
`);
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
if (!Array.isArray(r.smartRules)) {
|
|
278
|
+
process.stderr.write(`[node9] Shield file missing 'smartRules' array: ${filePath}
|
|
279
|
+
`);
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
if (!Array.isArray(r.dangerousWords)) {
|
|
283
|
+
process.stderr.write(`[node9] Shield file missing 'dangerousWords' array: ${filePath}
|
|
284
|
+
`);
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
return r;
|
|
288
|
+
}
|
|
289
|
+
function loadShieldsFromDir(dir, label) {
|
|
290
|
+
const result = {};
|
|
291
|
+
let entries;
|
|
292
|
+
try {
|
|
293
|
+
entries = fs2.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
294
|
+
} catch (err) {
|
|
295
|
+
if (err.code !== "ENOENT") {
|
|
296
|
+
process.stderr.write(`[node9] Could not read ${label} shields dir ${dir}: ${String(err)}
|
|
297
|
+
`);
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
for (const file of entries) {
|
|
302
|
+
const filePath = path2.join(dir, file);
|
|
303
|
+
try {
|
|
304
|
+
const raw = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
305
|
+
const shield = validateShieldDefinition(raw, filePath);
|
|
306
|
+
if (shield) result[shield.name] = shield;
|
|
307
|
+
} catch (err) {
|
|
308
|
+
process.stderr.write(`[node9] Failed to load ${label} shield ${file}: ${String(err)}
|
|
309
|
+
`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
function buildSHIELDS() {
|
|
315
|
+
const builtins = loadShieldsFromDir(BUILTIN_DIR, "builtin");
|
|
316
|
+
const userShields = loadShieldsFromDir(USER_SHIELDS_DIR, "user");
|
|
317
|
+
return { ...builtins, ...userShields };
|
|
318
|
+
}
|
|
319
|
+
var SHIELDS = buildSHIELDS();
|
|
498
320
|
function resolveShieldName(input) {
|
|
499
321
|
const lower = input.toLowerCase();
|
|
500
322
|
if (SHIELDS[lower]) return lower;
|
|
@@ -2118,7 +1940,7 @@ function isDaemonRunning() {
|
|
|
2118
1940
|
return false;
|
|
2119
1941
|
}
|
|
2120
1942
|
}
|
|
2121
|
-
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly) {
|
|
1943
|
+
async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched) {
|
|
2122
1944
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
2123
1945
|
const ctrl = new AbortController();
|
|
2124
1946
|
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
@@ -2139,7 +1961,8 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
|
|
|
2139
1961
|
...cwd && { cwd },
|
|
2140
1962
|
...recoveryCommand && { recoveryCommand },
|
|
2141
1963
|
...skipBackgroundAuth && { skipBackgroundAuth: true },
|
|
2142
|
-
...viewOnly && { viewOnly: true }
|
|
1964
|
+
...viewOnly && { viewOnly: true },
|
|
1965
|
+
...localSmartRuleMatched && { localSmartRuleMatched: true }
|
|
2143
1966
|
}),
|
|
2144
1967
|
signal: ctrl.signal
|
|
2145
1968
|
});
|
|
@@ -2807,6 +2630,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2807
2630
|
let policyMatchedWord;
|
|
2808
2631
|
let riskMetadata;
|
|
2809
2632
|
let statefulRecoveryCommand;
|
|
2633
|
+
let localSmartRuleMatched = false;
|
|
2810
2634
|
let taintWarning = null;
|
|
2811
2635
|
if (isNetworkTool(toolName, args)) {
|
|
2812
2636
|
const filePaths = extractFilePaths(toolName, args);
|
|
@@ -2950,6 +2774,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2950
2774
|
explainableLabel = policyResult.blockedByLabel || "Local Config";
|
|
2951
2775
|
policyMatchedField = policyResult.matchedField;
|
|
2952
2776
|
policyMatchedWord = policyResult.matchedWord;
|
|
2777
|
+
if (policyResult.ruleName) localSmartRuleMatched = true;
|
|
2953
2778
|
riskMetadata = computeRiskMetadata(
|
|
2954
2779
|
args,
|
|
2955
2780
|
policyResult.tier ?? 6,
|
|
@@ -2992,22 +2817,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2992
2817
|
}
|
|
2993
2818
|
let cloudRequestId = null;
|
|
2994
2819
|
const cloudEnforced = approvers.cloud && !!creds?.apiKey;
|
|
2995
|
-
if (cloudEnforced) {
|
|
2820
|
+
if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
|
|
2996
2821
|
try {
|
|
2997
2822
|
const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
|
|
2998
2823
|
if (!initResult.pending) {
|
|
2999
2824
|
if (initResult.shadowMode) {
|
|
3000
2825
|
return { approved: true, checkedBy: "cloud" };
|
|
3001
2826
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
2827
|
+
if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
|
|
2828
|
+
return {
|
|
2829
|
+
approved: !!initResult.approved,
|
|
2830
|
+
reason: initResult.reason || (initResult.approved ? void 0 : "Action rejected by organization policy."),
|
|
2831
|
+
checkedBy: initResult.approved ? "cloud" : void 0,
|
|
2832
|
+
blockedBy: initResult.approved ? void 0 : "team-policy",
|
|
2833
|
+
blockedByLabel: "Organization Policy (SaaS)"
|
|
2834
|
+
};
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
|
|
2838
|
+
cloudRequestId = initResult.requestId || null;
|
|
3009
2839
|
}
|
|
3010
|
-
cloudRequestId = initResult.requestId || null;
|
|
3011
2840
|
if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
|
|
3012
2841
|
} catch {
|
|
3013
2842
|
}
|
|
@@ -3053,7 +2882,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3053
2882
|
riskMetadata,
|
|
3054
2883
|
options?.activityId,
|
|
3055
2884
|
options?.cwd,
|
|
3056
|
-
statefulRecoveryCommand
|
|
2885
|
+
statefulRecoveryCommand,
|
|
2886
|
+
void 0,
|
|
2887
|
+
void 0,
|
|
2888
|
+
localSmartRuleMatched || options?.localSmartRuleMatched
|
|
3057
2889
|
);
|
|
3058
2890
|
daemonEntryId = entry.id;
|
|
3059
2891
|
daemonAllowCount = entry.allowCount;
|
|
@@ -3061,7 +2893,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3061
2893
|
}
|
|
3062
2894
|
}
|
|
3063
2895
|
}
|
|
3064
|
-
if (cloudEnforced && cloudRequestId) {
|
|
2896
|
+
if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
|
|
3065
2897
|
racePromises.push(
|
|
3066
2898
|
(async () => {
|
|
3067
2899
|
try {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aws",
|
|
3
|
+
"description": "Protects AWS infrastructure from destructive AI operations",
|
|
4
|
+
"aliases": ["amazon"],
|
|
5
|
+
"smartRules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "shield:aws:block-delete-s3-bucket",
|
|
8
|
+
"tool": "*",
|
|
9
|
+
"conditions": [
|
|
10
|
+
{
|
|
11
|
+
"field": "command",
|
|
12
|
+
"op": "matches",
|
|
13
|
+
"value": "aws\\s+s3.*rb\\s|aws\\s+s3api\\s+delete-bucket",
|
|
14
|
+
"flags": "i"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"verdict": "block",
|
|
18
|
+
"reason": "S3 bucket deletion is irreversible — blocked by AWS shield"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "shield:aws:review-iam-changes",
|
|
22
|
+
"tool": "*",
|
|
23
|
+
"conditions": [
|
|
24
|
+
{
|
|
25
|
+
"field": "command",
|
|
26
|
+
"op": "matches",
|
|
27
|
+
"value": "aws\\s+iam\\s+(create|delete|attach|detach|put|remove)",
|
|
28
|
+
"flags": "i"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"verdict": "review",
|
|
32
|
+
"reason": "IAM changes require human approval (AWS shield)"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "shield:aws:block-ec2-terminate",
|
|
36
|
+
"tool": "*",
|
|
37
|
+
"conditions": [
|
|
38
|
+
{
|
|
39
|
+
"field": "command",
|
|
40
|
+
"op": "matches",
|
|
41
|
+
"value": "aws\\s+ec2\\s+terminate-instances",
|
|
42
|
+
"flags": "i"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"verdict": "block",
|
|
46
|
+
"reason": "EC2 instance termination is irreversible — blocked by AWS shield"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "shield:aws:review-rds-delete",
|
|
50
|
+
"tool": "*",
|
|
51
|
+
"conditions": [
|
|
52
|
+
{ "field": "command", "op": "matches", "value": "aws\\s+rds\\s+delete-", "flags": "i" }
|
|
53
|
+
],
|
|
54
|
+
"verdict": "review",
|
|
55
|
+
"reason": "RDS deletion requires human approval (AWS shield)"
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"dangerousWords": []
|
|
59
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bash-safe",
|
|
3
|
+
"description": "Blocks high-risk bash patterns: pipe-to-shell, rm -rf /, disk overwrites, eval",
|
|
4
|
+
"aliases": ["bash", "shell"],
|
|
5
|
+
"smartRules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "shield:bash-safe:block-pipe-to-shell",
|
|
8
|
+
"tool": "bash",
|
|
9
|
+
"conditions": [
|
|
10
|
+
{
|
|
11
|
+
"field": "command",
|
|
12
|
+
"op": "matches",
|
|
13
|
+
"value": "(curl|wget)\\s+[^|]*\\|\\s*(bash|sh|zsh|fish|python3?|ruby|perl|node)",
|
|
14
|
+
"flags": "i"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"verdict": "block",
|
|
18
|
+
"reason": "Pipe-to-shell is a common supply-chain attack vector — blocked by bash-safe shield"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "shield:bash-safe:block-obfuscated-exec",
|
|
22
|
+
"tool": "bash",
|
|
23
|
+
"conditions": [
|
|
24
|
+
{
|
|
25
|
+
"field": "command",
|
|
26
|
+
"op": "matches",
|
|
27
|
+
"value": "base64\\s+(-d|--decode).*\\|\\s*(bash|sh|zsh)",
|
|
28
|
+
"flags": "i"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"verdict": "block",
|
|
32
|
+
"reason": "Obfuscated execution via base64 decode — blocked by bash-safe shield"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "shield:bash-safe:block-rm-root",
|
|
36
|
+
"tool": "bash",
|
|
37
|
+
"conditions": [
|
|
38
|
+
{
|
|
39
|
+
"field": "command",
|
|
40
|
+
"op": "matches",
|
|
41
|
+
"value": "rm\\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)[a-zA-Z]*\\s+(\\/|~|\\$HOME|\\$\\{HOME\\})\\s*$",
|
|
42
|
+
"flags": "i"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"verdict": "block",
|
|
46
|
+
"reason": "rm -rf of root or home directory is catastrophic — blocked by bash-safe shield"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "shield:bash-safe:block-disk-overwrite",
|
|
50
|
+
"tool": "bash",
|
|
51
|
+
"conditions": [
|
|
52
|
+
{
|
|
53
|
+
"field": "command",
|
|
54
|
+
"op": "matches",
|
|
55
|
+
"value": "dd\\s+.*of=\\/dev\\/(sd|nvme|hd|vd|xvd)",
|
|
56
|
+
"flags": "i"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"verdict": "block",
|
|
60
|
+
"reason": "Writing directly to a block device is irreversible — blocked by bash-safe shield"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "shield:bash-safe:review-eval",
|
|
64
|
+
"tool": "bash",
|
|
65
|
+
"conditions": [
|
|
66
|
+
{
|
|
67
|
+
"field": "command",
|
|
68
|
+
"op": "matches",
|
|
69
|
+
"value": "\\beval\\s+[\\$`(\"]",
|
|
70
|
+
"flags": "i"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"verdict": "review",
|
|
74
|
+
"reason": "eval of dynamic content requires human approval (bash-safe shield)"
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"dangerousWords": []
|
|
78
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "filesystem",
|
|
3
|
+
"description": "Protects the local filesystem from dangerous AI operations",
|
|
4
|
+
"aliases": ["fs"],
|
|
5
|
+
"smartRules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "shield:filesystem:review-chmod-777",
|
|
8
|
+
"tool": "bash",
|
|
9
|
+
"conditions": [
|
|
10
|
+
{ "field": "command", "op": "matches", "value": "chmod\\s+(777|a\\+rwx)", "flags": "i" }
|
|
11
|
+
],
|
|
12
|
+
"verdict": "review",
|
|
13
|
+
"reason": "chmod 777 requires human approval (filesystem shield)"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "shield:filesystem:review-write-etc",
|
|
17
|
+
"tool": "bash",
|
|
18
|
+
"conditions": [
|
|
19
|
+
{
|
|
20
|
+
"field": "command",
|
|
21
|
+
"op": "matches",
|
|
22
|
+
"value": "(tee|\\bcp\\b|\\bmv\\b|install|>+)\\s+.*\\/etc\\/"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"verdict": "review",
|
|
26
|
+
"reason": "Writing to /etc requires human approval (filesystem shield)"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"dangerousWords": ["wipefs"]
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "github",
|
|
3
|
+
"description": "Protects GitHub repositories from destructive AI operations",
|
|
4
|
+
"aliases": ["git"],
|
|
5
|
+
"smartRules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "shield:github:review-delete-branch-remote",
|
|
8
|
+
"tool": "bash",
|
|
9
|
+
"conditions": [
|
|
10
|
+
{ "field": "command", "op": "matches", "value": "git\\s+push\\s+.*--delete", "flags": "i" }
|
|
11
|
+
],
|
|
12
|
+
"verdict": "review",
|
|
13
|
+
"reason": "Remote branch deletion requires human approval (GitHub shield)"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "shield:github:block-delete-repo",
|
|
17
|
+
"tool": "*",
|
|
18
|
+
"conditions": [
|
|
19
|
+
{ "field": "command", "op": "matches", "value": "gh\\s+repo\\s+delete", "flags": "i" }
|
|
20
|
+
],
|
|
21
|
+
"verdict": "block",
|
|
22
|
+
"reason": "Repository deletion is irreversible — blocked by GitHub shield"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"dangerousWords": []
|
|
26
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "postgres",
|
|
3
|
+
"description": "Protects PostgreSQL databases from destructive AI operations",
|
|
4
|
+
"aliases": ["pg", "postgresql"],
|
|
5
|
+
"smartRules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "shield:postgres:block-drop-table",
|
|
8
|
+
"tool": "*",
|
|
9
|
+
"conditions": [{ "field": "sql", "op": "matches", "value": "DROP\\s+TABLE", "flags": "i" }],
|
|
10
|
+
"verdict": "block",
|
|
11
|
+
"reason": "DROP TABLE is irreversible — blocked by Postgres shield"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "shield:postgres:block-truncate",
|
|
15
|
+
"tool": "*",
|
|
16
|
+
"conditions": [
|
|
17
|
+
{ "field": "sql", "op": "matches", "value": "TRUNCATE\\s+TABLE", "flags": "i" }
|
|
18
|
+
],
|
|
19
|
+
"verdict": "block",
|
|
20
|
+
"reason": "TRUNCATE is irreversible — blocked by Postgres shield"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "shield:postgres:block-drop-column",
|
|
24
|
+
"tool": "*",
|
|
25
|
+
"conditions": [
|
|
26
|
+
{ "field": "sql", "op": "matches", "value": "ALTER\\s+TABLE.*DROP\\s+COLUMN", "flags": "i" }
|
|
27
|
+
],
|
|
28
|
+
"verdict": "block",
|
|
29
|
+
"reason": "DROP COLUMN is irreversible — blocked by Postgres shield"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "shield:postgres:review-grant-revoke",
|
|
33
|
+
"tool": "*",
|
|
34
|
+
"conditions": [
|
|
35
|
+
{ "field": "sql", "op": "matches", "value": "\\b(GRANT|REVOKE)\\b", "flags": "i" }
|
|
36
|
+
],
|
|
37
|
+
"verdict": "review",
|
|
38
|
+
"reason": "Permission changes require human approval (Postgres shield)"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"dangerousWords": ["dropdb", "pg_dropcluster"]
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.2",
|
|
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",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"LICENSE"
|
|
49
49
|
],
|
|
50
50
|
"scripts": {
|
|
51
|
-
"build": "tsup",
|
|
51
|
+
"build": "tsup && node scripts/copy-shield-assets.js",
|
|
52
52
|
"dev": "tsup --watch",
|
|
53
53
|
"demo": "tsx examples/demo.ts",
|
|
54
54
|
"typecheck": "tsc --noEmit",
|