claude-warden 2.3.2 → 2.4.1
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/.github/hooks/warden.json +12 -0
- package/README.md +58 -1
- package/dist/cli.cjs +20684 -0
- package/dist/codex-export.cjs +1274 -1289
- package/dist/copilot.cjs +20653 -0
- package/dist/index.cjs +1233 -1255
- package/package.json +9 -2
package/dist/index.cjs
CHANGED
|
@@ -12481,7 +12481,7 @@ var require_log = __commonJS({
|
|
|
12481
12481
|
if (logLevel === "debug")
|
|
12482
12482
|
console.log(...messages);
|
|
12483
12483
|
}
|
|
12484
|
-
function
|
|
12484
|
+
function warn2(logLevel, warning) {
|
|
12485
12485
|
if (logLevel === "debug" || logLevel === "warn") {
|
|
12486
12486
|
if (typeof node_process.emitWarning === "function")
|
|
12487
12487
|
node_process.emitWarning(warning);
|
|
@@ -12490,7 +12490,7 @@ var require_log = __commonJS({
|
|
|
12490
12490
|
}
|
|
12491
12491
|
}
|
|
12492
12492
|
exports2.debug = debug;
|
|
12493
|
-
exports2.warn =
|
|
12493
|
+
exports2.warn = warn2;
|
|
12494
12494
|
}
|
|
12495
12495
|
});
|
|
12496
12496
|
|
|
@@ -18450,7 +18450,7 @@ function regexFallbackParse(input) {
|
|
|
18450
18450
|
}
|
|
18451
18451
|
|
|
18452
18452
|
// src/evaluator.ts
|
|
18453
|
-
var
|
|
18453
|
+
var import_os2 = require("os");
|
|
18454
18454
|
|
|
18455
18455
|
// src/targets.ts
|
|
18456
18456
|
var import_path2 = require("path");
|
|
@@ -18728,984 +18728,152 @@ function evaluateTargetPolicies(cmd, cwd, config) {
|
|
|
18728
18728
|
return results[0];
|
|
18729
18729
|
}
|
|
18730
18730
|
|
|
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" };
|
|
18731
|
+
// src/rules.ts
|
|
18732
|
+
var import_fs = require("fs");
|
|
18733
|
+
var import_yaml = __toESM(require_dist2(), 1);
|
|
18734
|
+
var import_os = require("os");
|
|
18735
|
+
var import_path3 = require("path");
|
|
18736
|
+
|
|
18737
|
+
// src/defaults.ts
|
|
18738
|
+
var SAFE_DEV_TOOLS = [
|
|
18739
|
+
"jest",
|
|
18740
|
+
"vitest",
|
|
18741
|
+
"tsc",
|
|
18742
|
+
"eslint",
|
|
18743
|
+
"prettier",
|
|
18744
|
+
"mkdirp",
|
|
18745
|
+
"concurrently",
|
|
18746
|
+
"turbo",
|
|
18747
|
+
"next",
|
|
18748
|
+
"nuxt",
|
|
18749
|
+
"vite",
|
|
18750
|
+
"astro",
|
|
18751
|
+
"playwright",
|
|
18752
|
+
"cypress",
|
|
18753
|
+
"mocha",
|
|
18754
|
+
"nyc",
|
|
18755
|
+
"c8",
|
|
18756
|
+
"ts-jest",
|
|
18757
|
+
"tsup",
|
|
18758
|
+
"esbuild",
|
|
18759
|
+
"rollup",
|
|
18760
|
+
"webpack",
|
|
18761
|
+
"prisma",
|
|
18762
|
+
"drizzle-kit",
|
|
18763
|
+
"typeorm",
|
|
18764
|
+
"knex",
|
|
18765
|
+
"sequelize-cli",
|
|
18766
|
+
"tailwindcss",
|
|
18767
|
+
"postcss",
|
|
18768
|
+
"autoprefixer",
|
|
18769
|
+
"lint-staged",
|
|
18770
|
+
"husky",
|
|
18771
|
+
"changeset",
|
|
18772
|
+
"semantic-release",
|
|
18773
|
+
"lerna",
|
|
18774
|
+
"nx",
|
|
18775
|
+
"create-react-app",
|
|
18776
|
+
"create-next-app",
|
|
18777
|
+
"create-vite",
|
|
18778
|
+
"degit",
|
|
18779
|
+
"storybook",
|
|
18780
|
+
"wrangler",
|
|
18781
|
+
"netlify",
|
|
18782
|
+
"vercel",
|
|
18783
|
+
"json",
|
|
18784
|
+
"biome"
|
|
18785
|
+
];
|
|
18786
|
+
var SCRIPT_RUNNERS = ["tsx", "ts-node", "nodemon"];
|
|
18787
|
+
var REGISTRY_OPS = ["publish", "unpublish", "deprecate", "owner", "access", "token", "adduser", "login", "logout"];
|
|
18788
|
+
var SAFE_PKG_MANAGER_CMDS = [
|
|
18789
|
+
"install",
|
|
18790
|
+
"add",
|
|
18791
|
+
"remove",
|
|
18792
|
+
"uninstall",
|
|
18793
|
+
"update",
|
|
18794
|
+
"upgrade",
|
|
18795
|
+
"outdated",
|
|
18796
|
+
"ls",
|
|
18797
|
+
"list",
|
|
18798
|
+
"run",
|
|
18799
|
+
"test",
|
|
18800
|
+
"start",
|
|
18801
|
+
"build",
|
|
18802
|
+
"init",
|
|
18803
|
+
"create",
|
|
18804
|
+
"info",
|
|
18805
|
+
"view",
|
|
18806
|
+
"show",
|
|
18807
|
+
"why",
|
|
18808
|
+
"pack",
|
|
18809
|
+
"cache",
|
|
18810
|
+
"config",
|
|
18811
|
+
"get",
|
|
18812
|
+
"set",
|
|
18813
|
+
"version",
|
|
18814
|
+
"help",
|
|
18815
|
+
"exec",
|
|
18816
|
+
"dedupe",
|
|
18817
|
+
"prune",
|
|
18818
|
+
"audit",
|
|
18819
|
+
"completion",
|
|
18820
|
+
"whoami"
|
|
18821
|
+
];
|
|
18822
|
+
var VERSION_HELP_FLAGS = {
|
|
18823
|
+
match: { anyArgMatches: ["^--(version|help)$", "^-[vh]$"] },
|
|
18824
|
+
decision: "allow",
|
|
18825
|
+
description: "Version/help flags"
|
|
18826
|
+
};
|
|
18827
|
+
function anyArgMatchesPattern(items) {
|
|
18828
|
+
return `^(${items.join("|")})$`;
|
|
18861
18829
|
}
|
|
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
|
-
}
|
|
18830
|
+
function safeDevToolsPattern() {
|
|
18879
18831
|
return {
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
18832
|
+
match: { anyArgMatches: [anyArgMatchesPattern(SAFE_DEV_TOOLS)] },
|
|
18833
|
+
decision: "allow",
|
|
18834
|
+
description: "Well-known dev tools"
|
|
18883
18835
|
};
|
|
18884
18836
|
}
|
|
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
|
-
}
|
|
18837
|
+
function scriptRunnersPattern() {
|
|
18915
18838
|
return {
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
reason: `Default for "${command}"`,
|
|
18920
|
-
matchedRule: `${command}:default`
|
|
18839
|
+
match: { anyArgMatches: [anyArgMatchesPattern(SCRIPT_RUNNERS)] },
|
|
18840
|
+
decision: "ask",
|
|
18841
|
+
reason: "Script runners can execute arbitrary code"
|
|
18921
18842
|
};
|
|
18922
18843
|
}
|
|
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);
|
|
18844
|
+
function registryOpsPattern() {
|
|
19005
18845
|
return {
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
|
|
19009
|
-
originalCommand: subcommand,
|
|
19010
|
-
args: subArgs,
|
|
19011
|
-
envPrefixes: [],
|
|
19012
|
-
raw: [subcommand, ...subArgs].join(" ")
|
|
19013
|
-
}
|
|
18846
|
+
match: { anyArgMatches: [anyArgMatchesPattern(REGISTRY_OPS)] },
|
|
18847
|
+
decision: "ask",
|
|
18848
|
+
reason: "Registry modification"
|
|
19014
18849
|
};
|
|
19015
18850
|
}
|
|
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);
|
|
18851
|
+
function pkgManagerRule(command, extraSafeCmds = []) {
|
|
18852
|
+
const safeCmds = [...SAFE_PKG_MANAGER_CMDS, ...extraSafeCmds];
|
|
19041
18853
|
return {
|
|
19042
18854
|
command,
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
18855
|
+
default: "ask",
|
|
18856
|
+
argPatterns: [
|
|
18857
|
+
registryOpsPattern(),
|
|
18858
|
+
{
|
|
18859
|
+
match: { anyArgMatches: [anyArgMatchesPattern(safeCmds)] },
|
|
18860
|
+
decision: "allow",
|
|
18861
|
+
description: `Standard ${command} commands`
|
|
18862
|
+
},
|
|
18863
|
+
VERSION_HELP_FLAGS
|
|
18864
|
+
]
|
|
19047
18865
|
};
|
|
19048
18866
|
}
|
|
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
|
-
};
|
|
18867
|
+
function pkgRunnerRule(command) {
|
|
18868
|
+
return {
|
|
18869
|
+
command,
|
|
18870
|
+
default: "ask",
|
|
18871
|
+
argPatterns: [
|
|
18872
|
+
safeDevToolsPattern(),
|
|
18873
|
+
scriptRunnersPattern(),
|
|
18874
|
+
VERSION_HELP_FLAGS
|
|
18875
|
+
]
|
|
18876
|
+
};
|
|
19709
18877
|
}
|
|
19710
18878
|
var DEFAULT_CONFIG = {
|
|
19711
18879
|
defaultDecision: "ask",
|
|
@@ -20288,316 +19456,1129 @@ var DEFAULT_CONFIG = {
|
|
|
20288
19456
|
}]
|
|
20289
19457
|
};
|
|
20290
19458
|
|
|
20291
|
-
// src/rules.ts
|
|
20292
|
-
var VALID_DECISIONS = /* @__PURE__ */ new Set(["allow", "deny", "ask"]);
|
|
20293
|
-
function isValidDecision(value) {
|
|
20294
|
-
return VALID_DECISIONS.has(value);
|
|
19459
|
+
// src/rules.ts
|
|
19460
|
+
var VALID_DECISIONS = /* @__PURE__ */ new Set(["allow", "deny", "ask"]);
|
|
19461
|
+
function isValidDecision(value) {
|
|
19462
|
+
return VALID_DECISIONS.has(value);
|
|
19463
|
+
}
|
|
19464
|
+
var quiet = true;
|
|
19465
|
+
function warn(message) {
|
|
19466
|
+
if (quiet) return;
|
|
19467
|
+
process.stderr.write(message);
|
|
19468
|
+
}
|
|
19469
|
+
var USER_CONFIG_PATHS = [
|
|
19470
|
+
(0, import_path3.join)((0, import_os.homedir)(), ".claude", "warden.yaml"),
|
|
19471
|
+
(0, import_path3.join)((0, import_os.homedir)(), ".claude", "warden.json")
|
|
19472
|
+
];
|
|
19473
|
+
var PROJECT_CONFIG_NAMES = [
|
|
19474
|
+
".claude/warden.yaml",
|
|
19475
|
+
".claude/warden.json"
|
|
19476
|
+
];
|
|
19477
|
+
function loadConfig(cwd) {
|
|
19478
|
+
const config = structuredClone(DEFAULT_CONFIG);
|
|
19479
|
+
const defaultLayer = config.layers[0];
|
|
19480
|
+
let userLayer = null;
|
|
19481
|
+
let userRaw = null;
|
|
19482
|
+
for (const configPath of USER_CONFIG_PATHS) {
|
|
19483
|
+
const result = tryLoadFile(configPath);
|
|
19484
|
+
if (result) {
|
|
19485
|
+
userLayer = extractLayer(result);
|
|
19486
|
+
userRaw = result;
|
|
19487
|
+
break;
|
|
19488
|
+
}
|
|
19489
|
+
}
|
|
19490
|
+
let workspaceLayer = null;
|
|
19491
|
+
let workspaceRaw = null;
|
|
19492
|
+
if (cwd) {
|
|
19493
|
+
for (const name of PROJECT_CONFIG_NAMES) {
|
|
19494
|
+
const result = tryLoadFile((0, import_path3.join)(cwd, name));
|
|
19495
|
+
if (result) {
|
|
19496
|
+
workspaceLayer = extractLayer(result);
|
|
19497
|
+
workspaceRaw = result;
|
|
19498
|
+
break;
|
|
19499
|
+
}
|
|
19500
|
+
}
|
|
19501
|
+
}
|
|
19502
|
+
config.layers = [
|
|
19503
|
+
...workspaceLayer ? [workspaceLayer] : [],
|
|
19504
|
+
...userLayer ? [userLayer] : [],
|
|
19505
|
+
defaultLayer
|
|
19506
|
+
];
|
|
19507
|
+
if (userRaw) mergeNonLayerFields(config, userRaw);
|
|
19508
|
+
if (workspaceRaw) mergeNonLayerFields(config, workspaceRaw);
|
|
19509
|
+
return config;
|
|
19510
|
+
}
|
|
19511
|
+
function tryLoadFile(filePath) {
|
|
19512
|
+
if (!(0, import_fs.existsSync)(filePath)) return null;
|
|
19513
|
+
try {
|
|
19514
|
+
const raw = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
19515
|
+
const parsed = filePath.endsWith(".yaml") || filePath.endsWith(".yml") ? (0, import_yaml.parse)(raw) : JSON.parse(raw);
|
|
19516
|
+
if (parsed && typeof parsed === "object") {
|
|
19517
|
+
return parsed;
|
|
19518
|
+
}
|
|
19519
|
+
} catch (err) {
|
|
19520
|
+
warn(`[warden] Warning: failed to parse config ${filePath}: ${err instanceof Error ? err.message : String(err)}
|
|
19521
|
+
`);
|
|
19522
|
+
}
|
|
19523
|
+
return null;
|
|
19524
|
+
}
|
|
19525
|
+
function extractLayer(raw) {
|
|
19526
|
+
const rules = Array.isArray(raw.rules) ? raw.rules : [];
|
|
19527
|
+
for (const rule of rules) {
|
|
19528
|
+
if (rule && typeof rule === "object") {
|
|
19529
|
+
if (rule.default && !isValidDecision(rule.default)) {
|
|
19530
|
+
warn(`[warden] Warning: invalid rule default "${rule.default}" for "${rule.command}", using "ask"
|
|
19531
|
+
`);
|
|
19532
|
+
rule.default = "ask";
|
|
19533
|
+
}
|
|
19534
|
+
if (Array.isArray(rule.argPatterns)) {
|
|
19535
|
+
for (const pattern of rule.argPatterns) {
|
|
19536
|
+
if (pattern?.decision && !isValidDecision(pattern.decision)) {
|
|
19537
|
+
warn(`[warden] Warning: invalid pattern decision "${pattern.decision}" for "${rule.command}", using "ask"
|
|
19538
|
+
`);
|
|
19539
|
+
pattern.decision = "ask";
|
|
19540
|
+
}
|
|
19541
|
+
}
|
|
19542
|
+
}
|
|
19543
|
+
}
|
|
19544
|
+
}
|
|
19545
|
+
return {
|
|
19546
|
+
alwaysAllow: Array.isArray(raw.alwaysAllow) ? raw.alwaysAllow : [],
|
|
19547
|
+
alwaysDeny: Array.isArray(raw.alwaysDeny) ? raw.alwaysDeny : [],
|
|
19548
|
+
rules
|
|
19549
|
+
};
|
|
19550
|
+
}
|
|
19551
|
+
function parseTrustedList(raw) {
|
|
19552
|
+
return raw.map((entry) => {
|
|
19553
|
+
if (typeof entry === "string") return { name: entry };
|
|
19554
|
+
if (entry && typeof entry === "object" && "name" in entry) {
|
|
19555
|
+
const obj = entry;
|
|
19556
|
+
const target = { name: String(obj.name) };
|
|
19557
|
+
if (obj.allowAll === true) target.allowAll = true;
|
|
19558
|
+
if (obj.overrides && typeof obj.overrides === "object") {
|
|
19559
|
+
target.overrides = extractLayer(obj.overrides);
|
|
19560
|
+
}
|
|
19561
|
+
return target;
|
|
19562
|
+
}
|
|
19563
|
+
return null;
|
|
19564
|
+
}).filter((t) => t !== null);
|
|
19565
|
+
}
|
|
19566
|
+
var VALID_REMOTE_CONTEXTS = /* @__PURE__ */ new Set(["ssh", "docker", "kubectl", "sprite", "fly"]);
|
|
19567
|
+
function parseTrustedRemotes(raw) {
|
|
19568
|
+
const results = [];
|
|
19569
|
+
for (const entry of raw) {
|
|
19570
|
+
if (!entry || typeof entry !== "object") continue;
|
|
19571
|
+
const obj = entry;
|
|
19572
|
+
const context = String(obj.context || "");
|
|
19573
|
+
if (!VALID_REMOTE_CONTEXTS.has(context)) {
|
|
19574
|
+
warn(`[warden] Warning: unknown remote context "${context}", skipping
|
|
19575
|
+
`);
|
|
19576
|
+
continue;
|
|
19577
|
+
}
|
|
19578
|
+
const name = String(obj.name || "");
|
|
19579
|
+
if (!name) continue;
|
|
19580
|
+
const remote = { name, context };
|
|
19581
|
+
if (obj.allowAll === true) remote.allowAll = true;
|
|
19582
|
+
if (obj.overrides && typeof obj.overrides === "object") {
|
|
19583
|
+
remote.overrides = extractLayer(obj.overrides);
|
|
19584
|
+
}
|
|
19585
|
+
results.push(remote);
|
|
19586
|
+
}
|
|
19587
|
+
return results;
|
|
19588
|
+
}
|
|
19589
|
+
function parseTargetPolicies(raw) {
|
|
19590
|
+
const results = [];
|
|
19591
|
+
for (const entry of raw) {
|
|
19592
|
+
if (!entry || typeof entry !== "object") continue;
|
|
19593
|
+
const obj = entry;
|
|
19594
|
+
const type = String(obj.type || "");
|
|
19595
|
+
const rawDecision = String(obj.decision || "allow");
|
|
19596
|
+
const decision = isValidDecision(rawDecision) ? rawDecision : "allow";
|
|
19597
|
+
const base = {
|
|
19598
|
+
commands: Array.isArray(obj.commands) ? obj.commands.map(String) : void 0,
|
|
19599
|
+
decision,
|
|
19600
|
+
reason: obj.reason ? String(obj.reason) : void 0,
|
|
19601
|
+
allowAll: obj.allowAll === true ? true : void 0
|
|
19602
|
+
};
|
|
19603
|
+
switch (type) {
|
|
19604
|
+
case "path": {
|
|
19605
|
+
const path = String(obj.path || "");
|
|
19606
|
+
if (!path) continue;
|
|
19607
|
+
const policy = { ...base, type: "path", path };
|
|
19608
|
+
if (obj.recursive === false) policy.recursive = false;
|
|
19609
|
+
results.push(policy);
|
|
19610
|
+
break;
|
|
19611
|
+
}
|
|
19612
|
+
case "database": {
|
|
19613
|
+
const host = String(obj.host || "");
|
|
19614
|
+
if (!host) continue;
|
|
19615
|
+
const policy = { ...base, type: "database", host };
|
|
19616
|
+
if (typeof obj.port === "number") policy.port = obj.port;
|
|
19617
|
+
if (obj.database) policy.database = String(obj.database);
|
|
19618
|
+
results.push(policy);
|
|
19619
|
+
break;
|
|
19620
|
+
}
|
|
19621
|
+
case "endpoint": {
|
|
19622
|
+
const pattern = String(obj.pattern || "");
|
|
19623
|
+
if (!pattern) continue;
|
|
19624
|
+
results.push({ ...base, type: "endpoint", pattern });
|
|
19625
|
+
break;
|
|
19626
|
+
}
|
|
19627
|
+
default:
|
|
19628
|
+
warn(`[warden] Warning: unknown target policy type "${type}", skipping
|
|
19629
|
+
`);
|
|
19630
|
+
}
|
|
19631
|
+
}
|
|
19632
|
+
return results;
|
|
19633
|
+
}
|
|
19634
|
+
function parseLegacyPaths(raw) {
|
|
19635
|
+
const results = [];
|
|
19636
|
+
for (const e of raw) {
|
|
19637
|
+
if (!e || typeof e !== "object") continue;
|
|
19638
|
+
const obj = e;
|
|
19639
|
+
const decision = String(obj.decision || "allow");
|
|
19640
|
+
if (!isValidDecision(decision)) continue;
|
|
19641
|
+
const path = String(obj.path || "");
|
|
19642
|
+
if (!path) continue;
|
|
19643
|
+
const tp = { type: "path", path, decision };
|
|
19644
|
+
if (obj.recursive === false) tp.recursive = false;
|
|
19645
|
+
if (Array.isArray(obj.commands)) tp.commands = obj.commands.map(String);
|
|
19646
|
+
if (obj.reason) tp.reason = String(obj.reason);
|
|
19647
|
+
results.push(tp);
|
|
19648
|
+
}
|
|
19649
|
+
return results;
|
|
19650
|
+
}
|
|
19651
|
+
function parseLegacyDatabases(raw) {
|
|
19652
|
+
const results = [];
|
|
19653
|
+
for (const e of raw) {
|
|
19654
|
+
if (!e || typeof e !== "object") continue;
|
|
19655
|
+
const obj = e;
|
|
19656
|
+
const decision = String(obj.decision || "allow");
|
|
19657
|
+
if (!isValidDecision(decision)) continue;
|
|
19658
|
+
const host = String(obj.host || "");
|
|
19659
|
+
if (!host) continue;
|
|
19660
|
+
const td = { type: "database", host, decision };
|
|
19661
|
+
if (typeof obj.port === "number") td.port = obj.port;
|
|
19662
|
+
if (obj.database) td.database = String(obj.database);
|
|
19663
|
+
if (Array.isArray(obj.commands)) td.commands = obj.commands.map(String);
|
|
19664
|
+
if (obj.reason) td.reason = String(obj.reason);
|
|
19665
|
+
results.push(td);
|
|
19666
|
+
}
|
|
19667
|
+
return results;
|
|
19668
|
+
}
|
|
19669
|
+
function parseLegacyEndpoints(raw) {
|
|
19670
|
+
const results = [];
|
|
19671
|
+
for (const e of raw) {
|
|
19672
|
+
if (!e || typeof e !== "object") continue;
|
|
19673
|
+
const obj = e;
|
|
19674
|
+
const decision = String(obj.decision || "allow");
|
|
19675
|
+
if (!isValidDecision(decision)) continue;
|
|
19676
|
+
const pattern = String(obj.pattern || "");
|
|
19677
|
+
if (!pattern) continue;
|
|
19678
|
+
const te = { type: "endpoint", pattern, decision };
|
|
19679
|
+
if (Array.isArray(obj.commands)) te.commands = obj.commands.map(String);
|
|
19680
|
+
if (obj.reason) te.reason = String(obj.reason);
|
|
19681
|
+
results.push(te);
|
|
19682
|
+
}
|
|
19683
|
+
return results;
|
|
19684
|
+
}
|
|
19685
|
+
var LEGACY_REMOTE_MAP = {
|
|
19686
|
+
trustedSSHHosts: "ssh",
|
|
19687
|
+
trustedDockerContainers: "docker",
|
|
19688
|
+
trustedKubectlContexts: "kubectl",
|
|
19689
|
+
trustedSprites: "sprite",
|
|
19690
|
+
trustedFlyApps: "fly"
|
|
19691
|
+
};
|
|
19692
|
+
function mergeNonLayerFields(config, raw) {
|
|
19693
|
+
if (Array.isArray(raw.trustedRemotes)) {
|
|
19694
|
+
config.trustedRemotes = [...config.trustedRemotes || [], ...parseTrustedRemotes(raw.trustedRemotes)];
|
|
19695
|
+
}
|
|
19696
|
+
if (Array.isArray(raw.targetPolicies)) {
|
|
19697
|
+
config.targetPolicies = [...config.targetPolicies || [], ...parseTargetPolicies(raw.targetPolicies)];
|
|
19698
|
+
}
|
|
19699
|
+
for (const [key, context] of Object.entries(LEGACY_REMOTE_MAP)) {
|
|
19700
|
+
if (Array.isArray(raw[key])) {
|
|
19701
|
+
const remotes = parseTrustedList(raw[key]).map((t) => ({ ...t, context }));
|
|
19702
|
+
config.trustedRemotes = [...config.trustedRemotes || [], ...remotes];
|
|
19703
|
+
}
|
|
19704
|
+
}
|
|
19705
|
+
if (Array.isArray(raw.trustedPaths)) {
|
|
19706
|
+
config.targetPolicies = [...config.targetPolicies || [], ...parseLegacyPaths(raw.trustedPaths)];
|
|
19707
|
+
}
|
|
19708
|
+
if (Array.isArray(raw.trustedDatabases)) {
|
|
19709
|
+
config.targetPolicies = [...config.targetPolicies || [], ...parseLegacyDatabases(raw.trustedDatabases)];
|
|
19710
|
+
}
|
|
19711
|
+
if (Array.isArray(raw.trustedEndpoints)) {
|
|
19712
|
+
config.targetPolicies = [...config.targetPolicies || [], ...parseLegacyEndpoints(raw.trustedEndpoints)];
|
|
19713
|
+
}
|
|
19714
|
+
if (typeof raw.defaultDecision === "string") {
|
|
19715
|
+
if (isValidDecision(raw.defaultDecision)) {
|
|
19716
|
+
config.defaultDecision = raw.defaultDecision;
|
|
19717
|
+
} else {
|
|
19718
|
+
warn(`[warden] Warning: invalid defaultDecision "${raw.defaultDecision}", ignoring
|
|
19719
|
+
`);
|
|
19720
|
+
}
|
|
19721
|
+
}
|
|
19722
|
+
if (typeof raw.askOnSubshell === "boolean") {
|
|
19723
|
+
config.askOnSubshell = raw.askOnSubshell;
|
|
19724
|
+
}
|
|
19725
|
+
if (typeof raw.notifyOnAsk === "boolean") {
|
|
19726
|
+
config.notifyOnAsk = raw.notifyOnAsk;
|
|
19727
|
+
}
|
|
19728
|
+
if (typeof raw.notifyOnDeny === "boolean") {
|
|
19729
|
+
config.notifyOnDeny = raw.notifyOnDeny;
|
|
19730
|
+
}
|
|
19731
|
+
if (raw.trustedContextOverrides && typeof raw.trustedContextOverrides === "object") {
|
|
19732
|
+
const overrides = raw.trustedContextOverrides;
|
|
19733
|
+
const layer = extractLayer(overrides);
|
|
19734
|
+
if (config.trustedContextOverrides) {
|
|
19735
|
+
config.trustedContextOverrides = {
|
|
19736
|
+
alwaysAllow: [...layer.alwaysAllow, ...config.trustedContextOverrides.alwaysAllow],
|
|
19737
|
+
alwaysDeny: [...layer.alwaysDeny, ...config.trustedContextOverrides.alwaysDeny],
|
|
19738
|
+
rules: [...layer.rules, ...config.trustedContextOverrides.rules]
|
|
19739
|
+
};
|
|
19740
|
+
} else {
|
|
19741
|
+
config.trustedContextOverrides = layer;
|
|
19742
|
+
}
|
|
19743
|
+
}
|
|
19744
|
+
}
|
|
19745
|
+
|
|
19746
|
+
// src/evaluator.ts
|
|
19747
|
+
function safeRegexTest(pattern, input) {
|
|
19748
|
+
try {
|
|
19749
|
+
return new RegExp(pattern).test(input);
|
|
19750
|
+
} catch {
|
|
19751
|
+
warn(`[warden] Warning: invalid regex pattern: ${pattern}
|
|
19752
|
+
`);
|
|
19753
|
+
return false;
|
|
19754
|
+
}
|
|
19755
|
+
}
|
|
19756
|
+
function commandMatchesName(cmd, name) {
|
|
19757
|
+
if (name.startsWith("/")) {
|
|
19758
|
+
return cmd.originalCommand === name;
|
|
19759
|
+
}
|
|
19760
|
+
if (name.startsWith("~/")) {
|
|
19761
|
+
return cmd.originalCommand === (0, import_os2.homedir)() + name.slice(1);
|
|
19762
|
+
}
|
|
19763
|
+
return cmd.command === name;
|
|
19764
|
+
}
|
|
19765
|
+
var MAX_RECURSION_DEPTH = 10;
|
|
19766
|
+
function evaluate(parsed, config, cwdOrDepth, maybeDepth) {
|
|
19767
|
+
const cwd = typeof cwdOrDepth === "string" ? cwdOrDepth : void 0;
|
|
19768
|
+
const depth = typeof cwdOrDepth === "number" ? cwdOrDepth : maybeDepth ?? 0;
|
|
19769
|
+
if (depth > MAX_RECURSION_DEPTH) {
|
|
19770
|
+
return { decision: "ask", reason: "Maximum recursion depth exceeded", details: [] };
|
|
19771
|
+
}
|
|
19772
|
+
if (parsed.parseError) {
|
|
19773
|
+
return { decision: "ask", reason: "Could not parse command safely", details: [] };
|
|
19774
|
+
}
|
|
19775
|
+
if (parsed.commands.length === 0) {
|
|
19776
|
+
return { decision: "allow", reason: "Empty command", details: [] };
|
|
19777
|
+
}
|
|
19778
|
+
if (parsed.hasSubshell && parsed.subshellCommands.length > 0) {
|
|
19779
|
+
for (const subCmd of parsed.subshellCommands) {
|
|
19780
|
+
const subParsed = parseCommand(subCmd);
|
|
19781
|
+
const subResult = evaluate(subParsed, config, cwd, depth + 1);
|
|
19782
|
+
if (subResult.decision === "deny") {
|
|
19783
|
+
return { decision: "deny", reason: `Subshell command: ${subResult.reason}`, details: subResult.details };
|
|
19784
|
+
}
|
|
19785
|
+
if (subResult.decision === "ask") {
|
|
19786
|
+
return { decision: "ask", reason: `Subshell command: ${subResult.reason}`, details: subResult.details };
|
|
19787
|
+
}
|
|
19788
|
+
}
|
|
19789
|
+
} else if (parsed.hasSubshell && parsed.subshellCommands.length === 0 && config.askOnSubshell) {
|
|
19790
|
+
return { decision: "ask", reason: "Command contains subshell/command substitution", details: [] };
|
|
19791
|
+
}
|
|
19792
|
+
const details = [];
|
|
19793
|
+
for (const cmd of parsed.commands) {
|
|
19794
|
+
details.push(evaluateCommand(cmd, config, cwd, depth));
|
|
19795
|
+
}
|
|
19796
|
+
const decisions = details.map((d) => d.decision);
|
|
19797
|
+
if (decisions.includes("deny")) {
|
|
19798
|
+
const denied = details.filter((d) => d.decision === "deny");
|
|
19799
|
+
return {
|
|
19800
|
+
decision: "deny",
|
|
19801
|
+
reason: denied.map((d) => `${d.command}: ${d.reason}`).join("; "),
|
|
19802
|
+
details
|
|
19803
|
+
};
|
|
19804
|
+
}
|
|
19805
|
+
if (decisions.includes("ask")) {
|
|
19806
|
+
const asked = details.filter((d) => d.decision === "ask");
|
|
19807
|
+
return {
|
|
19808
|
+
decision: "ask",
|
|
19809
|
+
reason: asked.map((d) => `${d.command}: ${d.reason}`).join("; "),
|
|
19810
|
+
details
|
|
19811
|
+
};
|
|
19812
|
+
}
|
|
19813
|
+
return { decision: "allow", reason: "All commands are safe", details };
|
|
19814
|
+
}
|
|
19815
|
+
function evaluateCommand(cmd, config, cwd, depth = 0) {
|
|
19816
|
+
const { command, args: args2 } = cmd;
|
|
19817
|
+
for (const layer of config.layers) {
|
|
19818
|
+
if (layer.alwaysDeny.some((name) => commandMatchesName(cmd, name))) {
|
|
19819
|
+
return { command, args: args2, decision: "deny", reason: `"${command}" is blocked`, matchedRule: "alwaysDeny" };
|
|
19820
|
+
}
|
|
19821
|
+
if (layer.alwaysAllow.some((name) => commandMatchesName(cmd, name))) {
|
|
19822
|
+
return { command, args: args2, decision: "allow", reason: `"${command}" is safe`, matchedRule: "alwaysAllow" };
|
|
19823
|
+
}
|
|
19824
|
+
}
|
|
19825
|
+
if (cwd) {
|
|
19826
|
+
const targetResult = evaluateTargetPolicies(cmd, cwd, config);
|
|
19827
|
+
if (targetResult) return targetResult;
|
|
19828
|
+
}
|
|
19829
|
+
const remotes = config.trustedRemotes || [];
|
|
19830
|
+
if (command === "ssh" || command === "scp" || command === "rsync") {
|
|
19831
|
+
const sshTargets = remotes.filter((r) => r.context === "ssh");
|
|
19832
|
+
if (sshTargets.length) {
|
|
19833
|
+
const sshResult = evaluateSSHCommand(cmd, config, sshTargets, depth);
|
|
19834
|
+
if (sshResult) return sshResult;
|
|
19835
|
+
}
|
|
19836
|
+
}
|
|
19837
|
+
if (command === "docker") {
|
|
19838
|
+
const dockerTargets = remotes.filter((r) => r.context === "docker");
|
|
19839
|
+
if (dockerTargets.length) {
|
|
19840
|
+
const dockerResult = evaluateDockerExec(cmd, config, dockerTargets, depth);
|
|
19841
|
+
if (dockerResult) return dockerResult;
|
|
19842
|
+
}
|
|
19843
|
+
}
|
|
19844
|
+
if (command === "kubectl") {
|
|
19845
|
+
const kubectlTargets = remotes.filter((r) => r.context === "kubectl");
|
|
19846
|
+
if (kubectlTargets.length) {
|
|
19847
|
+
const kubectlResult = evaluateKubectlExec(cmd, config, kubectlTargets, depth);
|
|
19848
|
+
if (kubectlResult) return kubectlResult;
|
|
19849
|
+
}
|
|
19850
|
+
}
|
|
19851
|
+
if (command === "sprite") {
|
|
19852
|
+
const spriteTargets = remotes.filter((r) => r.context === "sprite");
|
|
19853
|
+
if (spriteTargets.length) {
|
|
19854
|
+
const spriteResult = evaluateSpriteExec(cmd, config, spriteTargets, depth);
|
|
19855
|
+
if (spriteResult) return spriteResult;
|
|
19856
|
+
}
|
|
19857
|
+
}
|
|
19858
|
+
if (command === "fly" || command === "flyctl") {
|
|
19859
|
+
const flyTargets = remotes.filter((r) => r.context === "fly");
|
|
19860
|
+
if (flyTargets.length) {
|
|
19861
|
+
const flyResult = evaluateFlyCommand(cmd, config, flyTargets, depth);
|
|
19862
|
+
if (flyResult) return flyResult;
|
|
19863
|
+
}
|
|
19864
|
+
}
|
|
19865
|
+
if (command === "xargs") {
|
|
19866
|
+
return evaluateXargsCommand(cmd, config, cwd, depth);
|
|
19867
|
+
}
|
|
19868
|
+
if (command === "find") {
|
|
19869
|
+
return evaluateFindCommand(cmd, config, cwd, depth);
|
|
19870
|
+
}
|
|
19871
|
+
const mergedRule = collectMergedRule(cmd, config);
|
|
19872
|
+
if (mergedRule) {
|
|
19873
|
+
return evaluateRule(cmd, mergedRule);
|
|
19874
|
+
}
|
|
19875
|
+
return { command, args: args2, decision: config.defaultDecision, reason: `No rule for "${command}"`, matchedRule: "default" };
|
|
19876
|
+
}
|
|
19877
|
+
function collectMergedRule(cmd, config) {
|
|
19878
|
+
const matchingRules = [];
|
|
19879
|
+
for (const layer of config.layers) {
|
|
19880
|
+
const rule = layer.rules.find((r) => commandMatchesName(cmd, r.command));
|
|
19881
|
+
if (rule) {
|
|
19882
|
+
matchingRules.push(rule);
|
|
19883
|
+
if (rule.override) break;
|
|
19884
|
+
}
|
|
19885
|
+
}
|
|
19886
|
+
if (matchingRules.length === 0) return null;
|
|
19887
|
+
if (matchingRules.length === 1) return matchingRules[0];
|
|
19888
|
+
const mergedPatterns = [];
|
|
19889
|
+
for (const rule of matchingRules) {
|
|
19890
|
+
if (rule.argPatterns) {
|
|
19891
|
+
mergedPatterns.push(...rule.argPatterns);
|
|
19892
|
+
}
|
|
19893
|
+
}
|
|
19894
|
+
return {
|
|
19895
|
+
command: matchingRules[0].command,
|
|
19896
|
+
default: matchingRules[0].default,
|
|
19897
|
+
argPatterns: mergedPatterns
|
|
19898
|
+
};
|
|
19899
|
+
}
|
|
19900
|
+
function evaluateRule(cmd, rule) {
|
|
19901
|
+
const { command, args: args2 } = cmd;
|
|
19902
|
+
const argsJoined = args2.join(" ");
|
|
19903
|
+
for (const pattern of rule.argPatterns || []) {
|
|
19904
|
+
const m = pattern.match;
|
|
19905
|
+
let matched = true;
|
|
19906
|
+
if (m.noArgs !== void 0) {
|
|
19907
|
+
matched = matched && m.noArgs === (args2.length === 0);
|
|
19908
|
+
}
|
|
19909
|
+
if (m.argsMatch && matched) {
|
|
19910
|
+
matched = m.argsMatch.some((re) => safeRegexTest(re, argsJoined));
|
|
19911
|
+
}
|
|
19912
|
+
if (m.anyArgMatches && matched) {
|
|
19913
|
+
matched = args2.some((arg) => m.anyArgMatches.some((re) => safeRegexTest(re, arg)));
|
|
19914
|
+
}
|
|
19915
|
+
if (m.argCount && matched) {
|
|
19916
|
+
if (m.argCount.min !== void 0) matched = matched && args2.length >= m.argCount.min;
|
|
19917
|
+
if (m.argCount.max !== void 0) matched = matched && args2.length <= m.argCount.max;
|
|
19918
|
+
}
|
|
19919
|
+
if (m.not) matched = !matched;
|
|
19920
|
+
if (matched) {
|
|
19921
|
+
return {
|
|
19922
|
+
command,
|
|
19923
|
+
args: args2,
|
|
19924
|
+
decision: pattern.decision,
|
|
19925
|
+
reason: pattern.reason || pattern.description || `Matched pattern for "${command}"`,
|
|
19926
|
+
matchedRule: `${command}:argPattern`
|
|
19927
|
+
};
|
|
19928
|
+
}
|
|
19929
|
+
}
|
|
19930
|
+
return {
|
|
19931
|
+
command,
|
|
19932
|
+
args: args2,
|
|
19933
|
+
decision: rule.default,
|
|
19934
|
+
reason: `Default for "${command}"`,
|
|
19935
|
+
matchedRule: `${command}:default`
|
|
19936
|
+
};
|
|
19937
|
+
}
|
|
19938
|
+
var XARGS_SHORT_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set(["E", "I", "L", "n", "P", "s", "S", "d", "a"]);
|
|
19939
|
+
var XARGS_SHORT_FLAGS_NO_VALUE = /* @__PURE__ */ new Set(["0", "e", "o", "p", "r", "t", "x"]);
|
|
19940
|
+
var XARGS_LONG_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
19941
|
+
"--eof",
|
|
19942
|
+
"--replace",
|
|
19943
|
+
"--max-lines",
|
|
19944
|
+
"--max-args",
|
|
19945
|
+
"--max-procs",
|
|
19946
|
+
"--max-chars",
|
|
19947
|
+
"--arg-file",
|
|
19948
|
+
"--delimiter"
|
|
19949
|
+
]);
|
|
19950
|
+
var XARGS_LONG_FLAGS_NO_VALUE = /* @__PURE__ */ new Set([
|
|
19951
|
+
"--null",
|
|
19952
|
+
"--exit",
|
|
19953
|
+
"--open-tty",
|
|
19954
|
+
"--interactive",
|
|
19955
|
+
"--no-run-if-empty",
|
|
19956
|
+
"--verbose",
|
|
19957
|
+
"--show-limits"
|
|
19958
|
+
]);
|
|
19959
|
+
function parseXargsSubcommand(args2) {
|
|
19960
|
+
let i = 0;
|
|
19961
|
+
while (i < args2.length) {
|
|
19962
|
+
const arg = args2[i];
|
|
19963
|
+
if (arg === "--") {
|
|
19964
|
+
i++;
|
|
19965
|
+
break;
|
|
19966
|
+
}
|
|
19967
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
19968
|
+
break;
|
|
19969
|
+
}
|
|
19970
|
+
if (arg.startsWith("--")) {
|
|
19971
|
+
const eqIndex = arg.indexOf("=");
|
|
19972
|
+
const longFlag = eqIndex === -1 ? arg : arg.slice(0, eqIndex);
|
|
19973
|
+
if (XARGS_LONG_FLAGS_WITH_VALUE.has(longFlag)) {
|
|
19974
|
+
if (eqIndex !== -1) {
|
|
19975
|
+
i++;
|
|
19976
|
+
continue;
|
|
19977
|
+
}
|
|
19978
|
+
if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
|
|
19979
|
+
i += 2;
|
|
19980
|
+
continue;
|
|
19981
|
+
}
|
|
19982
|
+
if (XARGS_LONG_FLAGS_NO_VALUE.has(longFlag)) {
|
|
19983
|
+
i++;
|
|
19984
|
+
continue;
|
|
19985
|
+
}
|
|
19986
|
+
return { subcommand: null, unresolved: true };
|
|
19987
|
+
}
|
|
19988
|
+
const short = arg[1];
|
|
19989
|
+
if (XARGS_SHORT_FLAGS_WITH_VALUE.has(short)) {
|
|
19990
|
+
if (arg.length > 2) {
|
|
19991
|
+
i++;
|
|
19992
|
+
continue;
|
|
19993
|
+
}
|
|
19994
|
+
if (i + 1 >= args2.length) return { subcommand: null, unresolved: true };
|
|
19995
|
+
i += 2;
|
|
19996
|
+
continue;
|
|
19997
|
+
}
|
|
19998
|
+
const grouped = arg.slice(1).split("");
|
|
19999
|
+
const allKnownNoValue = grouped.every((ch) => XARGS_SHORT_FLAGS_NO_VALUE.has(ch));
|
|
20000
|
+
if (allKnownNoValue) {
|
|
20001
|
+
i++;
|
|
20002
|
+
continue;
|
|
20003
|
+
}
|
|
20004
|
+
return { subcommand: null, unresolved: true };
|
|
20005
|
+
}
|
|
20006
|
+
if (i >= args2.length) {
|
|
20007
|
+
return {
|
|
20008
|
+
unresolved: false,
|
|
20009
|
+
subcommand: {
|
|
20010
|
+
command: "echo",
|
|
20011
|
+
originalCommand: "echo",
|
|
20012
|
+
args: [],
|
|
20013
|
+
envPrefixes: [],
|
|
20014
|
+
raw: "echo"
|
|
20015
|
+
}
|
|
20016
|
+
};
|
|
20017
|
+
}
|
|
20018
|
+
const subcommand = args2[i];
|
|
20019
|
+
const subArgs = args2.slice(i + 1);
|
|
20020
|
+
return {
|
|
20021
|
+
unresolved: false,
|
|
20022
|
+
subcommand: {
|
|
20023
|
+
command: subcommand,
|
|
20024
|
+
originalCommand: subcommand,
|
|
20025
|
+
args: subArgs,
|
|
20026
|
+
envPrefixes: [],
|
|
20027
|
+
raw: [subcommand, ...subArgs].join(" ")
|
|
20028
|
+
}
|
|
20029
|
+
};
|
|
20030
|
+
}
|
|
20031
|
+
function evaluateXargsCommand(cmd, config, cwd, depth = 0) {
|
|
20032
|
+
const { command, args: args2 } = cmd;
|
|
20033
|
+
const { subcommand, unresolved } = parseXargsSubcommand(args2);
|
|
20034
|
+
if (unresolved || !subcommand) {
|
|
20035
|
+
return {
|
|
20036
|
+
command,
|
|
20037
|
+
args: args2,
|
|
20038
|
+
decision: "ask",
|
|
20039
|
+
reason: "xargs subcommand could not be resolved safely",
|
|
20040
|
+
matchedRule: "xargs:subcommand"
|
|
20041
|
+
};
|
|
20042
|
+
}
|
|
20043
|
+
const isShellExec = (subcommand.command === "sh" || subcommand.command === "bash" || subcommand.command === "zsh") && subcommand.args.length >= 2 && subcommand.args[0] === "-c";
|
|
20044
|
+
let parsed;
|
|
20045
|
+
if (isShellExec) {
|
|
20046
|
+
const innerResult = parseCommand(subcommand.args[1]);
|
|
20047
|
+
if (innerResult.parseError) {
|
|
20048
|
+
parsed = { commands: [subcommand], hasSubshell: false, subshellCommands: [], parseError: false };
|
|
20049
|
+
} else {
|
|
20050
|
+
parsed = innerResult;
|
|
20051
|
+
}
|
|
20052
|
+
} else {
|
|
20053
|
+
parsed = { commands: [subcommand], hasSubshell: false, subshellCommands: [], parseError: false };
|
|
20054
|
+
}
|
|
20055
|
+
const result = evaluate(parsed, config, cwd, depth + 1);
|
|
20056
|
+
return {
|
|
20057
|
+
command,
|
|
20058
|
+
args: args2,
|
|
20059
|
+
decision: result.decision,
|
|
20060
|
+
reason: `xargs subcommand "${subcommand.command}": ${result.reason}`,
|
|
20061
|
+
matchedRule: "xargs:subcommand"
|
|
20062
|
+
};
|
|
20063
|
+
}
|
|
20064
|
+
function parseFindExecCommands(args2) {
|
|
20065
|
+
const commands = [];
|
|
20066
|
+
let i = 0;
|
|
20067
|
+
while (i < args2.length) {
|
|
20068
|
+
if (args2[i] === "-exec" || args2[i] === "-execdir") {
|
|
20069
|
+
i++;
|
|
20070
|
+
const cmdArgs = [];
|
|
20071
|
+
while (i < args2.length && args2[i] !== ";" && args2[i] !== "+") {
|
|
20072
|
+
if (args2[i] !== "{}") {
|
|
20073
|
+
cmdArgs.push(args2[i]);
|
|
20074
|
+
}
|
|
20075
|
+
i++;
|
|
20076
|
+
}
|
|
20077
|
+
i++;
|
|
20078
|
+
if (cmdArgs.length > 0) {
|
|
20079
|
+
commands.push({
|
|
20080
|
+
command: cmdArgs[0],
|
|
20081
|
+
originalCommand: cmdArgs[0],
|
|
20082
|
+
args: cmdArgs.slice(1),
|
|
20083
|
+
envPrefixes: [],
|
|
20084
|
+
raw: cmdArgs.join(" ")
|
|
20085
|
+
});
|
|
20086
|
+
}
|
|
20087
|
+
} else {
|
|
20088
|
+
i++;
|
|
20089
|
+
}
|
|
20090
|
+
}
|
|
20091
|
+
return commands;
|
|
20092
|
+
}
|
|
20093
|
+
function evaluateFindCommand(cmd, config, cwd, depth = 0) {
|
|
20094
|
+
const { command, args: args2 } = cmd;
|
|
20095
|
+
if (args2.some((a) => a === "-delete")) {
|
|
20096
|
+
return { command, args: args2, decision: "ask", reason: "find -delete can remove files", matchedRule: "find:delete" };
|
|
20097
|
+
}
|
|
20098
|
+
if (args2.some((a) => a === "-ok" || a === "-okdir")) {
|
|
20099
|
+
return { command, args: args2, decision: "ask", reason: "find -ok/-okdir can execute commands interactively", matchedRule: "find:ok" };
|
|
20100
|
+
}
|
|
20101
|
+
const execCommands = parseFindExecCommands(args2);
|
|
20102
|
+
if (execCommands.length === 0) {
|
|
20103
|
+
return { command, args: args2, decision: "allow", reason: "find without dangerous flags", matchedRule: "find:safe" };
|
|
20104
|
+
}
|
|
20105
|
+
for (const execCmd of execCommands) {
|
|
20106
|
+
const parsed = {
|
|
20107
|
+
commands: [execCmd],
|
|
20108
|
+
hasSubshell: false,
|
|
20109
|
+
subshellCommands: [],
|
|
20110
|
+
parseError: false
|
|
20111
|
+
};
|
|
20112
|
+
const result = evaluate(parsed, config, cwd, depth + 1);
|
|
20113
|
+
if (result.decision === "deny") {
|
|
20114
|
+
return { command, args: args2, decision: "deny", reason: `find -exec: ${result.reason}`, matchedRule: "find:exec" };
|
|
20115
|
+
}
|
|
20116
|
+
if (result.decision === "ask") {
|
|
20117
|
+
return { command, args: args2, decision: "ask", reason: `find -exec: ${result.reason}`, matchedRule: "find:exec" };
|
|
20118
|
+
}
|
|
20119
|
+
}
|
|
20120
|
+
return { command, args: args2, decision: "allow", reason: "find -exec commands are safe", matchedRule: "find:exec" };
|
|
20121
|
+
}
|
|
20122
|
+
var SSH_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20123
|
+
"-b",
|
|
20124
|
+
"-c",
|
|
20125
|
+
"-D",
|
|
20126
|
+
"-E",
|
|
20127
|
+
"-e",
|
|
20128
|
+
"-F",
|
|
20129
|
+
"-I",
|
|
20130
|
+
"-i",
|
|
20131
|
+
"-J",
|
|
20132
|
+
"-L",
|
|
20133
|
+
"-l",
|
|
20134
|
+
"-m",
|
|
20135
|
+
"-O",
|
|
20136
|
+
"-o",
|
|
20137
|
+
"-p",
|
|
20138
|
+
"-Q",
|
|
20139
|
+
"-R",
|
|
20140
|
+
"-S",
|
|
20141
|
+
"-W",
|
|
20142
|
+
"-w"
|
|
20143
|
+
]);
|
|
20144
|
+
function findMatchingTarget(value, targets) {
|
|
20145
|
+
return targets.find((t) => globToRegex(t.name).test(value)) || null;
|
|
20146
|
+
}
|
|
20147
|
+
function parseSSHArgs(args2) {
|
|
20148
|
+
let host = null;
|
|
20149
|
+
const remoteArgs = [];
|
|
20150
|
+
let i = 0;
|
|
20151
|
+
while (i < args2.length) {
|
|
20152
|
+
const arg = args2[i];
|
|
20153
|
+
if (SSH_FLAGS_WITH_VALUE.has(arg)) {
|
|
20154
|
+
i += 2;
|
|
20155
|
+
continue;
|
|
20156
|
+
}
|
|
20157
|
+
if (arg.startsWith("-")) {
|
|
20158
|
+
i++;
|
|
20159
|
+
continue;
|
|
20160
|
+
}
|
|
20161
|
+
if (!host) {
|
|
20162
|
+
host = arg.includes("@") ? arg.split("@").pop() : arg;
|
|
20163
|
+
i++;
|
|
20164
|
+
while (i < args2.length) {
|
|
20165
|
+
remoteArgs.push(args2[i]);
|
|
20166
|
+
i++;
|
|
20167
|
+
}
|
|
20168
|
+
break;
|
|
20169
|
+
}
|
|
20170
|
+
i++;
|
|
20171
|
+
}
|
|
20172
|
+
return {
|
|
20173
|
+
host,
|
|
20174
|
+
remoteCommand: remoteArgs.length > 0 ? remoteArgs.map(shellQuote).join(" ") : null
|
|
20175
|
+
};
|
|
20176
|
+
}
|
|
20177
|
+
function extractHostFromRemotePath(args2) {
|
|
20178
|
+
for (const arg of args2) {
|
|
20179
|
+
const match = arg.match(/^(?:[^@]+@)?([^:]+):/);
|
|
20180
|
+
if (match) return match[1];
|
|
20181
|
+
}
|
|
20182
|
+
return null;
|
|
20183
|
+
}
|
|
20184
|
+
function evaluateSSHCommand(cmd, config, targets, depth = 0) {
|
|
20185
|
+
const { command, args: args2 } = cmd;
|
|
20186
|
+
if (command === "scp" || command === "rsync") {
|
|
20187
|
+
const host2 = extractHostFromRemotePath(args2);
|
|
20188
|
+
if (!host2) return null;
|
|
20189
|
+
const target2 = findMatchingTarget(host2, targets);
|
|
20190
|
+
if (!target2) return null;
|
|
20191
|
+
if (target2.allowAll || !target2.overrides) {
|
|
20192
|
+
return {
|
|
20193
|
+
command,
|
|
20194
|
+
args: args2,
|
|
20195
|
+
decision: "allow",
|
|
20196
|
+
reason: `Trusted SSH host "${host2}"${target2.allowAll ? " (allowAll)" : ""}`,
|
|
20197
|
+
matchedRule: "trustedSSHHosts"
|
|
20198
|
+
};
|
|
20199
|
+
}
|
|
20200
|
+
if (target2.overrides.alwaysDeny.some((name) => name === command)) {
|
|
20201
|
+
return {
|
|
20202
|
+
command,
|
|
20203
|
+
args: args2,
|
|
20204
|
+
decision: "deny",
|
|
20205
|
+
reason: `Trusted SSH host "${host2}": "${command}" blocked by overrides`,
|
|
20206
|
+
matchedRule: "trustedSSHHosts"
|
|
20207
|
+
};
|
|
20208
|
+
}
|
|
20209
|
+
return {
|
|
20210
|
+
command,
|
|
20211
|
+
args: args2,
|
|
20212
|
+
decision: "allow",
|
|
20213
|
+
reason: `Trusted SSH host "${host2}"`,
|
|
20214
|
+
matchedRule: "trustedSSHHosts"
|
|
20215
|
+
};
|
|
20216
|
+
}
|
|
20217
|
+
const { host, remoteCommand } = parseSSHArgs(args2);
|
|
20218
|
+
if (!host) return null;
|
|
20219
|
+
const target = findMatchingTarget(host, targets);
|
|
20220
|
+
if (!target) return null;
|
|
20221
|
+
if (!remoteCommand) {
|
|
20222
|
+
return {
|
|
20223
|
+
command,
|
|
20224
|
+
args: args2,
|
|
20225
|
+
decision: "allow",
|
|
20226
|
+
reason: `Trusted SSH host "${host}" (interactive)`,
|
|
20227
|
+
matchedRule: "trustedSSHHosts"
|
|
20228
|
+
};
|
|
20229
|
+
}
|
|
20230
|
+
if (target.allowAll) {
|
|
20231
|
+
return {
|
|
20232
|
+
command,
|
|
20233
|
+
args: args2,
|
|
20234
|
+
decision: "allow",
|
|
20235
|
+
reason: `Trusted SSH host "${host}" (allowAll)`,
|
|
20236
|
+
matchedRule: "trustedSSHHosts"
|
|
20237
|
+
};
|
|
20238
|
+
}
|
|
20239
|
+
const parsed = parseCommand(remoteCommand);
|
|
20240
|
+
const result = evaluate(parsed, configWithContextOverrides(config, target), depth + 1);
|
|
20241
|
+
return {
|
|
20242
|
+
command,
|
|
20243
|
+
args: args2,
|
|
20244
|
+
decision: result.decision,
|
|
20245
|
+
reason: `Trusted SSH host "${host}": ${result.reason}`,
|
|
20246
|
+
matchedRule: "trustedSSHHosts"
|
|
20247
|
+
};
|
|
20248
|
+
}
|
|
20249
|
+
var DOCKER_EXEC_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20250
|
+
"-e",
|
|
20251
|
+
"--env",
|
|
20252
|
+
"--env-file",
|
|
20253
|
+
"-u",
|
|
20254
|
+
"--user",
|
|
20255
|
+
"-w",
|
|
20256
|
+
"--workdir",
|
|
20257
|
+
"--detach-keys"
|
|
20258
|
+
]);
|
|
20259
|
+
var INTERACTIVE_SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh"]);
|
|
20260
|
+
function shellQuote(arg) {
|
|
20261
|
+
if (/[\s"'\\$`!#&|;()<>]/.test(arg)) {
|
|
20262
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
20263
|
+
}
|
|
20264
|
+
return arg;
|
|
20265
|
+
}
|
|
20266
|
+
function configWithContextOverrides(config, target) {
|
|
20267
|
+
const overrideLayers = [];
|
|
20268
|
+
if (target?.overrides) overrideLayers.push(target.overrides);
|
|
20269
|
+
if (config.trustedContextOverrides) overrideLayers.push(config.trustedContextOverrides);
|
|
20270
|
+
if (overrideLayers.length === 0) return config;
|
|
20271
|
+
return {
|
|
20272
|
+
...config,
|
|
20273
|
+
layers: [...overrideLayers, ...config.layers]
|
|
20274
|
+
};
|
|
20275
|
+
}
|
|
20276
|
+
function evaluateRemoteCommand(remoteArgs, config, target, depth = 0) {
|
|
20277
|
+
if (target?.allowAll) {
|
|
20278
|
+
return { decision: "allow", reason: "allowAll target", details: [] };
|
|
20279
|
+
}
|
|
20280
|
+
const overriddenConfig = configWithContextOverrides(config, target);
|
|
20281
|
+
if (remoteArgs.length === 0) {
|
|
20282
|
+
return { decision: "allow", reason: "interactive", details: [] };
|
|
20283
|
+
}
|
|
20284
|
+
const remoteCmd = remoteArgs[0];
|
|
20285
|
+
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs.length === 1) {
|
|
20286
|
+
return { decision: "allow", reason: "interactive shell", details: [] };
|
|
20287
|
+
}
|
|
20288
|
+
if (INTERACTIVE_SHELLS.has(remoteCmd) && remoteArgs[1] === "-c" && remoteArgs.length >= 3) {
|
|
20289
|
+
const innerCommand = remoteArgs.slice(2).join(" ");
|
|
20290
|
+
const parsed2 = parseCommand(innerCommand);
|
|
20291
|
+
return evaluate(parsed2, overriddenConfig, depth + 1);
|
|
20292
|
+
}
|
|
20293
|
+
const parsed = {
|
|
20294
|
+
commands: [{ command: remoteCmd, originalCommand: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
|
|
20295
|
+
hasSubshell: false,
|
|
20296
|
+
subshellCommands: [],
|
|
20297
|
+
parseError: false
|
|
20298
|
+
};
|
|
20299
|
+
return evaluate(parsed, overriddenConfig, depth + 1);
|
|
20295
20300
|
}
|
|
20296
|
-
|
|
20297
|
-
|
|
20298
|
-
|
|
20299
|
-
|
|
20300
|
-
|
|
20301
|
-
|
|
20302
|
-
|
|
20303
|
-
|
|
20304
|
-
|
|
20305
|
-
|
|
20306
|
-
|
|
20307
|
-
|
|
20308
|
-
|
|
20309
|
-
|
|
20310
|
-
|
|
20311
|
-
|
|
20312
|
-
|
|
20313
|
-
|
|
20301
|
+
function parseDockerExecArgs(args2) {
|
|
20302
|
+
let target = null;
|
|
20303
|
+
const remoteArgs = [];
|
|
20304
|
+
let i = 0;
|
|
20305
|
+
while (i < args2.length) {
|
|
20306
|
+
const arg = args2[i];
|
|
20307
|
+
if (DOCKER_EXEC_FLAGS_WITH_VALUE.has(arg)) {
|
|
20308
|
+
i += 2;
|
|
20309
|
+
continue;
|
|
20310
|
+
}
|
|
20311
|
+
if (arg.startsWith("-")) {
|
|
20312
|
+
i++;
|
|
20313
|
+
continue;
|
|
20314
|
+
}
|
|
20315
|
+
if (!target) {
|
|
20316
|
+
target = arg;
|
|
20317
|
+
i++;
|
|
20318
|
+
while (i < args2.length) {
|
|
20319
|
+
remoteArgs.push(args2[i]);
|
|
20320
|
+
i++;
|
|
20321
|
+
}
|
|
20314
20322
|
break;
|
|
20315
20323
|
}
|
|
20324
|
+
i++;
|
|
20316
20325
|
}
|
|
20317
|
-
|
|
20318
|
-
|
|
20319
|
-
|
|
20320
|
-
|
|
20321
|
-
|
|
20322
|
-
|
|
20323
|
-
|
|
20324
|
-
|
|
20325
|
-
|
|
20326
|
+
return { target, remoteArgs };
|
|
20327
|
+
}
|
|
20328
|
+
function evaluateDockerExec(cmd, config, targets, depth = 0) {
|
|
20329
|
+
const { command, args: args2 } = cmd;
|
|
20330
|
+
if (args2[0] !== "exec") return null;
|
|
20331
|
+
const { target: containerName, remoteArgs } = parseDockerExecArgs(args2.slice(1));
|
|
20332
|
+
if (!containerName) return null;
|
|
20333
|
+
const matched = findMatchingTarget(containerName, targets);
|
|
20334
|
+
if (!matched) return null;
|
|
20335
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
20336
|
+
return {
|
|
20337
|
+
command,
|
|
20338
|
+
args: args2,
|
|
20339
|
+
decision: result.decision,
|
|
20340
|
+
reason: `Trusted Docker container "${containerName}" (${result.reason})`,
|
|
20341
|
+
matchedRule: "trustedDockerContainers"
|
|
20342
|
+
};
|
|
20343
|
+
}
|
|
20344
|
+
var KUBECTL_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20345
|
+
"-n",
|
|
20346
|
+
"--namespace",
|
|
20347
|
+
"-c",
|
|
20348
|
+
"--container",
|
|
20349
|
+
"--context",
|
|
20350
|
+
"--cluster",
|
|
20351
|
+
"--kubeconfig",
|
|
20352
|
+
"-s",
|
|
20353
|
+
"--server",
|
|
20354
|
+
"--token",
|
|
20355
|
+
"--user",
|
|
20356
|
+
"--as",
|
|
20357
|
+
"--as-group",
|
|
20358
|
+
"--certificate-authority",
|
|
20359
|
+
"--client-certificate",
|
|
20360
|
+
"--client-key",
|
|
20361
|
+
"-l",
|
|
20362
|
+
"--selector",
|
|
20363
|
+
"-f",
|
|
20364
|
+
"--filename",
|
|
20365
|
+
"--cache-dir",
|
|
20366
|
+
"--request-timeout",
|
|
20367
|
+
"-o",
|
|
20368
|
+
"--output"
|
|
20369
|
+
]);
|
|
20370
|
+
function parseKubectlExecArgs(args2) {
|
|
20371
|
+
let context = null;
|
|
20372
|
+
let pod = null;
|
|
20373
|
+
const remoteArgs = [];
|
|
20374
|
+
let i = 0;
|
|
20375
|
+
while (i < args2.length) {
|
|
20376
|
+
const arg = args2[i];
|
|
20377
|
+
if (arg === "--") {
|
|
20378
|
+
i++;
|
|
20379
|
+
while (i < args2.length) {
|
|
20380
|
+
remoteArgs.push(args2[i]);
|
|
20381
|
+
i++;
|
|
20326
20382
|
}
|
|
20383
|
+
break;
|
|
20384
|
+
}
|
|
20385
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
20386
|
+
if (arg.startsWith("--context=")) {
|
|
20387
|
+
context = arg.split("=")[1];
|
|
20388
|
+
}
|
|
20389
|
+
i++;
|
|
20390
|
+
continue;
|
|
20391
|
+
}
|
|
20392
|
+
if (KUBECTL_FLAGS_WITH_VALUE.has(arg)) {
|
|
20393
|
+
if (arg === "--context") context = args2[i + 1] || null;
|
|
20394
|
+
i += 2;
|
|
20395
|
+
continue;
|
|
20396
|
+
}
|
|
20397
|
+
if (arg.startsWith("-")) {
|
|
20398
|
+
i++;
|
|
20399
|
+
continue;
|
|
20400
|
+
}
|
|
20401
|
+
if (!pod) {
|
|
20402
|
+
pod = arg;
|
|
20327
20403
|
}
|
|
20404
|
+
i++;
|
|
20328
20405
|
}
|
|
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;
|
|
20406
|
+
return { context, pod, remoteArgs };
|
|
20337
20407
|
}
|
|
20338
|
-
function
|
|
20339
|
-
|
|
20340
|
-
|
|
20341
|
-
|
|
20342
|
-
|
|
20343
|
-
|
|
20344
|
-
|
|
20408
|
+
function evaluateKubectlExec(cmd, config, targets, depth = 0) {
|
|
20409
|
+
const { command, args: args2 } = cmd;
|
|
20410
|
+
if (args2[0] !== "exec") return null;
|
|
20411
|
+
const { context, pod, remoteArgs } = parseKubectlExecArgs(args2.slice(1));
|
|
20412
|
+
if (!context) return null;
|
|
20413
|
+
const matched = findMatchingTarget(context, targets);
|
|
20414
|
+
if (!matched) return null;
|
|
20415
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
20416
|
+
return {
|
|
20417
|
+
command,
|
|
20418
|
+
args: args2,
|
|
20419
|
+
decision: result.decision,
|
|
20420
|
+
reason: `Trusted kubectl context "${context}"${pod ? `, pod "${pod}"` : ""} (${result.reason})`,
|
|
20421
|
+
matchedRule: "trustedKubectlContexts"
|
|
20422
|
+
};
|
|
20423
|
+
}
|
|
20424
|
+
var SPRITE_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20425
|
+
"-o",
|
|
20426
|
+
"--org",
|
|
20427
|
+
"-s",
|
|
20428
|
+
"--sprite"
|
|
20429
|
+
]);
|
|
20430
|
+
function parseSpriteExecArgs(args2) {
|
|
20431
|
+
let spriteName = null;
|
|
20432
|
+
const remoteArgs = [];
|
|
20433
|
+
let foundExec = false;
|
|
20434
|
+
let i = 0;
|
|
20435
|
+
while (i < args2.length) {
|
|
20436
|
+
const arg = args2[i];
|
|
20437
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
20438
|
+
if (arg.startsWith("--sprite=")) {
|
|
20439
|
+
spriteName = arg.split("=")[1];
|
|
20440
|
+
}
|
|
20441
|
+
i++;
|
|
20442
|
+
continue;
|
|
20345
20443
|
}
|
|
20346
|
-
|
|
20347
|
-
|
|
20348
|
-
|
|
20444
|
+
if (SPRITE_FLAGS_WITH_VALUE.has(arg)) {
|
|
20445
|
+
if (arg === "-s" || arg === "--sprite") {
|
|
20446
|
+
spriteName = args2[i + 1] || null;
|
|
20447
|
+
}
|
|
20448
|
+
i += 2;
|
|
20449
|
+
continue;
|
|
20450
|
+
}
|
|
20451
|
+
if (arg === "--debug") {
|
|
20452
|
+
i++;
|
|
20453
|
+
continue;
|
|
20454
|
+
}
|
|
20455
|
+
if (arg.startsWith("-")) {
|
|
20456
|
+
i++;
|
|
20457
|
+
continue;
|
|
20458
|
+
}
|
|
20459
|
+
if (!foundExec) {
|
|
20460
|
+
if (arg === "exec" || arg === "x" || arg === "console" || arg === "c") {
|
|
20461
|
+
foundExec = true;
|
|
20462
|
+
i++;
|
|
20463
|
+
continue;
|
|
20464
|
+
}
|
|
20465
|
+
return { spriteName: null, remoteArgs: [] };
|
|
20466
|
+
}
|
|
20467
|
+
while (i < args2.length) {
|
|
20468
|
+
remoteArgs.push(args2[i]);
|
|
20469
|
+
i++;
|
|
20470
|
+
}
|
|
20471
|
+
break;
|
|
20349
20472
|
}
|
|
20350
|
-
return
|
|
20473
|
+
return { spriteName, remoteArgs };
|
|
20474
|
+
}
|
|
20475
|
+
function evaluateSpriteExec(cmd, config, targets, depth = 0) {
|
|
20476
|
+
const { command, args: args2 } = cmd;
|
|
20477
|
+
const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
|
|
20478
|
+
if (!spriteName) return null;
|
|
20479
|
+
const matched = findMatchingTarget(spriteName, targets);
|
|
20480
|
+
if (!matched) return null;
|
|
20481
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
20482
|
+
return {
|
|
20483
|
+
command,
|
|
20484
|
+
args: args2,
|
|
20485
|
+
decision: result.decision,
|
|
20486
|
+
reason: `Trusted sprite "${spriteName}" (${result.reason})`,
|
|
20487
|
+
matchedRule: "trustedSprites"
|
|
20488
|
+
};
|
|
20351
20489
|
}
|
|
20352
|
-
var
|
|
20353
|
-
|
|
20354
|
-
|
|
20355
|
-
|
|
20490
|
+
var FLY_SSH_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
20491
|
+
"-a",
|
|
20492
|
+
"--app",
|
|
20493
|
+
"-C",
|
|
20494
|
+
"--command",
|
|
20495
|
+
"-o",
|
|
20496
|
+
"--org",
|
|
20497
|
+
"-r",
|
|
20498
|
+
"--region",
|
|
20499
|
+
"-u",
|
|
20500
|
+
"--user",
|
|
20501
|
+
"--address"
|
|
20356
20502
|
]);
|
|
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
|
-
}
|
|
20503
|
+
function parseFlySSHArgs(args2) {
|
|
20504
|
+
let app = null;
|
|
20505
|
+
const remoteArgs = [];
|
|
20506
|
+
let isSSH = false;
|
|
20507
|
+
let foundConsole = false;
|
|
20508
|
+
let i = 0;
|
|
20509
|
+
while (i < args2.length) {
|
|
20510
|
+
const arg = args2[i];
|
|
20511
|
+
if (arg.startsWith("--app=")) {
|
|
20512
|
+
app = arg.slice(6);
|
|
20513
|
+
i++;
|
|
20514
|
+
continue;
|
|
20374
20515
|
}
|
|
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";
|
|
20516
|
+
if (FLY_SSH_FLAGS_WITH_VALUE.has(arg)) {
|
|
20517
|
+
if (arg === "-a" || arg === "--app") {
|
|
20518
|
+
app = args2[i + 1] || null;
|
|
20389
20519
|
}
|
|
20390
|
-
if (
|
|
20391
|
-
|
|
20392
|
-
|
|
20393
|
-
|
|
20394
|
-
|
|
20395
|
-
|
|
20520
|
+
if ((arg === "-C" || arg === "--command") && foundConsole) {
|
|
20521
|
+
const cmdValue = args2[i + 1];
|
|
20522
|
+
if (cmdValue) {
|
|
20523
|
+
const parsed = parseCommand(cmdValue);
|
|
20524
|
+
if (!parsed.parseError && parsed.commands.length > 0) {
|
|
20525
|
+
const cmd = parsed.commands[0];
|
|
20526
|
+
remoteArgs.push(cmd.command, ...cmd.args);
|
|
20396
20527
|
}
|
|
20397
20528
|
}
|
|
20529
|
+
i += 2;
|
|
20530
|
+
continue;
|
|
20398
20531
|
}
|
|
20399
|
-
|
|
20532
|
+
i += 2;
|
|
20533
|
+
continue;
|
|
20400
20534
|
}
|
|
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);
|
|
20535
|
+
if (arg === "--") {
|
|
20536
|
+
i++;
|
|
20537
|
+
while (i < args2.length) {
|
|
20538
|
+
remoteArgs.push(args2[i]);
|
|
20539
|
+
i++;
|
|
20417
20540
|
}
|
|
20418
|
-
|
|
20541
|
+
break;
|
|
20419
20542
|
}
|
|
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
|
-
`);
|
|
20543
|
+
if (arg.startsWith("-")) {
|
|
20544
|
+
i++;
|
|
20433
20545
|
continue;
|
|
20434
20546
|
}
|
|
20435
|
-
|
|
20436
|
-
|
|
20437
|
-
|
|
20438
|
-
|
|
20439
|
-
if (obj.overrides && typeof obj.overrides === "object") {
|
|
20440
|
-
remote.overrides = extractLayer(obj.overrides);
|
|
20547
|
+
if (!isSSH && arg === "ssh") {
|
|
20548
|
+
isSSH = true;
|
|
20549
|
+
i++;
|
|
20550
|
+
continue;
|
|
20441
20551
|
}
|
|
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
|
-
`);
|
|
20552
|
+
if (isSSH && !foundConsole && (arg === "console" || arg === "sftp")) {
|
|
20553
|
+
foundConsole = true;
|
|
20554
|
+
i++;
|
|
20555
|
+
continue;
|
|
20487
20556
|
}
|
|
20557
|
+
i++;
|
|
20488
20558
|
}
|
|
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;
|
|
20559
|
+
return { app, remoteArgs, isSSH: isSSH && foundConsole };
|
|
20525
20560
|
}
|
|
20526
|
-
function
|
|
20527
|
-
const
|
|
20528
|
-
|
|
20529
|
-
|
|
20530
|
-
|
|
20531
|
-
|
|
20532
|
-
|
|
20533
|
-
|
|
20534
|
-
|
|
20535
|
-
|
|
20536
|
-
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
|
|
20540
|
-
|
|
20561
|
+
function evaluateFlyCommand(cmd, config, targets, depth = 0) {
|
|
20562
|
+
const { command, args: args2 } = cmd;
|
|
20563
|
+
const { app, remoteArgs, isSSH } = parseFlySSHArgs(args2);
|
|
20564
|
+
if (!isSSH) return null;
|
|
20565
|
+
if (!app) return null;
|
|
20566
|
+
const matched = findMatchingTarget(app, targets);
|
|
20567
|
+
if (!matched) return null;
|
|
20568
|
+
const result = evaluateRemoteCommand(remoteArgs, config, matched, depth);
|
|
20569
|
+
return {
|
|
20570
|
+
command,
|
|
20571
|
+
args: args2,
|
|
20572
|
+
decision: result.decision,
|
|
20573
|
+
reason: `Trusted Fly app "${app}" (${result.reason})`,
|
|
20574
|
+
matchedRule: "trustedFlyApps"
|
|
20575
|
+
};
|
|
20541
20576
|
}
|
|
20542
|
-
|
|
20543
|
-
|
|
20544
|
-
|
|
20545
|
-
|
|
20546
|
-
|
|
20547
|
-
trustedFlyApps: "fly"
|
|
20548
|
-
};
|
|
20549
|
-
function mergeNonLayerFields(config, raw) {
|
|
20550
|
-
if (Array.isArray(raw.trustedRemotes)) {
|
|
20551
|
-
config.trustedRemotes = [...config.trustedRemotes || [], ...parseTrustedRemotes(raw.trustedRemotes)];
|
|
20552
|
-
}
|
|
20553
|
-
if (Array.isArray(raw.targetPolicies)) {
|
|
20554
|
-
config.targetPolicies = [...config.targetPolicies || [], ...parseTargetPolicies(raw.targetPolicies)];
|
|
20555
|
-
}
|
|
20556
|
-
for (const [key, context] of Object.entries(LEGACY_REMOTE_MAP)) {
|
|
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
|
-
}
|
|
20577
|
+
|
|
20578
|
+
// src/core.ts
|
|
20579
|
+
function wardenEvalWithConfig(command, config, cwd) {
|
|
20580
|
+
const parsed = parseCommand(command);
|
|
20581
|
+
return evaluate(parsed, config, cwd);
|
|
20601
20582
|
}
|
|
20602
20583
|
|
|
20603
20584
|
// src/suggest.ts
|
|
@@ -20865,11 +20846,10 @@ async function main() {
|
|
|
20865
20846
|
process.exit(0);
|
|
20866
20847
|
}
|
|
20867
20848
|
const config = loadConfig(input.cwd);
|
|
20849
|
+
const result = wardenEvalWithConfig(command, config, input.cwd);
|
|
20868
20850
|
const yoloState = getYoloState(input.session_id);
|
|
20869
20851
|
if (yoloState) {
|
|
20870
|
-
|
|
20871
|
-
const result2 = evaluate(parsed2, config, input.cwd);
|
|
20872
|
-
if (result2.decision === "deny" && !yoloState.bypassDeny) {
|
|
20852
|
+
if (result.decision === "deny" && !yoloState.bypassDeny) {
|
|
20873
20853
|
} else {
|
|
20874
20854
|
const expiryInfo = yoloState.expiresAt ? `expires ${new Date(yoloState.expiresAt).toLocaleTimeString()}` : "full session";
|
|
20875
20855
|
const output2 = {
|
|
@@ -20883,8 +20863,6 @@ async function main() {
|
|
|
20883
20863
|
process.exit(0);
|
|
20884
20864
|
}
|
|
20885
20865
|
}
|
|
20886
|
-
const parsed = parseCommand(command);
|
|
20887
|
-
const result = evaluate(parsed, config, input.cwd);
|
|
20888
20866
|
if (result.decision === "allow") {
|
|
20889
20867
|
const output2 = {
|
|
20890
20868
|
hookSpecificOutput: {
|