Back to skill

Security audit

zy-action-platform-en

Security checks across malware telemetry and agentic risk

Overview

The skill has a coherent platform-assistant purpose, but its credential handling and generic request path checks create material review concerns before installation.

Install only if you are comfortable granting an AI assistant authenticated access to ZY Action Platform. Use a dedicated read-only account, avoid admin credentials, avoid putting passwords or bearer tokens in command-line arguments or prompts, prefer logout when finished, and avoid generic pass-through requests until the path canonicalization and secret-entry issues are fixed.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/zy_platform.py:894
Finding
Generic Request Route Allowlist Bypass Through Non-Canonical Paths<![CDATA[ ## Vulnerability Details **File Location**: `scripts/zy_platform.py:894-925` and `scripts/zy_platform.py:937-946` **Vulnerability Type**: Improper URL path canonicalization before authorization checks **Risk Level**: High ### Vulnerable Code ```python def _split_segments(path): p = path.split("?", 1)[0].split("#", 1)[0] return [seg for seg in p.split("/") if seg] def _passthrough_check(method, path): segs = _split_segments(path) if not segs: fail(1, "pass-through path is empty.") denied = [s for s in segs if s.lower() in DENY_SEGMENTS] if denied: fail(1, "security policy: the pass-through path contains sensitive/admin route segments ({}); refused.".format( ", ".join(denied))) if segs[0].lower() == "health": return families = {s.lower() for s in segs} if method in SAFE_METHODS: if not (families & READ_PASS_THROUGH_FAMILIES): fail(1, "security policy: the read-only pass-through path is not in the allowlist. Allowed families: {}.".format( ", ".join(sorted(READ_PASS_THROUGH_FAMILIES)))) else: if segs[0].lower() not in WRITE_PASS_THROUGH_FAMILIES: fail(1, "security policy: pass-through writes are only allowed in the {} families " "(e.g. chat, ontology/semantic-search); use built-in commands for other writes.".format( ", ".join(sorted(WRITE_PASS_THROUGH_FAMILIES)))) ``` ```python origin = parse_origin(args.base_url, product) path = args.path.strip().lstrip("/") if "|" in path or "\\" in path or re.search(r"[\x00-\x1f\x7f]", path): fail(1, "--path contains illegal characters.") if "#" in path: fail(1, "security policy: --path must not contain a fragment (#).") _passthrough_check(method, path) if path.startswith("health"): url = origin.server_root() + "/" + path else: url = origin.api_root() + "/" + path ``` ### Technical Analysis The pass-through authorization pol ...[truncated 2857 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Percent-decode the path exactly once before applying any route policy. 2. Reject paths that retain percent escapes after decoding when double-decoding by downstream systems cannot be ruled out. 3. Reject literal or encoded `.` and `..` segments rather than relying on downstream normalization. 4. Reject encoded path separators such as `%2f` and `%5c`, including case-insensitive variants. 5. Canonicalize the path before both policy evaluation and URL construction, ensuring that the exact same representation is checked and transmitted. 6. Require the canonical **first** segment to belong to the relevant allowlist. Do not approve a request merely because any later segment matches an allowed family. 7. Apply the sensitive-segment denylist to every canonical, decoded segment. 8. Safely quote canonical path segments when reconstructing the outgoing URL. 9. Add regression tests covering: - `%61uth` - `%2e%2e` - `%2f` and `%5c` - double-encoded values such as `%252e%252e` - mixed-case encodings - allowed names appearing only in later path segments 10. Continue enforcing authorization independently on the server. The client-side allowlist should provide defense in depth, not serve as the primary access-control boundary. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/zy_platform.py:1061
Finding
Passwords and Bearer Tokens Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/zy_platform.py:1061-1063`; insecure invocation is also documented in `SKILL.md:53-54` and `references/examples.md:15-16,43-44` **Vulnerability Type**: Sensitive information exposure through process arguments and shell history **Risk Level**: Medium ### Vulnerable Code ```python p.add_argument("--username", default=None) p.add_argument("--password", default=None) p.add_argument("--token", default=None, help="explicit token (takes precedence over the session cache)") ``` The documented login flow instructs callers to place the password directly in the command: ```bash python3 scripts/zy_platform.py login --product <product> --username <user> --password <pass> --dry-run python3 scripts/zy_platform.py login --product <product> --username <user> --password <pass> ``` ### Technical Analysis The client accepts passwords and explicit bearer tokens through ordinary command-line arguments. Command-line arguments are commonly visible through operating-system process inspection facilities while the process is running. They may also be retained in shell history, terminal transcripts, Agent tool-call records, diagnostics, monitoring systems, or process-execution telemetry. The documented dry-run flow unnecessarily requires a password even though it sends no credentials. In `run_login()`, both the username and password are validated before the dry-run response is returned: ```python def run_login(args, product, dry_run=False): if not args.username or not args.password: fail(1, "login requires --username and --password. A fresh install seeds admin/admin1; " "self-registration is also available in the platform UI.") origin = parse_origin(args.base_url, product) login_url = origin.api_root() + "/auth/login" sys.stderr.write("[zy_platform] login destination (normalized): POST {}\n".format(login_url)) if dry_run: return {"dry_run": True, "destination": login_ur ...[truncated 1891 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove normal password entry through `--password`. 2. Read passwords from a hidden interactive prompt using `getpass.getpass()` when a terminal is available. 3. For non-interactive or Agent execution, accept the secret through protected standard input or a dedicated inherited file descriptor. 4. Avoid environment variables for long-lived secrets where the operating system exposes process environments to other processes. 5. Store reusable bearer tokens only through the existing OS credential-store mechanism or a permission-hardened token file. 6. If explicit token injection is required, support a protected token file or file descriptor instead of `--token <value>`. 7. Change login dry-run validation so that it requires no password. Destination normalization depends only on the product and base URL; username should also be optional for this preview. 8. Remove password-bearing commands from `SKILL.md` and `references/examples.md`. Replace them with hidden-prompt or standard-input examples. 9. Warn users not to place passwords or bearer tokens in shell history, scripts, Agent prompts, or telemetry-visible command arguments. 10. Add tests verifying that secrets do not appear in process arguments, stdout, stderr, audit logs, dry-run output, or exception messages. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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
90% confidence
Finding
The skill declares only allowed tools, but its instructions clearly require network access, shell execution, reading local files, and writing local state such as token caches and audit logs. This mismatch weakens review and enforcement because operators may underestimate what the skill can actually do, especially since it handles credentials and can invoke remote endpoints.

Description-Behavior Mismatch

Medium
Confidence
87% confidence
Finding
The document explicitly exposes a self-registration endpoint even though the skill's stated scope is limited to install/configuration guidance, login, querying, workflows, and inspection. Expanding the reachable capability surface to account creation can enable unauthorized provisioning or policy bypass if an agent follows the reference rather than the higher-level manifest.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
Allowing pass-through DELETE requests broadens the skill from guided inspection and constrained writes into arbitrary destructive operations. Even with some confirmation language, a generic pass-through that includes DELETE materially increases the risk of data loss, workflow disruption, or state destruction if the agent is prompted into unsafe actions.

Vague Triggers

Medium
Confidence
80% confidence
Finding
The trigger conditions are broad enough to activate on generic requests such as querying data, running workflows, or checking deployments, which can overlap with unrelated enterprise tasks. Over-broad activation increases the chance the skill engages in contexts where users did not intend platform access, potentially prompting credential collection or shell/network actions unnecessarily.

Credential Access

High
Category
Privilege Escalation
Content
- **Transport**: non-loopback destinations are forced to https; remote plaintext http is always rejected. Destinations are parsed with `urllib.parse.urlsplit`; base-urls carrying userinfo (`user:pass@host`), fragments (`#`) or query strings are refused. Authenticated requests only accept same-origin redirects; cross-origin redirects are blocked and never carry authorization.
- **Credential display**: before sending login credentials, run `login --dry-run`, show the normalized destination, and get consent; never display the user's password in the conversation, never write tokens/passwords into skill files.
- **Token storage (minimal)**: tokens prefer the OS credential store (enabled automatically when the python `keyring` module is installed); otherwise the fallback file `~/.workbuddy/zy_action_session.json` is used (dir 0700, file 0600, atomic replacement via temp file, symlinks rejected, ownership verified on POSIX, permissions tightened before read/write). Only the token and binding metadata are stored — **no usernames/passwords/terminal info**; expired tokens are purged automatically on read/write.
- **Pass-through discipline**: read-only GET/HEAD by default; writes need `--allow-write` plus a prior `--dry-run` confirmation of method/URL/body with the user; sensitive admin route segments are rejected and paths are gated by a family allowlist; cached tokens are never auto-attached to pass-through requests (require `--with-token` with consent).
- **Least-privilege account**: recommend a dedicated **read-only platform account** for the AI assistant's query/list/search operations; switch to a suitable account only with user consent when writes are needed — don't run daily queries with an admin account.
- **High-risk actions**: deletions, bulk writes and publish operations always require restating and confirming with the user first; never execute without authorization.
Confidence
75% confidence
Finding
The skill is designed to collect login credentials and persist authentication tokens locally via keyring or a fallback file. Even with mitigations described, credential and token handling is inherently sensitive: compromise of the local environment, insecure fallback storage, or overbroad use of privileged accounts could expose access to enterprise systems.

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

Detected: suspicious.secret_argv_exposure

Instructions pass high-value credentials through process argv.

Critical
Code
suspicious.secret_argv_exposure
Location
references/examples.md:15

Instructions pass high-value credentials through process argv.

Critical
Code
suspicious.secret_argv_exposure
Location
SKILL.md:53