Skip to content

fix(backend): i128 cap, event-history auth, SSE client ids, cancel signing docs (#1532, #1533, #1534, #1547) - #1630

Merged
K1NGD4VID merged 4 commits into
LabsCrypt:mainfrom
fadesany:fix/audit-1532-1533-1534-1547
Oct 18, 2026
Merged

K1NGD4VID merged 4 commits into
LabsCrypt:mainfrom
fadesany:fix/audit-1532-1533-1534-1547

Conversation

@fadesany

Copy link
Copy Markdown
Contributor

Summary

Fixes four third-wave audit issues in one branch.

#1532 — depositedAmount has no i128 cap

createStreamSchema is already wired into POST /v1/streams, but its depositedAmount field only enforced the numeric format, so a decimal string above i128::MAX passed validation and reached BigInt arithmetic / the i128 DB columns. The i128 bound check is now shared between ratePerSecond and depositedAmount in backend/src/validators/stream.validator.ts.

  • Regression tests: schema-level (reject > max, accept == max) plus controller-level and route-level POST /v1/streams tests asserting 400.

#1533 — GET /v1/users/:publicKey/events had no auth

The route now uses requireAuth, and getUserEvents rejects (403) when the authenticated caller's public key differs from the :publicKey path param, mirroring GET /v1/events. Missing auth is rejected with 401 (middleware and controller guard).

  • Regression tests: unit tests for 401/403, and integration tests asserting an unauthenticated request is rejected and a different-wallet request is rejected.
  • OpenAPI spec and generated API types regenerated.

#1534 — SSE clientId uses Math.random()

backend/src/controllers/sse.controller.ts now generates clientId with crypto.randomUUID(), matching every other id generator in the backend. The subscribe test asserts the UUID format and that the connected event carries the same id.

#1547 — Action Signing Model omitted Cancel

Both docs/ARCHITECTURE.md and backend/docs/ARCHITECTURE.md now include a Cancel row and corrected prose.

Note: the issue's premise no longer matches main. backend/src/controllers/stream/cancel.ts does not read KEEPER_SECRET_KEY; it requires senderSecret in the request body and passes it to sorobanService.cancelStream(). A grep of backend/src confirms topUpStream() is the only caller of the keeper secret, so documenting Cancel as custodial would make the docs wrong. Instead the docs now state that cancel is submitted server-side but authorized with the caller's own key, and explicitly list the set of custodial vs. user-authorized actions, with the grep evidence.

Verification

  • cd backend && npx tsc --noEmit — clean
  • cd backend && npx vitest run --exclude='tests/integration/**' --coverage.enabled=false — 59 files, 727 passed, 3 skipped
  • cd backend && npx vitest run --coverage.enabled=false tests/stream.validator.test.ts tests/stream.controller.test.ts tests/user.controller.test.ts tests/sse.controller.test.ts tests/integration/events-list.test.ts tests/integration/sse-subscribe.test.ts tests/stream.test.ts — all passing
  • npm run codegen:openapi + npm run codegen:api-types regenerate cleanly (spec/types committed)

Closes #1532
Closes #1533
Closes #1534
Closes #1547

fadesany and others added 4 commits October 18, 2026 04:12
createStreamSchema is wired into POST /v1/streams, but its depositedAmount
field only checked the numeric format, so a value larger than 2^127-1 could
reach BigInt arithmetic and the i128 DB columns unchecked. Share the i128
bound helper between ratePerSecond and depositedAmount and cover both the
schema and the HTTP route with regression tests.

Closes LabsCrypt#1532

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
GET /v1/users/:publicKey/events was unauthenticated and unscoped, so any
caller could enumerate any wallet's stream history by public key. Require
a bearer token and reject 403 unless the path publicKey matches the
authenticated wallet, mirroring GET /v1/events. Regenerate the OpenAPI
spec and API types, and add route-level regression tests.

Closes LabsCrypt#1533

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The SSE subscribe handler built client ids from Date.now() plus a short
Math.random() suffix, so two concurrently connecting clients could collide
and overwrite each other's registration. Use the CSPRNG-backed randomUUID
already used by every other id generator in the backend, and assert the
format in the subscribe test.

Closes LabsCrypt#1534

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The action signing tables omitted Cancel entirely, leaving reviewers
unable to tell whether cancellation holds custodial risk. Add a Cancel row
to both architecture docs and state the actual rule: only top-up is signed
with KEEPER_SECRET_KEY, while cancel is submitted server-side using the
caller-supplied senderSecret.

Closes LabsCrypt#1547

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@K1NGD4VID
K1NGD4VID merged commit a857ec8 into LabsCrypt:main Oct 18, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment