Back to skill

Security audit

境外投资补税测算

Security checks for vulnerabilities and agentic risk

Overview

The skill does not show malware behavior, but it needs review because it handles sensitive financial statements and its tax calculations and raw outputs are not safely scoped enough for reliable tax use.

Install only if you are comfortable reviewing the code and limiting inputs to local, redacted copies of the minimum brokerage statements needed. Do not rely on its tax number without manual verification, especially for mixed years, mixed currencies, partial records, non-HTSC brokers, or bank statements. Treat CLI JSON output as sensitive because it can include names, full account identifiers, addresses, holdings, balances, and transactions.

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

Warning
Location
scripts/crs_generator.py:64
Finding
Unnecessary Extraction and Unredacted Output of Personal Financial Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/crs_generator.py:64-66`, `scripts/crs_generator.py:78-104`, and `scripts/crs_generator.py:386-391` **Vulnerability Type**: Excessive sensitive-data processing and plaintext disclosure **Risk Level**: Medium ### Vulnerable Code ```python data = { "client_name": None, "account_number": None, "account_type": None, "address": None, "statement_date": None, "currencies": {}, "holdings": [], "total_value_hkd": None, "transactions": [], "fees": {}, } ``` ```python for line in lines: # Pattern: "张三 (0123456789) 客户户口 : 0123456789" m = re.search(r"(\S+)\s*\((\d+)\).*客户户口\s*:\s*(\d+)", line) if m: data["client_name"] = m.group(1) data["account_number"] = m.group(3) # Account type if "户口类别" in line: m = re.search(r"户口类别\s*:\s*(.+?)\s", line) if m: data["account_type"] = m.group(1) # Address if "區" in line or "市" in line or "省" in line: if not data["address"] and "省" not in line: continue if re.search(r"[\u4e00-\u9fff]{2,}(?:省|市|區|镇|路|號|号|座)", line): if not re.search(r"(?:皇后|中心|大道|Tel|电话|傳真)", line): data["address"] = line.strip() # --- Address: collect multi-line --- addr_lines = [] capture = False for line in lines: if re.search(r"[\u4e00-\u9fff]{2,}省[\u4e00-\u9fff]{2,}市", line): capture = True if capture: if re.search(r"(?:客户主任|列印|户口类别)", line): break clean = line.strip() if clean and not re.search(r"(?:皇后|中心|大道|Tel|电话|傳真|^$)", clean): addr_lines.append(clean) if addr_lines: data["address"] = "".join(addr_lines) ``` ```python def main(): import argparse parser = argparse.ArgumentParser(description="Parse a local broker PDF into JSON.") parser.add_argument("pdf") args = parser.parse_args() print(json.dumps(parse_statement(args.pdf), ensure_ascii=False, ind ...[truncated 1534 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `client_name` and `address` extraction because these values are not needed for the declared calculation. - Replace full account numbers with a stable masked identifier, such as the final four digits, before storing or returning parser output. - Return a deliberately allowlisted data structure containing only required transaction, currency, date, and balance fields. - Add an explicit `--include-sensitive-metadata` diagnostic option if identity extraction is operationally unavoidable, and keep it disabled by default. - Send diagnostic information to a controlled logger with redaction rather than printing the full parsed object. - Add tests confirming that names, addresses, and complete account numbers cannot appear in default output. - Document local retention and deletion expectations for generated output. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/offset.py:4
Finding
Annual Tax Aggregation Does Not Enforce Year Boundaries<![CDATA[ ## Vulnerability Details **File Location**: `scripts/offset.py:4-17` **Vulnerability Type**: Missing temporal-scope validation in financial aggregation **Risk Level**: High ### Vulnerable Code ```python def offset_year( matched_sales: list[MatchedSale], income_items: list[IncomeItem] ) -> OffsetResult: transfer_net = max(sum(sale.gain for sale in matched_sales), 0) dividend_net = sum( item.amount for item in income_items if item.kind == "dividend" ) interest_net = sum( item.amount for item in income_items if item.kind == "interest" ) return OffsetResult( transfer_net=transfer_net, dividend_net=dividend_net, interest_net=interest_net, ) ``` The model includes year fields that the aggregation does not use: ```python @dataclass class MatchedSale: sale: Trade buy: Trade qty: float proceeds: float cost: float gain: float year: int | None = None ``` ```python @dataclass class IncomeItem: kind: str amount: float year: int ``` ### Technical Analysis Despite being named `offset_year`, the function does not accept a target year and does not filter either matched sales or income items by year. All supplied gains, dividends, and interest are aggregated into one result. The pipeline also provides no target-year boundary before invoking this function. Consequently, records from different tax years can be offset or taxed together, contrary to the Skill's annual-estimation behavior. ### Attack Path 1. The caller supplies trades or income records from more than one year. 2. Trade matching returns all matched sales without imposing a target tax year. 3. `offset_year()` sums every gain and every qualifying income item. 4. Losses from one year may offset gains from another, while dividends and interest from multiple years are combined. 5. The resulting value is passed to `estimate_tax()` and presented as a single annual estimate. ### Impact Assessment ...[truncated 302 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Change the interface to require an explicit target year: ```python def offset_year( target_year: int, matched_sales: list[MatchedSale], income_items: list[IncomeItem], ) -> OffsetResult: ``` - Derive sale years from validated sale dates or populate and validate `MatchedSale.year`. - Filter all sales and income items against `target_year` before aggregation. - Reject records with missing, malformed, or contradictory dates rather than silently including them. - Ensure purchase records from earlier years may establish cost basis without causing their year to become the sale-tax year. - Add regression tests containing mixed-year gains, losses, dividends, and interest. - Include the selected year in `OffsetResult` and `ClientReport` so downstream code cannot lose the calculation scope. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/matching.py:20
Finding
Partially Unmatched Sales Are Incorrectly Included in the Tax Basis<![CDATA[ ## Vulnerability Details **File Location**: `scripts/matching.py:20-48` and `scripts/pipeline.py:8-14` **Vulnerability Type**: Incomplete-record handling and unsafe partial financial calculation **Risk Level**: High ### Vulnerable Code ```python remaining_qty = trade.qty while remaining_qty > 0 and buys[key]: buy, available_qty = buys[key][0] match_qty = min(remaining_qty, available_qty) proceeds = trade.amount * match_qty / trade.qty cost = buy.amount * match_qty / buy.qty matched.append( MatchedSale( sale=trade, buy=buy, qty=match_qty, proceeds=proceeds, cost=cost, gain=proceeds - cost, ) ) remaining_qty -= match_qty available_qty -= match_qty if available_qty == 0: buys[key].popleft() else: buys[key][0][1] = available_qty if remaining_qty > 0: unmatched.append( UnmatchedSale( sale=trade, qty=remaining_qty, proceeds=trade.amount * remaining_qty / trade.qty, ) ) ``` The partially matched result is then taxed: ```python def run_pipeline(trades: list[Trade], income_items: list[IncomeItem]) -> ClientReport: matched, unmatched = match_trades(trades) offset = offset_year(matched, income_items) taxed = estimate_tax(offset) report = build_client_report(taxed, unmatched) report.unmatched = unmatched report.tax_due_inputs = taxed return report ``` ### Technical Analysis The matching implementation commits each matched lot immediately. If the available purchase history covers only part of a sale, the matched portion remains in `matched`, while only the residual quantity is placed in `unmatched`. This contradicts the documented policy that a sale lacking complete acquisition records must be excluded in full. Because `run_pipeline()` taxes all entries in `matched`, an incompletely supported sale contributes to the tax estimate. ...[truncated 1017 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Process each sale transactionally: stage proposed FIFO matches without mutating the main purchase-lot queues. - Commit staged matches only when the entire sold quantity is covered. - If coverage is incomplete, classify the full sale quantity and full proceeds as unmatched. - Preserve all purchase lots when an attempted sale match is rejected. - Report the original sale reference and full missing quantity so users can locate the required records. - Add tests for fully matched, entirely unmatched, and partially matched sales. - Add an invariant asserting that every sale is either fully represented by matched quantities or fully represented by one unmatched record, never both. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/matching.py:7
Finding
Trade Matching and Tax Aggregation Combine Incompatible Currencies<![CDATA[ ## Vulnerability Details **File Location**: `scripts/matching.py:7-34` and `scripts/offset.py:4-17` **Vulnerability Type**: Missing currency isolation and exchange-rate controls **Risk Level**: High ### Vulnerable Code ```python def match_trades(trades: list[Trade]) -> tuple[list[MatchedSale], list[UnmatchedSale]]: buys: dict[tuple[str, str | None], deque[list[float | Trade]]] = defaultdict(deque) matched: list[MatchedSale] = [] unmatched: list[UnmatchedSale] = [] for trade in sorted(trades, key=lambda item: item.date): key = (trade.symbol, trade.account) if trade.side == "buy": buys[key].append([trade, trade.qty]) continue if trade.side != "sell": continue remaining_qty = trade.qty while remaining_qty > 0 and buys[key]: buy, available_qty = buys[key][0] match_qty = min(remaining_qty, available_qty) proceeds = trade.amount * match_qty / trade.qty cost = buy.amount * match_qty / buy.qty matched.append( MatchedSale( sale=trade, buy=buy, qty=match_qty, proceeds=proceeds, cost=cost, gain=proceeds - cost, ) ) ``` ```python def offset_year( matched_sales: list[MatchedSale], income_items: list[IncomeItem] ) -> OffsetResult: transfer_net = max(sum(sale.gain for sale in matched_sales), 0) dividend_net = sum( item.amount for item in income_items if item.kind == "dividend" ) interest_net = sum( item.amount for item in income_items if item.kind == "interest" ) ``` The trade model contains a currency field that is not enforced: ```python @dataclass class Trade: side: str symbol: str qty: float amount: float currency: str date: str source: str ``` ### Technical Analysis The FIFO k ...[truncated 1420 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Include normalized currency in every matching key: ```python key = (trade.symbol, trade.account, trade.currency.upper()) ``` - Reject a proposed match if the purchase and sale currencies differ. - Add `currency` to `IncomeItem`, `MatchedSale`, and aggregate result models. - Select one explicit reporting currency for each report. - Convert amounts using a documented exchange-rate source and applicable conversion date. - Preserve the original amount, original currency, exchange rate, conversion date, and converted amount for auditability. - Never sum monetary values until they have been normalized to the same reporting currency. - Add tests covering cross-currency symbols, income, and unsupported currencies. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/matching.py:20
Finding
Missing Numeric Validation Allows Calculation Failure and Negative Tax Results<![CDATA[ ## Vulnerability Details **File Location**: `scripts/matching.py:20-24` and `scripts/estimate.py:7-14` **Vulnerability Type**: Improper validation of financial inputs **Risk Level**: Medium ### Vulnerable Code ```python remaining_qty = trade.qty while remaining_qty > 0 and buys[key]: buy, available_qty = buys[key][0] match_qty = min(remaining_qty, available_qty) proceeds = trade.amount * match_qty / trade.qty cost = buy.amount * match_qty / buy.qty ``` ```python def estimate_tax(offset: OffsetResult) -> OffsetResult: return OffsetResult( transfer_net=offset.transfer_net, dividend_net=offset.dividend_net, interest_net=offset.interest_net, dividend_tax=round((offset.dividend_net + offset.interest_net) * DIVIDEND_RATE, 2), transfer_tax=round(offset.transfer_net * TRANSFER_RATE, 2), ) ``` ### Technical Analysis The data classes accept unrestricted floating-point values, and the calculation functions do not validate them. Zero quantities can cause division by zero. Negative, infinite, or `NaN` quantities and amounts can produce invalid matching behavior or non-finite results. Negative dividend or interest amounts are directly multiplied by the tax rate and can create negative tax. Validation cannot be delegated safely to Python type annotations because dataclass annotations are not runtime constraints. The pipeline accepts already-created model objects without an intervening validation layer. ### Attack Path 1. A parser, integration, or direct caller creates a `Trade` with a zero or invalid quantity, or an `IncomeItem` with a negative or non-finite amount. 2. `match_trades()` divides by the invalid quantity, causing a runtime exception or corrupt arithmetic. 3. Alternatively, `estimate_tax()` multiplies negative or non-finite income directly by the tax rate. 4. The pipeline either terminates, producing denial of service, or returns a negative/non-finite tax estimate. ### Impact Assessm ...[truncated 260 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Add centralized model validation before matching or estimation. - Require quantities to be finite and strictly positive. - Require monetary amounts and computed values to be finite. - Define and enforce whether transaction amounts are signed or unsigned; normalize them consistently before matching. - Reject unknown trade sides, income kinds, currencies, and malformed dates. - Prohibit negative dividend and interest inputs unless an explicit adjustment model exists. - Ensure final tax values cannot be negative: ```python taxable_income = max(offset.dividend_net + offset.interest_net, 0) transfer_income = max(offset.transfer_net, 0) ``` - Return structured validation errors instead of allowing arithmetic exceptions to terminate execution. - Add tests for zero, negative, infinite, and `NaN` values. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description promises a tax-focused workflow: estimating China IIT, preparing documents for the tax bureau, and offsetting gains/losses based on overseas brokerage or bank statements and local chat inputs. The supplied code does something narrower and different: it parses local PDF brokerage statements into structured JSON. It extracts fields such as client/account info, holdings, balances, transactions, and margin interest, and only for supported broker formats detected in the PDF text. There is no tax calculation logic, no tax-form/document generation, no gain/loss aggregation or offset engine, no bank statement parser, and no integration with WorkBuddy or 豆包 Work. While statement parsing could be a supporting component for a tax tool, this code chunk by itself materially underdelivers relative to the declared primary purpose, so it is a clear description-behavior mismatch.

Known Vulnerable Dependency: pytest==8.3.5 — 2 advisory(ies): CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)

High
Category
Supply Chain
Confidence
95% confidence
Finding
The file pins pytest==8.3.5, which is reported as affected by a tmpdir-handling vulnerability. Even though pytest is typically a development/test dependency rather than a production runtime component, keeping a known-vulnerable version can still expose developer or CI environments if untrusted tests, plugins, or filesystem interactions are involved. In this skill context, the dependency appears unrelated to the core tax-report functionality, which makes the issue less dangerous than a vulnerable runtime package but still a real supply-chain hygiene problem.

Natural-Language Policy Violations

Medium
Confidence
86% confidence
Finding
The entire document is written only in Chinese and does not indicate that the skill is region-specific or that users may choose another language. Under the language/locale policy rule, this can be a natural-language policy violation when a specific language is imposed without user opt-in or justification.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The entire interview script is written as mandatory Chinese-language instructions and does not indicate that users may choose another language or locale. This is a natural-language policy concern because it imposes a specific language experience without documented opt-in or justification.

Natural-Language Policy Violations

Medium
Confidence
87% confidence
Finding
Line L6 states that the default result shown to customers is a 'plain-language number' using Chinese phrasing, and the entire document is written only in Chinese with no indication that users may choose another language or locale. This can violate a language/locale policy when the skill implicitly enforces one language without user opt-in.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The file docstring narrows the script's behavior to parsing broker statement PDFs and explicitly says not to generate official tax filings. In contrast, the manifest presents the skill as a CRS/report generator used to prepare tax-bureau documents and support gain/loss offset workflows, which this code does not implement.

Intent-Code Divergence

Medium
Confidence
84% confidence
Finding
This is an active contradiction between inline documentation and the declared skill intent. The code itself only parses PDF content into JSON, and the docstring warns against official filing generation despite the skill being positioned for tax-report preparation.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
SQP-3 applies to all file types and covers language or locale policy violations. All user-facing prompts are written only in Chinese, which forces a specific language experience without any indication that the user can choose another language or has opted in to Chinese.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The workflow explicitly asks users to upload monthly and annual brokerage statements, which commonly contain highly sensitive financial and personal information. Without a user-facing warning, minimization guidance, or clear indication of what is necessary, users may overshare confidential data and increase privacy and data-handling risk.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The report section titles and user-facing report strings are hard-coded in Chinese, which imposes a specific language on users. The file does not indicate any user opt-in, language selection, or documented region-specific requirement that would justify the locale restriction.

Static analysis

No suspicious patterns detected.