claude-warden 2.4.0 → 2.5.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +45 -10
- package/dist/cli.cjs +1231 -1251
- package/dist/codex-export.cjs +1231 -1251
- package/dist/copilot.cjs +1227 -1251
- package/dist/index.cjs +1227 -1251
- package/package.json +16 -13
package/dist/cli.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
@@ -12481,7 +12482,7 @@ var require_log = __commonJS({
|
|
|
12481
12482
|
if (logLevel === "debug")
|
|
12482
12483
|
console.log(...messages);
|
|
12483
12484
|
}
|
|
12484
|
-
function
|
|
12485
|
+
function warn2(logLevel, warning) {
|
|
12485
12486
|
if (logLevel === "debug" || logLevel === "warn") {
|
|
12486
12487
|
if (typeof node_process.emitWarning === "function")
|
|
12487
12488
|
node_process.emitWarning(warning);
|
|
@@ -12490,7 +12491,7 @@ var require_log = __commonJS({
|
|
|
12490
12491
|
}
|
|
12491
12492
|
}
|
|
12492
12493
|
exports2.debug = debug;
|
|
12493
|
-
exports2.warn =
|
|
12494
|
+
exports2.warn = warn2;
|
|
12494
12495
|
}
|
|
12495
12496
|
});
|
|
12496
12497
|
|
|
@@ -18450,7 +18451,7 @@ function regexFallbackParse(input) {
|
|
|
18450
18451
|
}
|
|
18451
18452
|
|
|
18452
18453
|
// src/evaluator.ts
|
|
18453
|
-
var
|
|
18454
|
+
var import_os2 = require("os");
|
|
18454
18455
|
|
|
18455
18456
|
// src/targets.ts
|
|
18456
18457
|
var import_path2 = require("path");
|
|
@@ -18728,984 +18729,152 @@ function evaluateTargetPolicies(cmd, cwd, config) {
|
|
|
18728
18729
|
return results[0];
|
|
18729
18730
|
}
|
|
18730
18731
|
|
|
18731
|
-
// src/
|
|
18732
|
-
|
|
18733
|
-
|
|
18734
|
-
|
|
18735
|
-
|
|
18736
|
-
|
|
18737
|
-
|
|
18738
|
-
|
|
18739
|
-
|
|
18740
|
-
|
|
18741
|
-
|
|
18742
|
-
|
|
18743
|
-
|
|
18744
|
-
|
|
18745
|
-
|
|
18746
|
-
|
|
18747
|
-
|
|
18748
|
-
|
|
18749
|
-
|
|
18750
|
-
|
|
18751
|
-
|
|
18752
|
-
|
|
18753
|
-
|
|
18754
|
-
|
|
18755
|
-
|
|
18756
|
-
|
|
18757
|
-
|
|
18758
|
-
|
|
18759
|
-
|
|
18760
|
-
|
|
18761
|
-
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
18765
|
-
|
|
18766
|
-
|
|
18767
|
-
|
|
18768
|
-
|
|
18769
|
-
|
|
18770
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
18785
|
-
|
|
18786
|
-
|
|
18787
|
-
|
|
18788
|
-
|
|
18789
|
-
|
|
18790
|
-
|
|
18791
|
-
|
|
18792
|
-
|
|
18793
|
-
|
|
18794
|
-
|
|
18795
|
-
|
|
18796
|
-
|
|
18797
|
-
|
|
18798
|
-
|
|
18799
|
-
|
|
18800
|
-
|
|
18801
|
-
|
|
18802
|
-
|
|
18803
|
-
|
|
18804
|
-
|
|
18805
|
-
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
18812
|
-
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
18822
|
-
|
|
18823
|
-
|
|
18824
|
-
|
|
18825
|
-
|
|
18826
|
-
|
|
18827
|
-
|
|
18828
|
-
}
|
|
18829
|
-
if (command === "kubectl") {
|
|
18830
|
-
const kubectlTargets = remotes.filter((r) => r.context === "kubectl");
|
|
18831
|
-
if (kubectlTargets.length) {
|
|
18832
|
-
const kubectlResult = evaluateKubectlExec(cmd, config, kubectlTargets, depth);
|
|
18833
|
-
if (kubectlResult) return kubectlResult;
|
|
18834
|
-
}
|
|
18835
|
-
}
|
|
18836
|
-
if (command === "sprite") {
|
|
18837
|
-
const spriteTargets = remotes.filter((r) => r.context === "sprite");
|
|
18838
|
-
if (spriteTargets.length) {
|
|
18839
|
-
const spriteResult = evaluateSpriteExec(cmd, config, spriteTargets, depth);
|
|
18840
|
-
if (spriteResult) return spriteResult;
|
|
18841
|
-
}
|
|
18842
|
-
}
|
|
18843
|
-
if (command === "fly" || command === "flyctl") {
|
|
18844
|
-
const flyTargets = remotes.filter((r) => r.context === "fly");
|
|
18845
|
-
if (flyTargets.length) {
|
|
18846
|
-
const flyResult = evaluateFlyCommand(cmd, config, flyTargets, depth);
|
|
18847
|
-
if (flyResult) return flyResult;
|
|
18848
|
-
}
|
|
18849
|
-
}
|
|
18850
|
-
if (command === "xargs") {
|
|
18851
|
-
return evaluateXargsCommand(cmd, config, cwd, depth);
|
|
18852
|
-
}
|
|
18853
|
-
if (command === "find") {
|
|
18854
|
-
return evaluateFindCommand(cmd, config, cwd, depth);
|
|
18855
|
-
}
|
|
18856
|
-
const mergedRule = collectMergedRule(cmd, config);
|
|
18857
|
-
if (mergedRule) {
|
|
18858
|
-
return evaluateRule(cmd, mergedRule);
|
|
18859
|
-
}
|
|
18860
|
-
return { command, args: args2, decision: config.defaultDecision, reason: `No rule for "${command}"`, matchedRule: "default" };
|
|
18732
|
+
// src/rules.ts
|
|
18733
|
+
var import_fs = require("fs");
|
|
18734
|
+
var import_yaml = __toESM(require_dist2(), 1);
|
|
18735
|
+
var import_os = require("os");
|
|
18736
|
+
var import_path3 = require("path");
|
|
18737
|
+
|
|
18738
|
+
// src/defaults.ts
|
|
18739
|
+
var SAFE_DEV_TOOLS = [
|
|
18740
|
+
"jest",
|
|
18741
|
+
"vitest",
|
|
18742
|
+
"tsc",
|
|
18743
|
+
"eslint",
|
|
18744
|
+
"prettier",
|
|
18745
|
+
"mkdirp",
|
|
18746
|
+
"concurrently",
|
|
18747
|
+
"turbo",
|
|
18748
|
+
"next",
|
|
18749
|
+
"nuxt",
|
|
18750
|
+
"vite",
|
|
18751
|
+
"astro",
|
|
18752
|
+
"playwright",
|
|
18753
|
+
"cypress",
|
|
18754
|
+
"mocha",
|
|
18755
|
+
"nyc",
|
|
18756
|
+
"c8",
|
|
18757
|
+
"ts-jest",
|
|
18758
|
+
"tsup",
|
|
18759
|
+
"esbuild",
|
|
18760
|
+
"rollup",
|
|
18761
|
+
"webpack",
|
|
18762
|
+
"prisma",
|
|
18763
|
+
"drizzle-kit",
|
|
18764
|
+
"typeorm",
|
|
18765
|
+
"knex",
|
|
18766
|
+
"sequelize-cli",
|
|
18767
|
+
"tailwindcss",
|
|
18768
|
+
"postcss",
|
|
18769
|
+
"autoprefixer",
|
|
18770
|
+
"lint-staged",
|
|
18771
|
+
"husky",
|
|
18772
|
+
"changeset",
|
|
18773
|
+
"semantic-release",
|
|
18774
|
+
"lerna",
|
|
18775
|
+
"nx",
|
|
18776
|
+
"create-react-app",
|
|
18777
|
+
"create-next-app",
|
|
18778
|
+
"create-vite",
|
|
18779
|
+
"degit",
|
|
18780
|
+
"storybook",
|
|
18781
|
+
"wrangler",
|
|
18782
|
+
"netlify",
|
|
18783
|
+
"vercel",
|
|
18784
|
+
"json",
|
|
18785
|
+
"biome"
|
|
18786
|
+
];
|
|
18787
|
+
var SCRIPT_RUNNERS = ["tsx", "ts-node", "nodemon"];
|
|
18788
|
+
var REGISTRY_OPS = ["publish", "unpublish", "deprecate", "owner", "access", "token", "adduser", "login", "logout"];
|
|
18789
|
+
var SAFE_PKG_MANAGER_CMDS = [
|
|
18790
|
+
"install",
|
|
18791
|
+
"add",
|
|
18792
|
+
"remove",
|
|
18793
|
+
"uninstall",
|
|
18794
|
+
"update",
|
|
18795
|
+
"upgrade",
|
|
18796
|
+
"outdated",
|
|
18797
|
+
"ls",
|
|
18798
|
+
"list",
|
|
18799
|
+
"run",
|
|
18800
|
+
"test",
|
|
18801
|
+
"start",
|
|
18802
|
+
"build",
|
|
18803
|
+
"init",
|
|
18804
|
+
"create",
|
|
18805
|
+
"info",
|
|
18806
|
+
"view",
|
|
18807
|
+
"show",
|
|
18808
|
+
"why",
|
|
18809
|
+
"pack",
|
|
18810
|
+
"cache",
|
|
18811
|
+
"config",
|
|
18812
|
+
"get",
|
|
18813
|
+
"set",
|
|
18814
|
+
"version",
|
|
18815
|
+
"help",
|
|
18816
|
+
"exec",
|
|
18817
|
+
"dedupe",
|
|
18818
|
+
"prune",
|
|
18819
|
+
"audit",
|
|
18820
|
+
"completion",
|
|
18821
|
+
"whoami"
|
|
18822
|
+
];
|
|
18823
|
+
var VERSION_HELP_FLAGS = {
|
|
18824
|
+
match: { anyArgMatches: ["^--(version|help)$", "^-[vh]$"] },
|
|
18825
|
+
decision: "allow",
|
|
18826
|
+
description: "Version/help flags"
|
|
18827
|
+
};
|
|
18828
|
+
function anyArgMatchesPattern(items) {
|
|
18829
|
+
return `^(${items.join("|")})$`;
|
|
18861
18830
|
}
|
|
18862
|
-
function
|
|
18863
|
-
const matchingRules = [];
|
|
18864
|
-
for (const layer of config.layers) {
|
|
18865
|
-
const rule = layer.rules.find((r) => commandMatchesName(cmd, r.command));
|
|
18866
|
-
if (rule) {
|
|
18867
|
-
matchingRules.push(rule);
|
|
18868
|
-
if (rule.override) break;
|
|
18869
|
-
}
|
|
18870
|
-
}
|
|
18871
|
-
if (matchingRules.length === 0) return null;
|
|
18872
|
-
if (matchingRules.length === 1) return matchingRules[0];
|
|
18873
|
-
const mergedPatterns = [];
|
|
18874
|
-
for (const rule of matchingRules) {
|
|
18875
|
-
if (rule.argPatterns) {
|
|
18876
|
-
mergedPatterns.push(...rule.argPatterns);
|
|
18877
|
-
}
|
|
18878
|
-
}
|
|
18831
|
+
function safeDevToolsPattern() {
|
|
18879
18832
|
return {
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
18833
|
+
match: { anyArgMatches: [anyArgMatchesPattern(SAFE_DEV_TOOLS)] },
|
|
18834
|
+
decision: "allow",
|
|
18835
|
+
description: "Well-known dev tools"
|
|
18883
18836
|
};
|
|
18884
18837
|
}
|
|
18885
|
-
function
|
|
18886
|
-
const { command, args: args2 } = cmd;
|
|
18887
|
-
const argsJoined = args2.join(" ");
|
|
18888
|
-
for (const pattern of rule.argPatterns || []) {
|
|
18889
|
-
const m = pattern.match;
|
|
18890
|
-
let matched = true;
|
|
18891
|
-
if (m.noArgs !== void 0) {
|
|
18892
|
-
matched = matched && m.noArgs === (args2.length === 0);
|
|
18893
|
-
}
|
|
18894
|
-
if (m.argsMatch && matched) {
|
|
18895
|
-
matched = m.argsMatch.some((re) => safeRegexTest(re, argsJoined));
|
|
18896
|
-
}
|
|
18897
|
-
if (m.anyArgMatches && matched) {
|
|
18898
|
-
matched = args2.some((arg) => m.anyArgMatches.some((re) => safeRegexTest(re, arg)));
|
|
18899
|
-
}
|
|
18900
|
-
if (m.argCount && matched) {
|
|
18901
|
-
if (m.argCount.min !== void 0) matched = matched && args2.length >= m.argCount.min;
|
|
18902
|
-
if (m.argCount.max !== void 0) matched = matched && args2.length <= m.argCount.max;
|
|
18903
|
-
}
|
|
18904
|
-
if (m.not) matched = !matched;
|
|
18905
|
-
if (matched) {
|
|
18906
|
-
return {
|
|
18907
|
-
command,
|
|
18908
|
-
args: args2,
|
|
18909
|
-
decision: pattern.decision,
|
|
18910
|
-
reason: pattern.reason || pattern.description || `Matched pattern for "${command}"`,
|
|
18911
|
-
matchedRule: `${command}:argPattern`
|
|
18912
|
-
};
|
|
18913
|
-
}
|
|
18914
|
-
}
|
|
18838
|
+
function scriptRunnersPattern() {
|
|
18915
18839
|
return {
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
reason: `Default for "${command}"`,
|
|
18920
|
-
matchedRule: `${command}:default`
|
|
18840
|
+
match: { anyArgMatches: [anyArgMatchesPattern(SCRIPT_RUNNERS)] },
|
|
18841
|
+
decision: "ask",
|
|
18842
|
+
reason: "Script runners can execute arbitrary code"
|
|
18921
18843
|
};
|
|
18922
18844
|
}
|
|
18923
|
-
|
|
18924
|
-
var XARGS_SHORT_FLAGS_NO_VALUE = /* @__PURE__ */ new Set(["0", "e", "o", "p", "r", "t", "x"]);
|
|
18925
|
-
var XARGS_LONG_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
18926
|
-
"--eof",
|
|
18927
|
-
"--replace",
|
|
18928
|
-
"--max-lines",
|
|
18929
|
-
"--max-args",
|
|
18930
|
-
"--max-procs",
|
|
18931
|
-
"--max-chars",
|
|
18932
|
-
"--arg-file",
|
|
18933
|
-
"--delimiter"
|
|
18934
|
-
]);
|
|
18935
|
-
var XARGS_LONG_FLAGS_NO_VALUE = /* @__PURE__ */ new Set([
|
|
18936
|
-
"--null",
|
|
18937
|
-
"--exit",
|
|
18938
|
-
"--open-tty",
|
|
18939
|
-
"--interactive",
|
|
18940
|
-
"--no-run-if-empty",
|
|
18941
|
-
"--verbose",
|
|
18942
|
-
"--show-limits"
|
|
18943
|
-
]);
|
|
18944
|
-
function parseXargsSubcommand(args2) {
|
|
18945
|
-
let i = 0;
|
|
18946
|
-
while (i < args2.length) {
|
|
18947
|
-
const arg = args2[i];
|
|
18948
|
-
if (arg === "--") {
|
|
18949
|
-
i++;
|
|
18950
|
-
break;
|
|
18951
|
-
}
|
|
18952
|
-
if (!arg.startsWith("-") || arg === "-") {
|
|
18953
|
-
break;
|
|
18954
|
-
}
|
|
18955
|
-
if (arg.startsWith("--")) {
|
|
18956
|
-
const eqIndex = arg.indexOf("=");
|
|
18957
|
-
const longFlag = eqIndex === -1 ? arg : arg.slice(0, eqIndex);
|
|
18958
|
-
if (XARGS_LONG_FLAGS_WITH_VALUE.has(longFlag)) {
|
|
18959
|
-
if (eqIndex !== -1) {
|
|
18960
|
-
i++;
|
|
18961
|
-
continue;
|
|
18962
|
-
}
|
|
18963
|
-
if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
|
|
18964
|
-
i += 2;
|
|
18965
|
-
continue;
|
|
18966
|
-
}
|
|
18967
|
-
if (XARGS_LONG_FLAGS_NO_VALUE.has(longFlag)) {
|
|
18968
|
-
i++;
|
|
18969
|
-
continue;
|
|
18970
|
-
}
|
|
18971
|
-
return { subcommand: null, unresolved: true };
|
|
18972
|
-
}
|
|
18973
|
-
const short = arg[1];
|
|
18974
|
-
if (XARGS_SHORT_FLAGS_WITH_VALUE.has(short)) {
|
|
18975
|
-
if (arg.length > 2) {
|
|
18976
|
-
i++;
|
|
18977
|
-
continue;
|
|
18978
|
-
}
|
|
18979
|
-
if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
|
|
18980
|
-
i += 2;
|
|
18981
|
-
continue;
|
|
18982
|
-
}
|
|
18983
|
-
const grouped = arg.slice(1).split("");
|
|
18984
|
-
const allKnownNoValue = grouped.every((ch) => XARGS_SHORT_FLAGS_NO_VALUE.has(ch));
|
|
18985
|
-
if (allKnownNoValue) {
|
|
18986
|
-
i++;
|
|
18987
|
-
continue;
|
|
18988
|
-
}
|
|
18989
|
-
return { subcommand: null, unresolved: true };
|
|
18990
|
-
}
|
|
18991
|
-
if (i >= args2.length) {
|
|
18992
|
-
return {
|
|
18993
|
-
unresolved: false,
|
|
18994
|
-
subcommand: {
|
|
18995
|
-
command: "echo",
|
|
18996
|
-
originalCommand: "echo",
|
|
18997
|
-
args: [],
|
|
18998
|
-
envPrefixes: [],
|
|
18999
|
-
raw: "echo"
|
|
19000
|
-
}
|
|
19001
|
-
};
|
|
19002
|
-
}
|
|
19003
|
-
const subcommand = args2[i];
|
|
19004
|
-
const subArgs = args2.slice(i + 1);
|
|
18845
|
+
function registryOpsPattern() {
|
|
19005
18846
|
return {
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
|
|
19009
|
-
originalCommand: subcommand,
|
|
19010
|
-
args: subArgs,
|
|
19011
|
-
envPrefixes: [],
|
|
19012
|
-
raw: [subcommand, ...subArgs].join(" ")
|
|
19013
|
-
}
|
|
18847
|
+
match: { anyArgMatches: [anyArgMatchesPattern(REGISTRY_OPS)] },
|
|
18848
|
+
decision: "ask",
|
|
18849
|
+
reason: "Registry modification"
|
|
19014
18850
|
};
|
|
19015
18851
|
}
|
|
19016
|
-
function
|
|
19017
|
-
const
|
|
19018
|
-
const { subcommand, unresolved } = parseXargsSubcommand(args2);
|
|
19019
|
-
if (unresolved || !subcommand) {
|
|
19020
|
-
return {
|
|
19021
|
-
command,
|
|
19022
|
-
args: args2,
|
|
19023
|
-
decision: "ask",
|
|
19024
|
-
reason: "xargs subcommand could not be resolved safely",
|
|
19025
|
-
matchedRule: "xargs:subcommand"
|
|
19026
|
-
};
|
|
19027
|
-
}
|
|
19028
|
-
const isShellExec = (subcommand.command === "sh" || subcommand.command === "bash" || subcommand.command === "zsh") && subcommand.args.length >= 2 && subcommand.args[0] === "-c";
|
|
19029
|
-
let parsed;
|
|
19030
|
-
if (isShellExec) {
|
|
19031
|
-
const innerResult = parseCommand(subcommand.args[1]);
|
|
19032
|
-
if (innerResult.parseError) {
|
|
19033
|
-
parsed = { commands: [subcommand], hasSubshell: false, subshellCommands: [], parseError: false };
|
|
19034
|
-
} else {
|
|
19035
|
-
parsed = innerResult;
|
|
19036
|
-
}
|
|
19037
|
-
} else {
|
|
19038
|
-
parsed = { commands: [subcommand], hasSubshell: false, subshellCommands: [], parseError: false };
|
|
19039
|
-
}
|
|
19040
|
-
const result = evaluate(parsed, config, cwd, depth + 1);
|
|
18852
|
+
function pkgManagerRule(command, extraSafeCmds = []) {
|
|
18853
|
+
const safeCmds = [...SAFE_PKG_MANAGER_CMDS, ...extraSafeCmds];
|
|
19041
18854
|
return {
|
|
19042
18855
|
command,
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
18856
|
+
default: "ask",
|
|
18857
|
+
argPatterns: [
|
|
18858
|
+
registryOpsPattern(),
|
|
18859
|
+
{
|
|
18860
|
+
match: { anyArgMatches: [anyArgMatchesPattern(safeCmds)] },
|
|
18861
|
+
decision: "allow",
|
|
18862
|
+
description: `Standard ${command} commands`
|
|
18863
|
+
},
|
|
18864
|
+
VERSION_HELP_FLAGS
|
|
18865
|
+
]
|
|
19047
18866
|
};
|
|
19048
18867
|
}
|
|
19049
|
-
function
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
|
|
19053
|
-
|
|
19054
|
-
|
|
19055
|
-
|
|
19056
|
-
|
|
19057
|
-
|
|
19058
|
-
|
|
19059
|
-
}
|
|
19060
|
-
i++;
|
|
19061
|
-
}
|
|
19062
|
-
i++;
|
|
19063
|
-
if (cmdArgs.length > 0) {
|
|
19064
|
-
commands.push({
|
|
19065
|
-
command: cmdArgs[0],
|
|
19066
|
-
originalCommand: cmdArgs[0],
|
|
19067
|
-
args: cmdArgs.slice(1),
|
|
19068
|
-
envPrefixes: [],
|
|
19069
|
-
raw: cmdArgs.join(" ")
|
|
19070
|
-
});
|
|
19071
|
-
}
|
|
19072
|
-
} else {
|
|
19073
|
-
i++;
|
|
19074
|
-
}
|
|
19075
|
-
}
|
|
19076
|
-
return commands;
|
|
19077
|
-
}
|
|
19078
|
-
function evaluateFindCommand(cmd, config, cwd, depth = 0) {
|
|
19079
|
-
const { command, args: args2 } = cmd;
|
|
19080
|
-
if (args2.some((a) => a === "-delete")) {
|
|
19081
|
-
return { command, args: args2, decision: "ask", reason: "find -delete can remove files", matchedRule: "find:delete" };
|
|
19082
|
-
}
|
|
19083
|
-
if (args2.some((a) => a === "-ok" || a === "-okdir")) {
|
|
19084
|
-
return { command, args: args2, decision: "ask", reason: "find -ok/-okdir can execute commands interactively", matchedRule: "find:ok" };
|
|
19085
|
-
}
|
|
19086
|
-
const execCommands = parseFindExecCommands(args2);
|
|
19087
|
-
if (execCommands.length === 0) {
|
|
19088
|
-
return { command, args: args2, decision: "allow", reason: "find without dangerous flags", matchedRule: "find:safe" };
|
|
19089
|
-
}
|
|
19090
|
-
for (const execCmd of execCommands) {
|
|
19091
|
-
const parsed = {
|
|
19092
|
-
commands: [execCmd],
|
|
19093
|
-
hasSubshell: false,
|
|
19094
|
-
subshellCommands: [],
|
|
19095
|
-
parseError: false
|
|
19096
|
-
};
|
|
19097
|
-
const result = evaluate(parsed, config, cwd, depth + 1);
|
|
19098
|
-
if (result.decision === "deny") {
|
|
19099
|
-
return { command, args: args2, decision: "deny", reason: `find -exec: ${result.reason}`, matchedRule: "find:exec" };
|
|
19100
|
-
}
|
|
19101
|
-
if (result.decision === "ask") {
|
|
19102
|
-
return { command, args: args2, decision: "ask", reason: `find -exec: ${result.reason}`, matchedRule: "find:exec" };
|
|
19103
|
-
}
|
|
19104
|
-
}
|
|
19105
|
-
return { command, args: args2, decision: "allow", reason: "find -exec commands are safe", matchedRule: "find:exec" };
|
|
19106
|
-
}
|
|
19107
|
-
var SSH_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
19108
|
-
"-b",
|
|
19109
|
-
"-c",
|
|
19110
|
-
"-D",
|
|
19111
|
-
"-E",
|
|
19112
|
-
"-e",
|
|
19113
|
-
"-F",
|
|
19114
|
-
"-I",
|
|
19115
|
-
"-i",
|
|
19116
|
-
"-J",
|
|
19117
|
-
"-L",
|
|
19118
|
-
"-l",
|
|
19119
|
-
"-m",
|
|
19120
|
-
"-O",
|
|
19121
|
-
"-o",
|
|
19122
|
-
"-p",
|
|
19123
|
-
"-Q",
|
|
19124
|
-
"-R",
|
|
19125
|
-
"-S",
|
|
19126
|
-
"-W",
|
|
19127
|
-
"-w"
|
|
19128
|
-
]);
|
|
19129
|
-
function findMatchingTarget(value, targets) {
|
|
19130
|
-
return targets.find((t) => globToRegex(t.name).test(value)) || null;
|
|
19131
|
-
}
|
|
19132
|
-
function parseSSHArgs(args2) {
|
|
19133
|
-
let host = null;
|
|
19134
|
-
const remoteArgs = [];
|
|
19135
|
-
let i = 0;
|
|
19136
|
-
while (i < args2.length) {
|
|
19137
|
-
const arg = args2[i];
|
|
19138
|
-
if (SSH_FLAGS_WITH_VALUE.has(arg)) {
|
|
19139
|
-
i += 2;
|
|
19140
|
-
continue;
|
|
19141
|
-
}
|
|
19142
|
-
if (arg.startsWith("-")) {
|
|
19143
|
-
i++;
|
|
19144
|
-
continue;
|
|
19145
|
-
}
|
|
19146
|
-
if (!host) {
|
|
19147
|
-
host = arg.includes("@") ? arg.split("@").pop() : arg;
|
|
19148
|
-
i++;
|
|
19149
|
-
while (i < args2.length) {
|
|
19150
|
-
remoteArgs.push(args2[i]);
|
|
19151
|
-
i++;
|
|
19152
|
-
}
|
|
19153
|
-
break;
|
|
19154
|
-
}
|
|
19155
|
-
i++;
|
|
19156
|
-
}
|
|
19157
|
-
return {
|
|
19158
|
-
host,
|
|
19159
|
-
remoteCommand: remoteArgs.length > 0 ? remoteArgs.map(shellQuote).join(" ") : null
|
|
19160
|
-
};
|
|
19161
|
-
}
|
|
19162
|
-
function extractHostFromRemotePath(args2) {
|
|
19163
|
-
for (const arg of args2) {
|
|
19164
|
-
const match = arg.match(/^(?:[^@]+@)?([^:]+):/);
|
|
19165
|
-
if (match) return match[1];
|
|
19166
|
-
}
|
|
19167
|
-
return null;
|
|
19168
|
-
}
|
|
19169
|
-
function evaluateSSHCommand(cmd, config, targets, depth = 0) {
|
|
19170
|
-
const { command, args: args2 } = cmd;
|
|
19171
|
-
if (command === "scp" || command === "rsync") {
|
|
19172
|
-
const host2 = extractHostFromRemotePath(args2);
|
|
19173
|
-
if (!host2) return null;
|
|
19174
|
-
const target2 = findMatchingTarget(host2, targets);
|
|
19175
|
-
if (!target2) return null;
|
|
19176
|
-
if (target2.allowAll || !target2.overrides) {
|
|
19177
|
-
return {
|
|
19178
|
-
command,
|
|
19179
|
-
args: args2,
|
|
19180
|
-
decision: "allow",
|
|
19181
|
-
reason: `Trusted SSH host "${host2}"${target2.allowAll ? " (allowAll)" : ""}`,
|
|
19182
|
-
matchedRule: "trustedSSHHosts"
|
|
19183
|
-
};
|
|
19184
|
-
}
|
|
19185
|
-
if (target2.overrides.alwaysDeny.some((name) => name === command)) {
|
|
19186
|
-
return {
|
|
19187
|
-
command,
|
|
19188
|
-
args: args2,
|
|
19189
|
-
decision: "deny",
|
|
19190
|
-
reason: `Trusted SSH host "${host2}": "${command}" blocked by overrides`,
|
|
19191
|
-
matchedRule: "trustedSSHHosts"
|
|
19192
|
-
};
|
|
19193
|
-
}
|
|
19194
|
-
return {
|
|
19195
|
-
command,
|
|
19196
|
-
args: args2,
|
|
19197
|
-
decision: "allow",
|
|
19198
|
-
reason: `Trusted SSH host "${host2}"`,
|
|
19199
|
-
matchedRule: "trustedSSHHosts"
|
|
19200
|
-
};
|
|
19201
|
-
}
|
|
19202
|
-
const { host, remoteCommand } = parseSSHArgs(args2);
|
|
19203
|
-
if (!host) return null;
|
|
19204
|
-
const target = findMatchingTarget(host, targets);
|
|
19205
|
-
if (!target) return null;
|
|
19206
|
-
if (!remoteCommand) {
|
|
19207
|
-
return {
|
|
19208
|
-
command,
|
|
19209
|
-
args: args2,
|
|
19210
|
-
decision: "allow",
|
|
19211
|
-
reason: `Trusted SSH host "${host}" (interactive)`,
|
|
19212
|
-
matchedRule: "trustedSSHHosts"
|
|
19213
|
-
};
|
|
19214
|
-
}
|
|
19215
|
-
if (target.allowAll) {
|
|
19216
|
-
return {
|
|
19217
|
-
command,
|
|
19218
|
-
args: args2,
|
|
19219
|
-
decision: "allow",
|
|
19220
|
-
reason: `Trusted SSH host "${host}" (allowAll)`,
|
|
19221
|
-
matchedRule: "trustedSSHHosts"
|
|
19222
|
-
};
|
|
19223
|
-
}
|
|
19224
|
-
const parsed = parseCommand(remoteCommand);
|
|
19225
|
-
const result = evaluate(parsed, configWithContextOverrides(config, target), depth + 1);
|
|
19226
|
-
return {
|
|
19227
|
-
command,
|
|
19228
|
-
args: args2,
|
|
19229
|
-
decision: result.decision,
|
|
19230
|
-
reason: `Trusted SSH host "${host}": ${result.reason}`,
|
|
19231
|
-
matchedRule: "trustedSSHHosts"
|
|
19232
|
-
};
|
|
19233
|
-
}
|
|
19234
|
-
var DOCKER_EXEC_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
19235
|
-
"-e",
|
|
19236
|
-
"--env",
|
|
19237
|
-
"--env-file",
|
|
19238
|
-
"-u",
|
|
19239
|
-
"--user",
|
|
19240
|
-
"-w",
|
|
19241
|
-
"--workdir",
|
|
19242
|
-
"--detach-keys"
|
|
19243
|
-
]);
|
|
19244
|
-
var INTERACTIVE_SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh"]);
|
|
19245
|
-
function shellQuote(arg) {
|
|
19246
|
-
if (/[\s"'\\$`!#&|;()<>]/.test(arg)) {
|
|
19247
|
-
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
19248
|
-
}
|
|
19249
|
-
return arg;
|
|
19250
|
-
}
|
|
19251
|
-
function configWithContextOverrides(config, target) {
|
|
19252
|
-
const overrideLayers = [];
|
|
19253
|
-
if (target?.overrides) overrideLayers.push(target.overrides);
|
|
19254
|
-
if (config.trustedContextOverrides) overrideLayers.push(config.trustedContextOverrides);
|
|
19255
|
-
if (overrideLayers.length === 0) return config;
|
|
19256
|
-
return {
|
|
19257
|
-
...config,
|
|
19258
|
-
layers: [...overrideLayers, ...config.layers]
|
|
19259
|
-
};
|
|
19260
|
-
}
|
|
19261
|
-
function evaluateRemoteCommand(remoteArgs, config, target, depth = 0) {
|
|
19262
|
-
if (target?.allowAll) {
|
|
19263
|
-
return { decision: "allow", reason: "allowAll target", details: [] };
|
|
19264
|
-
}
|
|
19265
|
-
const overriddenConfig = configWithContextOverrides(config, target);
|
|
19266
|
-
if (remoteArgs.length === 0) {
|
|
19267
|
-
return { decision: "allow", reason: "interactive", details: [] };
|
|
19268
|
-
}
|
|
19269
|
-
const remoteCmd = remoteArgs[0];
|
|
19270
|
-
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs.length === 1) {
|
|
19271
|
-
return { decision: "allow", reason: "interactive shell", details: [] };
|
|
19272
|
-
}
|
|
19273
|
-
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs[1] === "-c" && remoteArgs.length >= 3) {
|
|
19274
|
-
const innerCommand = remoteArgs.slice(2).join(" ");
|
|
19275
|
-
const parsed2 = parseCommand(innerCommand);
|
|
19276
|
-
return evaluate(parsed2, overriddenConfig, depth + 1);
|
|
19277
|
-
}
|
|
19278
|
-
const parsed = {
|
|
19279
|
-
commands: [{ command: remoteCmd, originalCommand: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
|
|
19280
|
-
hasSubshell: false,
|
|
19281
|
-
subshellCommands: [],
|
|
19282
|
-
parseError: false
|
|
19283
|
-
};
|
|
19284
|
-
return evaluate(parsed, overriddenConfig, depth + 1);
|
|
19285
|
-
}
|
|
19286
|
-
function parseDockerExecArgs(args2) {
|
|
19287
|
-
let target = null;
|
|
19288
|
-
const remoteArgs = [];
|
|
19289
|
-
let i = 0;
|
|
19290
|
-
while (i < args2.length) {
|
|
19291
|
-
const arg = args2[i];
|
|
19292
|
-
if (DOCKER_EXEC_FLAGS_WITH_VALUE.has(arg)) {
|
|
19293
|
-
i += 2;
|
|
19294
|
-
continue;
|
|
19295
|
-
}
|
|
19296
|
-
if (arg.startsWith("-")) {
|
|
19297
|
-
i++;
|
|
19298
|
-
continue;
|
|
19299
|
-
}
|
|
19300
|
-
if (!target) {
|
|
19301
|
-
target = arg;
|
|
19302
|
-
i++;
|
|
19303
|
-
while (i < args2.length) {
|
|
19304
|
-
remoteArgs.push(args2[i]);
|
|
19305
|
-
i++;
|
|
19306
|
-
}
|
|
19307
|
-
break;
|
|
19308
|
-
}
|
|
19309
|
-
i++;
|
|
19310
|
-
}
|
|
19311
|
-
return { target, remoteArgs };
|
|
19312
|
-
}
|
|
19313
|
-
function evaluateDockerExec(cmd, config, targets, depth = 0) {
|
|
19314
|
-
const { command, args: args2 } = cmd;
|
|
19315
|
-
if (args2[0] !== "exec") return null;
|
|
19316
|
-
const { target: containerName, remoteArgs } = parseDockerExecArgs(args2.slice(1));
|
|
19317
|
-
if (!containerName) return null;
|
|
19318
|
-
const matched = findMatchingTarget(containerName, targets);
|
|
19319
|
-
if (!matched) return null;
|
|
19320
|
-
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
19321
|
-
return {
|
|
19322
|
-
command,
|
|
19323
|
-
args: args2,
|
|
19324
|
-
decision: result.decision,
|
|
19325
|
-
reason: `Trusted Docker container "${containerName}" (${result.reason})`,
|
|
19326
|
-
matchedRule: "trustedDockerContainers"
|
|
19327
|
-
};
|
|
19328
|
-
}
|
|
19329
|
-
var KUBECTL_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
19330
|
-
"-n",
|
|
19331
|
-
"--namespace",
|
|
19332
|
-
"-c",
|
|
19333
|
-
"--container",
|
|
19334
|
-
"--context",
|
|
19335
|
-
"--cluster",
|
|
19336
|
-
"--kubeconfig",
|
|
19337
|
-
"-s",
|
|
19338
|
-
"--server",
|
|
19339
|
-
"--token",
|
|
19340
|
-
"--user",
|
|
19341
|
-
"--as",
|
|
19342
|
-
"--as-group",
|
|
19343
|
-
"--certificate-authority",
|
|
19344
|
-
"--client-certificate",
|
|
19345
|
-
"--client-key",
|
|
19346
|
-
"-l",
|
|
19347
|
-
"--selector",
|
|
19348
|
-
"-f",
|
|
19349
|
-
"--filename",
|
|
19350
|
-
"--cache-dir",
|
|
19351
|
-
"--request-timeout",
|
|
19352
|
-
"-o",
|
|
19353
|
-
"--output"
|
|
19354
|
-
]);
|
|
19355
|
-
function parseKubectlExecArgs(args2) {
|
|
19356
|
-
let context = null;
|
|
19357
|
-
let pod = null;
|
|
19358
|
-
const remoteArgs = [];
|
|
19359
|
-
let i = 0;
|
|
19360
|
-
while (i < args2.length) {
|
|
19361
|
-
const arg = args2[i];
|
|
19362
|
-
if (arg === "--") {
|
|
19363
|
-
i++;
|
|
19364
|
-
while (i < args2.length) {
|
|
19365
|
-
remoteArgs.push(args2[i]);
|
|
19366
|
-
i++;
|
|
19367
|
-
}
|
|
19368
|
-
break;
|
|
19369
|
-
}
|
|
19370
|
-
if (arg.startsWith("--") && arg.includes("=")) {
|
|
19371
|
-
if (arg.startsWith("--context=")) {
|
|
19372
|
-
context = arg.split("=")[1];
|
|
19373
|
-
}
|
|
19374
|
-
i++;
|
|
19375
|
-
continue;
|
|
19376
|
-
}
|
|
19377
|
-
if (KUBECTL_FLAGS_WITH_VALUE.has(arg)) {
|
|
19378
|
-
if (arg === "--context") context = args2[i + 1] || null;
|
|
19379
|
-
i += 2;
|
|
19380
|
-
continue;
|
|
19381
|
-
}
|
|
19382
|
-
if (arg.startsWith("-")) {
|
|
19383
|
-
i++;
|
|
19384
|
-
continue;
|
|
19385
|
-
}
|
|
19386
|
-
if (!pod) {
|
|
19387
|
-
pod = arg;
|
|
19388
|
-
}
|
|
19389
|
-
i++;
|
|
19390
|
-
}
|
|
19391
|
-
return { context, pod, remoteArgs };
|
|
19392
|
-
}
|
|
19393
|
-
function evaluateKubectlExec(cmd, config, targets, depth = 0) {
|
|
19394
|
-
const { command, args: args2 } = cmd;
|
|
19395
|
-
if (args2[0] !== "exec") return null;
|
|
19396
|
-
const { context, pod, remoteArgs } = parseKubectlExecArgs(args2.slice(1));
|
|
19397
|
-
if (!context) return null;
|
|
19398
|
-
const matched = findMatchingTarget(context, targets);
|
|
19399
|
-
if (!matched) return null;
|
|
19400
|
-
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
19401
|
-
return {
|
|
19402
|
-
command,
|
|
19403
|
-
args: args2,
|
|
19404
|
-
decision: result.decision,
|
|
19405
|
-
reason: `Trusted kubectl context "${context}"${pod ? `, pod "${pod}"` : ""} (${result.reason})`,
|
|
19406
|
-
matchedRule: "trustedKubectlContexts"
|
|
19407
|
-
};
|
|
19408
|
-
}
|
|
19409
|
-
var SPRITE_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
19410
|
-
"-o",
|
|
19411
|
-
"--org",
|
|
19412
|
-
"-s",
|
|
19413
|
-
"--sprite"
|
|
19414
|
-
]);
|
|
19415
|
-
function parseSpriteExecArgs(args2) {
|
|
19416
|
-
let spriteName = null;
|
|
19417
|
-
const remoteArgs = [];
|
|
19418
|
-
let foundExec = false;
|
|
19419
|
-
let i = 0;
|
|
19420
|
-
while (i < args2.length) {
|
|
19421
|
-
const arg = args2[i];
|
|
19422
|
-
if (arg.startsWith("--") && arg.includes("=")) {
|
|
19423
|
-
if (arg.startsWith("--sprite=")) {
|
|
19424
|
-
spriteName = arg.split("=")[1];
|
|
19425
|
-
}
|
|
19426
|
-
i++;
|
|
19427
|
-
continue;
|
|
19428
|
-
}
|
|
19429
|
-
if (SPRITE_FLAGS_WITH_VALUE.has(arg)) {
|
|
19430
|
-
if (arg === "-s" || arg === "--sprite") {
|
|
19431
|
-
spriteName = args2[i + 1] || null;
|
|
19432
|
-
}
|
|
19433
|
-
i += 2;
|
|
19434
|
-
continue;
|
|
19435
|
-
}
|
|
19436
|
-
if (arg === "--debug") {
|
|
19437
|
-
i++;
|
|
19438
|
-
continue;
|
|
19439
|
-
}
|
|
19440
|
-
if (arg.startsWith("-")) {
|
|
19441
|
-
i++;
|
|
19442
|
-
continue;
|
|
19443
|
-
}
|
|
19444
|
-
if (!foundExec) {
|
|
19445
|
-
if (arg === "exec" || arg === "x" || arg === "console" || arg === "c") {
|
|
19446
|
-
foundExec = true;
|
|
19447
|
-
i++;
|
|
19448
|
-
continue;
|
|
19449
|
-
}
|
|
19450
|
-
return { spriteName: null, remoteArgs: [] };
|
|
19451
|
-
}
|
|
19452
|
-
while (i < args2.length) {
|
|
19453
|
-
remoteArgs.push(args2[i]);
|
|
19454
|
-
i++;
|
|
19455
|
-
}
|
|
19456
|
-
break;
|
|
19457
|
-
}
|
|
19458
|
-
return { spriteName, remoteArgs };
|
|
19459
|
-
}
|
|
19460
|
-
function evaluateSpriteExec(cmd, config, targets, depth = 0) {
|
|
19461
|
-
const { command, args: args2 } = cmd;
|
|
19462
|
-
const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
|
|
19463
|
-
if (!spriteName) return null;
|
|
19464
|
-
const matched = findMatchingTarget(spriteName, targets);
|
|
19465
|
-
if (!matched) return null;
|
|
19466
|
-
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
19467
|
-
return {
|
|
19468
|
-
command,
|
|
19469
|
-
args: args2,
|
|
19470
|
-
decision: result.decision,
|
|
19471
|
-
reason: `Trusted sprite "${spriteName}" (${result.reason})`,
|
|
19472
|
-
matchedRule: "trustedSprites"
|
|
19473
|
-
};
|
|
19474
|
-
}
|
|
19475
|
-
var FLY_SSH_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
19476
|
-
"-a",
|
|
19477
|
-
"--app",
|
|
19478
|
-
"-C",
|
|
19479
|
-
"--command",
|
|
19480
|
-
"-o",
|
|
19481
|
-
"--org",
|
|
19482
|
-
"-r",
|
|
19483
|
-
"--region",
|
|
19484
|
-
"-u",
|
|
19485
|
-
"--user",
|
|
19486
|
-
"--address"
|
|
19487
|
-
]);
|
|
19488
|
-
function parseFlySSHArgs(args2) {
|
|
19489
|
-
let app = null;
|
|
19490
|
-
const remoteArgs = [];
|
|
19491
|
-
let isSSH = false;
|
|
19492
|
-
let foundConsole = false;
|
|
19493
|
-
let i = 0;
|
|
19494
|
-
while (i < args2.length) {
|
|
19495
|
-
const arg = args2[i];
|
|
19496
|
-
if (arg.startsWith("--app=")) {
|
|
19497
|
-
app = arg.slice(6);
|
|
19498
|
-
i++;
|
|
19499
|
-
continue;
|
|
19500
|
-
}
|
|
19501
|
-
if (FLY_SSH_FLAGS_WITH_VALUE.has(arg)) {
|
|
19502
|
-
if (arg === "-a" || arg === "--app") {
|
|
19503
|
-
app = args2[i + 1] || null;
|
|
19504
|
-
}
|
|
19505
|
-
if ((arg === "-C" || arg === "--command") && foundConsole) {
|
|
19506
|
-
const cmdValue = args2[i + 1];
|
|
19507
|
-
if (cmdValue) {
|
|
19508
|
-
const parsed = parseCommand(cmdValue);
|
|
19509
|
-
if (!parsed.parseError && parsed.commands.length > 0) {
|
|
19510
|
-
const cmd = parsed.commands[0];
|
|
19511
|
-
remoteArgs.push(cmd.command, ...cmd.args);
|
|
19512
|
-
}
|
|
19513
|
-
}
|
|
19514
|
-
i += 2;
|
|
19515
|
-
continue;
|
|
19516
|
-
}
|
|
19517
|
-
i += 2;
|
|
19518
|
-
continue;
|
|
19519
|
-
}
|
|
19520
|
-
if (arg === "--") {
|
|
19521
|
-
i++;
|
|
19522
|
-
while (i < args2.length) {
|
|
19523
|
-
remoteArgs.push(args2[i]);
|
|
19524
|
-
i++;
|
|
19525
|
-
}
|
|
19526
|
-
break;
|
|
19527
|
-
}
|
|
19528
|
-
if (arg.startsWith("-")) {
|
|
19529
|
-
i++;
|
|
19530
|
-
continue;
|
|
19531
|
-
}
|
|
19532
|
-
if (!isSSH && arg === "ssh") {
|
|
19533
|
-
isSSH = true;
|
|
19534
|
-
i++;
|
|
19535
|
-
continue;
|
|
19536
|
-
}
|
|
19537
|
-
if (isSSH && !foundConsole && (arg === "console" || arg === "sftp")) {
|
|
19538
|
-
foundConsole = true;
|
|
19539
|
-
i++;
|
|
19540
|
-
continue;
|
|
19541
|
-
}
|
|
19542
|
-
i++;
|
|
19543
|
-
}
|
|
19544
|
-
return { app, remoteArgs, isSSH: isSSH && foundConsole };
|
|
19545
|
-
}
|
|
19546
|
-
function evaluateFlyCommand(cmd, config, targets, depth = 0) {
|
|
19547
|
-
const { command, args: args2 } = cmd;
|
|
19548
|
-
const { app, remoteArgs, isSSH } = parseFlySSHArgs(args2);
|
|
19549
|
-
if (!isSSH) return null;
|
|
19550
|
-
if (!app) return null;
|
|
19551
|
-
const matched = findMatchingTarget(app, targets);
|
|
19552
|
-
if (!matched) return null;
|
|
19553
|
-
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
19554
|
-
return {
|
|
19555
|
-
command,
|
|
19556
|
-
args: args2,
|
|
19557
|
-
decision: result.decision,
|
|
19558
|
-
reason: `Trusted Fly app "${app}" (${result.reason})`,
|
|
19559
|
-
matchedRule: "trustedFlyApps"
|
|
19560
|
-
};
|
|
19561
|
-
}
|
|
19562
|
-
|
|
19563
|
-
// src/rules.ts
|
|
19564
|
-
var import_fs = require("fs");
|
|
19565
|
-
var import_yaml = __toESM(require_dist2(), 1);
|
|
19566
|
-
var import_os2 = require("os");
|
|
19567
|
-
var import_path3 = require("path");
|
|
19568
|
-
|
|
19569
|
-
// src/defaults.ts
|
|
19570
|
-
var SAFE_DEV_TOOLS = [
|
|
19571
|
-
"jest",
|
|
19572
|
-
"vitest",
|
|
19573
|
-
"tsc",
|
|
19574
|
-
"eslint",
|
|
19575
|
-
"prettier",
|
|
19576
|
-
"mkdirp",
|
|
19577
|
-
"concurrently",
|
|
19578
|
-
"turbo",
|
|
19579
|
-
"next",
|
|
19580
|
-
"nuxt",
|
|
19581
|
-
"vite",
|
|
19582
|
-
"astro",
|
|
19583
|
-
"playwright",
|
|
19584
|
-
"cypress",
|
|
19585
|
-
"mocha",
|
|
19586
|
-
"nyc",
|
|
19587
|
-
"c8",
|
|
19588
|
-
"ts-jest",
|
|
19589
|
-
"tsup",
|
|
19590
|
-
"esbuild",
|
|
19591
|
-
"rollup",
|
|
19592
|
-
"webpack",
|
|
19593
|
-
"prisma",
|
|
19594
|
-
"drizzle-kit",
|
|
19595
|
-
"typeorm",
|
|
19596
|
-
"knex",
|
|
19597
|
-
"sequelize-cli",
|
|
19598
|
-
"tailwindcss",
|
|
19599
|
-
"postcss",
|
|
19600
|
-
"autoprefixer",
|
|
19601
|
-
"lint-staged",
|
|
19602
|
-
"husky",
|
|
19603
|
-
"changeset",
|
|
19604
|
-
"semantic-release",
|
|
19605
|
-
"lerna",
|
|
19606
|
-
"nx",
|
|
19607
|
-
"create-react-app",
|
|
19608
|
-
"create-next-app",
|
|
19609
|
-
"create-vite",
|
|
19610
|
-
"degit",
|
|
19611
|
-
"storybook",
|
|
19612
|
-
"wrangler",
|
|
19613
|
-
"netlify",
|
|
19614
|
-
"vercel",
|
|
19615
|
-
"json",
|
|
19616
|
-
"biome"
|
|
19617
|
-
];
|
|
19618
|
-
var SCRIPT_RUNNERS = ["tsx", "ts-node", "nodemon"];
|
|
19619
|
-
var REGISTRY_OPS = ["publish", "unpublish", "deprecate", "owner", "access", "token", "adduser", "login", "logout"];
|
|
19620
|
-
var SAFE_PKG_MANAGER_CMDS = [
|
|
19621
|
-
"install",
|
|
19622
|
-
"add",
|
|
19623
|
-
"remove",
|
|
19624
|
-
"uninstall",
|
|
19625
|
-
"update",
|
|
19626
|
-
"upgrade",
|
|
19627
|
-
"outdated",
|
|
19628
|
-
"ls",
|
|
19629
|
-
"list",
|
|
19630
|
-
"run",
|
|
19631
|
-
"test",
|
|
19632
|
-
"start",
|
|
19633
|
-
"build",
|
|
19634
|
-
"init",
|
|
19635
|
-
"create",
|
|
19636
|
-
"info",
|
|
19637
|
-
"view",
|
|
19638
|
-
"show",
|
|
19639
|
-
"why",
|
|
19640
|
-
"pack",
|
|
19641
|
-
"cache",
|
|
19642
|
-
"config",
|
|
19643
|
-
"get",
|
|
19644
|
-
"set",
|
|
19645
|
-
"version",
|
|
19646
|
-
"help",
|
|
19647
|
-
"exec",
|
|
19648
|
-
"dedupe",
|
|
19649
|
-
"prune",
|
|
19650
|
-
"audit",
|
|
19651
|
-
"completion",
|
|
19652
|
-
"whoami"
|
|
19653
|
-
];
|
|
19654
|
-
var VERSION_HELP_FLAGS = {
|
|
19655
|
-
match: { anyArgMatches: ["^--(version|help)$", "^-[vh]$"] },
|
|
19656
|
-
decision: "allow",
|
|
19657
|
-
description: "Version/help flags"
|
|
19658
|
-
};
|
|
19659
|
-
function anyArgMatchesPattern(items) {
|
|
19660
|
-
return `^(${items.join("|")})$`;
|
|
19661
|
-
}
|
|
19662
|
-
function safeDevToolsPattern() {
|
|
19663
|
-
return {
|
|
19664
|
-
match: { anyArgMatches: [anyArgMatchesPattern(SAFE_DEV_TOOLS)] },
|
|
19665
|
-
decision: "allow",
|
|
19666
|
-
description: "Well-known dev tools"
|
|
19667
|
-
};
|
|
19668
|
-
}
|
|
19669
|
-
function scriptRunnersPattern() {
|
|
19670
|
-
return {
|
|
19671
|
-
match: { anyArgMatches: [anyArgMatchesPattern(SCRIPT_RUNNERS)] },
|
|
19672
|
-
decision: "ask",
|
|
19673
|
-
reason: "Script runners can execute arbitrary code"
|
|
19674
|
-
};
|
|
19675
|
-
}
|
|
19676
|
-
function registryOpsPattern() {
|
|
19677
|
-
return {
|
|
19678
|
-
match: { anyArgMatches: [anyArgMatchesPattern(REGISTRY_OPS)] },
|
|
19679
|
-
decision: "ask",
|
|
19680
|
-
reason: "Registry modification"
|
|
19681
|
-
};
|
|
19682
|
-
}
|
|
19683
|
-
function pkgManagerRule(command, extraSafeCmds = []) {
|
|
19684
|
-
const safeCmds = [...SAFE_PKG_MANAGER_CMDS, ...extraSafeCmds];
|
|
19685
|
-
return {
|
|
19686
|
-
command,
|
|
19687
|
-
default: "ask",
|
|
19688
|
-
argPatterns: [
|
|
19689
|
-
registryOpsPattern(),
|
|
19690
|
-
{
|
|
19691
|
-
match: { anyArgMatches: [anyArgMatchesPattern(safeCmds)] },
|
|
19692
|
-
decision: "allow",
|
|
19693
|
-
description: `Standard ${command} commands`
|
|
19694
|
-
},
|
|
19695
|
-
VERSION_HELP_FLAGS
|
|
19696
|
-
]
|
|
19697
|
-
};
|
|
19698
|
-
}
|
|
19699
|
-
function pkgRunnerRule(command) {
|
|
19700
|
-
return {
|
|
19701
|
-
command,
|
|
19702
|
-
default: "ask",
|
|
19703
|
-
argPatterns: [
|
|
19704
|
-
safeDevToolsPattern(),
|
|
19705
|
-
scriptRunnersPattern(),
|
|
19706
|
-
VERSION_HELP_FLAGS
|
|
19707
|
-
]
|
|
19708
|
-
};
|
|
18868
|
+
function pkgRunnerRule(command) {
|
|
18869
|
+
return {
|
|
18870
|
+
command,
|
|
18871
|
+
default: "ask",
|
|
18872
|
+
argPatterns: [
|
|
18873
|
+
safeDevToolsPattern(),
|
|
18874
|
+
scriptRunnersPattern(),
|
|
18875
|
+
VERSION_HELP_FLAGS
|
|
18876
|
+
]
|
|
18877
|
+
};
|
|
19709
18878
|
}
|
|
19710
18879
|
var DEFAULT_CONFIG = {
|
|
19711
18880
|
defaultDecision: "ask",
|
|
@@ -20288,316 +19457,1126 @@ var DEFAULT_CONFIG = {
|
|
|
20288
19457
|
}]
|
|
20289
19458
|
};
|
|
20290
19459
|
|
|
20291
|
-
// src/rules.ts
|
|
20292
|
-
var VALID_DECISIONS = /* @__PURE__ */ new Set(["allow", "deny", "ask"]);
|
|
20293
|
-
function isValidDecision(value) {
|
|
20294
|
-
return VALID_DECISIONS.has(value);
|
|
19460
|
+
// src/rules.ts
|
|
19461
|
+
var VALID_DECISIONS = /* @__PURE__ */ new Set(["allow", "deny", "ask"]);
|
|
19462
|
+
function isValidDecision(value) {
|
|
19463
|
+
return VALID_DECISIONS.has(value);
|
|
19464
|
+
}
|
|
19465
|
+
var quiet = true;
|
|
19466
|
+
function setQuiet(value) {
|
|
19467
|
+
quiet = value;
|
|
19468
|
+
}
|
|
19469
|
+
function warn(message) {
|
|
19470
|
+
if (quiet) return;
|
|
19471
|
+
process.stderr.write(message);
|
|
19472
|
+
}
|
|
19473
|
+
var USER_CONFIG_PATHS = [
|
|
19474
|
+
(0, import_path3.join)((0, import_os.homedir)(), ".claude", "warden.yaml"),
|
|
19475
|
+
(0, import_path3.join)((0, import_os.homedir)(), ".claude", "warden.json")
|
|
19476
|
+
];
|
|
19477
|
+
var PROJECT_CONFIG_NAMES = [
|
|
19478
|
+
".claude/warden.yaml",
|
|
19479
|
+
".claude/warden.json"
|
|
19480
|
+
];
|
|
19481
|
+
function loadConfig(cwd) {
|
|
19482
|
+
const config = structuredClone(DEFAULT_CONFIG);
|
|
19483
|
+
const defaultLayer = config.layers[0];
|
|
19484
|
+
let userLayer = null;
|
|
19485
|
+
let userRaw = null;
|
|
19486
|
+
for (const configPath of USER_CONFIG_PATHS) {
|
|
19487
|
+
const result = tryLoadFile(configPath);
|
|
19488
|
+
if (result) {
|
|
19489
|
+
userLayer = extractLayer(result);
|
|
19490
|
+
userRaw = result;
|
|
19491
|
+
break;
|
|
19492
|
+
}
|
|
19493
|
+
}
|
|
19494
|
+
let workspaceLayer = null;
|
|
19495
|
+
let workspaceRaw = null;
|
|
19496
|
+
if (cwd) {
|
|
19497
|
+
for (const name of PROJECT_CONFIG_NAMES) {
|
|
19498
|
+
const result = tryLoadFile((0, import_path3.join)(cwd, name));
|
|
19499
|
+
if (result) {
|
|
19500
|
+
workspaceLayer = extractLayer(result);
|
|
19501
|
+
workspaceRaw = result;
|
|
19502
|
+
break;
|
|
19503
|
+
}
|
|
19504
|
+
}
|
|
19505
|
+
}
|
|
19506
|
+
config.layers = [
|
|
19507
|
+
...workspaceLayer ? [workspaceLayer] : [],
|
|
19508
|
+
...userLayer ? [userLayer] : [],
|
|
19509
|
+
defaultLayer
|
|
19510
|
+
];
|
|
19511
|
+
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19512
|
+
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19513
|
+
return config;
|
|
19514
|
+
}
|
|
19515
|
+
function tryLoadFile(filePath) {
|
|
19516
|
+
if (!(0, import_fs.existsSync)(filePath)) return null;
|
|
19517
|
+
try {
|
|
19518
|
+
const raw = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
19519
|
+
const parsed = filePath.endsWith(".yaml") || filePath.endsWith(".yml") ? (0, import_yaml.parse)(raw) : JSON.parse(raw);
|
|
19520
|
+
if (parsed && typeof parsed === "object") {
|
|
19521
|
+
return parsed;
|
|
19522
|
+
}
|
|
19523
|
+
} catch (err) {
|
|
19524
|
+
warn(`[warden] Warning: failed to parse config ${filePath}: ${err instanceof Error ? err.message : String(err)}
|
|
19525
|
+
`);
|
|
19526
|
+
}
|
|
19527
|
+
return null;
|
|
19528
|
+
}
|
|
19529
|
+
function extractLayer(raw) {
|
|
19530
|
+
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19531
|
+
for (const rule of rules) {
|
|
19532
|
+
if (rule && typeof rule === "object") {
|
|
19533
|
+
if (rule.default && !isValidDecision(rule.default)) {
|
|
19534
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule.command}", using "ask"
|
|
19535
|
+
`);
|
|
19536
|
+
rule.default = "ask";
|
|
19537
|
+
}
|
|
19538
|
+
if (Array.isArray(rule.argPatterns)) {
|
|
19539
|
+
for (const pattern of rule.argPatterns) {
|
|
19540
|
+
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19541
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule.command}", using "ask"
|
|
19542
|
+
`);
|
|
19543
|
+
pattern.decision = "ask";
|
|
19544
|
+
}
|
|
19545
|
+
}
|
|
19546
|
+
}
|
|
19547
|
+
}
|
|
19548
|
+
}
|
|
19549
|
+
return {
|
|
19550
|
+
alwaysAllow: Array.isArray(raw.alwaysAllow) ? raw.alwaysAllow : [],
|
|
19551
|
+
alwaysDeny: Array.isArray(raw.alwaysDeny) ? raw.alwaysDeny : [],
|
|
19552
|
+
rules
|
|
19553
|
+
};
|
|
19554
|
+
}
|
|
19555
|
+
function parseTrustedList(raw) {
|
|
19556
|
+
return raw.map((entry) => {
|
|
19557
|
+
if (typeof entry === "string") return { name: entry };
|
|
19558
|
+
if (entry && typeof entry === "object" && "name" in entry) {
|
|
19559
|
+
const obj = entry;
|
|
19560
|
+
const target = { name: String(obj.name) };
|
|
19561
|
+
if (obj.allowAll === true) target.allowAll = true;
|
|
19562
|
+
if (obj.overrides && typeof obj.overrides === "object") {
|
|
19563
|
+
target.overrides = extractLayer(obj.overrides);
|
|
19564
|
+
}
|
|
19565
|
+
return target;
|
|
19566
|
+
}
|
|
19567
|
+
return null;
|
|
19568
|
+
}).filter((t) => t !== null);
|
|
19569
|
+
}
|
|
19570
|
+
var VALID_REMOTE_CONTEXTS = /* @__PURE__ */ new Set(["ssh", "docker", "kubectl", "sprite", "fly"]);
|
|
19571
|
+
function parseTrustedRemotes(raw) {
|
|
19572
|
+
const results = [];
|
|
19573
|
+
for (const entry of raw) {
|
|
19574
|
+
if (!entry || typeof entry !== "object") continue;
|
|
19575
|
+
const obj = entry;
|
|
19576
|
+
const context = String(obj.context || "");
|
|
19577
|
+
if (!VALID_REMOTE_CONTEXTS.has(context)) {
|
|
19578
|
+
warn(`[warden] Warning: unknown remote context "${context}", skipping
|
|
19579
|
+
`);
|
|
19580
|
+
continue;
|
|
19581
|
+
}
|
|
19582
|
+
const name = String(obj.name || "");
|
|
19583
|
+
if (!name) continue;
|
|
19584
|
+
const remote = { name, context };
|
|
19585
|
+
if (obj.allowAll === true) remote.allowAll = true;
|
|
19586
|
+
if (obj.overrides && typeof obj.overrides === "object") {
|
|
19587
|
+
remote.overrides = extractLayer(obj.overrides);
|
|
19588
|
+
}
|
|
19589
|
+
results.push(remote);
|
|
19590
|
+
}
|
|
19591
|
+
return results;
|
|
19592
|
+
}
|
|
19593
|
+
function parseTargetPolicies(raw) {
|
|
19594
|
+
const results = [];
|
|
19595
|
+
for (const entry of raw) {
|
|
19596
|
+
if (!entry || typeof entry !== "object") continue;
|
|
19597
|
+
const obj = entry;
|
|
19598
|
+
const type = String(obj.type || "");
|
|
19599
|
+
const rawDecision = String(obj.decision || "allow");
|
|
19600
|
+
const decision = isValidDecision(rawDecision) ? rawDecision : "allow";
|
|
19601
|
+
const base = {
|
|
19602
|
+
commands: Array.isArray(obj.commands) ? obj.commands.map(String) : void 0,
|
|
19603
|
+
decision,
|
|
19604
|
+
reason: obj.reason ? String(obj.reason) : void 0,
|
|
19605
|
+
allowAll: obj.allowAll === true ? true : void 0
|
|
19606
|
+
};
|
|
19607
|
+
switch (type) {
|
|
19608
|
+
case "path": {
|
|
19609
|
+
const path = String(obj.path || "");
|
|
19610
|
+
if (!path) continue;
|
|
19611
|
+
const policy = { ...base, type: "path", path };
|
|
19612
|
+
if (obj.recursive === false) policy.recursive = false;
|
|
19613
|
+
results.push(policy);
|
|
19614
|
+
break;
|
|
19615
|
+
}
|
|
19616
|
+
case "database": {
|
|
19617
|
+
const host = String(obj.host || "");
|
|
19618
|
+
if (!host) continue;
|
|
19619
|
+
const policy = { ...base, type: "database", host };
|
|
19620
|
+
if (typeof obj.port === "number") policy.port = obj.port;
|
|
19621
|
+
if (obj.database) policy.database = String(obj.database);
|
|
19622
|
+
results.push(policy);
|
|
19623
|
+
break;
|
|
19624
|
+
}
|
|
19625
|
+
case "endpoint": {
|
|
19626
|
+
const pattern = String(obj.pattern || "");
|
|
19627
|
+
if (!pattern) continue;
|
|
19628
|
+
results.push({ ...base, type: "endpoint", pattern });
|
|
19629
|
+
break;
|
|
19630
|
+
}
|
|
19631
|
+
default:
|
|
19632
|
+
warn(`[warden] Warning: unknown target policy type "${type}", skipping
|
|
19633
|
+
`);
|
|
19634
|
+
}
|
|
19635
|
+
}
|
|
19636
|
+
return results;
|
|
19637
|
+
}
|
|
19638
|
+
function parseLegacyPaths(raw) {
|
|
19639
|
+
const results = [];
|
|
19640
|
+
for (const e of raw) {
|
|
19641
|
+
if (!e || typeof e !== "object") continue;
|
|
19642
|
+
const obj = e;
|
|
19643
|
+
const decision = String(obj.decision || "allow");
|
|
19644
|
+
if (!isValidDecision(decision)) continue;
|
|
19645
|
+
const path = String(obj.path || "");
|
|
19646
|
+
if (!path) continue;
|
|
19647
|
+
const tp = { type: "path", path, decision };
|
|
19648
|
+
if (obj.recursive === false) tp.recursive = false;
|
|
19649
|
+
if (Array.isArray(obj.commands)) tp.commands = obj.commands.map(String);
|
|
19650
|
+
if (obj.reason) tp.reason = String(obj.reason);
|
|
19651
|
+
results.push(tp);
|
|
19652
|
+
}
|
|
19653
|
+
return results;
|
|
19654
|
+
}
|
|
19655
|
+
function parseLegacyDatabases(raw) {
|
|
19656
|
+
const results = [];
|
|
19657
|
+
for (const e of raw) {
|
|
19658
|
+
if (!e || typeof e !== "object") continue;
|
|
19659
|
+
const obj = e;
|
|
19660
|
+
const decision = String(obj.decision || "allow");
|
|
19661
|
+
if (!isValidDecision(decision)) continue;
|
|
19662
|
+
const host = String(obj.host || "");
|
|
19663
|
+
if (!host) continue;
|
|
19664
|
+
const td = { type: "database", host, decision };
|
|
19665
|
+
if (typeof obj.port === "number") td.port = obj.port;
|
|
19666
|
+
if (obj.database) td.database = String(obj.database);
|
|
19667
|
+
if (Array.isArray(obj.commands)) td.commands = obj.commands.map(String);
|
|
19668
|
+
if (obj.reason) td.reason = String(obj.reason);
|
|
19669
|
+
results.push(td);
|
|
19670
|
+
}
|
|
19671
|
+
return results;
|
|
19672
|
+
}
|
|
19673
|
+
function parseLegacyEndpoints(raw) {
|
|
19674
|
+
const results = [];
|
|
19675
|
+
for (const e of raw) {
|
|
19676
|
+
if (!e || typeof e !== "object") continue;
|
|
19677
|
+
const obj = e;
|
|
19678
|
+
const decision = String(obj.decision || "allow");
|
|
19679
|
+
if (!isValidDecision(decision)) continue;
|
|
19680
|
+
const pattern = String(obj.pattern || "");
|
|
19681
|
+
if (!pattern) continue;
|
|
19682
|
+
const te = { type: "endpoint", pattern, decision };
|
|
19683
|
+
if (Array.isArray(obj.commands)) te.commands = obj.commands.map(String);
|
|
19684
|
+
if (obj.reason) te.reason = String(obj.reason);
|
|
19685
|
+
results.push(te);
|
|
19686
|
+
}
|
|
19687
|
+
return results;
|
|
19688
|
+
}
|
|
19689
|
+
var LEGACY_REMOTE_MAP = {
|
|
19690
|
+
trustedSSHHosts: "ssh",
|
|
19691
|
+
trustedDockerContainers: "docker",
|
|
19692
|
+
trustedKubectlContexts: "kubectl",
|
|
19693
|
+
trustedSprites: "sprite",
|
|
19694
|
+
trustedFlyApps: "fly"
|
|
19695
|
+
};
|
|
19696
|
+
function mergeNonLayerFields(config, raw) {
|
|
19697
|
+
if (Array.isArray(raw.trustedRemotes)) {
|
|
19698
|
+
config.trustedRemotes = [...config.trustedRemotes || [], ...parseTrustedRemotes(raw.trustedRemotes)];
|
|
19699
|
+
}
|
|
19700
|
+
if (Array.isArray(raw.targetPolicies)) {
|
|
19701
|
+
config.targetPolicies = [...config.targetPolicies || [], ...parseTargetPolicies(raw.targetPolicies)];
|
|
19702
|
+
}
|
|
19703
|
+
for (const [key, context] of Object.entries(LEGACY_REMOTE_MAP)) {
|
|
19704
|
+
if (Array.isArray(raw[key])) {
|
|
19705
|
+
const remotes = parseTrustedList(raw[key]).map((t) => ({ ...t, context }));
|
|
19706
|
+
config.trustedRemotes = [...config.trustedRemotes || [], ...remotes];
|
|
19707
|
+
}
|
|
19708
|
+
}
|
|
19709
|
+
if (Array.isArray(raw.trustedPaths)) {
|
|
19710
|
+
config.targetPolicies = [...config.targetPolicies || [], ...parseLegacyPaths(raw.trustedPaths)];
|
|
19711
|
+
}
|
|
19712
|
+
if (Array.isArray(raw.trustedDatabases)) {
|
|
19713
|
+
config.targetPolicies = [...config.targetPolicies || [], ...parseLegacyDatabases(raw.trustedDatabases)];
|
|
19714
|
+
}
|
|
19715
|
+
if (Array.isArray(raw.trustedEndpoints)) {
|
|
19716
|
+
config.targetPolicies = [...config.targetPolicies || [], ...parseLegacyEndpoints(raw.trustedEndpoints)];
|
|
19717
|
+
}
|
|
19718
|
+
if (typeof raw.defaultDecision === "string") {
|
|
19719
|
+
if (isValidDecision(raw.defaultDecision)) {
|
|
19720
|
+
config.defaultDecision = raw.defaultDecision;
|
|
19721
|
+
} else {
|
|
19722
|
+
warn(`[warden] Warning: invalid defaultDecision "${raw.defaultDecision}", ignoring
|
|
19723
|
+
`);
|
|
19724
|
+
}
|
|
19725
|
+
}
|
|
19726
|
+
if (typeof raw.askOnSubshell === "boolean") {
|
|
19727
|
+
config.askOnSubshell = raw.askOnSubshell;
|
|
19728
|
+
}
|
|
19729
|
+
if (typeof raw.notifyOnAsk === "boolean") {
|
|
19730
|
+
config.notifyOnAsk = raw.notifyOnAsk;
|
|
19731
|
+
}
|
|
19732
|
+
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19733
|
+
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19734
|
+
}
|
|
19735
|
+
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19736
|
+
const overrides = raw.trustedContextOverrides;
|
|
19737
|
+
const layer = extractLayer(overrides);
|
|
19738
|
+
if (config.trustedContextOverrides) {
|
|
19739
|
+
config.trustedContextOverrides = {
|
|
19740
|
+
alwaysAllow: [...layer.alwaysAllow, ...config.trustedContextOverrides.alwaysAllow],
|
|
19741
|
+
alwaysDeny: [...layer.alwaysDeny, ...config.trustedContextOverrides.alwaysDeny],
|
|
19742
|
+
rules: [...layer.rules, ...config.trustedContextOverrides.rules]
|
|
19743
|
+
};
|
|
19744
|
+
} else {
|
|
19745
|
+
config.trustedContextOverrides = layer;
|
|
19746
|
+
}
|
|
19747
|
+
}
|
|
19748
|
+
}
|
|
19749
|
+
|
|
19750
|
+
// src/evaluator.ts
|
|
19751
|
+
function safeRegexTest(pattern, input) {
|
|
19752
|
+
try {
|
|
19753
|
+
return new RegExp(pattern).test(input);
|
|
19754
|
+
} catch {
|
|
19755
|
+
warn(`[warden] Warning: invalid regex pattern: ${pattern}
|
|
19756
|
+
`);
|
|
19757
|
+
return false;
|
|
19758
|
+
}
|
|
19759
|
+
}
|
|
19760
|
+
function commandMatchesName(cmd, name) {
|
|
19761
|
+
if (name.startsWith("/")) {
|
|
19762
|
+
return cmd.originalCommand === name;
|
|
19763
|
+
}
|
|
19764
|
+
if (name.startsWith("~/")) {
|
|
19765
|
+
return cmd.originalCommand === (0, import_os2.homedir)() + name.slice(1);
|
|
19766
|
+
}
|
|
19767
|
+
return cmd.command === name;
|
|
19768
|
+
}
|
|
19769
|
+
var MAX_RECURSION_DEPTH = 10;
|
|
19770
|
+
function evaluate(parsed, config, cwdOrDepth, maybeDepth) {
|
|
19771
|
+
const cwd = typeof cwdOrDepth === "string" ? cwdOrDepth : void 0;
|
|
19772
|
+
const depth = typeof cwdOrDepth === "number" ? cwdOrDepth : maybeDepth ?? 0;
|
|
19773
|
+
if (depth > MAX_RECURSION_DEPTH) {
|
|
19774
|
+
return { decision: "ask", reason: "Maximum recursion depth exceeded", details: [] };
|
|
19775
|
+
}
|
|
19776
|
+
if (parsed.parseError) {
|
|
19777
|
+
return { decision: "ask", reason: "Could not parse command safely", details: [] };
|
|
19778
|
+
}
|
|
19779
|
+
if (parsed.commands.length === 0) {
|
|
19780
|
+
return { decision: "allow", reason: "Empty command", details: [] };
|
|
19781
|
+
}
|
|
19782
|
+
if (parsed.hasSubshell && parsed.subshellCommands.length > 0) {
|
|
19783
|
+
for (const subCmd of parsed.subshellCommands) {
|
|
19784
|
+
const subParsed = parseCommand(subCmd);
|
|
19785
|
+
const subResult = evaluate(subParsed, config, cwd, depth + 1);
|
|
19786
|
+
if (subResult.decision === "deny") {
|
|
19787
|
+
return { decision: "deny", reason: `Subshell command: ${subResult.reason}`, details: subResult.details };
|
|
19788
|
+
}
|
|
19789
|
+
if (subResult.decision === "ask") {
|
|
19790
|
+
return { decision: "ask", reason: `Subshell command: ${subResult.reason}`, details: subResult.details };
|
|
19791
|
+
}
|
|
19792
|
+
}
|
|
19793
|
+
} else if (parsed.hasSubshell && parsed.subshellCommands.length === 0 && config.askOnSubshell) {
|
|
19794
|
+
return { decision: "ask", reason: "Command contains subshell/command substitution", details: [] };
|
|
19795
|
+
}
|
|
19796
|
+
const details = [];
|
|
19797
|
+
for (const cmd of parsed.commands) {
|
|
19798
|
+
details.push(evaluateCommand(cmd, config, cwd, depth));
|
|
19799
|
+
}
|
|
19800
|
+
const decisions = details.map((d) => d.decision);
|
|
19801
|
+
if (decisions.includes("deny")) {
|
|
19802
|
+
const denied = details.filter((d) => d.decision === "deny");
|
|
19803
|
+
return {
|
|
19804
|
+
decision: "deny",
|
|
19805
|
+
reason: denied.map((d) => `${d.command}: ${d.reason}`).join("; "),
|
|
19806
|
+
details
|
|
19807
|
+
};
|
|
19808
|
+
}
|
|
19809
|
+
if (decisions.includes("ask")) {
|
|
19810
|
+
const asked = details.filter((d) => d.decision === "ask");
|
|
19811
|
+
return {
|
|
19812
|
+
decision: "ask",
|
|
19813
|
+
reason: asked.map((d) => `${d.command}: ${d.reason}`).join("; "),
|
|
19814
|
+
details
|
|
19815
|
+
};
|
|
19816
|
+
}
|
|
19817
|
+
return { decision: "allow", reason: "All commands are safe", details };
|
|
19818
|
+
}
|
|
19819
|
+
function evaluateCommand(cmd, config, cwd, depth = 0) {
|
|
19820
|
+
const { command, args: args2 } = cmd;
|
|
19821
|
+
for (const layer of config.layers) {
|
|
19822
|
+
if (layer.alwaysDeny.some((name) => commandMatchesName(cmd, name))) {
|
|
19823
|
+
return { command, args: args2, decision: "deny", reason: `"${command}" is blocked`, matchedRule: "alwaysDeny" };
|
|
19824
|
+
}
|
|
19825
|
+
if (layer.alwaysAllow.some((name) => commandMatchesName(cmd, name))) {
|
|
19826
|
+
return { command, args: args2, decision: "allow", reason: `"${command}" is safe`, matchedRule: "alwaysAllow" };
|
|
19827
|
+
}
|
|
19828
|
+
}
|
|
19829
|
+
if (cwd) {
|
|
19830
|
+
const targetResult = evaluateTargetPolicies(cmd, cwd, config);
|
|
19831
|
+
if (targetResult) return targetResult;
|
|
19832
|
+
}
|
|
19833
|
+
const remotes = config.trustedRemotes || [];
|
|
19834
|
+
if (command === "ssh" || command === "scp" || command === "rsync") {
|
|
19835
|
+
const sshTargets = remotes.filter((r) => r.context === "ssh");
|
|
19836
|
+
if (sshTargets.length) {
|
|
19837
|
+
const sshResult = evaluateSSHCommand(cmd, config, sshTargets, depth);
|
|
19838
|
+
if (sshResult) return sshResult;
|
|
19839
|
+
}
|
|
19840
|
+
}
|
|
19841
|
+
if (command === "docker") {
|
|
19842
|
+
const dockerTargets = remotes.filter((r) => r.context === "docker");
|
|
19843
|
+
if (dockerTargets.length) {
|
|
19844
|
+
const dockerResult = evaluateDockerExec(cmd, config, dockerTargets, depth);
|
|
19845
|
+
if (dockerResult) return dockerResult;
|
|
19846
|
+
}
|
|
19847
|
+
}
|
|
19848
|
+
if (command === "kubectl") {
|
|
19849
|
+
const kubectlTargets = remotes.filter((r) => r.context === "kubectl");
|
|
19850
|
+
if (kubectlTargets.length) {
|
|
19851
|
+
const kubectlResult = evaluateKubectlExec(cmd, config, kubectlTargets, depth);
|
|
19852
|
+
if (kubectlResult) return kubectlResult;
|
|
19853
|
+
}
|
|
19854
|
+
}
|
|
19855
|
+
if (command === "sprite") {
|
|
19856
|
+
const spriteTargets = remotes.filter((r) => r.context === "sprite");
|
|
19857
|
+
if (spriteTargets.length) {
|
|
19858
|
+
const spriteResult = evaluateSpriteExec(cmd, config, spriteTargets, depth);
|
|
19859
|
+
if (spriteResult) return spriteResult;
|
|
19860
|
+
}
|
|
19861
|
+
}
|
|
19862
|
+
if (command === "fly" || command === "flyctl") {
|
|
19863
|
+
const flyTargets = remotes.filter((r) => r.context === "fly");
|
|
19864
|
+
if (flyTargets.length) {
|
|
19865
|
+
const flyResult = evaluateFlyCommand(cmd, config, flyTargets, depth);
|
|
19866
|
+
if (flyResult) return flyResult;
|
|
19867
|
+
}
|
|
19868
|
+
}
|
|
19869
|
+
if (command === "xargs") {
|
|
19870
|
+
return evaluateXargsCommand(cmd, config, cwd, depth);
|
|
19871
|
+
}
|
|
19872
|
+
if (command === "find") {
|
|
19873
|
+
return evaluateFindCommand(cmd, config, cwd, depth);
|
|
19874
|
+
}
|
|
19875
|
+
const mergedRule = collectMergedRule(cmd, config);
|
|
19876
|
+
if (mergedRule) {
|
|
19877
|
+
return evaluateRule(cmd, mergedRule);
|
|
19878
|
+
}
|
|
19879
|
+
return { command, args: args2, decision: config.defaultDecision, reason: `No rule for "${command}"`, matchedRule: "default" };
|
|
19880
|
+
}
|
|
19881
|
+
function collectMergedRule(cmd, config) {
|
|
19882
|
+
const matchingRules = [];
|
|
19883
|
+
for (const layer of config.layers) {
|
|
19884
|
+
const rule = layer.rules.find((r) => commandMatchesName(cmd, r.command));
|
|
19885
|
+
if (rule) {
|
|
19886
|
+
matchingRules.push(rule);
|
|
19887
|
+
if (rule.override) break;
|
|
19888
|
+
}
|
|
19889
|
+
}
|
|
19890
|
+
if (matchingRules.length === 0) return null;
|
|
19891
|
+
if (matchingRules.length === 1) return matchingRules[0];
|
|
19892
|
+
const mergedPatterns = [];
|
|
19893
|
+
for (const rule of matchingRules) {
|
|
19894
|
+
if (rule.argPatterns) {
|
|
19895
|
+
mergedPatterns.push(...rule.argPatterns);
|
|
19896
|
+
}
|
|
19897
|
+
}
|
|
19898
|
+
return {
|
|
19899
|
+
command: matchingRules[0].command,
|
|
19900
|
+
default: matchingRules[0].default,
|
|
19901
|
+
argPatterns: mergedPatterns
|
|
19902
|
+
};
|
|
19903
|
+
}
|
|
19904
|
+
function evaluateRule(cmd, rule) {
|
|
19905
|
+
const { command, args: args2 } = cmd;
|
|
19906
|
+
const argsJoined = args2.join(" ");
|
|
19907
|
+
for (const pattern of rule.argPatterns || []) {
|
|
19908
|
+
const m = pattern.match;
|
|
19909
|
+
let matched = true;
|
|
19910
|
+
if (m.noArgs !== void 0) {
|
|
19911
|
+
matched = matched && m.noArgs === (args2.length === 0);
|
|
19912
|
+
}
|
|
19913
|
+
if (m.argsMatch && matched) {
|
|
19914
|
+
matched = m.argsMatch.some((re) => safeRegexTest(re, argsJoined));
|
|
19915
|
+
}
|
|
19916
|
+
if (m.anyArgMatches && matched) {
|
|
19917
|
+
matched = args2.some((arg) => m.anyArgMatches.some((re) => safeRegexTest(re, arg)));
|
|
19918
|
+
}
|
|
19919
|
+
if (m.argCount && matched) {
|
|
19920
|
+
if (m.argCount.min !== void 0) matched = matched && args2.length >= m.argCount.min;
|
|
19921
|
+
if (m.argCount.max !== void 0) matched = matched && args2.length <= m.argCount.max;
|
|
19922
|
+
}
|
|
19923
|
+
if (m.not) matched = !matched;
|
|
19924
|
+
if (matched) {
|
|
19925
|
+
return {
|
|
19926
|
+
command,
|
|
19927
|
+
args: args2,
|
|
19928
|
+
decision: pattern.decision,
|
|
19929
|
+
reason: pattern.reason || pattern.description || `Matched pattern for "${command}"`,
|
|
19930
|
+
matchedRule: `${command}:argPattern`
|
|
19931
|
+
};
|
|
19932
|
+
}
|
|
19933
|
+
}
|
|
19934
|
+
return {
|
|
19935
|
+
command,
|
|
19936
|
+
args: args2,
|
|
19937
|
+
decision: rule.default,
|
|
19938
|
+
reason: `Default for "${command}"`,
|
|
19939
|
+
matchedRule: `${command}:default`
|
|
19940
|
+
};
|
|
19941
|
+
}
|
|
19942
|
+
var XARGS_SHORT_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set(["E", "I", "L", "n", "P", "s", "S", "d", "a"]);
|
|
19943
|
+
var XARGS_SHORT_FLAGS_NO_VALUE = /* @__PURE__ */ new Set(["0", "e", "o", "p", "r", "t", "x"]);
|
|
19944
|
+
var XARGS_LONG_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
19945
|
+
"--eof",
|
|
19946
|
+
"--replace",
|
|
19947
|
+
"--max-lines",
|
|
19948
|
+
"--max-args",
|
|
19949
|
+
"--max-procs",
|
|
19950
|
+
"--max-chars",
|
|
19951
|
+
"--arg-file",
|
|
19952
|
+
"--delimiter"
|
|
19953
|
+
]);
|
|
19954
|
+
var XARGS_LONG_FLAGS_NO_VALUE = /* @__PURE__ */ new Set([
|
|
19955
|
+
"--null",
|
|
19956
|
+
"--exit",
|
|
19957
|
+
"--open-tty",
|
|
19958
|
+
"--interactive",
|
|
19959
|
+
"--no-run-if-empty",
|
|
19960
|
+
"--verbose",
|
|
19961
|
+
"--show-limits"
|
|
19962
|
+
]);
|
|
19963
|
+
function parseXargsSubcommand(args2) {
|
|
19964
|
+
let i = 0;
|
|
19965
|
+
while (i < args2.length) {
|
|
19966
|
+
const arg = args2[i];
|
|
19967
|
+
if (arg === "--") {
|
|
19968
|
+
i++;
|
|
19969
|
+
break;
|
|
19970
|
+
}
|
|
19971
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
19972
|
+
break;
|
|
19973
|
+
}
|
|
19974
|
+
if (arg.startsWith("--")) {
|
|
19975
|
+
const eqIndex = arg.indexOf("=");
|
|
19976
|
+
const longFlag = eqIndex === -1 ? arg : arg.slice(0, eqIndex);
|
|
19977
|
+
if (XARGS_LONG_FLAGS_WITH_VALUE.has(longFlag)) {
|
|
19978
|
+
if (eqIndex !== -1) {
|
|
19979
|
+
i++;
|
|
19980
|
+
continue;
|
|
19981
|
+
}
|
|
19982
|
+
if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
|
|
19983
|
+
i += 2;
|
|
19984
|
+
continue;
|
|
19985
|
+
}
|
|
19986
|
+
if (XARGS_LONG_FLAGS_NO_VALUE.has(longFlag)) {
|
|
19987
|
+
i++;
|
|
19988
|
+
continue;
|
|
19989
|
+
}
|
|
19990
|
+
return { subcommand: null, unresolved: true };
|
|
19991
|
+
}
|
|
19992
|
+
const short = arg[1];
|
|
19993
|
+
if (XARGS_SHORT_FLAGS_WITH_VALUE.has(short)) {
|
|
19994
|
+
if (arg.length > 2) {
|
|
19995
|
+
i++;
|
|
19996
|
+
continue;
|
|
19997
|
+
}
|
|
19998
|
+
if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
|
|
19999
|
+
i += 2;
|
|
20000
|
+
continue;
|
|
20001
|
+
}
|
|
20002
|
+
const grouped = arg.slice(1).split("");
|
|
20003
|
+
const allKnownNoValue = grouped.every((ch) => XARGS_SHORT_FLAGS_NO_VALUE.has(ch));
|
|
20004
|
+
if (allKnownNoValue) {
|
|
20005
|
+
i++;
|
|
20006
|
+
continue;
|
|
20007
|
+
}
|
|
20008
|
+
return { subcommand: null, unresolved: true };
|
|
20009
|
+
}
|
|
20010
|
+
if (i >= args2.length) {
|
|
20011
|
+
return {
|
|
20012
|
+
unresolved: false,
|
|
20013
|
+
subcommand: {
|
|
20014
|
+
command: "echo",
|
|
20015
|
+
originalCommand: "echo",
|
|
20016
|
+
args: [],
|
|
20017
|
+
envPrefixes: [],
|
|
20018
|
+
raw: "echo"
|
|
20019
|
+
}
|
|
20020
|
+
};
|
|
20021
|
+
}
|
|
20022
|
+
const subcommand = args2[i];
|
|
20023
|
+
const subArgs = args2.slice(i + 1);
|
|
20024
|
+
return {
|
|
20025
|
+
unresolved: false,
|
|
20026
|
+
subcommand: {
|
|
20027
|
+
command: subcommand,
|
|
20028
|
+
originalCommand: subcommand,
|
|
20029
|
+
args: subArgs,
|
|
20030
|
+
envPrefixes: [],
|
|
20031
|
+
raw: [subcommand, ...subArgs].join(" ")
|
|
20032
|
+
}
|
|
20033
|
+
};
|
|
20034
|
+
}
|
|
20035
|
+
function evaluateXargsCommand(cmd, config, cwd, depth = 0) {
|
|
20036
|
+
const { command, args: args2 } = cmd;
|
|
20037
|
+
const { subcommand, unresolved } = parseXargsSubcommand(args2);
|
|
20038
|
+
if (unresolved || !subcommand) {
|
|
20039
|
+
return {
|
|
20040
|
+
command,
|
|
20041
|
+
args: args2,
|
|
20042
|
+
decision: "ask",
|
|
20043
|
+
reason: "xargs subcommand could not be resolved safely",
|
|
20044
|
+
matchedRule: "xargs:subcommand"
|
|
20045
|
+
};
|
|
20046
|
+
}
|
|
20047
|
+
const isShellExec = (subcommand.command === "sh" || subcommand.command === "bash" || subcommand.command === "zsh") && subcommand.args.length >= 2 && subcommand.args[0] === "-c";
|
|
20048
|
+
let parsed;
|
|
20049
|
+
if (isShellExec) {
|
|
20050
|
+
const innerResult = parseCommand(subcommand.args[1]);
|
|
20051
|
+
if (innerResult.parseError) {
|
|
20052
|
+
parsed = { commands: [subcommand], hasSubshell: false, subshellCommands: [], parseError: false };
|
|
20053
|
+
} else {
|
|
20054
|
+
parsed = innerResult;
|
|
20055
|
+
}
|
|
20056
|
+
} else {
|
|
20057
|
+
parsed = { commands: [subcommand], hasSubshell: false, subshellCommands: [], parseError: false };
|
|
20058
|
+
}
|
|
20059
|
+
const result = evaluate(parsed, config, cwd, depth + 1);
|
|
20060
|
+
return {
|
|
20061
|
+
command,
|
|
20062
|
+
args: args2,
|
|
20063
|
+
decision: result.decision,
|
|
20064
|
+
reason: `xargs subcommand "${subcommand.command}": ${result.reason}`,
|
|
20065
|
+
matchedRule: "xargs:subcommand"
|
|
20066
|
+
};
|
|
20067
|
+
}
|
|
20068
|
+
function parseFindExecCommands(args2) {
|
|
20069
|
+
const commands = [];
|
|
20070
|
+
let i = 0;
|
|
20071
|
+
while (i < args2.length) {
|
|
20072
|
+
if (args2[i] === "-exec" || args2[i] === "-execdir") {
|
|
20073
|
+
i++;
|
|
20074
|
+
const cmdArgs = [];
|
|
20075
|
+
while (i < args2.length && args2[i] !== ";" && args2[i] !== "+") {
|
|
20076
|
+
if (args2[i] !== "{}") {
|
|
20077
|
+
cmdArgs.push(args2[i]);
|
|
20078
|
+
}
|
|
20079
|
+
i++;
|
|
20080
|
+
}
|
|
20081
|
+
i++;
|
|
20082
|
+
if (cmdArgs.length > 0) {
|
|
20083
|
+
commands.push({
|
|
20084
|
+
command: cmdArgs[0],
|
|
20085
|
+
originalCommand: cmdArgs[0],
|
|
20086
|
+
args: cmdArgs.slice(1),
|
|
20087
|
+
envPrefixes: [],
|
|
20088
|
+
raw: cmdArgs.join(" ")
|
|
20089
|
+
});
|
|
20090
|
+
}
|
|
20091
|
+
} else {
|
|
20092
|
+
i++;
|
|
20093
|
+
}
|
|
20094
|
+
}
|
|
20095
|
+
return commands;
|
|
20096
|
+
}
|
|
20097
|
+
function evaluateFindCommand(cmd, config, cwd, depth = 0) {
|
|
20098
|
+
const { command, args: args2 } = cmd;
|
|
20099
|
+
if (args2.some((a) => a === "-delete")) {
|
|
20100
|
+
return { command, args: args2, decision: "ask", reason: "find -delete can remove files", matchedRule: "find:delete" };
|
|
20101
|
+
}
|
|
20102
|
+
if (args2.some((a) => a === "-ok" || a === "-okdir")) {
|
|
20103
|
+
return { command, args: args2, decision: "ask", reason: "find -ok/-okdir can execute commands interactively", matchedRule: "find:ok" };
|
|
20104
|
+
}
|
|
20105
|
+
const execCommands = parseFindExecCommands(args2);
|
|
20106
|
+
if (execCommands.length === 0) {
|
|
20107
|
+
return { command, args: args2, decision: "allow", reason: "find without dangerous flags", matchedRule: "find:safe" };
|
|
20108
|
+
}
|
|
20109
|
+
for (const execCmd of execCommands) {
|
|
20110
|
+
const parsed = {
|
|
20111
|
+
commands: [execCmd],
|
|
20112
|
+
hasSubshell: false,
|
|
20113
|
+
subshellCommands: [],
|
|
20114
|
+
parseError: false
|
|
20115
|
+
};
|
|
20116
|
+
const result = evaluate(parsed, config, cwd, depth + 1);
|
|
20117
|
+
if (result.decision === "deny") {
|
|
20118
|
+
return { command, args: args2, decision: "deny", reason: `find -exec: ${result.reason}`, matchedRule: "find:exec" };
|
|
20119
|
+
}
|
|
20120
|
+
if (result.decision === "ask") {
|
|
20121
|
+
return { command, args: args2, decision: "ask", reason: `find -exec: ${result.reason}`, matchedRule: "find:exec" };
|
|
20122
|
+
}
|
|
20123
|
+
}
|
|
20124
|
+
return { command, args: args2, decision: "allow", reason: "find -exec commands are safe", matchedRule: "find:exec" };
|
|
20125
|
+
}
|
|
20126
|
+
var SSH_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20127
|
+
"-b",
|
|
20128
|
+
"-c",
|
|
20129
|
+
"-D",
|
|
20130
|
+
"-E",
|
|
20131
|
+
"-e",
|
|
20132
|
+
"-F",
|
|
20133
|
+
"-I",
|
|
20134
|
+
"-i",
|
|
20135
|
+
"-J",
|
|
20136
|
+
"-L",
|
|
20137
|
+
"-l",
|
|
20138
|
+
"-m",
|
|
20139
|
+
"-O",
|
|
20140
|
+
"-o",
|
|
20141
|
+
"-p",
|
|
20142
|
+
"-Q",
|
|
20143
|
+
"-R",
|
|
20144
|
+
"-S",
|
|
20145
|
+
"-W",
|
|
20146
|
+
"-w"
|
|
20147
|
+
]);
|
|
20148
|
+
function findMatchingTarget(value, targets) {
|
|
20149
|
+
return targets.find((t) => globToRegex(t.name).test(value)) || null;
|
|
20150
|
+
}
|
|
20151
|
+
function parseSSHArgs(args2) {
|
|
20152
|
+
let host = null;
|
|
20153
|
+
const remoteArgs = [];
|
|
20154
|
+
let i = 0;
|
|
20155
|
+
while (i < args2.length) {
|
|
20156
|
+
const arg = args2[i];
|
|
20157
|
+
if (SSH_FLAGS_WITH_VALUE.has(arg)) {
|
|
20158
|
+
i += 2;
|
|
20159
|
+
continue;
|
|
20160
|
+
}
|
|
20161
|
+
if (arg.startsWith("-")) {
|
|
20162
|
+
i++;
|
|
20163
|
+
continue;
|
|
20164
|
+
}
|
|
20165
|
+
if (!host) {
|
|
20166
|
+
host = arg.includes("@") ? arg.split("@").pop() : arg;
|
|
20167
|
+
i++;
|
|
20168
|
+
while (i < args2.length) {
|
|
20169
|
+
remoteArgs.push(args2[i]);
|
|
20170
|
+
i++;
|
|
20171
|
+
}
|
|
20172
|
+
break;
|
|
20173
|
+
}
|
|
20174
|
+
i++;
|
|
20175
|
+
}
|
|
20176
|
+
return {
|
|
20177
|
+
host,
|
|
20178
|
+
remoteCommand: remoteArgs.length > 0 ? remoteArgs.map(shellQuote).join(" ") : null
|
|
20179
|
+
};
|
|
20180
|
+
}
|
|
20181
|
+
function extractHostFromRemotePath(args2) {
|
|
20182
|
+
for (const arg of args2) {
|
|
20183
|
+
const match = arg.match(/^(?:[^@]+@)?([^:]+):/);
|
|
20184
|
+
if (match) return match[1];
|
|
20185
|
+
}
|
|
20186
|
+
return null;
|
|
20187
|
+
}
|
|
20188
|
+
function evaluateSSHCommand(cmd, config, targets, depth = 0) {
|
|
20189
|
+
const { command, args: args2 } = cmd;
|
|
20190
|
+
if (command === "scp" || command === "rsync") {
|
|
20191
|
+
const host2 = extractHostFromRemotePath(args2);
|
|
20192
|
+
if (!host2) return null;
|
|
20193
|
+
const target2 = findMatchingTarget(host2, targets);
|
|
20194
|
+
if (!target2) return null;
|
|
20195
|
+
if (target2.allowAll || !target2.overrides) {
|
|
20196
|
+
return {
|
|
20197
|
+
command,
|
|
20198
|
+
args: args2,
|
|
20199
|
+
decision: "allow",
|
|
20200
|
+
reason: `Trusted SSH host "${host2}"${target2.allowAll ? " (allowAll)" : ""}`,
|
|
20201
|
+
matchedRule: "trustedSSHHosts"
|
|
20202
|
+
};
|
|
20203
|
+
}
|
|
20204
|
+
if (target2.overrides.alwaysDeny.some((name) => name === command)) {
|
|
20205
|
+
return {
|
|
20206
|
+
command,
|
|
20207
|
+
args: args2,
|
|
20208
|
+
decision: "deny",
|
|
20209
|
+
reason: `Trusted SSH host "${host2}": "${command}" blocked by overrides`,
|
|
20210
|
+
matchedRule: "trustedSSHHosts"
|
|
20211
|
+
};
|
|
20212
|
+
}
|
|
20213
|
+
return {
|
|
20214
|
+
command,
|
|
20215
|
+
args: args2,
|
|
20216
|
+
decision: "allow",
|
|
20217
|
+
reason: `Trusted SSH host "${host2}"`,
|
|
20218
|
+
matchedRule: "trustedSSHHosts"
|
|
20219
|
+
};
|
|
20220
|
+
}
|
|
20221
|
+
const { host, remoteCommand } = parseSSHArgs(args2);
|
|
20222
|
+
if (!host) return null;
|
|
20223
|
+
const target = findMatchingTarget(host, targets);
|
|
20224
|
+
if (!target) return null;
|
|
20225
|
+
if (!remoteCommand) {
|
|
20226
|
+
return {
|
|
20227
|
+
command,
|
|
20228
|
+
args: args2,
|
|
20229
|
+
decision: "allow",
|
|
20230
|
+
reason: `Trusted SSH host "${host}" (interactive)`,
|
|
20231
|
+
matchedRule: "trustedSSHHosts"
|
|
20232
|
+
};
|
|
20233
|
+
}
|
|
20234
|
+
if (target.allowAll) {
|
|
20235
|
+
return {
|
|
20236
|
+
command,
|
|
20237
|
+
args: args2,
|
|
20238
|
+
decision: "allow",
|
|
20239
|
+
reason: `Trusted SSH host "${host}" (allowAll)`,
|
|
20240
|
+
matchedRule: "trustedSSHHosts"
|
|
20241
|
+
};
|
|
20242
|
+
}
|
|
20243
|
+
const parsed = parseCommand(remoteCommand);
|
|
20244
|
+
const result = evaluate(parsed, configWithContextOverrides(config, target), depth + 1);
|
|
20245
|
+
return {
|
|
20246
|
+
command,
|
|
20247
|
+
args: args2,
|
|
20248
|
+
decision: result.decision,
|
|
20249
|
+
reason: `Trusted SSH host "${host}": ${result.reason}`,
|
|
20250
|
+
matchedRule: "trustedSSHHosts"
|
|
20251
|
+
};
|
|
20252
|
+
}
|
|
20253
|
+
var DOCKER_EXEC_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20254
|
+
"-e",
|
|
20255
|
+
"--env",
|
|
20256
|
+
"--env-file",
|
|
20257
|
+
"-u",
|
|
20258
|
+
"--user",
|
|
20259
|
+
"-w",
|
|
20260
|
+
"--workdir",
|
|
20261
|
+
"--detach-keys"
|
|
20262
|
+
]);
|
|
20263
|
+
var INTERACTIVE_SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh"]);
|
|
20264
|
+
function shellQuote(arg) {
|
|
20265
|
+
if (/[\s"'\\$`!#&|;()<>]/.test(arg)) {
|
|
20266
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
20267
|
+
}
|
|
20268
|
+
return arg;
|
|
20269
|
+
}
|
|
20270
|
+
function configWithContextOverrides(config, target) {
|
|
20271
|
+
const overrideLayers = [];
|
|
20272
|
+
if (target?.overrides) overrideLayers.push(target.overrides);
|
|
20273
|
+
if (config.trustedContextOverrides) overrideLayers.push(config.trustedContextOverrides);
|
|
20274
|
+
if (overrideLayers.length === 0) return config;
|
|
20275
|
+
return {
|
|
20276
|
+
...config,
|
|
20277
|
+
layers: [...overrideLayers, ...config.layers]
|
|
20278
|
+
};
|
|
20279
|
+
}
|
|
20280
|
+
function evaluateRemoteCommand(remoteArgs, config, target, depth = 0) {
|
|
20281
|
+
if (target?.allowAll) {
|
|
20282
|
+
return { decision: "allow", reason: "allowAll target", details: [] };
|
|
20283
|
+
}
|
|
20284
|
+
const overriddenConfig = configWithContextOverrides(config, target);
|
|
20285
|
+
if (remoteArgs.length === 0) {
|
|
20286
|
+
return { decision: "allow", reason: "interactive", details: [] };
|
|
20287
|
+
}
|
|
20288
|
+
const remoteCmd = remoteArgs[0];
|
|
20289
|
+
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs.length === 1) {
|
|
20290
|
+
return { decision: "allow", reason: "interactive shell", details: [] };
|
|
20291
|
+
}
|
|
20292
|
+
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs[1] === "-c" && remoteArgs.length >= 3) {
|
|
20293
|
+
const innerCommand = remoteArgs.slice(2).join(" ");
|
|
20294
|
+
const parsed2 = parseCommand(innerCommand);
|
|
20295
|
+
return evaluate(parsed2, overriddenConfig, depth + 1);
|
|
20296
|
+
}
|
|
20297
|
+
const parsed = {
|
|
20298
|
+
commands: [{ command: remoteCmd, originalCommand: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
|
|
20299
|
+
hasSubshell: false,
|
|
20300
|
+
subshellCommands: [],
|
|
20301
|
+
parseError: false
|
|
20302
|
+
};
|
|
20303
|
+
return evaluate(parsed, overriddenConfig, depth + 1);
|
|
20295
20304
|
}
|
|
20296
|
-
|
|
20297
|
-
|
|
20298
|
-
|
|
20299
|
-
|
|
20300
|
-
|
|
20301
|
-
|
|
20302
|
-
|
|
20303
|
-
|
|
20304
|
-
|
|
20305
|
-
|
|
20306
|
-
|
|
20307
|
-
|
|
20308
|
-
|
|
20309
|
-
|
|
20310
|
-
|
|
20311
|
-
|
|
20312
|
-
|
|
20313
|
-
|
|
20305
|
+
function parseDockerExecArgs(args2) {
|
|
20306
|
+
let target = null;
|
|
20307
|
+
const remoteArgs = [];
|
|
20308
|
+
let i = 0;
|
|
20309
|
+
while (i < args2.length) {
|
|
20310
|
+
const arg = args2[i];
|
|
20311
|
+
if (DOCKER_EXEC_FLAGS_WITH_VALUE.has(arg)) {
|
|
20312
|
+
i += 2;
|
|
20313
|
+
continue;
|
|
20314
|
+
}
|
|
20315
|
+
if (arg.startsWith("-")) {
|
|
20316
|
+
i++;
|
|
20317
|
+
continue;
|
|
20318
|
+
}
|
|
20319
|
+
if (!target) {
|
|
20320
|
+
target = arg;
|
|
20321
|
+
i++;
|
|
20322
|
+
while (i < args2.length) {
|
|
20323
|
+
remoteArgs.push(args2[i]);
|
|
20324
|
+
i++;
|
|
20325
|
+
}
|
|
20314
20326
|
break;
|
|
20315
20327
|
}
|
|
20328
|
+
i++;
|
|
20316
20329
|
}
|
|
20317
|
-
|
|
20318
|
-
|
|
20319
|
-
|
|
20320
|
-
|
|
20321
|
-
|
|
20322
|
-
|
|
20323
|
-
|
|
20324
|
-
|
|
20325
|
-
|
|
20330
|
+
return { target, remoteArgs };
|
|
20331
|
+
}
|
|
20332
|
+
function evaluateDockerExec(cmd, config, targets, depth = 0) {
|
|
20333
|
+
const { command, args: args2 } = cmd;
|
|
20334
|
+
if (args2[0] !== "exec") return null;
|
|
20335
|
+
const { target: containerName, remoteArgs } = parseDockerExecArgs(args2.slice(1));
|
|
20336
|
+
if (!containerName) return null;
|
|
20337
|
+
const matched = findMatchingTarget(containerName, targets);
|
|
20338
|
+
if (!matched) return null;
|
|
20339
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
20340
|
+
return {
|
|
20341
|
+
command,
|
|
20342
|
+
args: args2,
|
|
20343
|
+
decision: result.decision,
|
|
20344
|
+
reason: `Trusted Docker container "${containerName}" (${result.reason})`,
|
|
20345
|
+
matchedRule: "trustedDockerContainers"
|
|
20346
|
+
};
|
|
20347
|
+
}
|
|
20348
|
+
var KUBECTL_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20349
|
+
"-n",
|
|
20350
|
+
"--namespace",
|
|
20351
|
+
"-c",
|
|
20352
|
+
"--container",
|
|
20353
|
+
"--context",
|
|
20354
|
+
"--cluster",
|
|
20355
|
+
"--kubeconfig",
|
|
20356
|
+
"-s",
|
|
20357
|
+
"--server",
|
|
20358
|
+
"--token",
|
|
20359
|
+
"--user",
|
|
20360
|
+
"--as",
|
|
20361
|
+
"--as-group",
|
|
20362
|
+
"--certificate-authority",
|
|
20363
|
+
"--client-certificate",
|
|
20364
|
+
"--client-key",
|
|
20365
|
+
"-l",
|
|
20366
|
+
"--selector",
|
|
20367
|
+
"-f",
|
|
20368
|
+
"--filename",
|
|
20369
|
+
"--cache-dir",
|
|
20370
|
+
"--request-timeout",
|
|
20371
|
+
"-o",
|
|
20372
|
+
"--output"
|
|
20373
|
+
]);
|
|
20374
|
+
function parseKubectlExecArgs(args2) {
|
|
20375
|
+
let context = null;
|
|
20376
|
+
let pod = null;
|
|
20377
|
+
const remoteArgs = [];
|
|
20378
|
+
let i = 0;
|
|
20379
|
+
while (i < args2.length) {
|
|
20380
|
+
const arg = args2[i];
|
|
20381
|
+
if (arg === "--") {
|
|
20382
|
+
i++;
|
|
20383
|
+
while (i < args2.length) {
|
|
20384
|
+
remoteArgs.push(args2[i]);
|
|
20385
|
+
i++;
|
|
20386
|
+
}
|
|
20387
|
+
break;
|
|
20388
|
+
}
|
|
20389
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
20390
|
+
if (arg.startsWith("--context=")) {
|
|
20391
|
+
context = arg.split("=")[1];
|
|
20326
20392
|
}
|
|
20393
|
+
i++;
|
|
20394
|
+
continue;
|
|
20395
|
+
}
|
|
20396
|
+
if (KUBECTL_FLAGS_WITH_VALUE.has(arg)) {
|
|
20397
|
+
if (arg === "--context") context = args2[i + 1] || null;
|
|
20398
|
+
i += 2;
|
|
20399
|
+
continue;
|
|
20400
|
+
}
|
|
20401
|
+
if (arg.startsWith("-")) {
|
|
20402
|
+
i++;
|
|
20403
|
+
continue;
|
|
20404
|
+
}
|
|
20405
|
+
if (!pod) {
|
|
20406
|
+
pod = arg;
|
|
20327
20407
|
}
|
|
20408
|
+
i++;
|
|
20328
20409
|
}
|
|
20329
|
-
|
|
20330
|
-
...workspaceLayer ? [workspaceLayer] : [],
|
|
20331
|
-
...userLayer ? [userLayer] : [],
|
|
20332
|
-
defaultLayer
|
|
20333
|
-
];
|
|
20334
|
-
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
20335
|
-
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
20336
|
-
return config;
|
|
20410
|
+
return { context, pod, remoteArgs };
|
|
20337
20411
|
}
|
|
20338
|
-
function
|
|
20339
|
-
|
|
20340
|
-
|
|
20341
|
-
|
|
20342
|
-
|
|
20343
|
-
|
|
20344
|
-
|
|
20412
|
+
function evaluateKubectlExec(cmd, config, targets, depth = 0) {
|
|
20413
|
+
const { command, args: args2 } = cmd;
|
|
20414
|
+
if (args2[0] !== "exec") return null;
|
|
20415
|
+
const { context, pod, remoteArgs } = parseKubectlExecArgs(args2.slice(1));
|
|
20416
|
+
if (!context) return null;
|
|
20417
|
+
const matched = findMatchingTarget(context, targets);
|
|
20418
|
+
if (!matched) return null;
|
|
20419
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
20420
|
+
return {
|
|
20421
|
+
command,
|
|
20422
|
+
args: args2,
|
|
20423
|
+
decision: result.decision,
|
|
20424
|
+
reason: `Trusted kubectl context "${context}"${pod ? `, pod "${pod}"` : ""} (${result.reason})`,
|
|
20425
|
+
matchedRule: "trustedKubectlContexts"
|
|
20426
|
+
};
|
|
20427
|
+
}
|
|
20428
|
+
var SPRITE_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20429
|
+
"-o",
|
|
20430
|
+
"--org",
|
|
20431
|
+
"-s",
|
|
20432
|
+
"--sprite"
|
|
20433
|
+
]);
|
|
20434
|
+
function parseSpriteExecArgs(args2) {
|
|
20435
|
+
let spriteName = null;
|
|
20436
|
+
const remoteArgs = [];
|
|
20437
|
+
let foundExec = false;
|
|
20438
|
+
let i = 0;
|
|
20439
|
+
while (i < args2.length) {
|
|
20440
|
+
const arg = args2[i];
|
|
20441
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
20442
|
+
if (arg.startsWith("--sprite=")) {
|
|
20443
|
+
spriteName = arg.split("=")[1];
|
|
20444
|
+
}
|
|
20445
|
+
i++;
|
|
20446
|
+
continue;
|
|
20345
20447
|
}
|
|
20346
|
-
|
|
20347
|
-
|
|
20348
|
-
|
|
20448
|
+
if (SPRITE_FLAGS_WITH_VALUE.has(arg)) {
|
|
20449
|
+
if (arg === "-s" || arg === "--sprite") {
|
|
20450
|
+
spriteName = args2[i + 1] || null;
|
|
20451
|
+
}
|
|
20452
|
+
i += 2;
|
|
20453
|
+
continue;
|
|
20454
|
+
}
|
|
20455
|
+
if (arg === "--debug") {
|
|
20456
|
+
i++;
|
|
20457
|
+
continue;
|
|
20458
|
+
}
|
|
20459
|
+
if (arg.startsWith("-")) {
|
|
20460
|
+
i++;
|
|
20461
|
+
continue;
|
|
20462
|
+
}
|
|
20463
|
+
if (!foundExec) {
|
|
20464
|
+
if (arg === "exec" || arg === "x" || arg === "console" || arg === "c") {
|
|
20465
|
+
foundExec = true;
|
|
20466
|
+
i++;
|
|
20467
|
+
continue;
|
|
20468
|
+
}
|
|
20469
|
+
return { spriteName: null, remoteArgs: [] };
|
|
20470
|
+
}
|
|
20471
|
+
while (i < args2.length) {
|
|
20472
|
+
remoteArgs.push(args2[i]);
|
|
20473
|
+
i++;
|
|
20474
|
+
}
|
|
20475
|
+
break;
|
|
20349
20476
|
}
|
|
20350
|
-
return
|
|
20477
|
+
return { spriteName, remoteArgs };
|
|
20478
|
+
}
|
|
20479
|
+
function evaluateSpriteExec(cmd, config, targets, depth = 0) {
|
|
20480
|
+
const { command, args: args2 } = cmd;
|
|
20481
|
+
const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
|
|
20482
|
+
if (!spriteName) return null;
|
|
20483
|
+
const matched = findMatchingTarget(spriteName, targets);
|
|
20484
|
+
if (!matched) return null;
|
|
20485
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
20486
|
+
return {
|
|
20487
|
+
command,
|
|
20488
|
+
args: args2,
|
|
20489
|
+
decision: result.decision,
|
|
20490
|
+
reason: `Trusted sprite "${spriteName}" (${result.reason})`,
|
|
20491
|
+
matchedRule: "trustedSprites"
|
|
20492
|
+
};
|
|
20351
20493
|
}
|
|
20352
|
-
var
|
|
20353
|
-
|
|
20354
|
-
|
|
20355
|
-
|
|
20494
|
+
var FLY_SSH_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20495
|
+
"-a",
|
|
20496
|
+
"--app",
|
|
20497
|
+
"-C",
|
|
20498
|
+
"--command",
|
|
20499
|
+
"-o",
|
|
20500
|
+
"--org",
|
|
20501
|
+
"-r",
|
|
20502
|
+
"--region",
|
|
20503
|
+
"-u",
|
|
20504
|
+
"--user",
|
|
20505
|
+
"--address"
|
|
20356
20506
|
]);
|
|
20357
|
-
function
|
|
20358
|
-
|
|
20359
|
-
|
|
20360
|
-
|
|
20361
|
-
|
|
20362
|
-
|
|
20363
|
-
|
|
20364
|
-
|
|
20365
|
-
|
|
20366
|
-
|
|
20367
|
-
|
|
20368
|
-
|
|
20369
|
-
`[warden] Warning: rule for "${rule.command}" has argPattern matching "${lit}" \u2014 this won't work as expected. Rules are matched by the command being run, not its arguments. If you want to control "${lit}", add a separate rule with command: "${lit}".
|
|
20370
|
-
`
|
|
20371
|
-
);
|
|
20372
|
-
}
|
|
20373
|
-
}
|
|
20507
|
+
function parseFlySSHArgs(args2) {
|
|
20508
|
+
let app = null;
|
|
20509
|
+
const remoteArgs = [];
|
|
20510
|
+
let isSSH = false;
|
|
20511
|
+
let foundConsole = false;
|
|
20512
|
+
let i = 0;
|
|
20513
|
+
while (i < args2.length) {
|
|
20514
|
+
const arg = args2[i];
|
|
20515
|
+
if (arg.startsWith("--app=")) {
|
|
20516
|
+
app = arg.slice(6);
|
|
20517
|
+
i++;
|
|
20518
|
+
continue;
|
|
20374
20519
|
}
|
|
20375
|
-
|
|
20376
|
-
|
|
20377
|
-
|
|
20378
|
-
let cleaned = pattern.replace(/^\^?\(?(.*?)\)?\$?$/, "$1");
|
|
20379
|
-
return cleaned.split("|").map((s) => s.trim()).filter((s) => /^[a-z][a-z0-9_-]*$/i.test(s));
|
|
20380
|
-
}
|
|
20381
|
-
function extractLayer(raw) {
|
|
20382
|
-
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
20383
|
-
for (const rule of rules) {
|
|
20384
|
-
if (rule && typeof rule === "object") {
|
|
20385
|
-
if (rule.default && !isValidDecision(rule.default)) {
|
|
20386
|
-
process.stderr.write(`[warden] Warning: invalid rule default "${rule.default}" for "${rule.command}", using "ask"
|
|
20387
|
-
`);
|
|
20388
|
-
rule.default = "ask";
|
|
20520
|
+
if (FLY_SSH_FLAGS_WITH_VALUE.has(arg)) {
|
|
20521
|
+
if (arg === "-a" || arg === "--app") {
|
|
20522
|
+
app = args2[i + 1] || null;
|
|
20389
20523
|
}
|
|
20390
|
-
if (
|
|
20391
|
-
|
|
20392
|
-
|
|
20393
|
-
|
|
20394
|
-
|
|
20395
|
-
|
|
20524
|
+
if ((arg === "-C" || arg === "--command") && foundConsole) {
|
|
20525
|
+
const cmdValue = args2[i + 1];
|
|
20526
|
+
if (cmdValue) {
|
|
20527
|
+
const parsed = parseCommand(cmdValue);
|
|
20528
|
+
if (!parsed.parseError && parsed.commands.length > 0) {
|
|
20529
|
+
const cmd = parsed.commands[0];
|
|
20530
|
+
remoteArgs.push(cmd.command, ...cmd.args);
|
|
20396
20531
|
}
|
|
20397
20532
|
}
|
|
20533
|
+
i += 2;
|
|
20534
|
+
continue;
|
|
20398
20535
|
}
|
|
20399
|
-
|
|
20536
|
+
i += 2;
|
|
20537
|
+
continue;
|
|
20400
20538
|
}
|
|
20401
|
-
|
|
20402
|
-
|
|
20403
|
-
|
|
20404
|
-
|
|
20405
|
-
|
|
20406
|
-
};
|
|
20407
|
-
}
|
|
20408
|
-
function parseTrustedList(raw) {
|
|
20409
|
-
return raw.map((entry) => {
|
|
20410
|
-
if (typeof entry === "string") return { name: entry };
|
|
20411
|
-
if (entry && typeof entry === "object" && "name" in entry) {
|
|
20412
|
-
const obj = entry;
|
|
20413
|
-
const target = { name: String(obj.name) };
|
|
20414
|
-
if (obj.allowAll === true) target.allowAll = true;
|
|
20415
|
-
if (obj.overrides && typeof obj.overrides === "object") {
|
|
20416
|
-
target.overrides = extractLayer(obj.overrides);
|
|
20539
|
+
if (arg === "--") {
|
|
20540
|
+
i++;
|
|
20541
|
+
while (i < args2.length) {
|
|
20542
|
+
remoteArgs.push(args2[i]);
|
|
20543
|
+
i++;
|
|
20417
20544
|
}
|
|
20418
|
-
|
|
20545
|
+
break;
|
|
20419
20546
|
}
|
|
20420
|
-
|
|
20421
|
-
|
|
20422
|
-
}
|
|
20423
|
-
var VALID_REMOTE_CONTEXTS = /* @__PURE__ */ new Set(["ssh", "docker", "kubectl", "sprite", "fly"]);
|
|
20424
|
-
function parseTrustedRemotes(raw) {
|
|
20425
|
-
const results = [];
|
|
20426
|
-
for (const entry of raw) {
|
|
20427
|
-
if (!entry || typeof entry !== "object") continue;
|
|
20428
|
-
const obj = entry;
|
|
20429
|
-
const context = String(obj.context || "");
|
|
20430
|
-
if (!VALID_REMOTE_CONTEXTS.has(context)) {
|
|
20431
|
-
process.stderr.write(`[warden] Warning: unknown remote context "${context}", skipping
|
|
20432
|
-
`);
|
|
20547
|
+
if (arg.startsWith("-")) {
|
|
20548
|
+
i++;
|
|
20433
20549
|
continue;
|
|
20434
20550
|
}
|
|
20435
|
-
|
|
20436
|
-
|
|
20437
|
-
|
|
20438
|
-
|
|
20439
|
-
if (obj.overrides && typeof obj.overrides === "object") {
|
|
20440
|
-
remote.overrides = extractLayer(obj.overrides);
|
|
20551
|
+
if (!isSSH && arg === "ssh") {
|
|
20552
|
+
isSSH = true;
|
|
20553
|
+
i++;
|
|
20554
|
+
continue;
|
|
20441
20555
|
}
|
|
20442
|
-
|
|
20443
|
-
|
|
20444
|
-
|
|
20445
|
-
|
|
20446
|
-
function parseTargetPolicies(raw) {
|
|
20447
|
-
const results = [];
|
|
20448
|
-
for (const entry of raw) {
|
|
20449
|
-
if (!entry || typeof entry !== "object") continue;
|
|
20450
|
-
const obj = entry;
|
|
20451
|
-
const type = String(obj.type || "");
|
|
20452
|
-
const rawDecision = String(obj.decision || "allow");
|
|
20453
|
-
const decision = isValidDecision(rawDecision) ? rawDecision : "allow";
|
|
20454
|
-
const base = {
|
|
20455
|
-
commands: Array.isArray(obj.commands) ? obj.commands.map(String) : void 0,
|
|
20456
|
-
decision,
|
|
20457
|
-
reason: obj.reason ? String(obj.reason) : void 0,
|
|
20458
|
-
allowAll: obj.allowAll === true ? true : void 0
|
|
20459
|
-
};
|
|
20460
|
-
switch (type) {
|
|
20461
|
-
case "path": {
|
|
20462
|
-
const path = String(obj.path || "");
|
|
20463
|
-
if (!path) continue;
|
|
20464
|
-
const policy = { ...base, type: "path", path };
|
|
20465
|
-
if (obj.recursive === false) policy.recursive = false;
|
|
20466
|
-
results.push(policy);
|
|
20467
|
-
break;
|
|
20468
|
-
}
|
|
20469
|
-
case "database": {
|
|
20470
|
-
const host = String(obj.host || "");
|
|
20471
|
-
if (!host) continue;
|
|
20472
|
-
const policy = { ...base, type: "database", host };
|
|
20473
|
-
if (typeof obj.port === "number") policy.port = obj.port;
|
|
20474
|
-
if (obj.database) policy.database = String(obj.database);
|
|
20475
|
-
results.push(policy);
|
|
20476
|
-
break;
|
|
20477
|
-
}
|
|
20478
|
-
case "endpoint": {
|
|
20479
|
-
const pattern = String(obj.pattern || "");
|
|
20480
|
-
if (!pattern) continue;
|
|
20481
|
-
results.push({ ...base, type: "endpoint", pattern });
|
|
20482
|
-
break;
|
|
20483
|
-
}
|
|
20484
|
-
default:
|
|
20485
|
-
process.stderr.write(`[warden] Warning: unknown target policy type "${type}", skipping
|
|
20486
|
-
`);
|
|
20556
|
+
if (isSSH && !foundConsole && (arg === "console" || arg === "sftp")) {
|
|
20557
|
+
foundConsole = true;
|
|
20558
|
+
i++;
|
|
20559
|
+
continue;
|
|
20487
20560
|
}
|
|
20561
|
+
i++;
|
|
20488
20562
|
}
|
|
20489
|
-
return
|
|
20490
|
-
}
|
|
20491
|
-
function parseLegacyPaths(raw) {
|
|
20492
|
-
const results = [];
|
|
20493
|
-
for (const e of raw) {
|
|
20494
|
-
if (!e || typeof e !== "object") continue;
|
|
20495
|
-
const obj = e;
|
|
20496
|
-
const decision = String(obj.decision || "allow");
|
|
20497
|
-
if (!isValidDecision(decision)) continue;
|
|
20498
|
-
const path = String(obj.path || "");
|
|
20499
|
-
if (!path) continue;
|
|
20500
|
-
const tp = { type: "path", path, decision };
|
|
20501
|
-
if (obj.recursive === false) tp.recursive = false;
|
|
20502
|
-
if (Array.isArray(obj.commands)) tp.commands = obj.commands.map(String);
|
|
20503
|
-
if (obj.reason) tp.reason = String(obj.reason);
|
|
20504
|
-
results.push(tp);
|
|
20505
|
-
}
|
|
20506
|
-
return results;
|
|
20507
|
-
}
|
|
20508
|
-
function parseLegacyDatabases(raw) {
|
|
20509
|
-
const results = [];
|
|
20510
|
-
for (const e of raw) {
|
|
20511
|
-
if (!e || typeof e !== "object") continue;
|
|
20512
|
-
const obj = e;
|
|
20513
|
-
const decision = String(obj.decision || "allow");
|
|
20514
|
-
if (!isValidDecision(decision)) continue;
|
|
20515
|
-
const host = String(obj.host || "");
|
|
20516
|
-
if (!host) continue;
|
|
20517
|
-
const td = { type: "database", host, decision };
|
|
20518
|
-
if (typeof obj.port === "number") td.port = obj.port;
|
|
20519
|
-
if (obj.database) td.database = String(obj.database);
|
|
20520
|
-
if (Array.isArray(obj.commands)) td.commands = obj.commands.map(String);
|
|
20521
|
-
if (obj.reason) td.reason = String(obj.reason);
|
|
20522
|
-
results.push(td);
|
|
20523
|
-
}
|
|
20524
|
-
return results;
|
|
20525
|
-
}
|
|
20526
|
-
function parseLegacyEndpoints(raw) {
|
|
20527
|
-
const results = [];
|
|
20528
|
-
for (const e of raw) {
|
|
20529
|
-
if (!e || typeof e !== "object") continue;
|
|
20530
|
-
const obj = e;
|
|
20531
|
-
const decision = String(obj.decision || "allow");
|
|
20532
|
-
if (!isValidDecision(decision)) continue;
|
|
20533
|
-
const pattern = String(obj.pattern || "");
|
|
20534
|
-
if (!pattern) continue;
|
|
20535
|
-
const te = { type: "endpoint", pattern, decision };
|
|
20536
|
-
if (Array.isArray(obj.commands)) te.commands = obj.commands.map(String);
|
|
20537
|
-
if (obj.reason) te.reason = String(obj.reason);
|
|
20538
|
-
results.push(te);
|
|
20539
|
-
}
|
|
20540
|
-
return results;
|
|
20563
|
+
return { app, remoteArgs, isSSH: isSSH && foundConsole };
|
|
20541
20564
|
}
|
|
20542
|
-
|
|
20543
|
-
|
|
20544
|
-
|
|
20545
|
-
|
|
20546
|
-
|
|
20547
|
-
|
|
20548
|
-
|
|
20549
|
-
|
|
20550
|
-
|
|
20551
|
-
|
|
20552
|
-
|
|
20553
|
-
|
|
20554
|
-
|
|
20555
|
-
|
|
20556
|
-
|
|
20557
|
-
if (Array.isArray(raw[key])) {
|
|
20558
|
-
const remotes = parseTrustedList(raw[key]).map((t) => ({ ...t, context }));
|
|
20559
|
-
config.trustedRemotes = [...config.trustedRemotes || [], ...remotes];
|
|
20560
|
-
}
|
|
20561
|
-
}
|
|
20562
|
-
if (Array.isArray(raw.trustedPaths)) {
|
|
20563
|
-
config.targetPolicies = [...config.targetPolicies || [], ...parseLegacyPaths(raw.trustedPaths)];
|
|
20564
|
-
}
|
|
20565
|
-
if (Array.isArray(raw.trustedDatabases)) {
|
|
20566
|
-
config.targetPolicies = [...config.targetPolicies || [], ...parseLegacyDatabases(raw.trustedDatabases)];
|
|
20567
|
-
}
|
|
20568
|
-
if (Array.isArray(raw.trustedEndpoints)) {
|
|
20569
|
-
config.targetPolicies = [...config.targetPolicies || [], ...parseLegacyEndpoints(raw.trustedEndpoints)];
|
|
20570
|
-
}
|
|
20571
|
-
if (typeof raw.defaultDecision === "string") {
|
|
20572
|
-
if (isValidDecision(raw.defaultDecision)) {
|
|
20573
|
-
config.defaultDecision = raw.defaultDecision;
|
|
20574
|
-
} else {
|
|
20575
|
-
process.stderr.write(`[warden] Warning: invalid defaultDecision "${raw.defaultDecision}", ignoring
|
|
20576
|
-
`);
|
|
20577
|
-
}
|
|
20578
|
-
}
|
|
20579
|
-
if (typeof raw.askOnSubshell === "boolean") {
|
|
20580
|
-
config.askOnSubshell = raw.askOnSubshell;
|
|
20581
|
-
}
|
|
20582
|
-
if (typeof raw.notifyOnAsk === "boolean") {
|
|
20583
|
-
config.notifyOnAsk = raw.notifyOnAsk;
|
|
20584
|
-
}
|
|
20585
|
-
if (typeof raw.notifyOnDeny === "boolean") {
|
|
20586
|
-
config.notifyOnDeny = raw.notifyOnDeny;
|
|
20587
|
-
}
|
|
20588
|
-
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
20589
|
-
const overrides = raw.trustedContextOverrides;
|
|
20590
|
-
const layer = extractLayer(overrides);
|
|
20591
|
-
if (config.trustedContextOverrides) {
|
|
20592
|
-
config.trustedContextOverrides = {
|
|
20593
|
-
alwaysAllow: [...layer.alwaysAllow, ...config.trustedContextOverrides.alwaysAllow],
|
|
20594
|
-
alwaysDeny: [...layer.alwaysDeny, ...config.trustedContextOverrides.alwaysDeny],
|
|
20595
|
-
rules: [...layer.rules, ...config.trustedContextOverrides.rules]
|
|
20596
|
-
};
|
|
20597
|
-
} else {
|
|
20598
|
-
config.trustedContextOverrides = layer;
|
|
20599
|
-
}
|
|
20600
|
-
}
|
|
20565
|
+
function evaluateFlyCommand(cmd, config, targets, depth = 0) {
|
|
20566
|
+
const { command, args: args2 } = cmd;
|
|
20567
|
+
const { app, remoteArgs, isSSH } = parseFlySSHArgs(args2);
|
|
20568
|
+
if (!isSSH) return null;
|
|
20569
|
+
if (!app) return null;
|
|
20570
|
+
const matched = findMatchingTarget(app, targets);
|
|
20571
|
+
if (!matched) return null;
|
|
20572
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
20573
|
+
return {
|
|
20574
|
+
command,
|
|
20575
|
+
args: args2,
|
|
20576
|
+
decision: result.decision,
|
|
20577
|
+
reason: `Trusted Fly app "${app}" (${result.reason})`,
|
|
20578
|
+
matchedRule: "trustedFlyApps"
|
|
20579
|
+
};
|
|
20601
20580
|
}
|
|
20602
20581
|
|
|
20603
20582
|
// src/core.ts
|
|
@@ -20611,6 +20590,7 @@ function wardenEvalWithConfig(command, config, cwd) {
|
|
|
20611
20590
|
}
|
|
20612
20591
|
|
|
20613
20592
|
// src/cli.ts
|
|
20593
|
+
setQuiet(false);
|
|
20614
20594
|
function printHelp() {
|
|
20615
20595
|
process.stdout.write(
|
|
20616
20596
|
[
|