Description
Bring account loading up to the endpoint-safety standard the rest of the SDK enforces.
Problem Statement
src/account/loader.ts:79-98 does const url = \${horizonUrl}/accounts/${publicKey}`; const response = await fetch(url);`
This bypasses validateUrl from src/network/index.ts entirely — the module whose header documents exactly this failure (a plaintext or hostile endpoint misreporting on-chain state, vero-guardian-dashboard#288).
publicKey is interpolated unencoded, so a caller-supplied value containing ../ or ?/# redirects the request to a different Horizon resource. There is no AbortSignal, so a non-responding Horizon hangs the caller indefinitely.
The catch on lines 92-97 also flattens every failure into new Error(...), discarding status codes and defeating the SDK's own VeroError/normalizeError contract.
Proposed Changes
Technical Implementation Scaffolding
- Target Repository: vero-sdk
- Target Path: src/account/loader.ts
- Branch Naming: fix/issue--account-loader-url-safety
- Authority Context: Security-sensitive — endpoint validation bypass
Acceptance Criteria
Definition of Done
Description
Bring account loading up to the endpoint-safety standard the rest of the SDK enforces.
Problem Statement
src/account/loader.ts:79-98doesconst url = \${horizonUrl}/accounts/${publicKey}`; const response = await fetch(url);`This bypasses
validateUrlfromsrc/network/index.tsentirely — the module whose header documents exactly this failure (a plaintext or hostile endpoint misreporting on-chain state, vero-guardian-dashboard#288).publicKeyis interpolated unencoded, so a caller-supplied value containing../or?/#redirects the request to a different Horizon resource. There is noAbortSignal, so a non-responding Horizon hangs the caller indefinitely.The catch on lines 92-97 also flattens every failure into
new Error(...), discarding status codes and defeating the SDK's ownVeroError/normalizeErrorcontract.Proposed Changes
validateUrl(horizonUrl, opts)before building the requestnew URL(\accounts/${encodeURIComponent(publicKey)}`, base)and assert the resulting origin matches, mirroringsrc/rpc/index.ts:125-131`AbortControllersignal with a configurable timeoutVeroErrorwithAccountNotFound/RpcRequestFailedcodes vianormalizeErrorinstead of a bareErrorTechnical Implementation Scaffolding
Acceptance Criteria
loadAccount('http://evil.example', ...)throwsVeroErrorwith codeINVALID_URLpublicKeyof../ledgers/1does not produce a request outside/accounts/loadAccountto reject within the timeoutVeroErrorCode.AccountNotFoundDefinition of Done