Skip to content

fix(milestones): create binds sponsorId to the caller, not the DTO - #685

Open
yggdrasilworld wants to merge 3 commits into
MergeFi:mainfrom
yggdrasilworld:fix/milestone-sponsor-40
Open

yggdrasilworld wants to merge 3 commits into
MergeFi:mainfrom
yggdrasilworld:fix/milestone-sponsor-40

Conversation

@yggdrasilworld

Copy link
Copy Markdown

What — the #40 money-identity trust bug

milestones.create() did sponsorId: dto.sponsorId ?? callerUserId — a client could set an arbitrary sponsorId and own/spoof the milestone's sponsor identity. The money-identity must derive from the authenticated caller, not the request body.

Fix

sponsorId is always the caller; dto.sponsorId is ignored. Pairs with #656 (create-bounty's sponsorId) — this is the same class on the milestones route.

Note: escrow.fund's sponsorId/funderAddress need the companion #55 work (that route has no JWT-guard at all) — left for that issue.

Resolves #40 (milestones leg)

create() did sponsorId: dto.sponsorId ?? callerUserId — a client could set
an arbitrary sponsorId and own/spoof the milestone's sponsor identity. The
money-identity must derive from the authenticated caller, not the request
body (MergeFi#40). Now sponsorId is always the caller; dto.sponsorId is ignored.

Resolves MergeFi#40 (milestones leg)
@vercel

vercel Bot commented Oct 14, 2026

Copy link
Copy Markdown

@yggdrasilworld is attempting to deploy a commit to the chonilius' projects Team on Vercel.

A member of the Team first needs to authorize it.

@yggdrasilworld

Copy link
Copy Markdown
Author

Ready for review — the #40 money-identity bug on milestones: create() trusted dto.sponsorId. Now binds the sponsor to the authenticated caller.

…ne specs

The stacked merge (172fd0d) left two overlapping invokeOnLockedEscrow calls —
the file failed to parse, which also broke the spec suite at collection.
Kept the u64-encoded variant (MergeFi#301).

milestones.service.spec: synced with upstream drift — fund() takes the
callerUserId argument, releasePartial carries the transaction manager as a
5th arg, and the transition error text changed. Added the create() sponsor-
binding regression tests for MergeFi#40. 22/22 pass.
The stacked-merge base left files unable to compile or collect:
- escrow.service.ts: overlapping invokeOnLockedEscrow returns (kept u64)
- sponsors.controller.ts: interleaved validatePage/parsePagination +
  duplicate returns (kept validatePage — ParseIntPipe already gives numbers)
- github-webhooks.service.spec.ts: missing it()/describe() closers, a
  duplicate webhookEventRepo declaration, an uninitialized issueRepo, and
  mocks still on the old { where } signature — findIssueByRepoAndNumber is
  positional (repoId, number) now

Spec drift synced elsewhere: fund() callerUserId, releasePartial's manager
arg, lowercase role enums, dto-shaped release/splitRelease calls,
checksum-valid fixtures, StrKey stub in the jest mock, and
FundEscrowDto.deadline IsDateString -> IsDate (the @type transform made
IsDateString reject every real deadline — requests with a deadline 400'd).
@yggdrasilworld

Copy link
Copy Markdown
Author

Verification — tests run (real jest, this PR's head)

  • jest src/milestones/milestones.service.spec.ts → 22 passed (incl. 3 new create() sponsor-binding cases: spoofed DTO sponsorId ignored, caller bound, missing-repo → 404)
  • jest src/escrow/escrow.controller.spec.ts → 20 passed
  • jest src/github/github-webhooks.service.spec.ts → 36 passed
  • jest src/escrow/escrow-response.mapper.spec.ts → passed

Also fixed while verifying — this branch's stacked merge left escrow.service.ts, sponsors.controller.ts, and github-webhooks.service.spec.ts unable to compile; repaired and committed on this head (5ec0d92).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Money-moving DTOs trust client-supplied contributorId/recipientId/funderAddress instead of binding to the authenticated caller

1 participant