Back to skill

Security audit

fxUSD

Security checks for vulnerabilities and agentic risk

Overview

This skill is not overtly malicious, but it prepares real DeFi wallet transactions and relies on third-party data in ways users should review carefully before execution.

Install only if you are comfortable with a skill that prepares real wallet transactions for DeFi protocols. Before submitting any Bankr step, verify the token, spender, destination contract, amount, chain, vault or market, and slippage/minimum-output protections yourself; use a dedicated hot wallet and avoid executing plans based solely on the generated JSON.

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/fxusd_morpho.py:460
Finding
Untrusted remote market metadata controls approval spenders and transaction destinations## Vulnerability Details **File Location**: `scripts/fxusd_morpho.py:214-232`, `scripts/fxusd_morpho.py:460-507`, `scripts/fxusd_morpho.py:553-557`, `scripts/fxusd_morpho.py:664-687`, and `scripts/fxusd_morpho.py:1078-1151` **Vulnerability Type**: Insufficient validation of execution-critical remote data **Risk Level**: High ### Vulnerable Code The Morpho GraphQL response is accepted directly: ```python def request_graphql(query: str, variables: dict[str, Any]) -> Any: last_error: Exception | None = None payload = json.dumps({"query": query, "variables": variables}).encode("utf-8") for delay in (0.0, 0.4, 1.0): if delay: time.sleep(delay) request = urllib.request.Request( GRAPHQL_URL, data=payload, headers={ "Content-Type": "application/json", "Accept": "application/json", "User-Agent": USER_AGENT, }, method="POST", ) try: with urllib.request.urlopen(request, timeout=20) as response: decoded = json.loads(response.read().decode("utf-8")) if decoded.get("errors"): raise ValueError(f"GraphQL error: {decoded['errors']}") return decoded["data"] ``` Remote metadata supplies the contract destination: ```python def normalize_market(market: dict[str, Any]) -> dict[str, Any]: state = market.get("state") or {} collateral_asset = market.get("collateralAsset") or {} loan_asset = market.get("loanAsset") or {} morpho_blue = market.get("morphoBlue") or {} oracle = market.get("oracle") or {} warnings = market.get("warnings") or [] collateral_symbol = collateral_asset.get("symbol") risk_class, risk_summary = classify_collateral(collateral_symbol) return { "uniqueKey": market.get("uniqueKey"), "tit ...[truncated 5922 chars]
Remediation
## Remediation Suggestions 1. Require the normalized Morpho destination to exactly match the known Base deployment: ```python remote_morpho = validate_address( morpho_blue.get("address") or "", "Morpho Blue address", ) if remote_morpho.lower() != MORPHO_BLUE_ADDRESS.lower(): raise ValueError("Unexpected Morpho Blue contract address.") ``` 2. Maintain chain-specific allowlists for all contracts capable of receiving approvals or write transactions. 3. Verify market parameters independently on-chain before transaction construction. Recompute the market identifier from the loan token, collateral token, oracle, IRM, and LLTV, then compare it with the API result. 4. Ensure the returned loan-token address exactly matches the token requested by the user. Verify token decimals on-chain rather than trusting remote metadata. 5. Validate that the API response identifies Base chain ID `8453` and reject absent, malformed, or inconsistent chain metadata. 6. Decode every generated transaction before presenting it as Bankr-ready. Display and require confirmation of: - Destination contract - Function selector - Approval spender - Token and amount - Receiver and beneficiary - Market parameters 7. For fxSAVE, validate the hosted backend's returned approval spender and main transaction destination against deployment-specific allowlists before execution. 8. Treat API data as advisory only. Fail closed when independent contract verification cannot be completed.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fxusd_hydrex.py:166
Finding
Hydrex execution plans disable minimum-output and slippage protection## Vulnerability Details **File Location**: `scripts/fxusd_hydrex.py:166-208`, with execution-ready use at `scripts/fxusd_hydrex.py:526-590` and `scripts/fxusd_hydrex.py:620-697` **Vulnerability Type**: Missing slippage and minimum-output validation **Risk Level**: High ### Vulnerable Code The complete deposit transaction builder hardcodes `minimumShares` to zero: ```python def build_hydrex_deposit_transaction( vault: str, token: str, amount: str, user_address: str, ) -> dict[str, Any]: data = encode_call( "0x5d123e3f", [ pad_hex(vault), pad_hex(VAULT_DEPLOYER), pad_hex(token), encode_uint256(amount), encode_uint256(0), pad_hex(user_address), ], ) return { "to": DEPOSIT_GUARD, "chainId": 8453, "value": "0", "data": data, } ``` The complete withdrawal transaction builder hardcodes both minimum token outputs to zero: ```python def build_hydrex_withdraw_transaction( vault: str, shares: str, user_address: str, ) -> dict[str, Any]: data = encode_call( "0x1a0e8cdf", [ pad_hex(vault), pad_hex(VAULT_DEPLOYER), encode_uint256(shares), pad_hex(user_address), encode_uint256(0), encode_uint256(0), ], ) return { "to": DEPOSIT_GUARD, "chainId": 8453, "value": "0", "data": data, } ``` The output confirms that these zero values represent the execution constraints: ```python "depositCall": { "chainId": 8453, "to": DEPOSIT_GUARD, "function": "forwardDepositToICHIVault(address vault, address vaultDeployer, address token, uint256 amount, uint256 minimumShares, address userAddress)", "args": { "vault": strategy["addres ...[truncated 2997 chars]
Remediation
## Remediation Suggestions 1. Add an explicit slippage option with a conservative upper bound, for example: ```text --max-slippage-bps 50 ``` 2. Obtain expected deposit shares and withdrawal token amounts from verified on-chain preview functions or a trusted quote mechanism immediately before constructing the transaction. 3. Calculate protected outputs: ```python minimum = expected * (10_000 - max_slippage_bps) // 10_000 ``` 4. Populate `minimumShares`, `minAmount0`, and `minAmount1` with nonzero values derived from the current quote. 5. Refuse to produce `bankrReady.steps` when expected outputs cannot be calculated reliably. In that case, return a planning-only result with a clear blocking warning. 6. Include quote timestamp, block number, expected outputs, minimum outputs, and slippage basis points in the displayed execution summary. 7. Refresh the quote after approval confirmation and immediately before constructing or submitting the main transaction. 8. Enforce maximum quote age and reject execution after the deadline or after excessive state movement. 9. Preserve the mixed-token withdrawal warning, but distinguish composition risk from slippage protection; disclosure alone is not an adequate transaction-level control.
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 (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
Advertising one set of supported protocols while implementing a different set of reads, writes, or monitoring features undermines trust boundaries and auditability. Extra undeclared behavior, especially around position monitoring and transaction planning, can expose wallet data, produce unexpected network access, or trigger decisions outside the user's informed consent.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
Advertising one set of supported protocols while implementing a different set of reads, writes, or monitoring features undermines trust boundaries and auditability. Extra undeclared behavior, especially around position monitoring and transaction planning, can expose wallet data, produce unexpected network access, or trigger decisions outside the user's informed consent.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
Advertising one set of supported protocols while implementing a different set of reads, writes, or monitoring features undermines trust boundaries and auditability. Extra undeclared behavior, especially around position monitoring and transaction planning, can expose wallet data, produce unexpected network access, or trigger decisions outside the user's informed consent.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill describes multiple network-dependent operations and live transaction planning/execution paths, but it does not declare any explicit tool scope such as allowed tools or permissions. That creates a governance and review gap: an agent runtime may permit broader network access than reviewers expect, increasing the chance of unintended outbound requests or execution against user wallets.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- Stay well below max LTV. A safer planning posture is to keep meaningful headroom instead of optimizing for maximum borrow.
- Treat oracle, curator, and market-parameter changes as live risks.
- If rewards are routed through third-party claim-and-swap paths, review that transaction path carefully.
- Do not auto-execute a withdraw from a position that also has active borrow shares or collateral without explicit review.
- Do not auto-execute a borrow plan just because the protocol would allow it; use the recommended LTV buffer as the operational ceiling.
- Do not assume an add-collateral plan is feasible without checking actual collateral token balance and allowance.
- For full repay, prefer share-based repayment planning to reduce borrow-share rounding risk.
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
- Stay well below max LTV. A safer planning posture is to keep meaningful headroom instead of optimizing for maximum borrow.
- Treat oracle, curator, and market-parameter changes as live risks.
- If rewards are routed through third-party claim-and-swap paths, review that transaction path carefully.
- Do not auto-execute a withdraw from a position that also has active borrow shares or collateral without explicit review.
- Do not auto-execute a borrow plan just because the protocol would allow it; use the recommended LTV buffer as the operational ceiling.
- Do not assume an add-collateral plan is feasible without checking actual collateral token balance and allowance.
- For full repay, prefer share-based repayment planning to reduce borrow-share rounding risk.
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
- Stay well below max LTV. A safer planning posture is to keep meaningful headroom instead of optimizing for maximum borrow.
- Treat oracle, curator, and market-parameter changes as live risks.
- If rewards are routed through third-party claim-and-swap paths, review that transaction path carefully.
- Do not auto-execute a withdraw from a position that also has active borrow shares or collateral without explicit review.
- Do not auto-execute a borrow plan just because the protocol would allow it; use the recommended LTV buffer as the operational ceiling.
- Do not assume an add-collateral plan is feasible without checking actual collateral token balance and allowance.
- For full repay, prefer share-based repayment planning to reduce borrow-share rounding risk.
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
- If rewards are routed through third-party claim-and-swap paths, review that transaction path carefully.
- Do not auto-execute a withdraw from a position that also has active borrow shares or collateral without explicit review.
- Do not auto-execute a borrow plan just because the protocol would allow it; use the recommended LTV buffer as the operational ceiling.
- Do not assume an add-collateral plan is feasible without checking actual collateral token balance and allowance.
- For full repay, prefer share-based repayment planning to reduce borrow-share rounding risk.

## Vulnerabilities and failure modes
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.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The CLI sends wallet addresses, token addresses, and transaction-planning details to a remote backend by default, but the code provides no explicit disclosure, consent prompt, or privacy warning at the point of use. In a crypto workflow, these details can reveal wallet activity and strategy intent, and the default remote endpoint increases the sensitivity because users may assume the tool is local-only or purely deterministic.

External Transmission

Medium
Category
Data Exfiltration
Content
from typing import Any


API_BASE = "https://api.hydrex.fi/strategies"
BASE_RPC_URL = "https://mainnet.base.org"
USER_AGENT = "fxusd-hydrex/0.1 (+https://github.com/huwangtao123/fxsave-dapp)"
DEPOSIT_GUARD = "0x9A0EBEc47c85fD30F1fdc90F57d2b178e84DC8d8"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from typing import Any


API_BASE = "https://api.hydrex.fi/strategies"
BASE_RPC_URL = "https://mainnet.base.org"
USER_AGENT = "fxusd-hydrex/0.1 (+https://github.com/huwangtao123/fxsave-dapp)"
DEPOSIT_GUARD = "0x9A0EBEc47c85fD30F1fdc90F57d2b178e84DC8d8"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from typing import Any


API_BASE = "https://api.hydrex.fi/strategies"
BASE_RPC_URL = "https://mainnet.base.org"
USER_AGENT = "fxusd-hydrex/0.1 (+https://github.com/huwangtao123/fxsave-dapp)"
DEPOSIT_GUARD = "0x9A0EBEc47c85fD30F1fdc90F57d2b178e84DC8d8"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
This script generates execution-ready approval and transaction payloads for DeFi actions, including token approvals and borrow/repay/withdraw flows, and even auto-selects a recommended market in some cases. In an agent-skill context, producing ready-to-submit transactions without a strong, explicit confirmation and risk banner increases the chance of unsafe or unintended on-chain execution, especially because approvals and DeFi state changes are irreversible once broadcast.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def resolve_token(args: argparse.Namespace, prefix: str) -> Token:
    preset_value = getattr(args, f"{prefix}_token", None)
    custom_address = getattr(args, f"{prefix}_address", None)
    custom_symbol = getattr(args, f"{prefix}_symbol", None)
    custom_decimals = getattr(args, f"{prefix}_decimals", None)
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def resolve_token(args: argparse.Namespace, prefix: str) -> Token:
    preset_value = getattr(args, f"{prefix}_token", None)
    custom_address = getattr(args, f"{prefix}_address", None)
    custom_symbol = getattr(args, f"{prefix}_symbol", None)
    custom_decimals = getattr(args, f"{prefix}_decimals", None)
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def resolve_token(args: argparse.Namespace, prefix: str) -> Token:
    preset_value = getattr(args, f"{prefix}_token", None)
    custom_address = getattr(args, f"{prefix}_address", None)
    custom_symbol = getattr(args, f"{prefix}_symbol", None)
    custom_decimals = getattr(args, f"{prefix}_decimals", None)

    if preset_value and preset_value != "custom":
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
preset_value = getattr(args, f"{prefix}_token", None)
    custom_address = getattr(args, f"{prefix}_address", None)
    custom_symbol = getattr(args, f"{prefix}_symbol", None)
    custom_decimals = getattr(args, f"{prefix}_decimals", None)

    if preset_value and preset_value != "custom":
        return TOKEN_REGISTRY[preset_value]
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Missing User Warnings

Low
Confidence
90% confidence
Finding
This code file makes external network requests to Morpho GraphQL and Base RPC endpoints using user-supplied wallet addresses, which exposes user portfolio and position information to third-party services. Although the network access is core to the skill's purpose, there is no visible print/log/comment at the call sites warning that wallet data will be sent off-host.

Static analysis

No suspicious patterns detected.