Skip to content

Lock down SECURITY DEFINER functions and privileged columns - #653

Merged
ralyodio merged 1 commit into
masterfrom
fix/secdef-lockdown
Oct 26, 2026
Merged

ralyodio merged 1 commit into
masterfrom
fix/secdef-lockdown

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Security audit 2026-10-26. 56 SECURITY DEFINER functions in public were executable by anon/authenticated; 9 remain, all public read-only or scoped to the caller.

Dangerous functions (now service-role only)

  • withdraw_balance(p_user_id, p_amount): anyone could zero any user's cached balance, or pass a negative amount to mint one. (Withdrawals check the real LNbits balance, so no sats left, but wallets.balance_sats is read by affiliate commission accounting.)
  • create_notification(any user): anon could send notifications to anyone.
  • increment_gig_usage / increment_gig_usage_for_active / decrement_gig_usage: reset or inflate any user's monthly free-tier counter.
  • get_api_key_user, update_api_key_last_used, moderate_affiliate_application (caller-chosen seller id), increment_affiliate_offer_total_affiliates, bump_autoblog_integration, and all 36 trigger functions.
  • blocked_user_ids / users_are_blocked now answer only about the caller (server keeps full access).
  • Default privileges: a new browser RPC or RLS helper needs grant execute ... to anon, authenticated.

Policies / privileged columns

  • profiles.is_admin was writable by the user (the /admin, payouts and email broadcast gate); also credits, rating/follow counters, referral_code. Pinned.
  • subscriptions: users could PATCH plan/status/current_period_end. Now only cancel_at_period_end.
  • funding_payments: UPDATE USING (true) and INSERT true for everyone, SELECT exposed contributor_email. Now own rows + service role.
  • skill_security_scans: "Service role manages scans" had no role, so anyone could write scan results.
  • Invoices/escrows: money fields and paid/funded status come from the webhook only (a poster could mark an invoice paid and become a "verified poster", or fake a funded escrow). Bounty payout_status 'paid' likewise. Applicants may only withdraw/re-open. Testimonial text/rating, other people's messages, listing counters + scan status, gig views pinned.
  • Login redirect also rejects tabs/newlines.

Applied by hand on dev2 (schema backup first). Live check (anon key + throwaway users, cleaned up): internal RPCs 42501; is_admin/credits/plan/invoice paid/amount/self-accept all unchanged after PATCH; withdraw, reject, cancel, feed, subscriptions and profile APIs still work. Abuse check: one admin (Anthony), 0 credits, 0 non-free subscriptions, all 267 paid invoices carry a CoinPay id, no unreferenced paid funding rows.

🤖 Generated with Claude Code

- Revoke internal SECURITY DEFINER functions from anon/authenticated:
  withdraw_balance (any user, any amount; a negative amount minted
  balance), create_notification (any user), the gig-usage counters,
  get_api_key_user, update_api_key_last_used, the affiliate offer
  counter, moderate_affiliate_application (caller-supplied seller id),
  bump_autoblog_integration, and every trigger function.
- blocked_user_ids / users_are_blocked answer only for the caller.
- New functions are no longer executable by PUBLIC/anon/authenticated by
  default; browser RPCs need an explicit GRANT.
- funding_payments: UPDATE and INSERT were open to anyone and SELECT
  exposed contributor emails; now own rows + service role.
  skill_security_scans: the 'service role' policy had no role, so anyone
  could write scan results.
- Pin privileged columns behind update-own policies: profiles.is_admin,
  credits, rating/follow counters, referral code; subscriptions (users
  may only toggle cancel_at_period_end); invoice and escrow money fields
  and paid/funded status; bounty payout status; application status
  (applicant may only withdraw or re-open); testimonial text and rating;
  other people's messages; listing counters and scan status; gig views
  and future-dated boosts.
- Login redirect also rejects tabs/newlines ('/\t/host' parses to
  '//host').

Migration applied by hand on dev2 (backup taken first).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

60 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 12 | LOW: 47

Severity Rule Location
HIGH js-ssrf-outbound-request scripts/scan-all-skills.ts:38
MEDIUM js-open-redirect src/app/agent-login/AgentLoginForm.tsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.tsx:80
MEDIUM js-open-redirect src/app/checkout/CheckoutClient.tsx:77
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:102
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:114
MEDIUM js-unescaped-html-sink src/app/page.tsx:70
MEDIUM js-unescaped-html-sink src/app/pricing/page.tsx:84
MEDIUM js-open-redirect src/components/funding/FundingClient.tsx:138
MEDIUM js-open-redirect src/components/gigs/BuyNowButton.tsx:106
MEDIUM js-unescaped-html-sink src/components/layout/PitFooter.tsx:25
MEDIUM js-dynamic-code-execution src/lib/skills/metadata-extract.ts:300
MEDIUM js-dynamic-code-execution src/lib/skills/security-scan.ts:48
LOW secret-generic-credential cli/src/commands/auth.test.ts:66
LOW secret-generic-credential cli/src/commands/auth.test.ts:85
LOW secret-generic-api-key docs/agents/integration-guide.md:893
LOW secret-generic-credential src/app/api/auth/agent-register/route.test.ts:27
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:53
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:68
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:87
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:172
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:196
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:215
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:228
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:267
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:297
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:329
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:346
LOW secret-generic-credential src/app/api/gigs/[id]/buy/route.test.ts:48
LOW secret-generic-credential src/app/api/gigs/[id]/buy/route.test.ts:99
LOW secret-generic-credential src/app/api/gigs/[id]/buy/route.test.ts:158
LOW secret-generic-credential src/app/api/orders/[id]/release/route.test.ts:18
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:212
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:223
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:239
LOW secret-generic-credential src/lib/api.test.ts:126
LOW secret-generic-credential src/lib/api.test.ts:131
LOW js-dynamic-code-execution src/lib/skills/composite-scanner.test.ts:106
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:36
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:44
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:66
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:81
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:94
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:103
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:118
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:144
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:162
LOW js-dynamic-code-execution src/lib/skills/security-scan.test.ts:28
LOW js-dynamic-code-execution src/lib/skills/url-import.test.ts:178
LOW js-dynamic-code-execution src/lib/skills/url-import.test.ts:191

…and 10 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit e477a74 into master Oct 26, 2026
6 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

Development

Successfully merging this pull request may close these issues.

1 participant