Back to skill

Security audit

cz-studio-agent

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real Studio Agent bridge, but it should be reviewed because it can silently approve remote action requests and expose the auth token in local config output.

Install only if you trust the configured Studio endpoint and understand that requests and identity metadata are sent to that service. Before use, set `CZ_INTERRUPT_DECISION_MODE` to `off` or `auto_reject`, avoid broad `CZ_ALWAYS_ALLOW_TOOLS`, keep the OpenClaw config private, and do not paste validate/apply output containing `CZ_AGENT_WS_URL` into logs or support chats. Rotate any token that has already appeared in command output or shared transcripts.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/cz-agent-proxy.mjs:1108
Finding
Remote tool actions are automatically approved without explicit user authorization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cz-agent-proxy.mjs:1108-1152`; default enabled in `scripts/cz-agent-oneshot.mjs:154-157` and `scripts/cz-agent-proxy.mjs:11-15` **Vulnerability Type**: Automatic authorization of remote action requests **Risk Level**: High ### Vulnerable Code ```js async handleInterruptRequest(message) { if (!this.activeRequest) { return false; } if (this.interruptDecisionMode === "off") { writeEvent( createErrorEvent({ requestId: message.requestId, conversationId: message.conversationId, code: CODE_PROTOCOL_ERROR, message: "interrupt_request received but CZ_INTERRUPT_DECISION_MODE=off. Set mode to auto_approve or auto_reject.", }), ); this.clearActiveRequest(); return false; } const decisionValue = this.interruptDecisionMode === "auto_reject" ? "reject" : "approve"; const interruptDecisions = buildInterruptDecisions(message, decisionValue); const baseMetadata = asObjectOrUndefined(this.activeRequest.baseMetadata) ?? {}; const decisionMetadata = { source: "openclaw", auto_interrupt_decision: decisionValue, }; if ( Array.isArray(baseMetadata.always_allow_tools) && baseMetadata.always_allow_tools.length > 0 ) { decisionMetadata.always_allow_tools = [...baseMetadata.always_allow_tools]; } const decisionMessage = { op_type: "interrupt_decision", identity: this.activeRequest.identity, request_id: message.requestId, conversation_id: message.conversationId, interrupt_decisions: interruptDecisions, timestamp: nowMs(), metadata: decisionMetadata, }; const sent = await this.sendStudioMessage(decisionMessage); if (!sent) { this.emitActiveNetworkError("failed to send interrupt_decision to Studio WebSocket"); return false; } ``` The one-shot runner establishes the unsafe default: ```js if (!asTrimmedString(env.CZ_INTERRUPT_DECISION_MODE)) { env.CZ_INTERRUPT_D ...[truncated 2170 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Change the default decision mode to `off` or `auto_reject` in both the proxy and one-shot runner. 2. Present every interrupt request to the user with the tool name, operation, arguments, target resource, and expected effects. 3. Require a fresh, explicit user decision before sending an approval. 4. If unattended operation is necessary, implement a local allowlist covering both tool names and narrowly constrained argument patterns. 5. Reject unknown tools, malformed requests, dangerous operations, and requests outside the configured project or workspace. 6. Bind approvals to the expected request ID, conversation ID, interrupt ID, and tool-call ID, and prevent replay. 7. Record an audit event for every requested, approved, and rejected action without logging credentials or sensitive arguments. 8. Add tests confirming that missing configuration results in rejection and that remote requests cannot silently select approval mode. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/configure-skill.mjs:306
Finding
Authentication token is exposed through plaintext WebSocket URLs and configuration output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/configure-skill.mjs:306-312`, `scripts/configure-skill.mjs:624`, `scripts/configure-skill.mjs:653`, `scripts/configure-skill.mjs:658`, and `scripts/configure-skill.mjs:664` **Vulnerability Type**: Plaintext credential exposure **Risk Level**: Medium ### Vulnerable Code The token is embedded directly in the WebSocket URL: ```js if (!parsed.searchParams.has("env")) { parsed.searchParams.set("env", "prod"); } else { // Skill convention: normalize env to prod. parsed.searchParams.set("env", "prod"); } parsed.searchParams.set("x-clickzetta-token", tokenText); return parsed.toString(); ``` The resulting environment, including the secret-bearing URL, is printed during validation: ```js if (command === "validate") { console.log(`${JSON.stringify({ env: normalized.env }, null, 2)}\n`); return; } ``` It is also printed during dry-run: ```js if (opts.dryRun) { console.error(`\n[dry-run] target config: ${configPath}`); console.error(`- added: ${diff.added.join(", ") || "(none)"}`); console.error(`- updated: ${diff.updated.join(", ") || "(none)"}`); console.error(`- removed: ${diff.removed.join(", ") || "(none)"}`); console.log(`${JSON.stringify({ env: next }, null, 2)}\n`); return; } ``` Finally, the complete configuration is written and the secret-bearing environment is printed again: ```js fs.mkdirSync(path.dirname(configPath), { recursive: true }); fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8"); console.error(`\nApplied ${SKILL_KEY} env config to: ${configPath}`); console.error(`- added: ${diff.added.join(", ") || "(none)"}`); console.error(`- updated: ${diff.updated.join(", ") || "(none)"}`); console.error(`- removed: ${diff.removed.join(", ") || "(none)"}`); console.log(`${JSON.stringify({ env: next }, null, 2)}\n`); ``` ### Technical Analysis The configuration manager removes `CZ_AGENT_TOKEN` by default, but places the same token in the `x-cli ...[truncated 1981 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never print `CZ_AGENT_WS_URL` without redacting the `x-clickzetta-token` value. 2. Implement a centralized redaction function and apply it to validation, dry-run, success, diagnostic, and error output. 3. Store the token separately from the URL, preferably in a secret manager or protected environment variable. 4. Where supported, transmit authentication through an authorization header or another mechanism that is less likely to be logged than a URL query parameter. 5. Create new configuration files with mode `0600` and verify or correct permissions on existing files before writing secrets. 6. Avoid returning complete environment maps; print only key names and non-sensitive status information. 7. Ensure logs and error messages never contain the raw token or complete authenticated URL. 8. Document token rotation and revocation procedures and advise users to rotate any token previously exposed in logs or transcripts. 9. Add automated tests that use sentinel credentials and fail if the sentinel appears in stdout, stderr, generated diagnostics, or snapshots. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (25)

Ae1

High
Category
analysis-evasion
Content
- A local proxy script: `scripts/cz-agent-proxy.mjs`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- A local proxy script: `scripts/cz-agent-proxy.mjs`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- A local proxy script: `scripts/cz-agent-proxy.mjs`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- A local proxy script: `scripts/cz-agent-proxy.mjs`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- A one-shot runner script: `scripts/cz-agent-oneshot.mjs` (recommended default)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- A one-shot runner script: `scripts/cz-agent-oneshot.mjs` (recommended default)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
}

  const diff = diffKeys(before, next);
  config.skills.entries[SKILL_KEY].env = next;

  if (opts.dryRun) {
    console.error(`\n[dry-run] target config: ${configPath}`);
Confidence
89% confidence
Finding
The script persists sensitive connection material into the OpenClaw config under the skill's `env`, and earlier logic embeds `x-clickzetta-token` directly into `CZ_AGENT_WS_URL`. Writing secrets into a local JSON config file increases exposure through filesystem disclosure, backups, logs, shell history from dry-run output, or accidental sharing of the config file.

Credential Access

High
Category
Privilege Escalation
Content
const parsed = JSON.parse(raw);
  const env = parsed?.skills?.entries?.[SKILL_KEY]?.env;
  if (!isRecord(env)) {
    throw new Error(`Missing skills.entries.${SKILL_KEY}.env in ${fullPath}`);
  }
  return { configPath: fullPath, env };
}
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

High
Confidence
98% confidence
Finding
The proxy defaults CZ_INTERRUPT_DECISION_MODE to auto_approve, and later automatically sends interrupt_decision messages approving tool/action requests without any user confirmation. In an agent bridge, this removes the safety gate around privileged tool execution and can allow a remote agent or compromised backend workflow to perform unintended actions automatically.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill instructs users to place real tokens and connection details into a local config and then apply them into runtime environment settings, but it gives no explicit warning about credential sensitivity, storage risks, or log exposure. In a skill that bridges to a remote agent over WebSocket, this increases the chance of accidental credential disclosure through config files, process environments, command history, or platform config persistence.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Execution policy

- Do not ask the user for `CZ_*` env vars up front.
- Assume env is already wired via OpenClaw config (`skills.entries["studio-agent"].env`) unless runtime errors prove otherwise.
- When runtime reports missing/invalid Studio config, return a copy-paste setup block:
  - `cp skills/studio-agent/studio-agent.config.example.json studio-agent.config.json`
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- For `user_input`, the proxy auto-normalizes metadata to frontend-compatible shape:
  - default `metadata.source = "openclaw"` when missing
  - default `metadata.configs = [{"type":"text","value":"<user_input>"}]` when missing
- For `interrupt_request`, proxy auto-sends `interrupt_decision` by default (`CZ_INTERRUPT_DECISION_MODE=auto_approve`) so requests do not hang waiting for manual confirmation.
- Proxy defaults to compact output for OpenClaw context safety:
  - `CZ_EMIT_ASSISTANT_DELTAS=false` by default (only `assistant_final` / `error` are emitted)
  - when deltas are enabled, `content` is omitted from delta events to avoid repeated cumulative payloads
Confidence
96% confidence
Finding
Defaulting `CZ_INTERRUPT_DECISION_MODE=auto_approve` gives the system authority to make approval decisions automatically during an interaction with a remote agent. In this skill's context, that is materially more dangerous because the bridge is designed to relay multi-turn requests to an external service that may trigger follow-up actions, so automatic approval can bypass user intent and consent.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill states that interrupt requests are auto-approved by default, without any user-facing warning or confirmation requirement. Because this bridge delegates work to a remote agent that may perform task, query, or tool-related actions, automatic approval can allow the remote side to continue or authorize sensitive operations without meaningful human review.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- `CZ_STARTUP_CONNECT_TIMEOUT_SECONDS` (default `10`)
- `CZ_RECONNECT_MAX_ATTEMPTS` (default `3`)
- `CZ_ALWAYS_ALLOW_TOOLS` (optional): comma-separated tool names injected as `metadata.always_allow_tools` when request metadata omits it
- `CZ_INTERRUPT_DECISION_MODE` (optional): `auto_approve` (default) | `auto_reject` | `off`
- `CZ_EMIT_ASSISTANT_DELTAS` (optional, default `false`): emit `assistant_delta` events when true

Optional identity enrichments (recommended for task/query permissions in some Studio deployments):
Confidence
91% confidence
Finding
Documenting `CZ_INTERRUPT_DECISION_MODE` with `auto_approve` as the default normalizes autonomous approval behavior and encourages insecure deployment choices. While this line is a configuration reference rather than executable logic, in context it directly supports a risky default for a remote-agent bridge capable of carrying privileged actions.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
"Input expectation (minimal):",
    "  wsUrl/baseUrl (host:port or ws URL), token, instanceId, instanceName, projectId, workspace",
    "  optional: alwaysAllowTools (comma-separated tool names)",
    "  optional: interruptDecisionMode (auto_approve|auto_reject|off), emitAssistantDeltas (true|false)",
    "  Script builds CZ_AGENT_WS_URL as: <base>/<path>?x-clickzetta-token=<token>&env=prod",
    "  default scheme policy when scheme missing: local -> ws://, remote -> wss://",
    "  default path policy when path missing: local -> /ws, remote -> /ai",
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
"Input expectation (minimal):",
    "  wsUrl/baseUrl (host:port or ws URL), token, instanceId, instanceName, projectId, workspace",
    "  optional: alwaysAllowTools (comma-separated tool names)",
    "  optional: interruptDecisionMode (auto_approve|auto_reject|off), emitAssistantDeltas (true|false)",
    "  Script builds CZ_AGENT_WS_URL as: <base>/<path>?x-clickzetta-token=<token>&env=prod",
    "  default scheme policy when scheme missing: local -> ws://, remote -> wss://",
    "  default path policy when path missing: local -> /ws, remote -> /ai",
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
"Input expectation (minimal):",
    "  wsUrl/baseUrl (host:port or ws URL), token, instanceId, instanceName, projectId, workspace",
    "  optional: alwaysAllowTools (comma-separated tool names)",
    "  optional: interruptDecisionMode (auto_approve|auto_reject|off), emitAssistantDeltas (true|false)",
    "  Script builds CZ_AGENT_WS_URL as: <base>/<path>?x-clickzetta-token=<token>&env=prod",
    "  default scheme policy when scheme missing: local -> ws://, remote -> wss://",
    "  default path policy when path missing: local -> /ws, remote -> /ai",
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
"Input expectation (minimal):",
    "  wsUrl/baseUrl (host:port or ws URL), token, instanceId, instanceName, projectId, workspace",
    "  optional: alwaysAllowTools (comma-separated tool names)",
    "  optional: interruptDecisionMode (auto_approve|auto_reject|off), emitAssistantDeltas (true|false)",
    "  Script builds CZ_AGENT_WS_URL as: <base>/<path>?x-clickzetta-token=<token>&env=prod",
    "  default scheme policy when scheme missing: local -> ws://, remote -> wss://",
    "  default path policy when path missing: local -> /ws, remote -> /ai",
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The runner constructs the child environment by cloning the entire parent process environment and then overlaying selected CZ_ variables from the skill config. This can unintentionally forward unrelated secrets, tokens, proxy settings, and credentials to the local proxy process and any remote service it communicates with, expanding the blast radius if the proxy or downstream agent is compromised or logs its environment.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
env.CZ_REQUEST_TIMEOUT_SECONDS = String(opts.requestTimeoutSeconds);
  env.CZ_STARTUP_CONNECT_TIMEOUT_SECONDS = String(opts.startupTimeoutSeconds);
  if (!asTrimmedString(env.CZ_INTERRUPT_DECISION_MODE)) {
    env.CZ_INTERRUPT_DECISION_MODE = "auto_approve";
  }
  if (!asTrimmedString(env.CZ_EMIT_ASSISTANT_DELTAS)) {
    env.CZ_EMIT_ASSISTANT_DELTAS = "false";
Confidence
90% confidence
Finding
Defaulting CZ_INTERRUPT_DECISION_MODE to "auto_approve" enables autonomous approval behavior when no safer setting is configured. For a skill that brokers requests to a remote agent, this reduces human oversight and can permit unintended actions or sensitive data handling to proceed automatically.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The script automatically spawns a subprocess, sends user-provided input to it, and gives it the inherited environment without any confirmation, disclosure, or safety gate. In this skill's context, the proxy bridges to a remote Studio Agent over WebSocket, so local prompts and ambient credentials may be transmitted off-host even when the user may not realize the execution and forwarding semantics.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
const DEFAULT_ALWAYS_ALLOW_TOOLS = readStringListEnv("CZ_ALWAYS_ALLOW_TOOLS");
const DEFAULT_INTERRUPT_DECISION_MODE = readInterruptDecisionModeEnv(
  "CZ_INTERRUPT_DECISION_MODE",
  "auto_approve",
);
const DEFAULT_EMIT_ASSISTANT_DELTAS = readBoolEnv("CZ_EMIT_ASSISTANT_DELTAS", false);
Confidence
97% confidence
Finding
The literal default of auto_approve means the system is intentionally configured for autonomous approval of interrupt requests unless overridden. In this skill context, interrupts appear to represent tool/action approval checkpoints, so bypassing them materially increases the chance of unsafe remote actions.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
return fallback;
  }
  const normalized = value.toLowerCase();
  if (["auto_approve", "approve", "auto-approve"].includes(normalized)) {
    return "auto_approve";
  }
  if (["auto_reject", "reject", "auto-reject"].includes(normalized)) {
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The proxy establishes a WebSocket connection and includes authentication data in the URL, then sends messages containing identity fields and token-derived data. There is no visible print/log statement, prompt, or explanatory comment warning users that user/system identity data will be transmitted to a remote service.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/cz-agent-oneshot.mjs:238