July 31, 2026
When Trust Becomes the Product: A Cybersecurity Case Study on Watchly, a Community Safety…
Exploring How Security, Privacy, and User Trust Shape the Success of Community Safety Platforms

By Okoh Nzubechukwu
6 min read
By Nzubechukwu Okoh (Cybersecurity Analyst)
Overview
A false security alert doesn't just waste someone's time, it can send them down the wrong street, or make them ignore the one warning that was actually real. That's the risk Watchly was built to eliminate, and it's also, unavoidably, a security problem: the moment you build a system whose job is to tell people this incident is verified, trust it, you've also built a system that has to survive someone trying to make it lie.
Watchly is a community safety intelligence platform built for Nigerian residential communities. Residents report local safety incidents, other members corroborate what they know, and Community Admins make human-led verification decisions. Verified incidents trigger SMS alerts, so residents are reached even when they aren't active in the app.
The numbers explain why this matters. A 74-respondent survey found that 63% of respondents had personally witnessed or experienced a security incident, 66% had encountered a false or exaggerated alert, and the average trust score for WhatsApp-based safety information sat at 3 out of 5: used constantly, trusted barely. Local government officials have gone as far as publicly warning residents about false alerts circulating on social media. Watchly's bet is that a structured, human-verified pipeline can fix what an unmoderated group chat never could and every control in this case study exists to make sure that bet isn't undermined by a single bad actor.
The MVP was built during Learnable Build Season 2026 by a 16-person cross-functional team over a four-week window, covering one end-to-end loop: Register → Join Community → Report → Corroborate → Admin Review → Status Update → SMS Alert.
My Role
I was the sole cybersecurity team member on this build, responsible for the threat model, authentication/RBAC (Role Based Access Control) review, RLS (Row Level Security) policy audit, privacy review, abuse assessment, and the security test plan working alongside a one-person backend, one-person Quality analyst and a four-week delivery deadline.
Methodology
The threat model followed an asset-first approach rather than a generic checklist:
- Identify assets: enumerate every piece of data or capability worth protecting (incident content, reporter identity, session tokens, phone numbers, role flags, corroboration records, SMS logs).
- Map attack surfaces: identify every entry point where untrusted input or a malicious request could reach the system (registration/OTP, the report form, image uploads, corroboration endpoint, admin status-change endpoint, Platform Admin endpoints, incident-detail queries, the SMS gateway integration).
- Build a threat table: pair each asset with its realistic threat, its entry point, and a severity rating.
- Assign severity: rated by potential impact and reach: how many people are affected, and how badly.
- Document mitigations: map each threat to a specific, verifiable control, not a UI-level assumption.
- Validate: define a concrete test plan to confirm each control holds under direct testing (API calls, not just clicking through the app).
Key Findings and Severity Ranking
Ten assets were assessed. Findings were rated High, Medium, or Low based on impact scope and severity of consequence.
High Risk (5):
- Stored XSS: malicious script submitted through incident report fields (category, description, location) could execute whenever the report is viewed by other members.
- Reporter identity exposure: reporter identity is meant to be visible only to the reporter and Community Admins; a flaw in the visibility rule would expose who filed a report, creating real-world risk to that person.
- Session token theft: Supabase stores session tokens in localStorage, which is readable by JavaScript and therefore a target if an XSS attack succeeds elsewhere.
- Admin privilege escalation: a regular Member changing an incident's status by calling the endpoint directly, bypassing the UI that would normally restrict this action to Admins.
- Platform Admin unauthorized access: compromise at this level affects every community on the platform, not just one.
Medium Risk (4): status-history/admin-name exposure to unauthorized members, corroboration manipulation (self- or duplicate confirmation), malicious image uploads (disguised file types), and SMS data leakage through the third-party gateway.
Low Risk (1): rate-limit abuse on report and OTP endpoints.
Remediation Approach
The guiding principle throughout was simple: a control that only lives in the UI is a convenience, not a security mechanism. Every mitigation below is enforced server-side, so it holds regardless of what the frontend displays or hides.
The three highest-leverage fixes came almost for free from the stack itself. React's default output escaping neutralizes stored XSS across every free-text field (category, description, location) without extra code, as long as dangerouslySetInnerHTML is never used on user input. Row Level Security (RLS) enforces data isolation at the database layer, so a query is checked against the requester's actual session before any row is returned, meaning one user structurally cannot reach another user's data, independent of what the application layer assumes. And every privileged action; admin status changes, Platform Admin approvals, and conditionally visible fields like reporter_id and status-change reasons is gated by an explicit server-side check (community-scoped role, or is_platform_admin = true), returning null or 403 rather than trusting a hidden button to keep the wrong user out.
The remaining fixes closed off narrower, more specific abuse paths: file uploads are validated by actual file header rather than extension (blocking disguised file types), corroboration is protected against both self-confirmation and duplicate/simultaneous submissions via backend checks and a database-level constraint, SMS content is kept deliberately minimal so no reporter identity or precise location ever leaves the system through the third-party gateway, and rate limits on reporting, corroborating, and OTP requests prevent automated abuse of the core loop.
Validation (Security Test Plan)
Each mitigation was verified through direct testing rather than assumed from the design:
-
Submitted script-like input into report fields and confirmed it rendered as plain text.
-
Requested incident details as a non-reporter, non-admin member and confirmed reporter_id and status-change reasons returned null.
-
Attempted a status change via the API as a Member (not Admin) and confirmed a 403 response.
-
Attempted Platform Admin actions as a regular user and as a Community Admin and confirmed 403 on both; confirmed a Platform Admin with no memberships still could not see incident data.
-
Attempted self-corroboration (expected 403) and duplicate corroboration, including across two simultaneous sessions (expected 409).
-
Uploaded a non-image file renamed with a
.jpgextension and confirmed rejection by file-header check. -
Submitted 6 reports within an hour and confirmed the 6th returned 429.
-
Attempted an invalid status transition (Reported → Verified directly) and confirmed a 422 response.
Accepted Risks
Not every risk was engineered away, and documenting that distinction was as important as fixing what could be fixed:
- Session tokens in localStorage: a default Supabase Auth behavior. Rather than building custom token storage under a four-week deadline, the team accepted this and focused effort on preventing XSS, the only realistic path to exploiting it.
- SMS gateway (Termii) as a third-party dependency: delivery failure is outside the team's direct control. The mitigation was architectural: the in-app alert always fires as a fallback, so no user is left uninformed even if SMS delivery fails.
- Community Admin as a volunteer role with no guaranteed review SLA: classified explicitly as an operational risk, not a security one, and monitored rather than enforced.
Lessons Learned
-
Security controls need to be provable, not just designed: A well-reasoned mitigation on paper is not the same as a mitigation confirmed under direct API testing. The gap between "the UI prevents this" and "the server rejects this independently of the UI" is where most real vulnerabilities hide.
-
Severity should be scored by reach, not just technical complexity: Platform Admin compromise was rated High not because it was the easiest attack to pull off, but because its blast radius spans every community on the platform rather than one.
-
Accepting a risk explicitly is a legitimate security decision, not a shortcut: Documenting why a risk is accepted (localStorage tokens, third-party SMS delivery, volunteer admin SLAs) is more valuable under real delivery constraints than chasing every theoretical risk to zero.
-
Threat modeling works best as a parallel process, not a downstream review: Running the threat model alongside the TRD, rather than after it was finalized, meant mitigations like RLS enforcement and role checks were built into the architecture from the start rather than changes made after.
Links to Artefacts
Watchly PRD:
Watchly TRD:
https://docs.google.com/document/d/1gdaQUIqAVu7OwxY8xE1dQ_KkCSNmA4xbvDTx6nW2CAA/edit?usp=sharing
Watchly Threat Model Document:
https://docs.google.com/document/d/19Zj8PnwC9gpZJxyeLOLxXOa7FfWtUvZAzJVGuBEF5M4/edit?usp=sharing
Watchly Security Review Document:
https://docs.google.com/document/d/17PgKc9jn86omfYYDR2zRrBNtW5hDfW38YDvbgDjEWWE/edit?usp=sharing
Conclusion
A false alarm is worse than no alarm at all; it teaches people to stop listening. That was the real test Watchly had to pass: not just "can this app be built," but "can it be trusted enough that residents actually act on what it tells them." This case study set out to break that trust before an attacker could, and then to prove the fix held.
Ten assets, ten attack surfaces, eleven findings: five High, four Medium, one Low came out of the threat model. Every High-severity risk targeted the same two guarantees Watchly is built on: that a reporter stays anonymous, and that only a real admin gets the final word. Each one was closed at the design layer, then confirmed under direct testing rather than taken on faith. Script injected into a report rendered as plain text. A crafted request for someone else's reporter identity came back empty. A Member who tried to act as an Admin by hitting the API directly was turned away, every time, regardless of what the interface allowed.
That's the detail worth remembering here: none of these controls depended on the UI behaving itself. They held even when the UI was skipped entirely which is the only version of "secure" that actually counts.
Watchly's pilot communities are inheriting a platform that was attacked on paper first, so it wouldn't have to be attacked for real later. The sentence "this is verified, trust it" isn't a hope anymore. It's a claim this review put to the test and one that held.