What a passcode protects
No accounts, by default
Section titled “No accounts, by default”In open mode a space is guarded by a shared passcode, not by identity.
Anyone holding the code can join, see the roster, vote, write their own standup
entry, and change the passcode. Story mutations and meeting controls are
facilitator-only. Joins are broadcast to the room, so lurking is visible.
Open mode is trusted-network-only. For public reachability, keep spaces passcode-protected or require an external SSO/authentication proxy before Parley, and add request and connection abuse controls at the ingress. Built-in OIDC identifies participants but does not by itself restrict who reaches the instance or which space they may join. The reverse proxy guide shows where those controls sit.
Passcodes
Section titled “Passcodes”Six characters from a 25-character alphabet, and wrong guesses are throttled per client address.
They are stored readable in the database on purpose. A passcode is meant to be read off the space page by any member and passed on, the way a Meet or Zoom code is, so hashing it would only mean nobody could ever see it again.
Copy invite builds a link carrying the passcode in the URL fragment
(/o/acme/s/team#c=V4KW7J), so one click both opens the space and presents the code.
A fragment is never sent to the server and never appears in a Referer header,
which keeps it out of access logs, proxy history, and the analytics of any site
clicked through to afterwards; the app wipes it from the address bar on arrival
so it does not persist in history or a bookmark. None of that makes the link
less sensitive than the passcode it contains — anywhere you would not paste the
code, do not paste the link.
Under an identity provider there is one extra hop: taking a seat from an invite
link is a full-page trip to the provider and back, and a URL fragment does not
survive that. So the passcode is held in sessionStorage for the trip — written
only in that mode, scoped to the one space, spent on the first read, expired
after five minutes, and gone when the tab closes. Open-mode instances never
write it anywhere.
CodeQL flags that write as js/clear-text-storage-of-sensitive-data, and the
alert is dismissed deliberately rather than silently. A space passcode is a
shared door code: it is printed on the space page for every member to read and
is meant to be pasted into a chat. It is not a per-person credential, and any
same-origin script able to read it out of storage could equally read it off the
page. The finding is real about the mechanism and immaterial about the asset —
which is the same reason the passcode is stored readable in the database.
Treat a database dump as disclosing the passcodes of every space — but not any member’s identity. Session cookies are opaque random tokens stored hashed, so a backup contains no credentials that impersonate a person.
Any member can mint a new code, retiring the old one, or open the space entirely.
What the server does enforce
Section titled “What the server does enforce”- Acting in a space requires having joined it.
- Promoting, demoting, and removing a member are owner-only. A space can have several owners and can never be left without at least one. See Authorization and Organizations for org-admin custody.
- A protected space refuses joins without the code.
- A session’s existence is never disclosed to non-members — the reply to a stranger is the same 404 either way.
- Facilitator-only actions — including story creation, editing, reordering, selection and deletion, plus reveal, reset, estimate saving and session progression — are checked server-side, not hidden in the UI. Ordinary members may still vote, edit their own standup entry, and set their own standup readiness signal.
- Active WebSockets revalidate their shared-store token at least every 30
seconds; revocation or expiry closes them with policy code
1008. Logout is immediate rather than waiting for that interval — the replica serving it disconnects synchronously and publishes the revocation to every other replica, which do the same on receipt. The periodic check is the fallback. - CSV cells beginning with
=are escaped, so an export cannot run formulas in a spreadsheet.
Reporting something
Section titled “Reporting something”Do not use a public Issue or Discussion. Use GitHub private vulnerability reporting (enabled on this repository) or email security@letsparley.io.