@node9/proxy 1.0.13 → 1.0.14
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 +117 -119
- package/dist/cli.js +736 -360
- package/dist/cli.mjs +731 -355
- package/dist/index.js +441 -123
- package/dist/index.mjs +441 -123
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -37,9 +37,9 @@ module.exports = __toCommonJS(src_exports);
|
|
|
37
37
|
// src/core.ts
|
|
38
38
|
var import_chalk2 = __toESM(require("chalk"));
|
|
39
39
|
var import_prompts = require("@inquirer/prompts");
|
|
40
|
-
var
|
|
41
|
-
var
|
|
42
|
-
var
|
|
40
|
+
var import_fs2 = __toESM(require("fs"));
|
|
41
|
+
var import_path4 = __toESM(require("path"));
|
|
42
|
+
var import_os2 = __toESM(require("os"));
|
|
43
43
|
var import_picomatch = __toESM(require("picomatch"));
|
|
44
44
|
var import_sh_syntax = require("sh-syntax");
|
|
45
45
|
|
|
@@ -369,25 +369,26 @@ var import_zod = require("zod");
|
|
|
369
369
|
var noNewlines = import_zod.z.string().refine((s) => !s.includes("\n") && !s.includes("\r"), {
|
|
370
370
|
message: "Value must not contain literal newline characters (use \\n instead)"
|
|
371
371
|
});
|
|
372
|
-
var validRegex = noNewlines.refine(
|
|
373
|
-
(s) => {
|
|
374
|
-
try {
|
|
375
|
-
new RegExp(s);
|
|
376
|
-
return true;
|
|
377
|
-
} catch {
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
{ message: "Value must be a valid regular expression" }
|
|
382
|
-
);
|
|
383
372
|
var SmartConditionSchema = import_zod.z.object({
|
|
384
373
|
field: import_zod.z.string().min(1, "Condition field must not be empty"),
|
|
385
|
-
op: import_zod.z.enum(
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
374
|
+
op: import_zod.z.enum(
|
|
375
|
+
[
|
|
376
|
+
"matches",
|
|
377
|
+
"notMatches",
|
|
378
|
+
"contains",
|
|
379
|
+
"notContains",
|
|
380
|
+
"exists",
|
|
381
|
+
"notExists",
|
|
382
|
+
"matchesGlob",
|
|
383
|
+
"notMatchesGlob"
|
|
384
|
+
],
|
|
385
|
+
{
|
|
386
|
+
errorMap: () => ({
|
|
387
|
+
message: "op must be one of: matches, notMatches, contains, notContains, exists, notExists, matchesGlob, notMatchesGlob"
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
),
|
|
391
|
+
value: import_zod.z.string().optional(),
|
|
391
392
|
flags: import_zod.z.string().optional()
|
|
392
393
|
});
|
|
393
394
|
var SmartRuleSchema = import_zod.z.object({
|
|
@@ -400,11 +401,6 @@ var SmartRuleSchema = import_zod.z.object({
|
|
|
400
401
|
}),
|
|
401
402
|
reason: import_zod.z.string().optional()
|
|
402
403
|
});
|
|
403
|
-
var PolicyRuleSchema = import_zod.z.object({
|
|
404
|
-
action: import_zod.z.string().min(1),
|
|
405
|
-
allowPaths: import_zod.z.array(import_zod.z.string()).optional(),
|
|
406
|
-
blockPaths: import_zod.z.array(import_zod.z.string()).optional()
|
|
407
|
-
});
|
|
408
404
|
var ConfigFileSchema = import_zod.z.object({
|
|
409
405
|
version: import_zod.z.string().optional(),
|
|
410
406
|
settings: import_zod.z.object({
|
|
@@ -429,12 +425,15 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
429
425
|
dangerousWords: import_zod.z.array(noNewlines).optional(),
|
|
430
426
|
ignoredTools: import_zod.z.array(import_zod.z.string()).optional(),
|
|
431
427
|
toolInspection: import_zod.z.record(import_zod.z.string()).optional(),
|
|
432
|
-
rules: import_zod.z.array(PolicyRuleSchema).optional(),
|
|
433
428
|
smartRules: import_zod.z.array(SmartRuleSchema).optional(),
|
|
434
429
|
snapshot: import_zod.z.object({
|
|
435
430
|
tools: import_zod.z.array(import_zod.z.string()).optional(),
|
|
436
431
|
onlyPaths: import_zod.z.array(import_zod.z.string()).optional(),
|
|
437
432
|
ignorePaths: import_zod.z.array(import_zod.z.string()).optional()
|
|
433
|
+
}).optional(),
|
|
434
|
+
dlp: import_zod.z.object({
|
|
435
|
+
enabled: import_zod.z.boolean().optional(),
|
|
436
|
+
scanIgnoredTools: import_zod.z.boolean().optional()
|
|
438
437
|
}).optional()
|
|
439
438
|
}).optional(),
|
|
440
439
|
environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
|
|
@@ -456,8 +455,8 @@ function sanitizeConfig(raw) {
|
|
|
456
455
|
}
|
|
457
456
|
}
|
|
458
457
|
const lines = result.error.issues.map((issue) => {
|
|
459
|
-
const
|
|
460
|
-
return ` \u2022 ${
|
|
458
|
+
const path5 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
459
|
+
return ` \u2022 ${path5}: ${issue.message}`;
|
|
461
460
|
});
|
|
462
461
|
return {
|
|
463
462
|
sanitized,
|
|
@@ -466,18 +465,291 @@ ${lines.join("\n")}`
|
|
|
466
465
|
};
|
|
467
466
|
}
|
|
468
467
|
|
|
468
|
+
// src/shields.ts
|
|
469
|
+
var import_fs = __toESM(require("fs"));
|
|
470
|
+
var import_path3 = __toESM(require("path"));
|
|
471
|
+
var import_os = __toESM(require("os"));
|
|
472
|
+
var SHIELDS = {
|
|
473
|
+
postgres: {
|
|
474
|
+
name: "postgres",
|
|
475
|
+
description: "Protects PostgreSQL databases from destructive AI operations",
|
|
476
|
+
aliases: ["pg", "postgresql"],
|
|
477
|
+
smartRules: [
|
|
478
|
+
{
|
|
479
|
+
name: "shield:postgres:block-drop-table",
|
|
480
|
+
tool: "*",
|
|
481
|
+
conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
|
|
482
|
+
verdict: "block",
|
|
483
|
+
reason: "DROP TABLE is irreversible \u2014 blocked by Postgres shield"
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "shield:postgres:block-truncate",
|
|
487
|
+
tool: "*",
|
|
488
|
+
conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
|
|
489
|
+
verdict: "block",
|
|
490
|
+
reason: "TRUNCATE is irreversible \u2014 blocked by Postgres shield"
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: "shield:postgres:block-drop-column",
|
|
494
|
+
tool: "*",
|
|
495
|
+
conditions: [
|
|
496
|
+
{ field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
|
|
497
|
+
],
|
|
498
|
+
verdict: "block",
|
|
499
|
+
reason: "DROP COLUMN is irreversible \u2014 blocked by Postgres shield"
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
name: "shield:postgres:review-grant-revoke",
|
|
503
|
+
tool: "*",
|
|
504
|
+
conditions: [{ field: "sql", op: "matches", value: "\\b(GRANT|REVOKE)\\b", flags: "i" }],
|
|
505
|
+
verdict: "review",
|
|
506
|
+
reason: "Permission changes require human approval (Postgres shield)"
|
|
507
|
+
}
|
|
508
|
+
],
|
|
509
|
+
dangerousWords: ["dropdb", "pg_dropcluster"]
|
|
510
|
+
},
|
|
511
|
+
github: {
|
|
512
|
+
name: "github",
|
|
513
|
+
description: "Protects GitHub repositories from destructive AI operations",
|
|
514
|
+
aliases: ["git"],
|
|
515
|
+
smartRules: [
|
|
516
|
+
{
|
|
517
|
+
// Note: git branch -d/-D is already caught by the built-in review-git-destructive rule.
|
|
518
|
+
// This rule adds coverage for `git push --delete` which the built-in does not match.
|
|
519
|
+
name: "shield:github:review-delete-branch-remote",
|
|
520
|
+
tool: "bash",
|
|
521
|
+
conditions: [
|
|
522
|
+
{
|
|
523
|
+
field: "command",
|
|
524
|
+
op: "matches",
|
|
525
|
+
value: "git\\s+push\\s+.*--delete",
|
|
526
|
+
flags: "i"
|
|
527
|
+
}
|
|
528
|
+
],
|
|
529
|
+
verdict: "review",
|
|
530
|
+
reason: "Remote branch deletion requires human approval (GitHub shield)"
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
name: "shield:github:block-delete-repo",
|
|
534
|
+
tool: "*",
|
|
535
|
+
conditions: [
|
|
536
|
+
{ field: "command", op: "matches", value: "gh\\s+repo\\s+delete", flags: "i" }
|
|
537
|
+
],
|
|
538
|
+
verdict: "block",
|
|
539
|
+
reason: "Repository deletion is irreversible \u2014 blocked by GitHub shield"
|
|
540
|
+
}
|
|
541
|
+
],
|
|
542
|
+
dangerousWords: []
|
|
543
|
+
},
|
|
544
|
+
aws: {
|
|
545
|
+
name: "aws",
|
|
546
|
+
description: "Protects AWS infrastructure from destructive AI operations",
|
|
547
|
+
aliases: ["amazon"],
|
|
548
|
+
smartRules: [
|
|
549
|
+
{
|
|
550
|
+
name: "shield:aws:block-delete-s3-bucket",
|
|
551
|
+
tool: "*",
|
|
552
|
+
conditions: [
|
|
553
|
+
{
|
|
554
|
+
field: "command",
|
|
555
|
+
op: "matches",
|
|
556
|
+
value: "aws\\s+s3.*rb\\s|aws\\s+s3api\\s+delete-bucket",
|
|
557
|
+
flags: "i"
|
|
558
|
+
}
|
|
559
|
+
],
|
|
560
|
+
verdict: "block",
|
|
561
|
+
reason: "S3 bucket deletion is irreversible \u2014 blocked by AWS shield"
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
name: "shield:aws:review-iam-changes",
|
|
565
|
+
tool: "*",
|
|
566
|
+
conditions: [
|
|
567
|
+
{
|
|
568
|
+
field: "command",
|
|
569
|
+
op: "matches",
|
|
570
|
+
value: "aws\\s+iam\\s+(create|delete|attach|detach|put|remove)",
|
|
571
|
+
flags: "i"
|
|
572
|
+
}
|
|
573
|
+
],
|
|
574
|
+
verdict: "review",
|
|
575
|
+
reason: "IAM changes require human approval (AWS shield)"
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
name: "shield:aws:block-ec2-terminate",
|
|
579
|
+
tool: "*",
|
|
580
|
+
conditions: [
|
|
581
|
+
{
|
|
582
|
+
field: "command",
|
|
583
|
+
op: "matches",
|
|
584
|
+
value: "aws\\s+ec2\\s+terminate-instances",
|
|
585
|
+
flags: "i"
|
|
586
|
+
}
|
|
587
|
+
],
|
|
588
|
+
verdict: "block",
|
|
589
|
+
reason: "EC2 instance termination is irreversible \u2014 blocked by AWS shield"
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
name: "shield:aws:review-rds-delete",
|
|
593
|
+
tool: "*",
|
|
594
|
+
conditions: [
|
|
595
|
+
{ field: "command", op: "matches", value: "aws\\s+rds\\s+delete-", flags: "i" }
|
|
596
|
+
],
|
|
597
|
+
verdict: "review",
|
|
598
|
+
reason: "RDS deletion requires human approval (AWS shield)"
|
|
599
|
+
}
|
|
600
|
+
],
|
|
601
|
+
dangerousWords: []
|
|
602
|
+
},
|
|
603
|
+
filesystem: {
|
|
604
|
+
name: "filesystem",
|
|
605
|
+
description: "Protects the local filesystem from dangerous AI operations",
|
|
606
|
+
aliases: ["fs"],
|
|
607
|
+
smartRules: [
|
|
608
|
+
{
|
|
609
|
+
name: "shield:filesystem:review-chmod-777",
|
|
610
|
+
tool: "bash",
|
|
611
|
+
conditions: [
|
|
612
|
+
{ field: "command", op: "matches", value: "chmod\\s+(777|a\\+rwx)", flags: "i" }
|
|
613
|
+
],
|
|
614
|
+
verdict: "review",
|
|
615
|
+
reason: "chmod 777 requires human approval (filesystem shield)"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
name: "shield:filesystem:review-write-etc",
|
|
619
|
+
tool: "bash",
|
|
620
|
+
conditions: [
|
|
621
|
+
{
|
|
622
|
+
field: "command",
|
|
623
|
+
// Narrow to write-indicative operations to avoid approval fatigue on reads.
|
|
624
|
+
// Matches: tee /etc/*, cp .../etc/*, mv .../etc/*, > /etc/*, install .../etc/*
|
|
625
|
+
op: "matches",
|
|
626
|
+
value: "(tee|\\bcp\\b|\\bmv\\b|install|>+)\\s+.*\\/etc\\/"
|
|
627
|
+
}
|
|
628
|
+
],
|
|
629
|
+
verdict: "review",
|
|
630
|
+
reason: "Writing to /etc requires human approval (filesystem shield)"
|
|
631
|
+
}
|
|
632
|
+
],
|
|
633
|
+
// dd removed: too common as a legitimate tool (disk imaging, file ops).
|
|
634
|
+
// mkfs removed: already in the built-in DANGEROUS_WORDS baseline.
|
|
635
|
+
// wipefs retained: rarely legitimate in an agent context and not in built-ins.
|
|
636
|
+
dangerousWords: ["wipefs"]
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
function resolveShieldName(input) {
|
|
640
|
+
const lower = input.toLowerCase();
|
|
641
|
+
if (SHIELDS[lower]) return lower;
|
|
642
|
+
for (const [name, def] of Object.entries(SHIELDS)) {
|
|
643
|
+
if (def.aliases.includes(lower)) return name;
|
|
644
|
+
}
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
function getShield(name) {
|
|
648
|
+
const resolved = resolveShieldName(name);
|
|
649
|
+
return resolved ? SHIELDS[resolved] : null;
|
|
650
|
+
}
|
|
651
|
+
var SHIELDS_STATE_FILE = import_path3.default.join(import_os.default.homedir(), ".node9", "shields.json");
|
|
652
|
+
function readActiveShields() {
|
|
653
|
+
try {
|
|
654
|
+
const raw = import_fs.default.readFileSync(SHIELDS_STATE_FILE, "utf-8");
|
|
655
|
+
if (!raw.trim()) return [];
|
|
656
|
+
const parsed = JSON.parse(raw);
|
|
657
|
+
if (Array.isArray(parsed.active)) {
|
|
658
|
+
return parsed.active.filter(
|
|
659
|
+
(e) => typeof e === "string" && e.length > 0 && e in SHIELDS
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
} catch (err) {
|
|
663
|
+
if (err.code !== "ENOENT") {
|
|
664
|
+
process.stderr.write(`[node9] Warning: could not read shields state: ${String(err)}
|
|
665
|
+
`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/dlp.ts
|
|
672
|
+
var DLP_PATTERNS = [
|
|
673
|
+
{ name: "AWS Access Key ID", regex: /\bAKIA[0-9A-Z]{16}\b/, severity: "block" },
|
|
674
|
+
{ name: "GitHub Token", regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/, severity: "block" },
|
|
675
|
+
{ name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]+\b/, severity: "block" },
|
|
676
|
+
{ name: "OpenAI API Key", regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/, severity: "block" },
|
|
677
|
+
{ name: "Stripe Secret Key", regex: /\bsk_(?:live|test)_[0-9a-zA-Z]{24}\b/, severity: "block" },
|
|
678
|
+
{
|
|
679
|
+
name: "Private Key (PEM)",
|
|
680
|
+
regex: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/,
|
|
681
|
+
severity: "block"
|
|
682
|
+
},
|
|
683
|
+
{ name: "Bearer Token", regex: /Bearer\s+[a-zA-Z0-9\-._~+/]+=*/i, severity: "review" }
|
|
684
|
+
];
|
|
685
|
+
function maskSecret(raw, pattern) {
|
|
686
|
+
const match = raw.match(pattern);
|
|
687
|
+
if (!match) return "****";
|
|
688
|
+
const secret = match[0];
|
|
689
|
+
if (secret.length < 8) return "****";
|
|
690
|
+
const prefix = secret.slice(0, 4);
|
|
691
|
+
const suffix = secret.slice(-4);
|
|
692
|
+
const stars = "*".repeat(Math.min(secret.length - 8, 12));
|
|
693
|
+
return `${prefix}${stars}${suffix}`;
|
|
694
|
+
}
|
|
695
|
+
var MAX_DEPTH = 5;
|
|
696
|
+
var MAX_STRING_BYTES = 1e5;
|
|
697
|
+
var MAX_JSON_PARSE_BYTES = 1e4;
|
|
698
|
+
function scanArgs(args, depth = 0, fieldPath = "args") {
|
|
699
|
+
if (depth > MAX_DEPTH || args === null || args === void 0) return null;
|
|
700
|
+
if (Array.isArray(args)) {
|
|
701
|
+
for (let i = 0; i < args.length; i++) {
|
|
702
|
+
const match = scanArgs(args[i], depth + 1, `${fieldPath}[${i}]`);
|
|
703
|
+
if (match) return match;
|
|
704
|
+
}
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
if (typeof args === "object") {
|
|
708
|
+
for (const [key, value] of Object.entries(args)) {
|
|
709
|
+
const match = scanArgs(value, depth + 1, `${fieldPath}.${key}`);
|
|
710
|
+
if (match) return match;
|
|
711
|
+
}
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
if (typeof args === "string") {
|
|
715
|
+
const text = args.length > MAX_STRING_BYTES ? args.slice(0, MAX_STRING_BYTES) : args;
|
|
716
|
+
for (const pattern of DLP_PATTERNS) {
|
|
717
|
+
if (pattern.regex.test(text)) {
|
|
718
|
+
return {
|
|
719
|
+
patternName: pattern.name,
|
|
720
|
+
fieldPath,
|
|
721
|
+
redactedSample: maskSecret(text, pattern.regex),
|
|
722
|
+
severity: pattern.severity
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (text.length < MAX_JSON_PARSE_BYTES) {
|
|
727
|
+
const trimmed = text.trim();
|
|
728
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
729
|
+
try {
|
|
730
|
+
const parsed = JSON.parse(text);
|
|
731
|
+
const inner = scanArgs(parsed, depth + 1, fieldPath);
|
|
732
|
+
if (inner) return inner;
|
|
733
|
+
} catch {
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
|
|
469
741
|
// src/core.ts
|
|
470
|
-
var PAUSED_FILE =
|
|
471
|
-
var TRUST_FILE =
|
|
472
|
-
var LOCAL_AUDIT_LOG =
|
|
473
|
-
var HOOK_DEBUG_LOG =
|
|
742
|
+
var PAUSED_FILE = import_path4.default.join(import_os2.default.homedir(), ".node9", "PAUSED");
|
|
743
|
+
var TRUST_FILE = import_path4.default.join(import_os2.default.homedir(), ".node9", "trust.json");
|
|
744
|
+
var LOCAL_AUDIT_LOG = import_path4.default.join(import_os2.default.homedir(), ".node9", "audit.log");
|
|
745
|
+
var HOOK_DEBUG_LOG = import_path4.default.join(import_os2.default.homedir(), ".node9", "hook-debug.log");
|
|
474
746
|
function checkPause() {
|
|
475
747
|
try {
|
|
476
|
-
if (!
|
|
477
|
-
const state = JSON.parse(
|
|
748
|
+
if (!import_fs2.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
749
|
+
const state = JSON.parse(import_fs2.default.readFileSync(PAUSED_FILE, "utf-8"));
|
|
478
750
|
if (state.expiry > 0 && Date.now() >= state.expiry) {
|
|
479
751
|
try {
|
|
480
|
-
|
|
752
|
+
import_fs2.default.unlinkSync(PAUSED_FILE);
|
|
481
753
|
} catch {
|
|
482
754
|
}
|
|
483
755
|
return { paused: false };
|
|
@@ -488,20 +760,20 @@ function checkPause() {
|
|
|
488
760
|
}
|
|
489
761
|
}
|
|
490
762
|
function atomicWriteSync(filePath, data, options) {
|
|
491
|
-
const dir =
|
|
492
|
-
if (!
|
|
493
|
-
const tmpPath = `${filePath}.${
|
|
494
|
-
|
|
495
|
-
|
|
763
|
+
const dir = import_path4.default.dirname(filePath);
|
|
764
|
+
if (!import_fs2.default.existsSync(dir)) import_fs2.default.mkdirSync(dir, { recursive: true });
|
|
765
|
+
const tmpPath = `${filePath}.${import_os2.default.hostname()}.${process.pid}.tmp`;
|
|
766
|
+
import_fs2.default.writeFileSync(tmpPath, data, options);
|
|
767
|
+
import_fs2.default.renameSync(tmpPath, filePath);
|
|
496
768
|
}
|
|
497
769
|
function getActiveTrustSession(toolName) {
|
|
498
770
|
try {
|
|
499
|
-
if (!
|
|
500
|
-
const trust = JSON.parse(
|
|
771
|
+
if (!import_fs2.default.existsSync(TRUST_FILE)) return false;
|
|
772
|
+
const trust = JSON.parse(import_fs2.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
501
773
|
const now = Date.now();
|
|
502
774
|
const active = trust.entries.filter((e) => e.expiry > now);
|
|
503
775
|
if (active.length !== trust.entries.length) {
|
|
504
|
-
|
|
776
|
+
import_fs2.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
505
777
|
}
|
|
506
778
|
return active.some((e) => e.tool === toolName || matchesPattern(toolName, e.tool));
|
|
507
779
|
} catch {
|
|
@@ -512,8 +784,8 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
512
784
|
try {
|
|
513
785
|
let trust = { entries: [] };
|
|
514
786
|
try {
|
|
515
|
-
if (
|
|
516
|
-
trust = JSON.parse(
|
|
787
|
+
if (import_fs2.default.existsSync(TRUST_FILE)) {
|
|
788
|
+
trust = JSON.parse(import_fs2.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
517
789
|
}
|
|
518
790
|
} catch {
|
|
519
791
|
}
|
|
@@ -529,9 +801,9 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
529
801
|
}
|
|
530
802
|
function appendToLog(logPath, entry) {
|
|
531
803
|
try {
|
|
532
|
-
const dir =
|
|
533
|
-
if (!
|
|
534
|
-
|
|
804
|
+
const dir = import_path4.default.dirname(logPath);
|
|
805
|
+
if (!import_fs2.default.existsSync(dir)) import_fs2.default.mkdirSync(dir, { recursive: true });
|
|
806
|
+
import_fs2.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
535
807
|
} catch {
|
|
536
808
|
}
|
|
537
809
|
}
|
|
@@ -543,7 +815,7 @@ function appendHookDebug(toolName, args, meta) {
|
|
|
543
815
|
args: safeArgs,
|
|
544
816
|
agent: meta?.agent,
|
|
545
817
|
mcpServer: meta?.mcpServer,
|
|
546
|
-
hostname:
|
|
818
|
+
hostname: import_os2.default.hostname(),
|
|
547
819
|
cwd: process.cwd()
|
|
548
820
|
});
|
|
549
821
|
}
|
|
@@ -557,7 +829,7 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta) {
|
|
|
557
829
|
checkedBy,
|
|
558
830
|
agent: meta?.agent,
|
|
559
831
|
mcpServer: meta?.mcpServer,
|
|
560
|
-
hostname:
|
|
832
|
+
hostname: import_os2.default.hostname()
|
|
561
833
|
});
|
|
562
834
|
}
|
|
563
835
|
function tokenize(toolName) {
|
|
@@ -573,9 +845,9 @@ function matchesPattern(text, patterns) {
|
|
|
573
845
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
574
846
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
575
847
|
}
|
|
576
|
-
function getNestedValue(obj,
|
|
848
|
+
function getNestedValue(obj, path5) {
|
|
577
849
|
if (!obj || typeof obj !== "object") return null;
|
|
578
|
-
return
|
|
850
|
+
return path5.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
579
851
|
}
|
|
580
852
|
function evaluateSmartConditions(args, rule) {
|
|
581
853
|
if (!rule.conditions || rule.conditions.length === 0) return true;
|
|
@@ -608,6 +880,10 @@ function evaluateSmartConditions(args, rule) {
|
|
|
608
880
|
return true;
|
|
609
881
|
}
|
|
610
882
|
}
|
|
883
|
+
case "matchesGlob":
|
|
884
|
+
return val !== null && cond.value ? import_picomatch.default.isMatch(val, cond.value) : false;
|
|
885
|
+
case "notMatchesGlob":
|
|
886
|
+
return val !== null && cond.value ? !import_picomatch.default.isMatch(val, cond.value) : true;
|
|
611
887
|
default:
|
|
612
888
|
return false;
|
|
613
889
|
}
|
|
@@ -771,25 +1047,27 @@ var DEFAULT_CONFIG = {
|
|
|
771
1047
|
onlyPaths: [],
|
|
772
1048
|
ignorePaths: ["**/node_modules/**", "dist/**", "build/**", ".next/**", "**/*.log"]
|
|
773
1049
|
},
|
|
774
|
-
rules: [
|
|
775
|
-
// Only use the legacy rules format for simple path-based rm control.
|
|
776
|
-
// All other command-level enforcement lives in smartRules below.
|
|
777
|
-
{
|
|
778
|
-
action: "rm",
|
|
779
|
-
allowPaths: [
|
|
780
|
-
"**/node_modules/**",
|
|
781
|
-
"dist/**",
|
|
782
|
-
"build/**",
|
|
783
|
-
".next/**",
|
|
784
|
-
"coverage/**",
|
|
785
|
-
".cache/**",
|
|
786
|
-
"tmp/**",
|
|
787
|
-
"temp/**",
|
|
788
|
-
".DS_Store"
|
|
789
|
-
]
|
|
790
|
-
}
|
|
791
|
-
],
|
|
792
1050
|
smartRules: [
|
|
1051
|
+
// ── rm safety (critical — always evaluated first) ──────────────────────
|
|
1052
|
+
{
|
|
1053
|
+
name: "block-rm-rf-home",
|
|
1054
|
+
tool: "bash",
|
|
1055
|
+
conditionMode: "all",
|
|
1056
|
+
conditions: [
|
|
1057
|
+
{
|
|
1058
|
+
field: "command",
|
|
1059
|
+
op: "matches",
|
|
1060
|
+
value: "rm\\b.*(-[rRfF]*[rR][rRfF]*|--recursive)"
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
field: "command",
|
|
1064
|
+
op: "matches",
|
|
1065
|
+
value: "(~|\\/root(\\/|$)|\\$HOME|\\/home\\/)"
|
|
1066
|
+
}
|
|
1067
|
+
],
|
|
1068
|
+
verdict: "block",
|
|
1069
|
+
reason: "Recursive delete of home directory is irreversible"
|
|
1070
|
+
},
|
|
793
1071
|
// ── SQL safety ────────────────────────────────────────────────────────
|
|
794
1072
|
{
|
|
795
1073
|
name: "no-delete-without-where",
|
|
@@ -880,16 +1158,42 @@ var DEFAULT_CONFIG = {
|
|
|
880
1158
|
verdict: "block",
|
|
881
1159
|
reason: "Piping remote script into a shell is a supply-chain attack vector"
|
|
882
1160
|
}
|
|
883
|
-
]
|
|
1161
|
+
],
|
|
1162
|
+
dlp: { enabled: true, scanIgnoredTools: true }
|
|
884
1163
|
},
|
|
885
1164
|
environments: {}
|
|
886
1165
|
};
|
|
1166
|
+
var ADVISORY_SMART_RULES = [
|
|
1167
|
+
{
|
|
1168
|
+
name: "allow-rm-safe-paths",
|
|
1169
|
+
tool: "*",
|
|
1170
|
+
conditionMode: "all",
|
|
1171
|
+
conditions: [
|
|
1172
|
+
{ field: "command", op: "matches", value: "(^|&&|\\|\\||;)\\s*rm\\b" },
|
|
1173
|
+
{
|
|
1174
|
+
field: "command",
|
|
1175
|
+
op: "matches",
|
|
1176
|
+
// Matches known-safe build artifact paths in the command.
|
|
1177
|
+
value: "(node_modules|\\bdist\\b|\\.next|\\bcoverage\\b|\\.cache|\\btmp\\b|\\btemp\\b|\\.DS_Store)(\\/|\\s|$)"
|
|
1178
|
+
}
|
|
1179
|
+
],
|
|
1180
|
+
verdict: "allow",
|
|
1181
|
+
reason: "Deleting a known-safe build artifact path"
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
name: "review-rm",
|
|
1185
|
+
tool: "*",
|
|
1186
|
+
conditions: [{ field: "command", op: "matches", value: "(^|&&|\\|\\||;)\\s*rm\\b" }],
|
|
1187
|
+
verdict: "review",
|
|
1188
|
+
reason: "rm can permanently delete files \u2014 confirm the target path"
|
|
1189
|
+
}
|
|
1190
|
+
];
|
|
887
1191
|
var cachedConfig = null;
|
|
888
1192
|
function getInternalToken() {
|
|
889
1193
|
try {
|
|
890
|
-
const pidFile =
|
|
891
|
-
if (!
|
|
892
|
-
const data = JSON.parse(
|
|
1194
|
+
const pidFile = import_path4.default.join(import_os2.default.homedir(), ".node9", "daemon.pid");
|
|
1195
|
+
if (!import_fs2.default.existsSync(pidFile)) return null;
|
|
1196
|
+
const data = JSON.parse(import_fs2.default.readFileSync(pidFile, "utf-8"));
|
|
893
1197
|
process.kill(data.pid, 0);
|
|
894
1198
|
return data.internalToken ?? null;
|
|
895
1199
|
} catch {
|
|
@@ -904,7 +1208,8 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
904
1208
|
(rule) => matchesPattern(toolName, rule.tool) && evaluateSmartConditions(args, rule)
|
|
905
1209
|
);
|
|
906
1210
|
if (matchedRule) {
|
|
907
|
-
if (matchedRule.verdict === "allow")
|
|
1211
|
+
if (matchedRule.verdict === "allow")
|
|
1212
|
+
return { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
|
|
908
1213
|
return {
|
|
909
1214
|
decision: matchedRule.verdict,
|
|
910
1215
|
blockedByLabel: `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`,
|
|
@@ -915,13 +1220,11 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
915
1220
|
}
|
|
916
1221
|
}
|
|
917
1222
|
let allTokens = [];
|
|
918
|
-
let actionTokens = [];
|
|
919
1223
|
let pathTokens = [];
|
|
920
1224
|
const shellCommand = extractShellCommand(toolName, args, config.policy.toolInspection);
|
|
921
1225
|
if (shellCommand) {
|
|
922
1226
|
const analyzed = await analyzeShellCommand(shellCommand);
|
|
923
1227
|
allTokens = analyzed.allTokens;
|
|
924
|
-
actionTokens = analyzed.actions;
|
|
925
1228
|
pathTokens = analyzed.paths;
|
|
926
1229
|
const INLINE_EXEC_PATTERN = /^(python3?|bash|sh|zsh|perl|ruby|node|php|lua)\s+(-c|-e|-eval)\s/i;
|
|
927
1230
|
if (INLINE_EXEC_PATTERN.test(shellCommand.trim())) {
|
|
@@ -929,11 +1232,9 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
929
1232
|
}
|
|
930
1233
|
if (isSqlTool(toolName, config.policy.toolInspection)) {
|
|
931
1234
|
allTokens = allTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
|
|
932
|
-
actionTokens = actionTokens.filter((t) => !SQL_DML_KEYWORDS.has(t.toLowerCase()));
|
|
933
1235
|
}
|
|
934
1236
|
} else {
|
|
935
1237
|
allTokens = tokenize(toolName);
|
|
936
|
-
actionTokens = [toolName];
|
|
937
1238
|
if (args && typeof args === "object") {
|
|
938
1239
|
const flattenedArgs = JSON.stringify(args).toLowerCase();
|
|
939
1240
|
const extraTokens = flattenedArgs.split(/[^a-zA-Z0-9]+/).filter((t) => t.length > 1);
|
|
@@ -956,29 +1257,6 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
956
1257
|
const allInSandbox = pathTokens.every((p) => matchesPattern(p, config.policy.sandboxPaths));
|
|
957
1258
|
if (allInSandbox) return { decision: "allow" };
|
|
958
1259
|
}
|
|
959
|
-
for (const action of actionTokens) {
|
|
960
|
-
const rule = config.policy.rules.find(
|
|
961
|
-
(r) => r.action === action || matchesPattern(action, r.action)
|
|
962
|
-
);
|
|
963
|
-
if (rule) {
|
|
964
|
-
if (pathTokens.length > 0) {
|
|
965
|
-
const anyBlocked = pathTokens.some((p) => matchesPattern(p, rule.blockPaths || []));
|
|
966
|
-
if (anyBlocked)
|
|
967
|
-
return {
|
|
968
|
-
decision: "review",
|
|
969
|
-
blockedByLabel: `Project/Global Config \u2014 rule "${rule.action}" (path blocked)`,
|
|
970
|
-
tier: 5
|
|
971
|
-
};
|
|
972
|
-
const allAllowed = pathTokens.every((p) => matchesPattern(p, rule.allowPaths || []));
|
|
973
|
-
if (allAllowed) return { decision: "allow" };
|
|
974
|
-
}
|
|
975
|
-
return {
|
|
976
|
-
decision: "review",
|
|
977
|
-
blockedByLabel: `Project/Global Config \u2014 rule "${rule.action}" (default block)`,
|
|
978
|
-
tier: 5
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
1260
|
let matchedDangerousWord;
|
|
983
1261
|
const isDangerous = allTokens.some(
|
|
984
1262
|
(token) => config.policy.dangerousWords.some((word) => {
|
|
@@ -1036,9 +1314,9 @@ var DAEMON_PORT = 7391;
|
|
|
1036
1314
|
var DAEMON_HOST = "127.0.0.1";
|
|
1037
1315
|
function isDaemonRunning() {
|
|
1038
1316
|
try {
|
|
1039
|
-
const pidFile =
|
|
1040
|
-
if (!
|
|
1041
|
-
const { pid, port } = JSON.parse(
|
|
1317
|
+
const pidFile = import_path4.default.join(import_os2.default.homedir(), ".node9", "daemon.pid");
|
|
1318
|
+
if (!import_fs2.default.existsSync(pidFile)) return false;
|
|
1319
|
+
const { pid, port } = JSON.parse(import_fs2.default.readFileSync(pidFile, "utf-8"));
|
|
1042
1320
|
if (port !== DAEMON_PORT) return false;
|
|
1043
1321
|
process.kill(pid, 0);
|
|
1044
1322
|
return true;
|
|
@@ -1048,9 +1326,9 @@ function isDaemonRunning() {
|
|
|
1048
1326
|
}
|
|
1049
1327
|
function getPersistentDecision(toolName) {
|
|
1050
1328
|
try {
|
|
1051
|
-
const file =
|
|
1052
|
-
if (!
|
|
1053
|
-
const decisions = JSON.parse(
|
|
1329
|
+
const file = import_path4.default.join(import_os2.default.homedir(), ".node9", "decisions.json");
|
|
1330
|
+
if (!import_fs2.default.existsSync(file)) return null;
|
|
1331
|
+
const decisions = JSON.parse(import_fs2.default.readFileSync(file, "utf-8"));
|
|
1054
1332
|
const d = decisions[toolName];
|
|
1055
1333
|
if (d === "allow" || d === "deny") return d;
|
|
1056
1334
|
} catch {
|
|
@@ -1149,6 +1427,22 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
1149
1427
|
let policyMatchedField;
|
|
1150
1428
|
let policyMatchedWord;
|
|
1151
1429
|
let riskMetadata;
|
|
1430
|
+
if (config.policy.dlp.enabled && (!isIgnoredTool(toolName) || config.policy.dlp.scanIgnoredTools)) {
|
|
1431
|
+
const dlpMatch = scanArgs(args);
|
|
1432
|
+
if (dlpMatch) {
|
|
1433
|
+
const dlpReason = `\u{1F6A8} DATA LOSS PREVENTION: ${dlpMatch.patternName} detected in field "${dlpMatch.fieldPath}" (${dlpMatch.redactedSample})`;
|
|
1434
|
+
if (dlpMatch.severity === "block") {
|
|
1435
|
+
if (!isManual) appendLocalAudit(toolName, args, "deny", "dlp-block", meta);
|
|
1436
|
+
return {
|
|
1437
|
+
approved: false,
|
|
1438
|
+
reason: dlpReason,
|
|
1439
|
+
blockedBy: "local-config",
|
|
1440
|
+
blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1152
1446
|
if (config.settings.mode === "audit") {
|
|
1153
1447
|
if (!isIgnoredTool(toolName)) {
|
|
1154
1448
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
@@ -1388,7 +1682,14 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
1388
1682
|
racePromises.push(
|
|
1389
1683
|
(async () => {
|
|
1390
1684
|
try {
|
|
1391
|
-
|
|
1685
|
+
if (explainableLabel.includes("DLP")) {
|
|
1686
|
+
console.log(import_chalk2.default.bgRed.white.bold(` \u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
1687
|
+
console.log(
|
|
1688
|
+
import_chalk2.default.red.bold(` A sensitive secret was detected in the tool arguments!`)
|
|
1689
|
+
);
|
|
1690
|
+
} else {
|
|
1691
|
+
console.log(import_chalk2.default.bgRed.white.bold(` \u{1F6D1} NODE9 INTERCEPTOR `));
|
|
1692
|
+
}
|
|
1392
1693
|
console.log(`${import_chalk2.default.bold("Action:")} ${import_chalk2.default.red(toolName)}`);
|
|
1393
1694
|
console.log(`${import_chalk2.default.bold("Flagged By:")} ${import_chalk2.default.yellow(explainableLabel)}`);
|
|
1394
1695
|
if (isRemoteLocked) {
|
|
@@ -1493,8 +1794,8 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
1493
1794
|
}
|
|
1494
1795
|
function getConfig() {
|
|
1495
1796
|
if (cachedConfig) return cachedConfig;
|
|
1496
|
-
const globalPath =
|
|
1497
|
-
const projectPath =
|
|
1797
|
+
const globalPath = import_path4.default.join(import_os2.default.homedir(), ".node9", "config.json");
|
|
1798
|
+
const projectPath = import_path4.default.join(process.cwd(), "node9.config.json");
|
|
1498
1799
|
const globalConfig = tryLoadConfig(globalPath);
|
|
1499
1800
|
const projectConfig = tryLoadConfig(projectPath);
|
|
1500
1801
|
const mergedSettings = {
|
|
@@ -1506,13 +1807,13 @@ function getConfig() {
|
|
|
1506
1807
|
dangerousWords: [...DEFAULT_CONFIG.policy.dangerousWords],
|
|
1507
1808
|
ignoredTools: [...DEFAULT_CONFIG.policy.ignoredTools],
|
|
1508
1809
|
toolInspection: { ...DEFAULT_CONFIG.policy.toolInspection },
|
|
1509
|
-
rules: [...DEFAULT_CONFIG.policy.rules],
|
|
1510
1810
|
smartRules: [...DEFAULT_CONFIG.policy.smartRules],
|
|
1511
1811
|
snapshot: {
|
|
1512
1812
|
tools: [...DEFAULT_CONFIG.policy.snapshot.tools],
|
|
1513
1813
|
onlyPaths: [...DEFAULT_CONFIG.policy.snapshot.onlyPaths],
|
|
1514
1814
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
1515
|
-
}
|
|
1815
|
+
},
|
|
1816
|
+
dlp: { ...DEFAULT_CONFIG.policy.dlp }
|
|
1516
1817
|
};
|
|
1517
1818
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
1518
1819
|
const applyLayer = (source) => {
|
|
@@ -1532,7 +1833,6 @@ function getConfig() {
|
|
|
1532
1833
|
if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
|
|
1533
1834
|
if (p.toolInspection)
|
|
1534
1835
|
mergedPolicy.toolInspection = { ...mergedPolicy.toolInspection, ...p.toolInspection };
|
|
1535
|
-
if (p.rules) mergedPolicy.rules.push(...p.rules);
|
|
1536
1836
|
if (p.smartRules) mergedPolicy.smartRules.push(...p.smartRules);
|
|
1537
1837
|
if (p.snapshot) {
|
|
1538
1838
|
const s2 = p.snapshot;
|
|
@@ -1540,6 +1840,11 @@ function getConfig() {
|
|
|
1540
1840
|
if (s2.onlyPaths) mergedPolicy.snapshot.onlyPaths.push(...s2.onlyPaths);
|
|
1541
1841
|
if (s2.ignorePaths) mergedPolicy.snapshot.ignorePaths.push(...s2.ignorePaths);
|
|
1542
1842
|
}
|
|
1843
|
+
if (p.dlp) {
|
|
1844
|
+
const d = p.dlp;
|
|
1845
|
+
if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
|
|
1846
|
+
if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
|
|
1847
|
+
}
|
|
1543
1848
|
const envs = source.environments || {};
|
|
1544
1849
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
1545
1850
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -1554,6 +1859,19 @@ function getConfig() {
|
|
|
1554
1859
|
};
|
|
1555
1860
|
applyLayer(globalConfig);
|
|
1556
1861
|
applyLayer(projectConfig);
|
|
1862
|
+
for (const shieldName of readActiveShields()) {
|
|
1863
|
+
const shield = getShield(shieldName);
|
|
1864
|
+
if (!shield) continue;
|
|
1865
|
+
const existingRuleNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
1866
|
+
for (const rule of shield.smartRules) {
|
|
1867
|
+
if (!existingRuleNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
|
|
1868
|
+
}
|
|
1869
|
+
for (const word of shield.dangerousWords) mergedPolicy.dangerousWords.push(word);
|
|
1870
|
+
}
|
|
1871
|
+
const existingAdvisoryNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
1872
|
+
for (const rule of ADVISORY_SMART_RULES) {
|
|
1873
|
+
if (!existingAdvisoryNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
|
|
1874
|
+
}
|
|
1557
1875
|
if (process.env.NODE9_MODE) mergedSettings.mode = process.env.NODE9_MODE;
|
|
1558
1876
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
1559
1877
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
@@ -1569,10 +1887,10 @@ function getConfig() {
|
|
|
1569
1887
|
return cachedConfig;
|
|
1570
1888
|
}
|
|
1571
1889
|
function tryLoadConfig(filePath) {
|
|
1572
|
-
if (!
|
|
1890
|
+
if (!import_fs2.default.existsSync(filePath)) return null;
|
|
1573
1891
|
let raw;
|
|
1574
1892
|
try {
|
|
1575
|
-
raw = JSON.parse(
|
|
1893
|
+
raw = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
|
|
1576
1894
|
} catch (err) {
|
|
1577
1895
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1578
1896
|
process.stderr.write(
|
|
@@ -1634,9 +1952,9 @@ function getCredentials() {
|
|
|
1634
1952
|
};
|
|
1635
1953
|
}
|
|
1636
1954
|
try {
|
|
1637
|
-
const credPath =
|
|
1638
|
-
if (
|
|
1639
|
-
const creds = JSON.parse(
|
|
1955
|
+
const credPath = import_path4.default.join(import_os2.default.homedir(), ".node9", "credentials.json");
|
|
1956
|
+
if (import_fs2.default.existsSync(credPath)) {
|
|
1957
|
+
const creds = JSON.parse(import_fs2.default.readFileSync(credPath, "utf-8"));
|
|
1640
1958
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
1641
1959
|
const profile = creds[profileName];
|
|
1642
1960
|
if (profile?.apiKey) {
|
|
@@ -1671,9 +1989,9 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
|
|
|
1671
1989
|
context: {
|
|
1672
1990
|
agent: meta?.agent,
|
|
1673
1991
|
mcpServer: meta?.mcpServer,
|
|
1674
|
-
hostname:
|
|
1992
|
+
hostname: import_os2.default.hostname(),
|
|
1675
1993
|
cwd: process.cwd(),
|
|
1676
|
-
platform:
|
|
1994
|
+
platform: import_os2.default.platform()
|
|
1677
1995
|
}
|
|
1678
1996
|
}),
|
|
1679
1997
|
signal: AbortSignal.timeout(5e3)
|
|
@@ -1694,9 +2012,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
|
|
|
1694
2012
|
context: {
|
|
1695
2013
|
agent: meta?.agent,
|
|
1696
2014
|
mcpServer: meta?.mcpServer,
|
|
1697
|
-
hostname:
|
|
2015
|
+
hostname: import_os2.default.hostname(),
|
|
1698
2016
|
cwd: process.cwd(),
|
|
1699
|
-
platform:
|
|
2017
|
+
platform: import_os2.default.platform()
|
|
1700
2018
|
},
|
|
1701
2019
|
...riskMetadata && { riskMetadata }
|
|
1702
2020
|
}),
|