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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

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

Content

Scanner excerpt · references/morpho.md (reported line 195)May include surrounding context.

md
- 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.

Autonomous Decision Making

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

Content

Scanner excerpt · references/morpho.md (reported line 196)May include surrounding context.

md
- 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.

Autonomous Decision Making

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

Content

Scanner excerpt · scripts/fxusd_morpho.py (reported line 1752)May include surrounding context.

python
- 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.

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 · references/morpho.md (reported line 197)May include surrounding context.

md
- 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

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

md
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"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/hydrex.md (reported line 24)May include surrounding context.

md
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"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/fxusd_hydrex.py (reported line 19)May include surrounding context.

python
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"

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/fxusd_cli.py (reported line 74)May include surrounding context.

python
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)

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/fxusd_cli.py (reported line 75)May include surrounding context.

python
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)

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/fxusd_cli.py (reported line 76)May include surrounding context.

python
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":

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/fxusd_cli.py (reported line 77)May include surrounding context.

python
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]

Missing User Warnings

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.