Designing KeelHub's First Access Control System for 400 Users
Summary
KeelHub was KeelWorks' internal platform for managing employees and volunteers across 15 projects. It had two roles at signup: basic user and admin. Users chose their own. Anyone could select admin and walk into a directory containing home addresses, visa status, country of birth, and personal contact information for every person in the system. I caught it, escalated it, and led the design of the org's first RBAC system from zero.
- Open security vulnerability closed: self-selected admin access to sensitive employee data removed before the product reached a single real user
- First documented access control system: replacing fragmented spreadsheets and manual HR coordination with a designed, stakeholder-approved architecture
- Named-role architecture: supporting multiple simultaneous roles per user, granular permission toggles, and role assignment at the point of invitation
Problems
The original login flow gave users direct control over their own access level with no enforcement, no identity check, and no backend validation. Below that, the underlying model was too simple to represent the actual org.
- 01
The original system put two roles in front of users at signup and asked them to pick one. There was no invite gate. No identity check. No backend enforcement. A volunteer could select admin, complete signup, and access private records for every employee and volunteer in the organization. Home addresses, visa status, country of birth, personal contact details — all of it, immediately available.
- 02
This wasn't a known risk that had been deprioritized. The product was moving fast and access control hadn't been thought through yet.
- 03
Beyond the signup flaw, the underlying model was too blunt for an org of this complexity. A two-tier system can't represent a program manager who also does data analysis, or a volunteer with narrowly scoped responsibilities, or a foundation director who needs visibility without editing authority. Everyone got forced into a category that didn't quite fit. As KeelHub scaled, that mismatch would compound.
Access changes were also handled manually. Permission requests went through HR via email. There was no audit trail, no single source of truth, and no way to see who had access to what without asking someone directly.
Discovery & Approach
I started by working on the login flow. Once I understood what self-selected roles actually meant in terms of data exposure, I flagged it. The flag didn't move on its own. The product manager had 300 pages of documentation and revisiting the access model meant revisiting those docs. I brought it to an all-hands meeting and walked through the vulnerability directly. Showing the path was more effective than describing it. RBAC went on the roadmap.
When the project started, my early framing of the system used a tiered model: Level 1, Level 2, Level 3, each adding permissions incrementally. That framing stuck. It made it into the product documentation and the first round of designs, where numbered level badges appeared in the UI. The problem was that tiers can't model real jobs. Assigning someone Level 3 to cover both their product and data responsibilities means over-provisioning everyone else at that level. Assigning Level 1 means blocking them from things they genuinely need. The model looked clean and it was wrong. I pushed for named roles with granular, toggleable permissions grouped by entity. The argument was simple: RBAC is complex by nature. Building a simplified version first doesn't reduce that complexity, it defers it. When you need to fix it later, you're touching permissions, roles, user assignments, and engineering logic all at once. Better to build it correctly once.
Solution
The core decision was to replace access levels with named roles that could be composed of specific permissions, and to allow users to hold more than one role simultaneously.
The admin view is a table of all users, paginated for scale, filterable by status, access level, and role. Each row shows invitation status, job title, assigned access role, and a 1+ badge where multiple roles apply. That badge makes cross-functional assignments visible at a glance without requiring the admin to open each profile.
Clicking into a user opens a profile with two views. Admins and HR see the full record, including sensitive fields. Everyone else sees a limited view. The original vulnerability was anyone seeing everything. The fix was a structural separation built into the profile itself.
The permissions page for each role uses toggles grouped by entity: Users and Access. The HR role has Invite New Users, Edit Users, Approve Onboarding, Assign to Projects, and Deactivate Users turned on. Create Role, Edit Roles, and Delete Role are off. An HR admin can manage people. They cannot restructure the permission system itself. The UI makes that distinction legible without instruction.
The invite flow assigns both a job title and an access role at the point of invitation, before the account exists. This removes the gap where access might be incorrectly set or forgotten during onboarding.
Impacts
Open Security Vulnerability Closed
Before this work, any user could sign up, select admin, and access employee home addresses, visa status, country of birth, and personal contact information. The path existed and had no gate. The RBAC system replaced self-selected roles with admin-assigned access, removed sensitive fields from non-privileged profile views, and introduced a permission structure that controlled what each role could see and do — before a single real user touched the product.
First Documented Access Control System
KeelHub had no formal access control before this project. Permissions lived across spreadsheets managed by HR. Access changes went through email. There was no audit trail and no single source of truth. This project produced the org's first designed, documented, and stakeholder-approved system for managing access across 400 employees and volunteers and 15 projects. It reached C-suite sign-off from the CEO, CTO, and COO, and 60% engineering implementation before the org was shut down due to funding changes.
Named-Role Architecture
The tiered model would have forced every user into a numbered level regardless of their actual responsibilities. The named-role system supports multiple simultaneous roles per user, granular permission toggles grouped by entity, and role assignments at the point of invitation. The old design showed Level 1, Level 2, Level 3 with colored number badges. The new design shows HR, Product Manager, Admin, with a 1+ indicator for users holding more than one role. The UI changed because the underlying model changed.
Design Benchmark
KeelHub became the most fully designed product at KeelWorks. When the org began building a company-wide design system, other teams referenced this product as the standard. That wasn't directed. It happened because the work was complete and the system thinking was visible.