Skip to content

fix: medium severity issues #140, #149, #150, #153#162

Merged
user1303836 merged 2 commits intomainfrom
fix/medium-issues-149-150-153-160
Mar 13, 2026
Merged

fix: medium severity issues #140, #149, #150, #153#162
user1303836 merged 2 commits intomainfrom
fix/medium-issues-149-150-153-160

Conversation

@user1303836
Copy link
Owner

Summary

Closes #149, closes #150, closes #153.

Test plan

  • cargo fmt --all --check passes
  • cargo clippy --workspace --all-targets -- -D warnings passes
  • cargo test --workspace — 294 passed, 1 pre-existing failure (test_semaphore_limits_concurrent_jobs fails on main as well)
  • New test test_is_private_ip_ipv4_compatible_ipv6 verifies ::10.0.0.1 (blocked), ::127.0.0.1 (blocked), ::8.8.8.8 (allowed)
  • All export job tests updated to expect 202 ACCEPTED
  • Auth test updated to expect 500 INTERNAL_SERVER_ERROR when no API key configured

🤖 Generated with Claude Code

…onfig, ledger index

- Close SSRF IPv6 gap: use to_ipv4() instead of to_ipv4_mapped() to
  block IPv4-compatible addresses like ::10.0.0.1 and ::127.0.0.1.
  Replace manual bitmask checks with stable std methods
  (is_unique_local, is_unicast_link_local, is_multicast).
- Return 202 Accepted from create_export_job() instead of 200 OK,
  matching HTTP semantics for async operations.
- Return 500 Internal Server Error when SPECTRAPLEX_API_KEY is not
  configured, distinguishing server misconfiguration from client
  auth failures (401).
- Add migration for index on ledger_entries.transaction_id to
  improve JOIN performance between bronze and silver layers.

Addresses #140, #149, #150, #153.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Owner Author

@user1303836 user1303836 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix: medium severity issues #140, #149, #150, #153

Good work overall. Three of the four fixes are correct and well-tested. One fix has a migration ordering issue that will break existing deployed databases and must be fixed before merging.


1. SSRF IPv4-compatible bypass (#140 follow-up) — Correct, with one note

The fix is correct and important. Switching from to_ipv4_mapped() to to_ipv4() closes a real SSRF bypass vector. to_ipv4_mapped() only catches ::ffff:x.x.x.x (IPv4-mapped), but to_ipv4() also catches ::x.x.x.x (IPv4-compatible), meaning an attacker could previously bypass SSRF protection by submitting a callback URL with an IPv4-compatible address like ::10.0.0.1 or ::127.0.0.1.

The std method replacements are correct. is_unique_local() (stable since 1.84.0), is_unicast_link_local() (stable since 1.84.0), and is_multicast() (stable since 1.7.0) are all available on rustc 1.92.0 and semantically equivalent to the manual bitmask checks they replace:

  • is_unique_local() checks fc00::/7 — same as (seg0 & 0xfe00) == 0xfc00
  • is_unicast_link_local() checks fe80::/10 — same as (seg0 & 0xffc0) == 0xfe80
  • is_multicast() checks ff00::/8 — same as (seg0 & 0xff00) == 0xff00

Edge case: ::1 and to_ipv4(). to_ipv4() converts ::1 (IPv6 loopback) to Ipv4Addr::new(0, 0, 0, 1), which is NOT matched by any of the IPv4 private checks (is_loopback, is_private, etc.). However, ::1 is already caught by v6.is_loopback() at the top of the IPv6 branch, so this is safe. The new test cases for ::10.0.0.1, ::127.0.0.1, and ::8.8.8.8 are good and directly validate the fix.

No issues here.


2. Export jobs return 202 Accepted (#153) — Correct and thorough

The status code change is correctly applied to the single async export creation path (create_export_job). Both the return type signature and the return value are updated consistently. The synchronous export endpoints (export_ledger at line 1295 and tax_export at line 3011) correctly remain at 200 OK since they return data inline rather than spawning background tasks.

All 12 affected test assertions were updated from StatusCode::OK to StatusCode::ACCEPTED. The response body structure (ExportJobStatus with state: "pending") is unchanged, which is correct — only the HTTP status code changes.

No issues here.


3. Missing API key returns 500 (#150) — Correct

The change correctly distinguishes between two failure modes in require_auth():

  • No API key configured on the server (line 413): returns 500 Internal Server Error — this is a server misconfiguration, not a client error.
  • Client provides wrong/missing key (line 430): still returns 401 Unauthorized — this is a client auth failure.

The error message "API key not configured" is slightly informative about server state, but this is acceptable since: (a) the endpoint already requires authentication so only authenticated users would see it in normal operation, and (b) knowing that an API key isn't configured doesn't give an attacker actionable information. The test at test_auth_rejects_when_no_key_configured is updated to expect 500.

No issues here.


4. Ledger entries index (#149) — Migration ordering bug, must fix

The index itself is correct. ledger_entries.transaction_id is used in JOINs between the bronze (transactions) and silver (ledger_entries) layers (e.g., the wallet_address backfill in migration 20251219010000), and an index on this FK column is a standard performance improvement. IF NOT EXISTS is used for idempotency. The write performance overhead is acceptable — this is a standard B-tree index on a UUID FK column in a table that receives batch writes during normalization, not high-frequency OLTP inserts.

However, the migration filename 20260313000000 sorts BEFORE the existing 20260313100000_add_enum_check_constraints.sql. The sqlx migrator (sqlx::migrate!) applies migrations in lexicographic order by version and will error with a VersionMissing-type error when it encounters a new migration with a version lower than an already-applied one. On any database that already has 20260313100000 applied, running this new migration will fail.

Fix: Rename the migration to use a timestamp that sorts AFTER 20260313100000, for example:

20260313200000_add_ledger_entries_transaction_id_index.sql

This is a blocking issue.

Renumbers 20260313000000 -> 20260313200000 so the ledger_entries
transaction_id index migration sorts after the existing
20260313100000_add_enum_check_constraints migration, preventing
sqlx migrator errors on databases that already have it applied.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: ce309c95f26d
@user1303836 user1303836 merged commit 4235e08 into main Mar 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant