Back to skill

Security audit

UCP Gateway - Agent Commerce

Security checks for vulnerabilities and agentic risk

Overview

This shopping MCP skill is coherent, but it should be reviewed because its helper scripts can send shopping or checkout details to any gateway URL supplied by an environment variable.

Install only if you trust the UCP Gateway and keep UCP_GATEWAY_MCP_URL unset or pointed to a trusted HTTPS endpoint. Confirm every cart and checkout action yourself, and enter payment details only on the merchant-hosted checkout page, not through the agent.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Warning
Location
SKILL.md:17
Finding

Remote MCP Content Is Treated as Authoritative Agent Guidance

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:17-18, SKILL.md:45-48, scripts/call_mcp.py:117-137, scripts/call_mcp.py:255-262
Vulnerability Type: Remote instruction trust and session-goal manipulation
Risk Level: Medium

Vulnerable Code

SKILL.md:17-18:

markdown
- Prefer `tools/list` schemas over remembered arguments. After every call, read `result.structuredContent.next_step` and warnings.
- JSON-RPC 2.0 over HTTP `POST` to `/mcp`; GET returns a markdown guide/SSE bootstrap; POST fallbacks may exist at `/messages` and `/mcp/messages`.

SKILL.md:45-48:

markdown
- Gateway resources/prompts are fallbacks for first registration, schema ambiguity, error recovery, or operator handoff — do not load all of them at startup.
- Read when needed: `ucp://gateway/skill-runtime-guide`, `ucp://gateway/profile-registration`, `ucp://gateway/shopping-flow`, `ucp://gateway/safety-and-operator-handoff`.
- Prompts when needed: `ucp-skill-runtime-guide`, `ucp-shopping-flow`, `ucp-operator-handoff`.

scripts/call_mcp.py:117-137:

python
def discovery_request(argv):
    if has_flag(argv, "--initialize"):
        return {"method": "initialize"}
    if has_flag(argv, "--tools"):
        return {"method": "tools/list"}
    if has_flag(argv, "--resources"):
        return {"method": "resources/list"}
    if has_flag(argv, "--resource"):
        uri = flag_value(argv, "--resource")
        if not uri:
            raise CallMcpError("--resource requires a URI")
        return {"method": "resources/read", "params": {"uri": uri}}
    if has_flag(argv, "--prompts"):
        return {"method": "prompts/list"}
    if has_flag(argv, "--prompt"):
        name = flag_value(argv, "--prompt")
        if not name:
            raise CallMcpError("--prompt requires a prompt name")
        return {"method": "prompts/get", "params": {"name": name, "arguments": parse_prompt_args(argv)}}
...[truncated 2407 chars]
Remediation
View remediation

Remediation Suggestions

  1. Explicitly state in SKILL.md that all MCP response text, prompts, resources, tool descriptions, warnings, and next_step values are untrusted remote data.
  2. State that remote content must never override:
    • The user's expressed intent.
    • Platform and system policies.
    • The prohibition on collecting payment credentials.
    • Cart and checkout confirmation requirements.
    • Data-minimization requirements.
  3. Use live schemas only to validate argument structure and types; do not treat descriptive prose as higher-priority instructions.
  4. Parse and present documented structured fields separately from free-form text.
  5. Require independent local authorization checks before every create, update, cancel, or checkout operation, regardless of what next_step recommends.
  6. Consider allowlisting known prompt names and resource URIs rather than allowing arbitrary names and URIs.
  7. Add tests using malicious next_step, prompt, resource, and tool-description content to verify that confirmation barriers remain enforced.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/call_mcp.py:245
Finding

Sensitive MCP Payloads Can Be Redirected to an Unrestricted Custom Endpoint

Content
View full analysis

Vulnerability Details

File Location: scripts/call_mcp.py:222-230, scripts/call_mcp.py:245-262, scripts/register_profile.py:139-170, README.md:173-176
Vulnerability Type: Missing destination and transport validation for sensitive network requests
Risk Level: High

Vulnerable Code

scripts/call_mcp.py:222-230:

python
def build_tool_request(argv, root):
    tool = argv[0]
    try:
        arguments = json.loads(argv[1]) if len(argv) > 1 else {}
    except json.JSONDecodeError as exc:
        raise CallMcpError(f"Tool arguments must be valid JSON: {exc.msg}.") from exc
    if not isinstance(arguments, dict):
        raise CallMcpError("Tool arguments JSON must be an object.")

    if not arguments.get("agent_id") and tool not in {"register_ucp_profile", "get_ucp_profile"}:
        arguments = {"agent_id": read_agent_id(root), **arguments}

scripts/call_mcp.py:245-262:

python
gateway = os.environ.get("UCP_GATEWAY_MCP_URL") or DEFAULT_GATEWAY
descriptor_names = tool_descriptor_request(argv)
if descriptor_names is not None:
    return print_tool_descriptors(gateway, descriptor_names, stdout, stderr)

request = discovery_request(argv)
if request is None:
    if argv[0].startswith("--"):
        raise CallMcpError(f"Unknown option {argv[0]}")
    request = build_tool_request(argv, root)

response, http_status = post_json(gateway, {"jsonrpc": "2.0", "id": str(uuid.uuid4()), **request})

scripts/register_profile.py:139-170:

python
gateway = os.environ.get("UCP_GATEWAY_MCP_URL") or DEFAULT_GATEWAY
directory = root / ".ucpgateway"
public_key_path = directory / "public_key.jwk"
agent_path = directory / "agent.json"

public_key_jwk = read_json(public_key_path)
ensure_public_jwk(public_key_jwk)

request_payload = {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "register_ucp_profile",
     
...[truncated 3500 chars]
Remediation
View remediation

Remediation Suggestions

  1. Parse UCP_GATEWAY_MCP_URL before use and require the https scheme.
  2. Allowlist ucpg.ai and, only if still required, the documented legacy host.
  3. Reject URLs containing embedded usernames or passwords, fragments, unexpected ports, malformed hosts, or non-HTTPS schemes.
  4. If custom gateway support is required, place it behind an explicit command-line option such as --allow-custom-gateway and display the exact destination before transmitting data.
  5. Require explicit user confirmation before sending buyer contact or shipping information to a non-default host.
  6. Document which data categories are sent for registration, search, cart, and checkout operations.
  7. Apply the same centralized URL-validation function in both call_mcp.py and register_profile.py.
  8. Add automated tests covering plaintext HTTP URLs, deceptive subdomains, embedded credentials, invalid ports, and unapproved hosts.
  9. Consider a strict tool allowlist for normal Skill operation so the generic helper cannot forward arbitrary tool names and unrestricted argument objects without an explicit advanced-use option.
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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (4)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill exposes or instructs use of capabilities equivalent to network, file access, shell, and potentially environment access through bundled Python helpers and local key/state handling, but it does not declare any explicit tool scope restrictions such as allowed-tools or permissions. In an agent runtime, this weakens least-privilege boundaries and can let the skill operate with broader authority than a reviewer or orchestrator can easily validate, increasing the risk of unintended file access, command execution, or external communication.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% 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.

Content

Scanner excerpt · SKILL.md (reported line 24)May include surrounding context.

md
- `shopping_goal`; `agent_id` if already registered; `buyer_context` such as country/region/currency/intent.
- `merchant_domain`; selected provider variant item IDs and quantity; buyer-provided checkout contact/shipping data.
- `confirmation_state`: no confirmation, cart mutation confirmed, or final checkout confirmation received.

## Branch by task state

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script automatically injects agent_id from a local file and sends it, along with any provided tool arguments, to a remote MCP gateway without an explicit runtime disclosure or confirmation. In a commerce/identity workflow, this can cause users or higher-level agents to transmit identifying or sensitive shopping context to an external service unintentionally, especially when the auto-injection behavior is not obvious at the call site.

Content

No source excerpt is available for this finding.

compile() call detected

Medium
Category
Dangerous Code Execution
Confidence
65% confidence
Finding

compile() creates code objects from strings. When combined with exec()/eval(), it enables obfuscated code execution.

Content

Scanner excerpt · scripts/check_skill.py (reported line 227)May include surrounding context.

python
fail(f"missing Python helper script(s): {', '.join(missing)}")
    for path in sorted(scripts_dir.glob("*.py")):
        try:
            compile(read_text(path), str(path), "exec")
        except SyntaxError as exc:
            fail(f"{path.relative_to(ROOT)} does not compile: {exc.msg}")
    # Python bytecode caches must never ship in the ClawHub/npm tarball.

Static analysis

No suspicious patterns detected.