Security Policy
The Nojoin team and community take all security vulnerabilities seriously. Thank you for your efforts to improve the security of Nojoin. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
Active Development
Nojoin is still in active development and all releases should be considered pre-release. There may be security vulnerabilities in the application. Nojoin’s maintainers are not responsible for any data loss or security breaches that may occur as a result of using the application. We also advise users to take additional security measures in general but especially when deploying Nojoin over a publically accessible URL. For example, we recommend using a VPN or a reverse proxy to secure your Nojoin instance.
First-Run Bootstrap Protection
Nojoin requires an operator-defined FIRST_RUN_PASSWORD before the first successful system initialisation can occur.
- The bootstrap password is only used while the system is uninitialised.
- The web client sends the bootstrap secret via the standard
Authorizationheader using theBootstrapscheme rather than a bespoke setup header. - If
FIRST_RUN_PASSWORDis missing, first-run setup fails closed until the operator sets it and restarts or redeploys. - After initialisation, normal authenticated admin operations do not use the bootstrap password path.
- The bootstrap password is never returned by the API or persisted into Nojoin configuration.
- Application log output redacts
Authorization, cookies, bootstrap credentials, passwords, tokens, and API-key fields if they are accidentally included in a log record.
Operators should treat FIRST_RUN_PASSWORD as a secret and ensure reverse proxies, ingress layers, and HTTP logging do not record Authorization headers or setup request bodies.
Nojoin also warns operators when FIRST_RUN_PASSWORD, DATA_ENCRYPTION_KEY,
or the tracked Redis/PostgreSQL placeholder secrets still match the shipped
deployment-template values. Those warnings appear in API or worker startup logs
and in the authenticated frontend, but they are advisory only and do not block
startup or first-run setup.
Browser Session Request Protection
Nojoin’s normal browser session uses a Secure HttpOnly cookie, but state-changing browser requests are not trusted solely because that cookie is present.
- Cookie-authenticated browser requests using unsafe methods must come from the trusted Nojoin web origin.
- The backend validates the standard
Originheader, or falls back toRefererwhen needed, for those cookie-authenticated unsafe requests. - Explicit bearer-token API clients are not subject to that browser-origin check.
- The session cookie remains
SameSite=Laxto preserve expected top-level redirect flows such as OAuth callbacks, so unsafe GET-style side effects should continue to be avoided.
Standard JWT Containment
Standard browser session and explicit API JWTs (token types session and api) support active invalidation in addition to natural expiry.
- Every issued session/API JWT carries a unique
jti(token id), aniat(issued-at) timestamp, and atvclaim that mirrors the user’s currenttoken_version. - Verification rejects a token whose
tvno longer matches the user record. Bumpingtoken_versiontherefore acts as an immediate kill-switch for every previously issued session and API token belonging to that user. token_versionis bumped automatically when the user changes their own password, when an admin resets the password, and when an account is deactivated.- Admins and Owners may also call
POST /api/v1/users/{user_id}/sessions/revoke-allto forcibly invalidate every session for another user. Users themselves may callPOST /api/v1/users/me/sessions/revoke-allto sign out of all other devices. - Calling
POST /api/v1/login/logoutrecords the cookie token’sjtiin a server-side denylist (revoked_jwts), so the captured JWT stops verifying immediately even if a copy was made outside the browser cookie. - The denylist also supports targeted revocation of an individual
jti. Expired entries are pruned opportunistically. Recording capture uses the normal browser session token. There is no separate desktop-helper JWT path for live recording.
JWT Signing Key Rotation
JWT signing material is stored as a small keyring rather than a single static value.
- The keyring is persisted to
<user_data>/.secret_keys.jsonand contains anactivekey id (kid) plus any prior keys that are still trusted. - Every issued JWT carries a
kidheader. Verification picks the matching key from the keyring; if thekidis not known the token is rejected. - Operators with shell access can rotate the key by calling
backend.core.security.rotate_signing_key(). Rotation generates a freshkid, makes it active, and (by default) keeps the previous key in the ring so currently outstanding tokens keep verifying until their natural expiry. - After enough time has passed for outstanding tokens to expire,
prune_signing_keys()removes retired keys from the keyring. Any token still signed by a removed key fails verification immediately, providing a hard cut-over. - Setting the
SECRET_KEYenvironment variable overrides the keyring with a single static key (intended for advanced deployments and tests). In that mode the rotation API is disabled. - Existing single-key installs are migrated automatically: the legacy
<user_data>/.secret_keyfile is loaded into the keyring askid="legacy"on first startup and the legacy file is renamed.
Browser Capture Security
Live recording is initiated and controlled by the authenticated web app.
- Browser recording endpoints use the normal Secure HttpOnly session cookie and enforce recording ownership on every init, segment, pause, resume, discard, and finalize request.
- Unsafe cookie-authenticated requests still require the trusted Nojoin web origin through the origin checks described above.
- Browser capture permissions are mediated by the browser. Nojoin cannot silently capture screen, tab, system audio, or microphone input without the user granting permission.
- The browser share picker determines the visible surface and whether shared audio is included on desktop. Nojoin warns when the browser does not grant a shared-audio track and then records microphone audio only. Mobile Chrome capture is microphone-only and does not expose tab, app, or system audio to Nojoin.
- A paused recording blocks new capture starts for that user until it is resumed or discarded, preventing overlapping segment streams.
- Refreshing, closing, or navigating away from the Nojoin tab moves the recording to
PAUSED; uploaded segments remain server-side and only the current in-memory tail is dropped. - Switching focus to another tab, window, or application does not pause recording.
- WebM/Opus, Ogg/Opus, and MP4 audio browser segments are transcoded in worker tasks before final WAV concatenation and final processing.
- Retired native-helper endpoints return structured
410 Goneresponses and do not issue credentials or accept uploads.
For end-user capture setup and troubleshooting, see CAPTURE.md.
Supported Versions
As Nojoin is in active development, only the latest version is supported. We encourage all users to use the most up-to-date version of the application.
| Version | Supported |
|---|---|
| latest | :white_check_mark: |
Reporting a Vulnerability
Please report any security vulnerabilities privately. Do not open public GitHub issues or discuss vulnerabilities in public forums before they have been resolved.
Private Reporting Channel
We use GitHub’s Private Vulnerability Reporting feature to receive security disclosures securely and privately.
- Submit a Report: Visit GitHub Private Vulnerability Report to submit your report.
- Provide Details: Please include a detailed description of the vulnerability, steps to reproduce, and any proof of concept (PoC) or exploit code.
Expected Workflow
- Acknowledgment: You will receive an initial response confirming receipt of your report within 48 hours.
- Evaluation: The maintainers will investigate the report and determine the severity and scope of the vulnerability.
- Remediation: If the vulnerability is confirmed, we will work on a patch. A security advisory will be drafted, and a fix will be released.
- Disclosure: Once a patch is available and deployed, we will coordinate public disclosure through a GitHub Security Advisory.