Back to skill

Security audit

coros-mcp

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real COROS MCP setup helper, but it handles account credentials and bearer tokens with under-scoped endpoint controls and risky local persistence.

Review before installing. Use only the browser/device login flow, avoid --legacy password login, do not set custom issuer or MCP URL values unless you fully trust the endpoint, and treat the local OpenClaw MCP config and ~/.coros-mcp-skill-gateway state as sensitive because they may contain reusable COROS tokens.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/coros_mcp_login.py:289
Finding

Legacy login transmits COROS credentials to an unvalidated redirect destination

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/coros_mcp_login.py:619
Finding

Arbitrary issuer and MCP endpoint selection can disclose OAuth tokens and user data

Content
View full analysis
str: normalized_issuer = normalize_issuer(issuer) if normalized_issuer != DEFAULT_ISSUER: return normalized_issuer try: response = SimpleHttpClient().request( "GET", f"{normalized_issuer}/.well-known/openid-configuration", timeout=8, ) payload = SimpleHttpClient.read_json(response) if response.status != 200: return normalized_issuer discovered_issuer = payload.get("issuer") if isinstance(discovered_issuer, str) and discovered_issuer.startswith("https://"): return normalize_issuer(discovered_issuer) except Exception as exc: print(f"warning: gateway discovery failed, using {normalized_issuer}: {exc}", file=sys.stderr) return normalized_issuer ``` ```python def build_helper(args: argparse.Namespace) -> CorosMcpLoginHelper: requested_issuer = normalize_issuer(args.issuer) issuer = requested_issuer if args.no_gateway_discovery else dis ...[truncated 3092 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/coros_mcp_login.py:384
Finding

Bearer access token is exposed through OpenClaw subprocess arguments

Content
View full analysis
str: return json.dumps( { "mcp": { "servers": { server_name: { "url": self.mcp_url, "transport": "streamable-http", "headers": { "Authorization": self.authorization_header(self.ensure_token()) }, } } } }, indent=2, ) def apply_openclaw(self, server_name: str) -> None: try: subprocess.run( ["openclaw", "mcp", "set", server_name, self.render_openclaw_config(server_name)], check=True, text=True, ) except FileNotFoundError as exc: raise AuthFlowError("openclaw command not found") from exc ``` ### Technical Analysis `render_openclaw_config()` embeds the bearer access token in a JSON string. `apply_openclaw()` then passes the complete string as a command-line argument to the `openclaw` process. Command-line arguments can be observed or retained by: - Process inspection tools - Same-user monitoring processes - Endpoint telemetry and audit systems - Debugging or crash-reporting utilities - Process execution wrappers Using an argument list prevents shell command injection, but it does not protect the confidentiality of the argument values. The implementation therefore exposes a sensitive bearer credential outside the minimum communication channel required to configure OpenClaw. ### Attack Path 1. The victim invokes `apply-openclaw`. 2. The helper obtains or refreshes a COROS access token. 3. The token is embedded in the JSON configuration passed to `openclaw`. 4. A local p ...[truncated 629 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/coros_mcp_login.py:116
Finding

Predictable temporary paths create secret-file symlink and race risks

Content
View full analysis
None: self.path.parent.mkdir(parents=True, exist_ok=True) temp_path = self.path.with_suffix(".tmp") temp_path.write_text(json.dumps(dataclasses.asdict(token_set), indent=2), encoding="utf-8") os.chmod(temp_path, 0o600) temp_path.replace(self.path) os.chmod(self.path, 0o600) ``` The same pattern is used for pending login state: ```python def save(self, pending_session: PendingLoginSession) -> None: self.path.parent.mkdir(parents=True, exist_ok=True) temp_path = self.path.with_suffix(".tmp") temp_path.write_text(json.dumps(dataclasses.asdict(pending_session), indent=2), encoding="utf-8") os.chmod(temp_path, 0o600) temp_path.replace(self.path) os.chmod(self.path, 0o600) ``` ### Technical Analysis The code derives a predictable temporary filename using `with_suffix(".tmp")`. It then opens the path through `Path.write_text()` before applying restrictive permissions. Security weaknesses include: - Existing symbolic links can be followed by the file-opening operation. - The file is created under the process umask before `chmod(0600)` is applied. - A custom cache path can place the predictable file in a shared directory such as `/tmp`. - The parent directory is created without explicitly enforcing mode `0700`. - Concurrent helper executions use the same temporary filename. The final `replace()` operation is atomic, but it does not make the preceding creation and write secure. Token files contain access and refresh tokens, while pending-login files contain the PKCE verifier, poll token, state, and other authorization-session material. ### Attack Path 1. The victim configures a token or pending-login path in a directory writable by ano ...[truncated 992 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/coros_mcp_login.py:43
Finding

Incomplete and bypassable error redaction can leak authentication secrets

Content
View full analysis
= deadline: raise AuthFlowError("cli session authorization timed out") time.sleep(interval) continue if status == "failed": raise AuthFlowError(f"cli session failed: {payload.get('errorCode', 'unknown_error')}") if status == "expired": raise AuthFlowError("cli session expired, restart login") if status == "claimed": raise AuthFlowError(f"cli session already claimed: {payload.get('errorCode', 'already_claimed')}") raise AuthFlowError(f"cli session returned unexpected status: {status or payload}") ``` The exception is ultimately printed: ```python except AuthFlowError as exc: print(f"error: {exc}") return 1 ``` ### Technical Analysis The payload sanitizer is shallow and case-sensitive. It redacts only exact top-level key matches. Consequently, it does not protect: - Sensitive keys nested inside dictionaries or lists - Alternate capitalization such as `Access_Token` or `authorization` - Sensitive values placed under unexpected field names - Nondictionary payloads that contain secre ...[truncated 1549 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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
Findings (9)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill is described as installing or refreshing an OpenClaw MCP connection, but the instructions show it also performs direct auth flows, stores and refreshes tokens, supports legacy username/password login, and actively invokes remote MCP tools. This hidden expansion of behavior is dangerous because operators may grant trust for a setup task while unknowingly enabling credential handling, persistent token storage, and arbitrary remote actions through the same helper.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill description says it installs or refreshes a COROS MCP connection, but the code also implements generic MCP initialization, tool enumeration, tool description, and tool invocation. That materially expands the capability from authentication/setup into a general remote action proxy, increasing the chance of unauthorized or surprising operations under the user's bearer token.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The call_tool path allows arbitrary remote MCP tool execution with attacker-controlled tool names and JSON arguments, using the stored access token for authorization. In a login helper, this is especially dangerous because users may run it expecting setup behavior, while the code can perform broad server-side actions unrelated to authentication.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
97% confidence
Finding

The skill exposes broad capabilities—shell, network, file read/write, and environment access—without declaring any explicit tool scope or permission boundaries in the manifest. That increases the blast radius if the skill is invoked unexpectedly or modified later, because consumers cannot easily tell what operations are intended or constrain execution to the minimum necessary set.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code renders an OpenClaw config containing a live Authorization bearer token and writes it via 'openclaw mcp set' without an explicit warning or safer credential-handling path. This can expose the token through local configuration storage, backups, diagnostics, or other local users/processes, enabling reuse of the bearer token against the MCP service.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/coros_mcp_login.py (reported line 485)May include surrounding context.

python
def apply_openclaw(self, server_name: str) -> None:
        try:
            subprocess.run(
                ["openclaw", "mcp", "set", server_name, self.render_openclaw_config(server_name)],
                check=True,
                text=True,

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The login request forces language to zh and country to CN for all users. This is a natural-language/locale policy issue because the script does not offer locale selection or explain that it is intended only for a China-specific environment.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tests/test_skill_script.py (reported line 41)May include surrounding context.

python
cls.module = load_skill_module()

    def test_help_command_is_available(self):
        completed = subprocess.run(
            ["python3", str(SKILL_SCRIPT), "--help"],
            check=False,
            capture_output=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · tests/test_skill_script.py (reported line 60)May include surrounding context.

python
isolated_script = isolated_scripts / "coros_mcp_login.py"
            isolated_script.write_text(SKILL_SCRIPT.read_text(encoding="utf-8"), encoding="utf-8")

            completed = subprocess.run(
                ["python3", str(isolated_script), "--help"],
                check=False,
                capture_output=True,