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