@node9/proxy 1.11.0 → 1.11.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 +52 -0
- package/dist/cli.js +1017 -487
- package/dist/cli.mjs +1013 -483
- package/dist/index.js +48 -24
- package/dist/index.mjs +48 -24
- package/dist/shields/builtin/bash-safe.json +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -503,9 +503,8 @@ var DEFAULT_CONFIG = {
|
|
|
503
503
|
{
|
|
504
504
|
field: "command",
|
|
505
505
|
op: "matches",
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
506
|
+
// Anchor rm as a shell command (not inside a string arg like a git commit message).
|
|
507
|
+
value: "(^|&&|\\|\\||;)\\s*rm\\b[^;&|]*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
509
508
|
},
|
|
510
509
|
{
|
|
511
510
|
field: "command",
|
|
@@ -534,6 +533,13 @@ var DEFAULT_CONFIG = {
|
|
|
534
533
|
name: "review-drop-truncate-shell",
|
|
535
534
|
tool: "bash",
|
|
536
535
|
conditions: [
|
|
536
|
+
{
|
|
537
|
+
field: "command",
|
|
538
|
+
op: "matches",
|
|
539
|
+
// Require a DB CLI in the command so grep/cat/echo of SQL strings don't trigger.
|
|
540
|
+
value: "(^|&&|\\|\\||;|\\|)\\s*(psql|mysql|sqlite3|sqlplus|cockroach|clickhouse-client|mongo)\\b",
|
|
541
|
+
flags: "i"
|
|
542
|
+
},
|
|
537
543
|
{
|
|
538
544
|
field: "command",
|
|
539
545
|
op: "matches",
|
|
@@ -554,7 +560,9 @@ var DEFAULT_CONFIG = {
|
|
|
554
560
|
{
|
|
555
561
|
field: "command",
|
|
556
562
|
op: "matches",
|
|
557
|
-
|
|
563
|
+
// Anchor git as a shell command so node -e / python -c scripts containing
|
|
564
|
+
// "git push --force" as a string don't false-positive.
|
|
565
|
+
value: "(^|&&|\\|\\||;)\\s*git\\s+push[^;&|]*(--force|--force-with-lease|-f\\b)",
|
|
558
566
|
flags: "i"
|
|
559
567
|
}
|
|
560
568
|
],
|
|
@@ -564,29 +572,20 @@ var DEFAULT_CONFIG = {
|
|
|
564
572
|
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."
|
|
565
573
|
},
|
|
566
574
|
{
|
|
567
|
-
name: "review-git-
|
|
575
|
+
name: "review-git-destructive",
|
|
568
576
|
tool: "bash",
|
|
569
577
|
conditions: [
|
|
570
578
|
{
|
|
571
579
|
field: "command",
|
|
572
580
|
op: "matches",
|
|
573
|
-
value: "\\bgit\\
|
|
581
|
+
value: "\\bgit\\s+(reset\\s+--hard|clean\\s+-[fdxX]|rebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
574
582
|
flags: "i"
|
|
575
|
-
}
|
|
576
|
-
],
|
|
577
|
-
conditionMode: "all",
|
|
578
|
-
verdict: "review",
|
|
579
|
-
reason: "git push sends changes to a shared remote",
|
|
580
|
-
description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
|
|
581
|
-
},
|
|
582
|
-
{
|
|
583
|
-
name: "review-git-destructive",
|
|
584
|
-
tool: "bash",
|
|
585
|
-
conditions: [
|
|
583
|
+
},
|
|
586
584
|
{
|
|
587
585
|
field: "command",
|
|
588
|
-
op: "
|
|
589
|
-
|
|
586
|
+
op: "notMatches",
|
|
587
|
+
// Exclude recovery ops — these resolve a conflict, not start a destructive action.
|
|
588
|
+
value: "\\bgit\\s+rebase\\s+--(abort|continue|skip)\\b",
|
|
590
589
|
flags: "i"
|
|
591
590
|
}
|
|
592
591
|
],
|
|
@@ -612,7 +611,9 @@ var DEFAULT_CONFIG = {
|
|
|
612
611
|
{
|
|
613
612
|
field: "command",
|
|
614
613
|
op: "matches",
|
|
615
|
-
|
|
614
|
+
// Anchor curl/wget as a shell command so node -e scripts testing this
|
|
615
|
+
// regex pattern don't self-match as a false positive.
|
|
616
|
+
value: "(^|&&|\\|\\||;)\\s*(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
|
|
616
617
|
flags: "i"
|
|
617
618
|
}
|
|
618
619
|
],
|
|
@@ -1013,7 +1014,7 @@ var DLP_PATTERNS = [
|
|
|
1013
1014
|
regex: /_authToken\s*=\s*[A-Za-z0-9_\-]{20,}/,
|
|
1014
1015
|
severity: "block"
|
|
1015
1016
|
},
|
|
1016
|
-
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]
|
|
1017
|
+
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i, severity: "review" }
|
|
1017
1018
|
];
|
|
1018
1019
|
var SENSITIVE_PATH_PATTERNS = [
|
|
1019
1020
|
/[/\\]\.ssh[/\\]/i,
|
|
@@ -1603,12 +1604,25 @@ function getNestedValue(obj, path15) {
|
|
|
1603
1604
|
if (!obj || typeof obj !== "object") return null;
|
|
1604
1605
|
return path15.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1605
1606
|
}
|
|
1607
|
+
function stripStringArguments(cmd) {
|
|
1608
|
+
let result = cmd;
|
|
1609
|
+
result = result.replace(
|
|
1610
|
+
/\b(node|python3?|ruby|perl|php|deno)\s+(-[ecr]|eval)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/gi,
|
|
1611
|
+
'$1 $2 ""'
|
|
1612
|
+
);
|
|
1613
|
+
result = result.replace(
|
|
1614
|
+
/\s(-m|--message|--body|--title|--description)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
|
|
1615
|
+
' $1 ""'
|
|
1616
|
+
);
|
|
1617
|
+
return result;
|
|
1618
|
+
}
|
|
1606
1619
|
function evaluateSmartConditions(args, rule) {
|
|
1607
1620
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
1608
1621
|
const mode = rule.conditionMode ?? "all";
|
|
1609
1622
|
const results = rule.conditions.map((cond) => {
|
|
1610
1623
|
const rawVal = getNestedValue(args, cond.field);
|
|
1611
|
-
const
|
|
1624
|
+
const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
|
|
1625
|
+
const val = cond.field === "command" && normalized !== null ? stripStringArguments(normalized) : normalized;
|
|
1612
1626
|
switch (cond.op) {
|
|
1613
1627
|
case "exists":
|
|
1614
1628
|
return val !== null && val !== "";
|
|
@@ -2413,7 +2427,8 @@ function escapePango(text) {
|
|
|
2413
2427
|
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2414
2428
|
const lines = [];
|
|
2415
2429
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2416
|
-
|
|
2430
|
+
const safeAgent = (agent ?? "AI Agent").replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80);
|
|
2431
|
+
lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
|
|
2417
2432
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2418
2433
|
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2419
2434
|
lines.push("");
|
|
@@ -2790,7 +2805,16 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
2790
2805
|
if (!options?.calledFromDaemon) {
|
|
2791
2806
|
const actId = (0, import_crypto3.randomUUID)();
|
|
2792
2807
|
const actTs = Date.now();
|
|
2793
|
-
await notifyActivity({
|
|
2808
|
+
await notifyActivity({
|
|
2809
|
+
id: actId,
|
|
2810
|
+
ts: actTs,
|
|
2811
|
+
tool: toolName,
|
|
2812
|
+
args,
|
|
2813
|
+
status: "pending",
|
|
2814
|
+
// Strip ANSI escape sequences — agent name comes from caller-supplied metadata
|
|
2815
|
+
// and may be displayed in a terminal (node9 tail/watch), enabling injection.
|
|
2816
|
+
agent: meta?.agent ? meta.agent.replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80) : void 0
|
|
2817
|
+
});
|
|
2794
2818
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
2795
2819
|
...options,
|
|
2796
2820
|
activityId: actId
|
package/dist/index.mjs
CHANGED
|
@@ -473,9 +473,8 @@ var DEFAULT_CONFIG = {
|
|
|
473
473
|
{
|
|
474
474
|
field: "command",
|
|
475
475
|
op: "matches",
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
476
|
+
// Anchor rm as a shell command (not inside a string arg like a git commit message).
|
|
477
|
+
value: "(^|&&|\\|\\||;)\\s*rm\\b[^;&|]*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
|
|
479
478
|
},
|
|
480
479
|
{
|
|
481
480
|
field: "command",
|
|
@@ -504,6 +503,13 @@ var DEFAULT_CONFIG = {
|
|
|
504
503
|
name: "review-drop-truncate-shell",
|
|
505
504
|
tool: "bash",
|
|
506
505
|
conditions: [
|
|
506
|
+
{
|
|
507
|
+
field: "command",
|
|
508
|
+
op: "matches",
|
|
509
|
+
// Require a DB CLI in the command so grep/cat/echo of SQL strings don't trigger.
|
|
510
|
+
value: "(^|&&|\\|\\||;|\\|)\\s*(psql|mysql|sqlite3|sqlplus|cockroach|clickhouse-client|mongo)\\b",
|
|
511
|
+
flags: "i"
|
|
512
|
+
},
|
|
507
513
|
{
|
|
508
514
|
field: "command",
|
|
509
515
|
op: "matches",
|
|
@@ -524,7 +530,9 @@ var DEFAULT_CONFIG = {
|
|
|
524
530
|
{
|
|
525
531
|
field: "command",
|
|
526
532
|
op: "matches",
|
|
527
|
-
|
|
533
|
+
// Anchor git as a shell command so node -e / python -c scripts containing
|
|
534
|
+
// "git push --force" as a string don't false-positive.
|
|
535
|
+
value: "(^|&&|\\|\\||;)\\s*git\\s+push[^;&|]*(--force|--force-with-lease|-f\\b)",
|
|
528
536
|
flags: "i"
|
|
529
537
|
}
|
|
530
538
|
],
|
|
@@ -534,29 +542,20 @@ var DEFAULT_CONFIG = {
|
|
|
534
542
|
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."
|
|
535
543
|
},
|
|
536
544
|
{
|
|
537
|
-
name: "review-git-
|
|
545
|
+
name: "review-git-destructive",
|
|
538
546
|
tool: "bash",
|
|
539
547
|
conditions: [
|
|
540
548
|
{
|
|
541
549
|
field: "command",
|
|
542
550
|
op: "matches",
|
|
543
|
-
value: "\\bgit\\
|
|
551
|
+
value: "\\bgit\\s+(reset\\s+--hard|clean\\s+-[fdxX]|rebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
544
552
|
flags: "i"
|
|
545
|
-
}
|
|
546
|
-
],
|
|
547
|
-
conditionMode: "all",
|
|
548
|
-
verdict: "review",
|
|
549
|
-
reason: "git push sends changes to a shared remote",
|
|
550
|
-
description: "The AI wants to push commits to a remote repository. Once pushed, those changes are visible to everyone with access."
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
name: "review-git-destructive",
|
|
554
|
-
tool: "bash",
|
|
555
|
-
conditions: [
|
|
553
|
+
},
|
|
556
554
|
{
|
|
557
555
|
field: "command",
|
|
558
|
-
op: "
|
|
559
|
-
|
|
556
|
+
op: "notMatches",
|
|
557
|
+
// Exclude recovery ops — these resolve a conflict, not start a destructive action.
|
|
558
|
+
value: "\\bgit\\s+rebase\\s+--(abort|continue|skip)\\b",
|
|
560
559
|
flags: "i"
|
|
561
560
|
}
|
|
562
561
|
],
|
|
@@ -582,7 +581,9 @@ var DEFAULT_CONFIG = {
|
|
|
582
581
|
{
|
|
583
582
|
field: "command",
|
|
584
583
|
op: "matches",
|
|
585
|
-
|
|
584
|
+
// Anchor curl/wget as a shell command so node -e scripts testing this
|
|
585
|
+
// regex pattern don't self-match as a false positive.
|
|
586
|
+
value: "(^|&&|\\|\\||;)\\s*(curl|wget)[^|]*\\|\\s*(ba|z|da|fi|c|k)?sh",
|
|
586
587
|
flags: "i"
|
|
587
588
|
}
|
|
588
589
|
],
|
|
@@ -983,7 +984,7 @@ var DLP_PATTERNS = [
|
|
|
983
984
|
regex: /_authToken\s*=\s*[A-Za-z0-9_\-]{20,}/,
|
|
984
985
|
severity: "block"
|
|
985
986
|
},
|
|
986
|
-
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]
|
|
987
|
+
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i, severity: "review" }
|
|
987
988
|
];
|
|
988
989
|
var SENSITIVE_PATH_PATTERNS = [
|
|
989
990
|
/[/\\]\.ssh[/\\]/i,
|
|
@@ -1573,12 +1574,25 @@ function getNestedValue(obj, path15) {
|
|
|
1573
1574
|
if (!obj || typeof obj !== "object") return null;
|
|
1574
1575
|
return path15.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1575
1576
|
}
|
|
1577
|
+
function stripStringArguments(cmd) {
|
|
1578
|
+
let result = cmd;
|
|
1579
|
+
result = result.replace(
|
|
1580
|
+
/\b(node|python3?|ruby|perl|php|deno)\s+(-[ecr]|eval)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/gi,
|
|
1581
|
+
'$1 $2 ""'
|
|
1582
|
+
);
|
|
1583
|
+
result = result.replace(
|
|
1584
|
+
/\s(-m|--message|--body|--title|--description)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
|
|
1585
|
+
' $1 ""'
|
|
1586
|
+
);
|
|
1587
|
+
return result;
|
|
1588
|
+
}
|
|
1576
1589
|
function evaluateSmartConditions(args, rule) {
|
|
1577
1590
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
1578
1591
|
const mode = rule.conditionMode ?? "all";
|
|
1579
1592
|
const results = rule.conditions.map((cond) => {
|
|
1580
1593
|
const rawVal = getNestedValue(args, cond.field);
|
|
1581
|
-
const
|
|
1594
|
+
const normalized = rawVal !== null && rawVal !== void 0 ? String(rawVal).replace(/\s+/g, " ").trim() : null;
|
|
1595
|
+
const val = cond.field === "command" && normalized !== null ? stripStringArguments(normalized) : normalized;
|
|
1582
1596
|
switch (cond.op) {
|
|
1583
1597
|
case "exists":
|
|
1584
1598
|
return val !== null && val !== "";
|
|
@@ -2383,7 +2397,8 @@ function escapePango(text) {
|
|
|
2383
2397
|
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
|
|
2384
2398
|
const lines = [];
|
|
2385
2399
|
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
2386
|
-
|
|
2400
|
+
const safeAgent = (agent ?? "AI Agent").replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80);
|
|
2401
|
+
lines.push(`\u{1F916} ${safeAgent} | \u{1F527} ${toolName}`);
|
|
2387
2402
|
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
2388
2403
|
if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
|
|
2389
2404
|
lines.push("");
|
|
@@ -2760,7 +2775,16 @@ async function authorizeHeadless(toolName, args, meta, options) {
|
|
|
2760
2775
|
if (!options?.calledFromDaemon) {
|
|
2761
2776
|
const actId = randomUUID();
|
|
2762
2777
|
const actTs = Date.now();
|
|
2763
|
-
await notifyActivity({
|
|
2778
|
+
await notifyActivity({
|
|
2779
|
+
id: actId,
|
|
2780
|
+
ts: actTs,
|
|
2781
|
+
tool: toolName,
|
|
2782
|
+
args,
|
|
2783
|
+
status: "pending",
|
|
2784
|
+
// Strip ANSI escape sequences — agent name comes from caller-supplied metadata
|
|
2785
|
+
// and may be displayed in a terminal (node9 tail/watch), enabling injection.
|
|
2786
|
+
agent: meta?.agent ? meta.agent.replace(/\x1b(?:\[[0-9;?]*[a-zA-Z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-_])/g, "").slice(0, 80) : void 0
|
|
2787
|
+
});
|
|
2764
2788
|
const result = await _authorizeHeadlessCore(toolName, args, meta, {
|
|
2765
2789
|
...options,
|
|
2766
2790
|
activityId: actId
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"field": "command",
|
|
12
12
|
"op": "matches",
|
|
13
|
-
"value": "(curl|wget)\\s+[^|]*\\|\\s*(?:(bash|sh|zsh|fish)|(python3?|ruby|perl|node)\\b(?!\\s+-[cem]\\b))",
|
|
13
|
+
"value": "(^|&&|\\|\\||;)\\s*(curl|wget)\\s+[^|]*\\|\\s*(?:(bash|sh|zsh|fish)|(python3?|ruby|perl|node)\\b(?!\\s+-[cem]\\b))",
|
|
14
14
|
"flags": "i"
|
|
15
15
|
}
|
|
16
16
|
],
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
{
|
|
25
25
|
"field": "command",
|
|
26
26
|
"op": "matches",
|
|
27
|
-
"value": "
|
|
27
|
+
"value": "\\bbase64\\s+(-d|--decode)[^|;&]*\\|\\s*(bash|sh|zsh)",
|
|
28
28
|
"flags": "i"
|
|
29
29
|
}
|
|
30
30
|
],
|