Taskly is a live marketplace handling real payments (Stripe) and personal data (Supabase, RLS-enforced). We take reports seriously.
- Email: tasklyapp.ai@gmail.com with subject
[SECURITY] - Please include: the affected URL/route or file, reproduction steps, and impact. A proof-of-concept is welcome; do not access other users' data while demonstrating.
- You'll get an acknowledgement within 72 hours.
taskly.ca(and regional domains) — the app in this repo, including/api/*and the community app under/community.- Out of scope: volumetric DoS, findings requiring a stolen device/session, social engineering, and reports from automated scanners without a demonstrated impact.
- Publicly disclose an issue before we've had a chance to fix it.
- Run destructive tests against production (data deletion, spam bookings, payment abuse). Escrow money moves are real.
No bug-bounty program exists yet; good-faith reports are appreciated and credited if you want.
The admin console supports Supabase's native MFA (TOTP — 1Password/Authy/Google Authenticator). No schema, no extra dependency, no migration.
- Enroll / manage:
/admin/security— always available, regardless of the flag. - Step-up challenge:
/admin-verify— deliberately outside the(admin)route group, so the admin layout's redirect can never loop. - Enforcement flag:
NEXT_PUBLIC_ADMIN_MFA_REQUIRED(default false).
Do these in order. Never flip the flag first.
- Deploy with
NEXT_PUBLIC_ADMIN_MFA_REQUIREDunset (orfalse). - Go to
/admin/security, enroll, scan the QR (or paste the setup key), and verify a 6-digit code. The status row should read Enabled. - Visit
/admin-verifyand confirm a code raises the session toaal2and returns you to the console. - Only then set
NEXT_PUBLIC_ADMIN_MFA_REQUIRED=trueand redeploy (it's aNEXT_PUBLIC_*var — it's inlined at build time, so a redeploy is required).
The enforcement decision (shouldChallengeAdmin in lib/admin/mfa.ts) fails open by
design: an admin with no verified factor is never challenged, even with the flag on,
and an unreadable assurance level lets the request through. Flipping the flag therefore
cannot lock out an admin who hasn't enrolled.
If an authenticator is lost, delete the factor row with the service-role key (SQL
editor in the Supabase dashboard runs as service role). Supabase stores factors in
auth.mfa_factors; deleting the row drops the account back to aal1 and, per the rule
above, enforcement then stops applying to that admin.
-- 1. Confirm what's enrolled for that admin.
select f.id, f.friendly_name, f.factor_type, f.status, f.created_at
from auth.mfa_factors f
join auth.users u on u.id = f.user_id
where u.email = 'admin@example.com';
-- 2. Delete it (challenges cascade). Sessions are cleared so the next sign-in is clean.
delete from auth.mfa_factors
where user_id = (select id from auth.users where email = 'admin@example.com');
delete from auth.sessions
where user_id = (select id from auth.users where email = 'admin@example.com');Belt-and-braces: setting NEXT_PUBLIC_ADMIN_MFA_REQUIRED=false and redeploying also
restores access immediately without touching the database.
Enrollment and removal are recorded in admin_audit_log as admin.mfa_enrolled /
admin.mfa_unenrolled.