Skip to content

What a passcode protects

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.

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.

  • 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.

Do not use a public Issue or Discussion. Use GitHub private vulnerability reporting (enabled on this repository) or email security@letsparley.io.