Back to skill

Security audit

Workflow Orchestrator

Security checks for vulnerabilities and agentic risk

Overview

This workflow runner is not clearly malicious, but it needs review because it can run local commands hands-free and its documented safety controls are weaker than they appear.

Install only if you will run workflows from trusted, reviewed files. Use dry-run first, avoid untrusted saved outputs in later commands, do not rely on dotted JSON conditions or rollback for security decisions, and run state-changing workflows in a contained low-privilege environment.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/orchestrator.py:42
Finding
Argument Injection Through Unsafe Saved-Output Substitution## Vulnerability Details **File Location**: `scripts/orchestrator.py`, lines 42–57 and 128–163 **Vulnerability Type**: Argument injection caused by unsafe string interpolation **Risk Level**: High ### Vulnerable Code ```python def substitute_vars(text, variables): """Replace {var_name} placeholders with actual values. SECURITY: {env.*} access is BLOCKED to prevent environment variable leakage. Only workflow-defined variables are substituted. """ def replacer(match): key = match.group(1) if key.startswith("env."): # BLOCKED: env var access via workflow variables is a credential leak vector return "{" + key + "}" value = variables.get(key) if value is None: return match.group(0) return str(value) return re.sub(r'\{(\w[\w.]*)\}', replacer, text) ``` ```python def run_step(step, variables, dry_run=False): """Execute a single workflow step.""" name = step.get("name", "unnamed") command = substitute_vars(step.get("command", ""), variables) on_fail = step.get("on_fail", "abort") condition = step.get("condition") save_output = step.get("save_output") timeout = step.get("timeout", 60) max_retries = 3 if on_fail == "retry" else 1 # Check condition if condition and not check_condition(condition, variables): print(f" SKIP [{name}] — condition not met: {condition}") return True, None # Validate command after substitution valid, reason = _validate_command(command) if not valid: print(f" BLOCKED [{name}] {reason}") return False, None if dry_run: print(f" [DRY] [{name}] {command}") return True, None for attempt in range(max_retries): if attempt > 0: print(f" RETRY [{name}] attempt {attempt + 1}/{max_retries}") time.sleep(2) pr ...[truncated 2771 chars]
Remediation
## Remediation Suggestions - Define commands as arrays of arguments rather than shell-like strings, for example `["program", "--input", "{result}"]`. - Perform substitution independently within each array element so one substituted value cannot create additional arguments. - Treat all saved command output as untrusted. - Require structured output, such as validated JSON, and expose only explicitly selected fields to subsequent steps. - Apply per-variable schemas, length limits, and allowlists for expected formats such as paths, identifiers, or enumeration values. - Where a value may begin with `-`, use the target application's end-of-options marker (`--`) when supported. - Add tests covering whitespace, quotation marks, leading option prefixes, embedded newlines, and multi-argument payloads.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/orchestrator.py:59
Finding
Security Conditions Fail Open for Documented Dotted-Field References## Vulnerability Details **File Location**: `scripts/orchestrator.py`, lines 59–101 **Vulnerability Type**: Fail-open authorization and security-gate evaluation **Risk Level**: High The affected syntax is explicitly documented in `SKILL.md`, lines 55–56: ```yaml condition: scan_result.verdict != "CRITICAL" ``` ### Vulnerable Code ```python def check_condition(condition, variables): """Evaluate a simple condition string against variables.""" if not condition: return True # Simple checks: "VAR_NAME not in saved_output" or "saved_output.field != value" condition = substitute_vars(condition, variables) # "X not in Y" check m = re.match(r'(.+)\s+not\s+in\s+(.+)', condition) if m: needle = m.group(1).strip().strip('"\'') haystack = str(variables.get(m.group(2).strip(), m.group(2).strip())) return needle not in haystack # "X in Y" check m = re.match(r'(.+)\s+in\s+(.+)', condition) if m: needle = m.group(1).strip().strip('"\'') haystack = str(variables.get(m.group(2).strip(), m.group(2).strip())) return needle in haystack # "X != Y" check m = re.match(r'(.+)\s*!=\s*(.+)', condition) if m: left = m.group(1).strip().strip('"\'') right = m.group(2).strip().strip('"\'') left_val = str(variables.get(left, left)) right_val = str(variables.get(right, right)) return left_val != right_val # "X == Y" check m = re.match(r'(.+)\s*==\s*(.+)', condition) if m: left = m.group(1).strip().strip('"\'') right = m.group(2).strip().strip('"\'') left_val = str(variables.get(left, left)) right_val = str(variables.get(right, right)) return left_val == right_val # Default: treat as truthy return bool(condition.strip()) ``` Saved output is stored without structured parsing: ```python ...[truncated 2123 chars]
Remediation
## Remediation Suggestions - Parse outputs declared as JSON into structured objects before making them available to conditions. - Implement explicit and tested dotted-path resolution for expressions such as `scan_result.verdict`. - Fail closed when a referenced variable or field is absent, malformed, or of an unexpected type. - Replace permissive regular-expression evaluation with a small, formally defined condition grammar or a safe expression parser. - Reject unsupported syntax during `validate_workflow` rather than treating a nonempty expression as true. - Validate scanner output against a schema that requires a recognized verdict value. - Prefer positive allow conditions such as `verdict == "SAFE"` over negative checks such as `verdict != "CRITICAL"`. - Add regression tests proving that missing fields, invalid JSON, unknown variables, and critical verdicts prevent protected steps from running.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/orchestrator.py:214
Finding
Rollback Mode Reports Success Without Reversing Completed Actions## Vulnerability Details **File Location**: `scripts/orchestrator.py`, lines 214–222 **Vulnerability Type**: Nonfunctional rollback and misleading recovery reporting **Risk Level**: Medium ### Vulnerable Code ```python else: if output == "rollback": print(f"\nRolling back {len(completed_steps)} completed steps...") # Rollback is conceptual — log it for the audit trail for cs in reversed(completed_steps): print(f" ROLLBACK [{cs.get('name', '?')}]") failed = True break ``` The documented behavior in `SKILL.md`, line 58, states: ```text on_fail — What to do if the step fails: abort (stop workflow), warn (log and continue), rollback (undo previous steps), retry (retry up to 3 times) ``` ### Technical Analysis The `rollback` failure policy is represented as an operation that will undo previously completed steps. The implementation only iterates over completed steps and prints `ROLLBACK` messages. It does not invoke compensating commands, restore files, revert deployments, or verify the resulting state. This creates a discrepancy between documented security behavior and actual behavior. It also produces audit output that can reasonably be interpreted as evidence that recovery occurred, even though all side effects from completed steps remain active. ### Attack Path 1. One or more workflow steps successfully perform state-changing operations, such as modifying files or deploying an artifact. 2. A later step fails and is configured with `on_fail: rollback`. 3. The orchestrator enters the rollback branch. 4. It prints a rollback message for each completed step but executes no reversal operation. 5. Operators or automated log consumers infer that the prior actions were undone. 6. The partial deployment or modification remains in place and may continue operating unnoticed. ### Impact Assessment This flaw does not independently grant additional operating-s ...[truncated 419 chars]
Remediation
## Remediation Suggestions - Require each state-changing step to define an explicit compensating `rollback_command`. - Execute rollback commands in reverse completion order and capture their exit status and output. - Mark rollback as successful only after every required compensating action has completed and its resulting state has been verified. - Distinguish clearly among “rollback requested,” “rollback attempted,” “rollback failed,” and “rollback completed” in logs. - Stop advertising this behavior as rollback if only logging is intended; rename it to a non-recovery policy such as `abort_and_log`. - Prefer transactional deployment mechanisms, atomic file replacement, snapshots, or versioned releases where feasible. - Add integration tests that verify system state is actually restored after failures.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (5)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documents direct shell execution via `python3 ... orchestrator.py run` and describes running arbitrary workflow-defined commands, but it does not declare any tool scope such as `permissions` or `allowed-tools`. That omission weakens security boundaries and makes it easier for an agent or user to invoke system-impacting shell actions without explicit capability disclosure or policy review.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The description emphasizes 'hands-free' automated execution of chained steps, including deployment and maintenance actions, but provides no explicit warning that workflows may run shell commands with real system effects. In this context, users may treat the skill as routine orchestration while unintentionally executing destructive, high-privilege, or security-sensitive operations defined in workflow files.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill explicitly supports `{env.VAR_NAME}` substitution in shell commands and also advertises audit logging and saved outputs, but it does not warn that secrets from environment variables can be exposed through process arguments, stdout, workflow state, or logs. In a workflow orchestrator, this is especially dangerous because sensitive values may be propagated across multiple steps and persisted for later inspection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
try:
            import shlex
            cmd_parts = shlex.split(command)
            result = subprocess.run(
                cmd_parts,
                shell=False,
                capture_output=True,
Confidence
88% confidence
Finding
This orchestrator executes workflow-defined commands from YAML/JSON files, so any user who can supply or modify a workflow can cause arbitrary local command execution. Although shell=False and metacharacter filtering reduce classic shell injection, they do not change the core risk that the product is effectively a command runner with weak policy controls, making this dangerous in a hands-free automation context.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The code announces a rollback and emits rollback log lines, but it does not actually undo any previously completed actions. In deployment, maintenance, or security-gated workflows, this can leave systems in a partially changed state while operators falsely believe recovery occurred, increasing the chance of inconsistent state, exposure, or failed remediation.

Static analysis

No suspicious patterns detected.