Back to skill

Security audit

IJPay支付SDK

Security checks for vulnerabilities and agentic risk

Overview

This payment skill is not malicious, but it needs review because its copyable examples can create insecure payment callbacks, refunds, and logging that could affect money and customer data.

Review this skill carefully before installing or using it for production payment work. Use sandbox accounts first, keep all payment keys and certificates out of source control, verify every provider callback before changing order state, derive payment and refund amounts from server-side records, protect refund/close actions with strong authorization and audit logs, and avoid logging full raw payment payloads.

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
SKILL.md:124
Finding
Alipay Callback Marks Orders as Paid Without Signature Verification<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 124-131 **Vulnerability Type**: Unauthenticated payment callback processing **Risk Level**: Critical ### Vulnerable Code ```java @PostMapping("/ali-callback") public String aliPayCallback(HttpServletRequest request) { Map<String, String> params = getParams(request); if (TradeStatus.SUCCESS.name().equals(params.get("trade_status"))) { String orderId = params.get("out_trade_no"); orderService.updatePaid(orderId); } return "success"; } ``` ### Technical Analysis The callback trusts the caller-controlled `trade_status` and `out_trade_no` parameters. It changes the local order state without first verifying the Alipay signature. Checking that `trade_status` equals `SUCCESS` does not authenticate the notification because any HTTP client can supply that value. The example also does not validate the notification's application ID, seller identity, transaction amount, currency, or correspondence with a pending server-side order. Although a later section contains a signature-verification example, the earlier code is presented as a complete payment controller and can be copied independently. ### Attack Path 1. An attacker discovers or predicts a valid pending order ID. 2. The attacker sends a direct POST request to `/api/pay/ali-callback`. 3. The request contains `trade_status=SUCCESS` and the target order ID as `out_trade_no`. 4. The endpoint accepts these values without authenticating the sender. 5. `orderService.updatePaid(orderId)` marks the unpaid order as paid. 6. Any fulfillment process triggered by the paid state may release goods or services without a real payment. ### Impact Assessment A remote unauthenticated attacker may forge successful payment notifications and change the state of accessible or predictable orders. The resulting scope depends on downstream business logic and may include unauthorized fulfillment, financial loss, accounting inconsisten ...[truncated 51 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the unsigned callback example so it cannot be copied into production. - Verify the Alipay signature with the configured Alipay public key before reading or acting on notification fields. - Reject notifications with an unexpected application ID, seller ID, encoding, or signature algorithm. - Load the order from trusted server-side storage and compare the notified amount and currency with the expected values. - Accept only valid state transitions from a pending order. - Store and enforce uniqueness for the Alipay transaction ID. - Perform the order-state transition atomically and idempotently. - Return `success` only after the authenticated event has been safely recorded. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/wxpay-guide.md:163
Finding
WeChat Pay API v3 Callback Trusts an Unverified and Undecrypted Request Body<![CDATA[ ## Vulnerability Details **File Location**: `references/wxpay-guide.md`, lines 163-185 **Vulnerability Type**: Missing callback authentication and API v3 resource decryption **Risk Level**: Critical ### Vulnerable Code ```java @PostMapping("/wx-callback") public String wxPayCallback(HttpServletRequest request) { try { String body = StreamUtils.readToString(request.getInputStream()); Map<String, Object> notify = JSON.parseObject(body); String tradeState = (String) notify.get("trade_state"); if ("SUCCESS".equals(tradeState)) { String orderId = (String) notify.get("out_trade_no"); String transactionId = (String) notify.get("transaction_id"); int total = ((Map) notify.get("amount")).get("total"); if (orderService.markPaidIfAbsent(orderId, transactionId)) { log.info("Order {} was paid with transaction {}", orderId, transactionId); } } return JSON.toJSONString( ImmutableMap.of("code", "SUCCESS", "message", "success") ); } catch (Exception e) { log.error("WeChat callback processing failed", e); return JSON.toJSONString( ImmutableMap.of("code", "FAIL", "message", "failure") ); } } ``` ### Technical Analysis The handler parses the raw HTTP body as trusted transaction data without verifying the WeChat Pay API v3 signature headers. No platform-certificate verification, timestamp validation, nonce validation, replay protection, or authenticated resource decryption is shown. The guide states that a framework has already performed verification, but no filter, interceptor, verified request wrapper, or other verification configuration exists in the audited project. API v3 notifications normally contain an encrypted `resource` object that must be authenticated and decrypted before transaction fields are trusted. The parsed amount is assigned to `total` but is not compare ...[truncated 964 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Verify all WeChat Pay API v3 callback signature headers using the current WeChat platform certificate. - Validate the notification timestamp against a narrow acceptance window and reject replayed nonce or event identifiers. - Decrypt and authenticate the API v3 `resource` object with the API v3 key. - Use an official or correctly configured SDK callback parser rather than parsing the raw body directly. - Validate merchant ID, application ID, transaction ID, amount, currency, and order ID against trusted server-side records. - Require the local order to be in an eligible pending state. - Persist the verified notification and enforce transaction-ID uniqueness before acknowledging it. - Do not claim that a framework has verified the callback unless the guide also provides the required verification configuration. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/alipay-guide.md:121
Finding
Payment and Refund Amounts Are Accepted from Untrusted Request Parameters<![CDATA[ ## Vulnerability Details **File Locations**: - `SKILL.md`, lines 98-112 and 140-151 - `references/alipay-guide.md`, lines 48-63, 72-86, 97-110, and 121-136 - `references/wxpay-guide.md`, lines 42-60, 80-104, and 116-135 **Vulnerability Type**: Client-side price manipulation and missing refund authorization **Risk Level**: High ### Vulnerable Code ```java @PostMapping("/refund") public String refund(@RequestParam("orderId") String orderId, @RequestParam("amount") BigDecimal amount) { AliPayApiConfig config = buildAliPayConfig(); AliPayApiConfigKit.setThreadLocalAliPayApiConfig(config); AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); request.setBizContent(JSON.toJSONString(ImmutableMap.of( "trade_no", "original transaction identifier", "out_trade_no", orderId, "refund_amount", amount.toString(), "refund_reason", "customer requested refund" ))); AlipayTradeRefundResponse resp = config.getAlipayClient().execute(request); return resp.isSuccess() ? "refund_success" : "refund_fail"; } ``` Representative payment creation methods use the same pattern: ```java public String pagePay(@RequestParam("orderId") String orderId, @RequestParam("amount") BigDecimal amount) ``` ```java public Map<String, String> wxH5Pay( @RequestParam("orderId") String orderId, @RequestParam("amount") int amountFen) ``` ### Technical Analysis The examples use caller-provided amounts when creating payment-provider requests. The amount is not loaded from the authoritative order record or compared with a server-calculated total. The refund endpoint also accepts both the order ID and refund amount directly. It does not demonstrate authentication, authorization, order ownership validation, refundable-balance validation, refund-state validation, or CSRF protection. Payment-sensitive values must be derived from trusted server-side state. Validation ...[truncated 1404 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Accept an opaque order identifier from the client, then load the amount, currency, merchant, and product description from trusted server-side storage. - Recalculate discounts, taxes, and totals on the server. - Never use a caller-provided amount as the authoritative payment or refund amount. - Bind every order operation to the authenticated principal or authorized merchant operator. - Restrict refund endpoints to dedicated privileged roles and require step-up approval where appropriate. - Validate the remaining refundable balance and order state before submitting a refund. - Use a unique refund request number and enforce idempotency. - Protect browser-accessible state-changing endpoints against CSRF when cookie-based authentication is used. - During callback processing, compare the provider-reported amount and currency with the stored order values before settlement. - Record immutable audit events for payment creation and refund approval. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/callback-handler.md:39
Finding
Redis Callback Deduplication Marker Is Deleted Immediately After Successful Processing<![CDATA[ ## Vulnerability Details **File Location**: `references/callback-handler.md`, lines 39-59 **Vulnerability Type**: Broken callback idempotency and unsafe lock release **Risk Level**: High ### Vulnerable Code ```java public boolean processCallback(String transactionId, Runnable business) { String lockKey = "pay:callback:" + transactionId; String lock = redisTemplate.opsForValue().get(lockKey); if ("processed".equals(lock)) { return true; } Boolean acquired = redisTemplate.opsForValue() .setIfAbsent(lockKey, "processing", 30, TimeUnit.SECONDS); if (!Boolean.TRUE.equals(acquired)) { return false; } try { business.run(); redisTemplate.opsForValue() .set(lockKey, "processed", 7, TimeUnit.DAYS); return true; } finally { redisTemplate.delete(lockKey); } } ``` ### Technical Analysis After successful business processing, the code writes a `processed` marker with a seven-day expiration. The unconditional `finally` block then immediately deletes the same key. Consequently, subsequent deliveries of the same provider notification do not encounter the intended deduplication marker and can execute `business.run()` again. The lock deletion is also not ownership-checked. If processing exceeds the 30-second lease, another worker can acquire the same key. The first worker may then delete the second worker's lock when its own `finally` block executes. Payment providers routinely retry callbacks, so idempotency is a mandatory correctness and security property rather than an optional optimization. ### Attack Path 1. A legitimate callback is received and acquires the Redis key. 2. Business processing completes and writes `processed`. 3. The `finally` block immediately deletes that marker. 4. The provider retries the same callback, or an attacker replays a captured valid callback. 5. The Redis lookup no longer identifies the transaction as processed. 6. The busin ...[truncated 574 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not delete a completed marker after successful processing; retain it for the full deduplication period. - Separate the temporary processing lock from the durable processed-event marker. - Release locks only when the stored random ownership token matches the current worker's token, using an atomic Lua script or equivalent primitive. - Renew the lease when processing can exceed the initial lock duration. - Enforce transaction-ID uniqueness in the database as the authoritative idempotency control. - Perform the order-state transition with a conditional update inside a database transaction. - Ensure downstream fulfillment and ledger operations are independently idempotent. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/callback-handler.md:64
Finding
Callback Is Acknowledged Before Unmanaged Asynchronous Processing Completes<![CDATA[ ## Vulnerability Details **File Location**: `references/callback-handler.md`, lines 64-95 **Vulnerability Type**: Premature callback acknowledgement and unsafe servlet-request reuse **Risk Level**: High ### Vulnerable Code ```java @PostMapping("/ali-callback") public String aliCallback(HttpServletRequest request) { String rawBody = StreamUtils.readToString(request.getInputStream()); log.info("Alipay callback raw body: {}", rawBody); if (!AliPaySignature.checksum( request.getParameterMap(), alipayPublicKey, "UTF-8")) { return "fail"; } new Thread(() -> processPayment(request)).start(); return "success"; } private void processPayment(HttpServletRequest request) { try { String orderId = request.getParameter("out_trade_no"); String tradeStatus = request.getParameter("trade_status"); String amount = request.getParameter("total_amount"); if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) { paymentService.settle( orderId, new BigDecimal(amount), tradeStatus ); } } catch (Exception e) { log.error("Asynchronous payment processing failed", e); compensationQueue.add( request.getParameter("out_trade_no") ); } } ``` ### Technical Analysis The endpoint returns `success` immediately after creating an unmanaged thread, before settlement or durable event recording completes. Once the payment provider receives the success response, it may stop retrying the callback even if the background operation subsequently fails. Creating a raw thread for each request bypasses the application's managed executor, capacity limits, lifecycle handling, security context propagation, and observability. An attacker able to send many valid or otherwise accepted callbacks may create excessive threads. The backgro ...[truncated 1361 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Verify the callback and persist the authenticated event durably before returning success. - Copy required immutable values from the request while still on the request thread. - Submit processing to a bounded, managed executor or durable message queue. - Do not retain `HttpServletRequest` outside its supported lifecycle. - Return success only after durable storage guarantees that processing can be retried internally. - Implement a transactional outbox or equivalent durable handoff mechanism. - Add retry limits, dead-letter handling, monitoring, and reconciliation jobs. - Apply idempotency controls at both event ingestion and business-processing layers. - Avoid logging complete raw payment payloads unless sensitive fields are redacted and access to logs is restricted. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (15)

Vague Triggers

Medium
Confidence
95% confidence
Finding
The description says the skill activates on generic keywords such as "配置支付", "微信支付", "支付宝", and even whenever a project "needs online payment functionality." These phrases are broad enough to overlap with ordinary discussion about payments and do not define clear exclusion conditions, which could cause unintended invocation.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The skill instructs users to configure sensitive payment material such as private keys, API keys, certificates, and public callback endpoints, but it does not clearly warn about the privacy and system-integrity impact of using real production credentials or exposing payment callbacks. A note against hardcoding secrets is present, but that is not the same as a user-facing warning about the risks of processing live payment data and webhook traffic.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The earlier Alipay callback example updates order status based solely on request parameters and trade status without first verifying the callback signature or source. In a payment integration skill, this can teach consumers to accept forged payment notifications, allowing unpaid orders to be marked as paid and creating direct financial loss.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The guide shows a YAML example with a private key embedded inline, which directly contradicts the later requirement that private keys must not be hardcoded. Even if presented as a placeholder, documentation like this is often copied verbatim into real projects, leading to insecure secret handling, accidental source control exposure, and long-term key compromise.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The document states that callback notifications must be signature-verified, but every payment example only sets notifyUrl and omits any callback handler demonstrating verification. In payment integrations, developers frequently mirror the examples; without explicit signature verification, attackers may forge payment-success notifications and trigger fulfillment, balance credits, or order state changes fraudulently.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The guide includes ready-to-use refund and close-order endpoints exposed as simple POST handlers with user-supplied order identifiers and amounts, but provides no warnings about authorization, audit controls, or irreversible business effects. In a payment skill, this context makes the omission more dangerous because integrators may expose these operations internally or externally without proper access control, enabling unauthorized refunds, transaction disruption, or fraud.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The guidance explicitly recommends recording the raw payment callback message in full, but payment callbacks commonly contain identifiers, account metadata, signatures, and transaction details that should not be broadly exposed in logs. In a payment-integration skill, developers are likely to copy this pattern directly into production callback handlers, increasing the risk of sensitive data leakage through log aggregation systems, support access, or incident dumps.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The example logs the entire Alipay callback body before processing, which can expose sensitive transaction and user data to application logs. In payment systems this is especially risky because logs are often replicated to centralized platforms and accessed by operators who do not need full payment payload visibility, turning routine observability into a data-exposure channel.

External Transmission

Medium
Category
Data Exfiltration
Content
String result = WxPayApi.push(
        null,                               // certPath(v2用)
        mchId,
        "https://api.mch.weixin.qq.com/v3/pay/transactions/native",
        null,
        WxPayEnum.DOMAIN_API.getUrl(),
        params
Confidence
50% 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
String result = WxPayApi.push(
        null,                               // certPath(v2用)
        mchId,
        "https://api.mch.weixin.qq.com/v3/pay/transactions/native",
        null,
        WxPayEnum.DOMAIN_API.getUrl(),
        params
Confidence
50% 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
String result = WxPayApi.push(
        null,                               // certPath(v2用)
        mchId,
        "https://api.mch.weixin.qq.com/v3/pay/transactions/native",
        null,
        WxPayEnum.DOMAIN_API.getUrl(),
        params
Confidence
50% 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
91% confidence
Finding
This markdown file instructs users to download and place payment certificates including a private key, and to modify filesystem permissions, but it does not include any explicit warning about the sensitivity of the key material or the risks of mishandling it. Under the markdown-specific missing-warning rule, documentation should disclose behaviors that could affect user data, privacy, or system integrity.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# 将证书放到服务器指定目录
mkdir -p /opt/deepfmt/certs
chmod 600 /opt/deepfmt/certs/apiclient_key.pem
chown deepfmt:deepfmt /opt/deepfmt/certs
```
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The callback example states that signature verification is already handled, but the shown code only reads and parses the raw request body before acting on payment status. In payment integrations, processing unverified callbacks can let an attacker spoof successful payments and trigger order fulfillment or account crediting without real payment.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
This markdown file presents all user-facing guidance exclusively in Chinese, which can amount to a language/locale constraint without user opt-in. The policy allows locale constraints when they are explicitly justified, but no such justification or alternative language option is provided here.

Static analysis

No suspicious patterns detected.