@clawops/cli 2.3.0 → 2.3.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/README.md CHANGED
@@ -14,7 +14,7 @@ and Cursor drive them through typed MCP tools with explicit safety controls.
14
14
 
15
15
  ---
16
16
 
17
- **What's new:** [2.0.1 and 2.0 release notes](#whats-new-in-201) below, and the full history
17
+ **What's new:** [2.3.1 release notes](#whats-new-in-231) below, and the full history
18
18
  in [CHANGELOG.md](CHANGELOG.md).
19
19
 
20
20
  ---
@@ -594,7 +594,7 @@ pnpm dev doctor # verify toolchain
594
594
  ```bash
595
595
  pnpm dev # run CLI from src/ via tsx
596
596
  pnpm build # tsup → dist/
597
- pnpm test # vitest (2115 tests, ~13s)
597
+ pnpm test # vitest (2122 tests, ~13s)
598
598
  pnpm test:changed # vitest --changed (fast edit loop)
599
599
  pnpm test:integration # Docker-based SSH integration tests
600
600
  pnpm test:e2e:local # builds, then bootstrap, the host-touching MCP tools and a 1.x migration, in systemd containers
@@ -653,6 +653,20 @@ Use `pnpm changeset` to record a release note before merging a `feat` or `fix`.
653
653
 
654
654
  ---
655
655
 
656
+ ## What's new in 2.3.1
657
+
658
+ A security fix. **Upgrade if you set gateway config through MCP.**
659
+
660
+ - **Before:** `clawops_config_set` wrote the value you set to the audit log
661
+ (`~/.clawops/mcp-audit.log`) in clear and redacted the config path instead. Setting
662
+ `models.providers.openai.apiKey` through an agent therefore logged the API key. The
663
+ confirmation prompt showed the value too.
664
+ - **Now:** the value is redacted whenever its path names a secret, and secret fields inside a
665
+ JSON object value are redacted one by one. Paths are logged as written. See
666
+ [redaction](docs/security/redaction.md#config-paths-and-values).
667
+ - **Existing logs are not rewritten.** If you used `clawops_config_set` for a secret before
668
+ 2.3.1, check your audit log and rotate that secret.
669
+
656
670
  ## What's new in 2.3
657
671
 
658
672
  Spending limits that an agent cannot lift, and a run of fixes from the backlog.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  applyPlan
4
- } from "./chunk-PRBDJSWN.js";
4
+ } from "./chunk-7LLVIQ7G.js";
5
5
  import "./chunk-BOPSG2LI.js";
6
6
  import "./chunk-CHPMOE4T.js";
7
7
  import "./chunk-AEEMRFZV.js";
@@ -48,7 +48,7 @@ ${validation.errors.join("\n")}`
48
48
  }
49
49
  const { guardOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
50
50
  await guardOpenclawVersion(plan.spec.openclaw.version);
51
- const { enforceInstanceType } = await import("./policy-4FFK7REN.js");
51
+ const { enforceInstanceType } = await import("./policy-AZGFCQNV.js");
52
52
  const { getConfig: getConfig2 } = await import("./store-BTQOOIJH.js");
53
53
  await enforceInstanceType(getConfig2(), plan.spec.provider, plan.spec.instanceType);
54
54
  const ctx = buildContext({
@@ -77,10 +77,10 @@ async function generatePlan(intent, _opts) {
77
77
  "plan/apply is not supported for the local provider. Use `clawops up` directly."
78
78
  );
79
79
  }
80
- const { version } = await import("./package-UOWSHBWP.js");
80
+ const { version } = await import("./package-HRMX7K3C.js");
81
81
  const config = getConfig();
82
82
  const instanceType = await resolveInstanceType(intent);
83
- const { enforceInstanceType } = await import("./policy-4FFK7REN.js");
83
+ const { enforceInstanceType } = await import("./policy-AZGFCQNV.js");
84
84
  await enforceInstanceType(config, intent.provider, instanceType);
85
85
  const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
86
86
  const openclawVersion = await guardOpenclawVersion(
@@ -27,7 +27,7 @@ import {
27
27
  } from "./chunk-G2ROJXYJ.js";
28
28
  import {
29
29
  applyPlan
30
- } from "./chunk-PRBDJSWN.js";
30
+ } from "./chunk-7LLVIQ7G.js";
31
31
  import {
32
32
  resolveConn
33
33
  } from "./chunk-SX75L2T3.js";
@@ -40,7 +40,7 @@ import {
40
40
  } from "./chunk-3QJBNAHW.js";
41
41
  import {
42
42
  generatePlan
43
- } from "./chunk-K7OWMI63.js";
43
+ } from "./chunk-LIAXA43Z.js";
44
44
  import {
45
45
  atomicWriteConfig,
46
46
  readRemoteConfig,
@@ -108,7 +108,39 @@ function getSessionId() {
108
108
  var SENSITIVE_KEYS = ["token", "secret", "key", "password", "connectionstring", "authorization", "credential"];
109
109
  var EXEMPT_KEYS = ["keyname", "keypath", "privateKeyPath", "knownHostsPath"];
110
110
  var ARN_RE = /arn:aws:[a-z0-9\-*]+:[a-z0-9\-]*:[0-9]*:[^\s,'"]+/gi;
111
+ function isSensitiveName(name) {
112
+ const n = name.toLowerCase();
113
+ const exempt = EXEMPT_KEYS.some((e) => n === e.toLowerCase());
114
+ return !exempt && SENSITIVE_KEYS.some((s) => n.includes(s));
115
+ }
116
+ function isSecretPath(dotPath) {
117
+ return dotPath.split(".").some(isSensitiveName);
118
+ }
119
+ function redactConfigValue(dotPath, value) {
120
+ if (isSecretPath(dotPath)) return "[REDACTED]";
121
+ let parsed;
122
+ try {
123
+ parsed = JSON.parse(value);
124
+ } catch {
125
+ return value;
126
+ }
127
+ if (parsed === null || typeof parsed !== "object") return value;
128
+ return JSON.stringify(sanitizeValue(parsed));
129
+ }
130
+ function sanitizeValue(v) {
131
+ if (Array.isArray(v)) return v.map(sanitizeValue);
132
+ if (v !== null && typeof v === "object") return sanitize(v);
133
+ return v;
134
+ }
111
135
  function sanitize(args) {
136
+ if (typeof args["key"] === "string") {
137
+ const { key, value, ...rest } = args;
138
+ const out = { ...sanitize(rest), key };
139
+ if (value !== void 0) {
140
+ out["value"] = typeof value === "string" ? redactConfigValue(key, value) : "[REDACTED]";
141
+ }
142
+ return out;
143
+ }
112
144
  const result = {};
113
145
  for (const [k, v] of Object.entries(args)) {
114
146
  const kl = k.toLowerCase();
@@ -836,7 +868,7 @@ async function handleConfigGet(input, _server) {
836
868
  }
837
869
  async function handleConfigSet(input, server) {
838
870
  const confirmation = await confirmDestructive(server, {
839
- message: `Set ${input.key} = ${input.value} on stack "${input.stackName ?? "default"}"?`,
871
+ message: `Set ${input.key} = ${redactConfigValue(input.key, input.value)} on stack "${input.stackName ?? "default"}"?`,
840
872
  title: "Confirm config change",
841
873
  what: "changing gateway configuration"
842
874
  });
@@ -1179,8 +1211,8 @@ async function handleUp(input, server) {
1179
1211
  const progressToken = void 0;
1180
1212
  const emit = makeProgressEmitter(server, progressToken);
1181
1213
  startTask(taskId, `clawops_up stack=${ctx.stackName}`);
1182
- const { generatePlan: generatePlan2 } = await import("./generate-OJ5UWTLV.js");
1183
- const { applyPlan: applyPlan2 } = await import("./apply-ODV3NOJJ.js");
1214
+ const { generatePlan: generatePlan2 } = await import("./generate-XJWEQKT6.js");
1215
+ const { applyPlan: applyPlan2 } = await import("./apply-TXPUNOGW.js");
1184
1216
  const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
1185
1217
  const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
1186
1218
  const network = await resolveNetworkFlags(
@@ -1400,7 +1432,7 @@ async function handleHarden(input, server) {
1400
1432
 
1401
1433
  // src/mcp/tools/cli/init.ts
1402
1434
  async function handleInit(input, _server) {
1403
- const { initStack } = await import("./init-RVQ72GZ5.js");
1435
+ const { initStack } = await import("./init-DYOLZX65.js");
1404
1436
  const result = await initStack({
1405
1437
  provider: input.provider,
1406
1438
  stackName: input.stackName,
@@ -2233,7 +2265,7 @@ function bearerFrom(header) {
2233
2265
  return match?.[1]?.trim();
2234
2266
  }
2235
2267
  async function serveMcp(opts) {
2236
- const { version } = await import("./package-UOWSHBWP.js");
2268
+ const { version } = await import("./package-HRMX7K3C.js");
2237
2269
  const makeServer = () => {
2238
2270
  const server = new McpServer({ name: "clawops", version });
2239
2271
  registerTools(server, opts);
@@ -109,7 +109,7 @@ async function initStack(opts) {
109
109
  reason: `Stack "${stackName}" already exists in ${path.join(getConfigDir(), "config.json")} (${existing.stacks[stackName].stateUrl}). Pass force to overwrite its settings, or use a different stack name to add another one.`
110
110
  };
111
111
  }
112
- const { stackCapRefusal } = await import("./policy-4FFK7REN.js");
112
+ const { stackCapRefusal } = await import("./policy-AZGFCQNV.js");
113
113
  const overCap = stackCapRefusal(existing, stackName);
114
114
  if (overCap) return { ok: false, reason: overCap };
115
115
  const configDir = getConfigDir();
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  initStack,
40
40
  resolveScopeAccount,
41
41
  validateStateBucket
42
- } from "./chunk-FGY262WZ.js";
42
+ } from "./chunk-Q56AM57B.js";
43
43
  import "./chunk-LT4XMO5D.js";
44
44
  import {
45
45
  execPrivileged,
@@ -256,8 +256,8 @@ var up_default = defineCommand2({
256
256
  }
257
257
  return;
258
258
  }
259
- const { generatePlan } = await import("./generate-OJ5UWTLV.js");
260
- const { applyPlan } = await import("./apply-ODV3NOJJ.js");
259
+ const { generatePlan } = await import("./generate-XJWEQKT6.js");
260
+ const { applyPlan } = await import("./apply-TXPUNOGW.js");
261
261
  const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
262
262
  const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
263
263
  const isDryRun = Boolean(args["dry-run"]);
@@ -552,7 +552,7 @@ var plan_default = defineCommand5({
552
552
  },
553
553
  async run({ args }) {
554
554
  const { buildContext } = await import("./context-4KTM4DKH.js");
555
- const { generatePlan } = await import("./generate-OJ5UWTLV.js");
555
+ const { generatePlan } = await import("./generate-XJWEQKT6.js");
556
556
  const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
557
557
  const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
558
558
  const ctx = buildContext(args);
@@ -668,7 +668,7 @@ var apply_default = defineCommand6({
668
668
  },
669
669
  async run({ args }) {
670
670
  const { validatePlan } = await import("./validate-CRXX7OYF.js");
671
- const { applyPlan } = await import("./apply-ODV3NOJJ.js");
671
+ const { applyPlan } = await import("./apply-TXPUNOGW.js");
672
672
  const planPath = args._?.[0];
673
673
  if (!planPath) {
674
674
  throw new UsageError("Usage: clawops apply <plan.json>");
@@ -1887,7 +1887,7 @@ var serveCmd = defineCommand18({
1887
1887
  inspector: { type: "boolean", description: "Enable MCP inspector" }
1888
1888
  },
1889
1889
  async run({ args }) {
1890
- const { serveMcp } = await import("./server-YH2JWZ2Q.js");
1890
+ const { serveMcp } = await import("./server-FR5LUAOI.js");
1891
1891
  await serveMcp({
1892
1892
  port: args.http ? Number(args.http) : void 0,
1893
1893
  bind: args.bind,
@@ -1968,8 +1968,8 @@ var wireCmd = defineCommand18({
1968
1968
  const { buildContext } = await import("./context-4KTM4DKH.js");
1969
1969
  const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
1970
1970
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1971
- const { wireGatewayMcp } = await import("./mcp-wire-HSGZFOG5.js");
1972
- const { MCP_HTTP_PORT } = await import("./server-YH2JWZ2Q.js");
1971
+ const { wireGatewayMcp } = await import("./mcp-wire-NAKI2WME.js");
1972
+ const { MCP_HTTP_PORT } = await import("./server-FR5LUAOI.js");
1973
1973
  const ac = new AbortController();
1974
1974
  process20.on("SIGINT", () => {
1975
1975
  ac.abort();
@@ -2180,7 +2180,7 @@ var setup_default = defineCommand19({
2180
2180
  }]);
2181
2181
  localSudoPassword = sudoPass;
2182
2182
  }
2183
- const { promptSpendingPolicy, allowedChoices, savePolicy } = await import("./setup-policy-OK5PGWP7.js");
2183
+ const { promptSpendingPolicy, allowedChoices, savePolicy } = await import("./setup-policy-HTO2KSZR.js");
2184
2184
  const { getConfig: readConfig } = await import("./store-BTQOOIJH.js");
2185
2185
  const configBefore = readConfig();
2186
2186
  const pendingPolicy = await promptSpendingPolicy({
@@ -2250,7 +2250,7 @@ var setup_default = defineCommand19({
2250
2250
  }
2251
2251
  ]);
2252
2252
  {
2253
- const { stackCapRefusal } = await import("./policy-4FFK7REN.js");
2253
+ const { stackCapRefusal } = await import("./policy-AZGFCQNV.js");
2254
2254
  const { getConfig } = await import("./store-BTQOOIJH.js");
2255
2255
  const overCap = stackCapRefusal(getConfig(), stackAnswers.stackName);
2256
2256
  if (overCap) throw new UsageError(overCap);
@@ -2430,9 +2430,9 @@ var setup_default = defineCommand19({
2430
2430
  failure(`Cannot read SSH public key at ${stackAnswers.sshKeyPath ?? ""}`);
2431
2431
  process21.exit(1);
2432
2432
  }
2433
- const { nativeInstanceType } = await import("./generate-OJ5UWTLV.js");
2433
+ const { nativeInstanceType } = await import("./generate-XJWEQKT6.js");
2434
2434
  const instanceType = await nativeInstanceType(provider, stackAnswers.instanceSize);
2435
- const { enforceInstanceType } = await import("./policy-4FFK7REN.js");
2435
+ const { enforceInstanceType } = await import("./policy-AZGFCQNV.js");
2436
2436
  const { getConfig: currentConfig } = await import("./store-BTQOOIJH.js");
2437
2437
  await enforceInstanceType(currentConfig(), provider, instanceType);
2438
2438
  const plan = {
@@ -2604,7 +2604,7 @@ var setup_default = defineCommand19({
2604
2604
  default: false
2605
2605
  }]);
2606
2606
  if (applyNow) {
2607
- const { applyPlan } = await import("./apply-ODV3NOJJ.js");
2607
+ const { applyPlan } = await import("./apply-TXPUNOGW.js");
2608
2608
  const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
2609
2609
  try {
2610
2610
  await applyPlan(plan, {
@@ -2640,7 +2640,7 @@ async function runLocalDeploy(opts) {
2640
2640
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
2641
2641
  const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
2642
2642
  const existing = getConfig();
2643
- const { stackCapRefusal } = await import("./policy-4FFK7REN.js");
2643
+ const { stackCapRefusal } = await import("./policy-AZGFCQNV.js");
2644
2644
  const overCap = stackCapRefusal(existing, opts.stackName);
2645
2645
  if (overCap) throw new UsageError(overCap);
2646
2646
  const stackEntry = {
@@ -3112,7 +3112,7 @@ async function maybeWireGatewayMcp(opts) {
3112
3112
  if (!wireGateway) return;
3113
3113
  const { connect } = await import("./ssh-HOM7GF3M.js");
3114
3114
  const { getConfigDir: getConfigDir2 } = await import("./store-BTQOOIJH.js");
3115
- const { wireGatewayMcp } = await import("./mcp-wire-HSGZFOG5.js");
3115
+ const { wireGatewayMcp } = await import("./mcp-wire-NAKI2WME.js");
3116
3116
  const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
3117
3117
  const spin = spinner("Wiring gateway MCP client...");
3118
3118
  const session = await connect({
@@ -3680,7 +3680,7 @@ import { accessSync, constants } from "fs";
3680
3680
  import { execSync as execSync2 } from "child_process";
3681
3681
  var ISSUES_URL = `${REPO_URL}/issues/new`;
3682
3682
  var BODY_CAP = 2e3;
3683
- var VERSION = true ? "2.3.0" : "dev";
3683
+ var VERSION = true ? "2.3.1" : "dev";
3684
3684
  async function collectContext() {
3685
3685
  const nodeVersion = process23.version;
3686
3686
  const os2 = `${process23.platform} ${process23.arch}`;
@@ -3967,7 +3967,7 @@ function resolveArgv(argv2, stdinIsTty) {
3967
3967
  }
3968
3968
 
3969
3969
  // src/cli/index.ts
3970
- var CLI_VERSION = true ? "2.3.0" : "dev";
3970
+ var CLI_VERSION = true ? "2.3.1" : "dev";
3971
3971
  var main = defineCommand24({
3972
3972
  meta: {
3973
3973
  name: "clawops",
@@ -6,7 +6,7 @@ import {
6
6
  nativeInstanceType,
7
7
  parseDiffForTest,
8
8
  planId
9
- } from "./chunk-K7OWMI63.js";
9
+ } from "./chunk-LIAXA43Z.js";
10
10
  import "./chunk-POYS3EVQ.js";
11
11
  import "./chunk-47CGHUVT.js";
12
12
  import "./chunk-SQPHNVCF.js";
@@ -3,7 +3,7 @@ import {
3
3
  PROVIDER_DEFAULTS,
4
4
  SUPPORTED_PROVIDERS,
5
5
  initStack
6
- } from "./chunk-FGY262WZ.js";
6
+ } from "./chunk-Q56AM57B.js";
7
7
  import "./chunk-GYMAC4MW.js";
8
8
  import "./chunk-KGXPLI7W.js";
9
9
  export {
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  MCP_HTTP_PORT
4
- } from "./chunk-EGGXYZC6.js";
4
+ } from "./chunk-ORBZGTNX.js";
5
5
  import "./chunk-27335SIH.js";
6
6
  import "./chunk-4SEFKZRL.js";
7
7
  import "./chunk-66CUJFXK.js";
8
8
  import "./chunk-G2ROJXYJ.js";
9
- import "./chunk-PRBDJSWN.js";
9
+ import "./chunk-7LLVIQ7G.js";
10
10
  import "./chunk-SX75L2T3.js";
11
11
  import "./chunk-TYNMOYVM.js";
12
12
  import "./chunk-BOPSG2LI.js";
13
13
  import "./chunk-3QJBNAHW.js";
14
14
  import "./chunk-CHPMOE4T.js";
15
15
  import "./chunk-AEEMRFZV.js";
16
- import "./chunk-K7OWMI63.js";
16
+ import "./chunk-LIAXA43Z.js";
17
17
  import "./chunk-POYS3EVQ.js";
18
18
  import "./chunk-4PY6PN4G.js";
19
19
  import "./chunk-MC5ATCXS.js";
@@ -2,7 +2,7 @@
2
2
 
3
3
  // package.json
4
4
  var name = "@clawops/cli";
5
- var version = "2.3.0";
5
+ var version = "2.3.1";
6
6
  var description = "Deploy self-hosted OpenClaw to AWS, GCP, Azure or a local VM \u2014 a CLI and an MCP server in one.";
7
7
  var keywords = [
8
8
  "openclaw",
@@ -15,13 +15,13 @@ function configFile() {
15
15
  async function allowedTypesFor(policy, provider) {
16
16
  const entries = policy?.allowedInstanceTypes?.[provider];
17
17
  if (!entries) return void 0;
18
- const { nativeInstanceType } = await import("./generate-OJ5UWTLV.js");
18
+ const { nativeInstanceType } = await import("./generate-XJWEQKT6.js");
19
19
  return [...new Set(await Promise.all(entries.map((e) => nativeInstanceType(provider, e))))];
20
20
  }
21
21
  async function enforceInstanceType(config, provider, instanceType) {
22
22
  const allowed = await allowedTypesFor(config?.policy, provider);
23
23
  if (!allowed) return;
24
- const { nativeInstanceType } = await import("./generate-OJ5UWTLV.js");
24
+ const { nativeInstanceType } = await import("./generate-XJWEQKT6.js");
25
25
  if (allowed.includes(await nativeInstanceType(provider, instanceType))) return;
26
26
  throw new UsageError(
27
27
  `Instance type "${instanceType}" is not allowed on ${provider} by this machine's spending policy (${configFile()} \u2192 policy.allowedInstanceTypes.${provider}). Allowed: ${allowed.join(", ")}. The operator can change it with \`clawops setup\` or by editing that file.`
@@ -5,19 +5,19 @@ import {
5
5
  bearerFrom,
6
6
  isLoopbackBind,
7
7
  serveMcp
8
- } from "./chunk-EGGXYZC6.js";
8
+ } from "./chunk-ORBZGTNX.js";
9
9
  import "./chunk-27335SIH.js";
10
10
  import "./chunk-4SEFKZRL.js";
11
11
  import "./chunk-66CUJFXK.js";
12
12
  import "./chunk-G2ROJXYJ.js";
13
- import "./chunk-PRBDJSWN.js";
13
+ import "./chunk-7LLVIQ7G.js";
14
14
  import "./chunk-SX75L2T3.js";
15
15
  import "./chunk-TYNMOYVM.js";
16
16
  import "./chunk-BOPSG2LI.js";
17
17
  import "./chunk-3QJBNAHW.js";
18
18
  import "./chunk-CHPMOE4T.js";
19
19
  import "./chunk-AEEMRFZV.js";
20
- import "./chunk-K7OWMI63.js";
20
+ import "./chunk-LIAXA43Z.js";
21
21
  import "./chunk-POYS3EVQ.js";
22
22
  import "./chunk-4PY6PN4G.js";
23
23
  import "./chunk-MC5ATCXS.js";
@@ -2,10 +2,10 @@
2
2
 
3
3
  // src/cli/setup-policy.ts
4
4
  async function allowedChoices(provider, choices, policy) {
5
- const { allowedTypesFor } = await import("./policy-4FFK7REN.js");
5
+ const { allowedTypesFor } = await import("./policy-AZGFCQNV.js");
6
6
  const allowed = await allowedTypesFor(policy, provider);
7
7
  if (!allowed) return choices;
8
- const { nativeInstanceType } = await import("./generate-OJ5UWTLV.js");
8
+ const { nativeInstanceType } = await import("./generate-XJWEQKT6.js");
9
9
  const kept = [];
10
10
  for (const c of choices) {
11
11
  if (allowed.includes(await nativeInstanceType(provider, c.value))) kept.push(c);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawops/cli",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Deploy self-hosted OpenClaw to AWS, GCP, Azure or a local VM — a CLI and an MCP server in one.",
5
5
  "keywords": [
6
6
  "openclaw",