Safeguard
Application Security

The Invitation Flow Is an Access Grant Wearing a Growth Feature's Interface

Someone mistypes a colleague's address and a stranger is in that company's tenant, because the invitation worked exactly as designed. It is built early, for frictionlessness, by whoever shipped the collaboration feature.

Aman Khan
AppSec Engineer
6 min read

Someone types a colleague's email address into your invite box and gets one character wrong. A stranger now has access to that company's tenant, and nobody will notice, because the invitation worked exactly as designed.

Invitation is how people get into a B2B product, and it is usually built early, by whoever was shipping the collaboration feature, with the goal of making the flow frictionless. It is also an access grant, and the two goals pull in opposite directions.

This post is what goes wrong in that flow. For whoever owns multi-tenant onboarding.

The invite token

Same properties as any credential, and it is treated with less care because it is described as a link.

Unguessable and single use. A sequential or derivable invite identifier means an attacker can enumerate pending invitations and accept somebody else's.

Bound to the invited address. This is the one most often missed. If the token grants access to whoever presents it, forwarding the email forwards the access, and an invitation sitting in a compromised mailbox is a working credential. The acceptance flow should require the recipient to authenticate as, or register with, the address the invitation was issued to.

Expiring. Invitations that work indefinitely accumulate. An invite sent two years ago to someone who left that company still opens a door.

Revocable, and actually revoked. Removing a pending invitation from the admin list should invalidate the token, not just hide the row.

The typo problem

There is no technical fix for a mistyped address, and there are three things that limit the damage.

Show what is about to happen, clearly. A confirmation step that displays the address in full, not truncated, before sending. Most typos are caught here if the interface makes the address prominent rather than a small grey line.

Notify the tenant's administrators when an invitation is accepted, with the address. This is the control that catches it afterwards, and it is cheap.

Restrict by domain where the customer wants that. Many B2B tenants only ever invite colleagues. An optional setting that refuses invitations outside the organisation's domains prevents the whole class for those customers, and it is the kind of control enterprise buyers ask for anyway.

Domain-based auto-join is a bigger decision than it looks

The feature where anyone with an email at a verified domain can join a tenant without an invitation. It is excellent for growth and it carries three risks worth stating to whoever asks for it.

Domain verification must be real. DNS record or a file at a well-known path, verified at the time of the claim and re-verified periodically. If verification is a one-time check, a domain that later changes hands carries the claim with it.

Shared and recycled addresses. info@, support@ and role addresses may be readable by many people. Some organisations recycle personal addresses after someone leaves.

Default role matters enormously. Auto-join should place people in the lowest useful role. A configuration where anyone at the domain lands with write access to production data is a decision somebody should have made deliberately, and it is frequently the default because it was convenient during development.

Check the role granted at acceptance

The invitation says what role the person gets. Two failures:

The role is in the token or the request, and can be changed. If acceptance reads the role from a parameter rather than from the stored invitation, the recipient chooses their own permissions. Test this directly; it is a single request.

The inviter could grant more than they hold. A member invites an administrator. The check that the inviter is permitted to grant the role they selected has to exist at invitation time and at acceptance time, because the inviter's own role may have changed in between.

The offboarding half

Invitation is one end of a lifecycle nobody owns the other end of.

Pending invitations to people who have left. Accepted members who are inactive. Members from a partner organisation the customer stopped working with. Each one is an access grant with no expiry and no review, inside your customer's tenant, and it is their problem in the sense that it is their data, and yours in the sense that they will hold you responsible for the tooling.

Give tenant administrators the means to see it: a members list with last-active dates, pending invitations with ages, and an easy way to remove both. Most products have the list and not the dates, which is what makes the review impossible in practice.

Test the flow

# Can the invite be accepted by a different address than it was sent to?
# (register/log in as someone else, then present the token)

# Can the role be changed at acceptance?
curl -s -X POST https://api.example.com/invitations/accept \
  -H 'Content-Type: application/json' \
  -d '{"token":"'"$INVITE_TOKEN"'","role":"admin"}'
# the stored invitation's role must win

# Does revoking a pending invitation invalidate the token?
# Does an expired invitation actually fail?
# Are invite identifiers guessable? Request several and compare.

The first two are the ones that produce real findings.

The concession

Every control here adds friction to the flow a product relies on for adoption, and binding the invitation to the address will generate genuine support load: people who were invited at a work address and want to accept with a personal one, contractors, people whose email changed between invitation and acceptance.

So the defensible position is to bind by default and provide a supported path to change the address on a pending invitation, performed by the inviting tenant's administrator rather than by the recipient. That keeps the decision inside the organisation that owns the data, which is the property that matters, while leaving a route for the legitimate cases.

The implication

Invitation is an access grant wearing the interface of a growth feature, and the same team rarely owns both concerns.

Read your acceptance handler as though it were a permission grant endpoint. It is one, and the question is whether the person presenting the token is the person it was issued to, and whether the role they receive is the role the inviter chose.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.