@node9/proxy 2.13.0 → 2.14.0

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/scan-ink.mjs CHANGED
@@ -709,13 +709,32 @@ var DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
709
709
  })
710
710
  );
711
711
  var SENSITIVE_PATH_PATTERNS = [
712
- /[/\\]\.ssh[/\\]/i,
713
- /[/\\]\.aws[/\\]/i,
712
+ /[/\\]\.ssh([/\\]|$)/i,
713
+ /[/\\]\.aws([/\\]|$)/i,
714
714
  /[/\\]\.config[/\\]gcloud[/\\]/i,
715
715
  /[/\\]\.azure[/\\]/i,
716
716
  /[/\\]\.kube[/\\]config$/i,
717
- /[/\\]\.env($|\.)/i,
718
- // .env, .env.local, .env.production — not .envoy
717
+ // ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
718
+ // (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
719
+ // structural suffix chain rather than a hand-written list, `example|sample|
720
+ // template` exempt because a fixture stays a fixture whatever follows, and
721
+ // `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
722
+ // committed template, `.env.test.local` is gitignored and holds real values.
723
+ //
724
+ // It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
725
+ // blocked while `cat .env.example` allowed: the same file, opposite verdicts,
726
+ // decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
727
+ // which is the contract that now holds these copies in step, and stage 5 of
728
+ // doc/credential-jail-architecture.md, which replaces them with one generated
729
+ // source.
730
+ // ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
731
+ // fixture whatever follows it -- `.env.example.md` is documentation -- but
732
+ // `.env.example.local` is gitignored by the `.env*.local` convention and holds
733
+ // real values, exactly the reasoning that anchors `(?!\.test$)` rather than
734
+ // using `\b`. Without this branch the fixture exemption also bought a two-step
735
+ // bypass: `cp .env .env.sample`, then read the copy.
736
+ /[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
737
+ // .env + any suffix chain; fixtures exempt unless .local
719
738
  /[/\\]\.git-credentials$/i,
720
739
  /[/\\]\.npmrc$/i,
721
740
  /[/\\]\.docker[/\\]config\.json$/i,
@@ -782,6 +801,10 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
782
801
  "od",
783
802
  "xxd",
784
803
  "hexdump",
804
+ // Emits the file's bytes, re-encoded, so it is a read by the set's own test
805
+ // ("does it emit file contents"). Absent until 2026-09-10, which is why
806
+ // `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
807
+ "base64",
785
808
  "strings",
786
809
  "sort",
787
810
  "uniq",
@@ -789,8 +812,55 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
789
812
  "nl",
790
813
  "dd"
791
814
  ]);
815
+ var SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
816
+ var RSYNC_SKIP = [
817
+ "e",
818
+ "--rsh",
819
+ "--exclude",
820
+ "--exclude-from",
821
+ "--include",
822
+ "--include-from",
823
+ "--files-from",
824
+ "f",
825
+ "--filter"
826
+ ];
827
+ var COPY_VERBS = {
828
+ cp: { source: "allButLast", targetDirFlag: true },
829
+ mv: { source: "allButLast", targetDirFlag: true },
830
+ install: { source: "allButLast", targetDirFlag: true },
831
+ ln: { source: "first", targetDirFlag: true },
832
+ scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS },
833
+ rsync: { source: "allButLast", skipFlags: RSYNC_SKIP },
834
+ tar: {
835
+ source: "archive",
836
+ archive: "tar",
837
+ skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"]
838
+ },
839
+ zip: { source: "archive", archive: "zip", skipFlags: ["x", "i", "--exclude", "--include"] },
840
+ ar: { source: "archive", archive: "ar" },
841
+ "7z": { source: "archive", archive: "7z", skipFlags: ["x", "--exclude"] },
842
+ gzip: { source: "all" },
843
+ bzip2: { source: "all" },
844
+ xz: { source: "all" },
845
+ "docker cp": { source: "allButLast" },
846
+ "kubectl cp": { source: "allButLast" },
847
+ "gsutil cp": { source: "allButLast" },
848
+ "gsutil rsync": { source: "allButLast" },
849
+ "rclone copy": { source: "allButLast" },
850
+ "rclone sync": { source: "allButLast" },
851
+ "aws s3 cp": { source: "allButLast" },
852
+ "aws s3 mv": { source: "allButLast" },
853
+ "aws s3 sync": { source: "allButLast" },
854
+ "gcloud storage cp": { source: "allButLast" },
855
+ "az storage blob upload": { source: "flagOperand", sourceFlags: ["f", "--file"] }
856
+ };
857
+ var COPY_VERB_HEADS = new Set(Object.keys(COPY_VERBS).map((k) => k.split(" ")[0]));
792
858
  var FS_OP_PRESCREEN_RE = new RegExp(
793
- `(?:^|[\\s|;&(\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`
859
+ // A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
860
+ // reader right after `"` / `'`, and without these two characters the
861
+ // prescreen rejected every string-wrapped read before the parser ran.
862
+ // Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
863
+ `(?:^|[\\s|;&("'\`\\n/])(?:rm|${[...FS_READ_TOOLS, ...COPY_VERB_HEADS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
794
864
  );
795
865
  function deriveRedirOp(sample) {
796
866
  try {
@@ -807,10 +877,14 @@ function deriveRedirOp(sample) {
807
877
  }
808
878
  }
809
879
  var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
880
+ var REDIR_FILE_IN_OPS = new Set(
881
+ [deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
882
+ );
810
883
  var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
811
884
  deriveRedirOp("cat <<X\nX"),
812
885
  deriveRedirOp("cat <<-X\nX")
813
886
  ]);
887
+ var SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
814
888
  var aws_default = {
815
889
  name: "aws",
816
890
  description: "Protects AWS infrastructure from destructive AI operations",
@@ -1362,7 +1436,7 @@ var project_jail_default = {
1362
1436
  {
1363
1437
  field: "command",
1364
1438
  op: "matches",
1365
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
1439
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
1366
1440
  flags: "i"
1367
1441
  }
1368
1442
  ],
@@ -1376,7 +1450,7 @@ var project_jail_default = {
1376
1450
  {
1377
1451
  field: "command",
1378
1452
  op: "matches",
1379
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
1453
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
1380
1454
  flags: "i"
1381
1455
  }
1382
1456
  ],
@@ -1390,7 +1464,7 @@ var project_jail_default = {
1390
1464
  {
1391
1465
  field: "command",
1392
1466
  op: "matches",
1393
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
1467
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
1394
1468
  flags: "i"
1395
1469
  }
1396
1470
  ],
@@ -1404,7 +1478,7 @@ var project_jail_default = {
1404
1478
  {
1405
1479
  field: "command",
1406
1480
  op: "matches",
1407
- value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
1481
+ value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
1408
1482
  flags: "i"
1409
1483
  }
1410
1484
  ],
@@ -1418,7 +1492,7 @@ var project_jail_default = {
1418
1492
  {
1419
1493
  field: "file_path",
1420
1494
  op: "matches",
1421
- value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
1495
+ value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
1422
1496
  flags: "i"
1423
1497
  }
1424
1498
  ],
@@ -1432,7 +1506,7 @@ var project_jail_default = {
1432
1506
  {
1433
1507
  field: "file_path",
1434
1508
  op: "matches",
1435
- value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
1509
+ value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
1436
1510
  flags: "i"
1437
1511
  }
1438
1512
  ],
@@ -1446,7 +1520,7 @@ var project_jail_default = {
1446
1520
  {
1447
1521
  field: "file_path",
1448
1522
  op: "matches",
1449
- value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
1523
+ value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
1450
1524
  flags: "i"
1451
1525
  }
1452
1526
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code, Codex, Gemini, Cursor, Opencode, Pi, and any MCP server.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",