The Invite Link That Shared a Hash — JavaScript Bug Hunt
Modelled on Slack, August 2022: Slack disclosed that when a user created or revoked a shareable invitation link for a workspace, a bug sent that user's…
- Language: JavaScript
- Layer: Backend
- Difficulty: Easy
- Concepts: Security, Privacy
- Modelled on: Slack · 2022
- Visible tests: creating a link stores and returns it; the creation event carries only the public profile
- Reward: 50 XP for a complete fix
Briefing
Modelled on Slack, August 2022: Slack disclosed that when a user created or revoked a shareable invitation link for a workspace, a bug sent that user's hashed password to the clients of other members of the workspace. The hashes were salted and not visible in the app, but Slack reset the passwords of the affected users.
This reconstruction broadcasts an event to the workspace whenever a link is created or revoked — and puts the whole stored user record in it.
Fix invites.js so events only ever carry the public profile.
Bug report
BUG-INVITE-HASH · Priority: Critical · Reported by: security
createInviteLink(workspace, user, code, bus) and revokeInviteLink(workspace, user, code, bus) broadcast an event on the workspace's channel. The event's actor must be the PUBLIC profile only: { id, name, avatar } — exactly users.PUBLIC_FIELDS, in that order. No other field of the stored record (passwordHash, email, …) may appear in any event.
- create: stores { code, workspace: workspace.id, active: true } in workspace.links, broadcasts { type: "invite_link_created", code, actor }, returns the link
- revoke: marks the link inactive, broadcasts { type: "invite_link_revoked", code, actor } and returns true; an unknown code returns false and broadcasts nothing
Observed: every member's client receives the actor's passwordHash and email.
Logs
[rt] T1 <- invite_link_created actor={"id":"U1","name":"Kai","email":"kai@example.com","passwordHash":"hash:9f2c…","avatar":"k.png"}The code as shipped
src/workspace/invites.js (editable)
exports.createInviteLink = function (workspace, user, code, bus) {
var link = { code: code, workspace: workspace.id, active: true };
workspace.links.push(link);
bus.broadcast(workspace.id, { type: "invite_link_created", code: code, actor: user });
return link;
};
exports.revokeInviteLink = function (workspace, user, code, bus) {
for (var i = 0; i < workspace.links.length; i++) {
var link = workspace.links[i];
if (link.code === code && link.active) {
link.active = false;
bus.broadcast(workspace.id, { type: "invite_link_revoked", code: code, actor: user });
return true;
}
}
return false;
};
Read-only context: src/workspace/bus.js, src/workspace/users.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.