* add test suite, mostly covers integration testing, tests are only backend side
* workflow runs the correct script
* workflow runs the correct script
* workflow runs the correct script
* unit tests incoming
* Fix multer silent rejections and error handler info leak
- Revert cb(null, false) to cb(new Error(...)) in auth.ts, collab.ts,
and files.ts so invalid uploads return an error instead of silently
dropping the file
- Error handler in app.ts now always returns 500 / "Internal server
error" instead of forwarding err.message to the client
* Use statusCode consistently for multer errors and error handler
- Error handler in app.ts reads err.statusCode to forward the correct
HTTP status while keeping the response body generic
Previously, when the JWT secret was used as a fallback encryption key,
nothing was written to data/.encryption_key. This meant that rotating
the JWT secret via the admin panel would silently break decryption of
all stored secrets on the next restart.
Now, whatever key is resolved — env var, JWT secret fallback, or
auto-generated — is immediately persisted to data/.encryption_key.
On all subsequent starts, the file is read directly and the fallback
chain is skipped entirely, making JWT rotation permanently safe.
The env var path also writes to the file so the key survives container
restarts if the env var is later removed.
process.exit(1) when ENCRYPTION_KEY is unset was a breaking change for
existing installs — a plain git pull would prevent the server from
starting.
Replace with a three-step fallback:
1. ENCRYPTION_KEY env var (explicit, takes priority)
2. data/.jwt_secret (existing installs: encrypted data stays readable
after upgrade with zero manual intervention)
3. data/.encryption_key auto-generated on first start (fresh installs)
A warning is logged when falling back to the JWT secret so operators
are nudged toward setting ENCRYPTION_KEY explicitly.
Update README env table and Docker Compose comment to reflect that
ENCRYPTION_KEY is recommended but no longer required.
The startup error now tells operators exactly where to find the old key
value (./data/.jwt_secret) rather than just saying "your old JWT_SECRET".
docker-compose.yml and README updated to mark ENCRYPTION_KEY as required
and remove the stale "auto-generated" comments.
Auto-generating and persisting the key to data/.encryption_key co-locates
the key with the database, defeating encryption at rest if an attacker can
read the data directory. It also silently loses all encrypted secrets if the
data volume is recreated.
Replace the auto-generation fallback with a hard startup error that tells
operators exactly what to do:
- Upgraders from the JWT_SECRET-derived encryption era: set ENCRYPTION_KEY
to their old JWT_SECRET so existing ciphertext remains readable.
- Fresh installs: generate a key with `openssl rand -hex 32`.
Setting JWT_SECRET via environment variable was broken by design:
the admin panel rotation updates the in-memory binding and persists
the new value to data/.jwt_secret, but an env var would silently
override it on the next restart, reverting the rotation.
The server now always loads JWT_SECRET from data/.jwt_secret
(auto-generating it on first start), making the file the single
source of truth. Rotation is handled exclusively through the admin
panel.
- config.ts: drop process.env.JWT_SECRET fallback and
JWT_SECRET_IS_GENERATED export; always read from / write to
data/.jwt_secret
- index.ts: remove the now-obsolete JWT_SECRET startup warning
- .env.example, docker-compose.yml, README: remove JWT_SECRET entries
- Helm chart: remove JWT_SECRET from secretEnv, secret.yaml, and
deployment.yaml; rename generateJwtSecret → generateEncryptionKey
and update NOTES.txt and README accordingly
Introduces a dedicated ENCRYPTION_KEY for encrypting stored secrets
(API keys, MFA TOTP, SMTP password, OIDC client secret) so that
rotating the JWT signing secret no longer invalidates encrypted data,
and a compromised JWT_SECRET no longer exposes stored credentials.
- server/src/config.ts: add ENCRYPTION_KEY (auto-generated to
data/.encryption_key if not set, same pattern as JWT_SECRET);
switch JWT_SECRET to `export let` so updateJwtSecret() keeps the
CJS module binding live for all importers without restart
- apiKeyCrypto.ts, mfaCrypto.ts: derive encryption keys from
ENCRYPTION_KEY instead of JWT_SECRET
- admin POST /rotate-jwt-secret: generates a new 32-byte hex secret,
persists it to data/.jwt_secret, updates the live in-process binding
via updateJwtSecret(), and writes an audit log entry
- Admin panel (Settings → Danger Zone): "Rotate JWT Secret" button
with a confirmation modal warning that all sessions will be
invalidated; on success the acting admin is logged out immediately
- docker-compose.yml, .env.example, README, Helm chart (values.yaml,
secret.yaml, deployment.yaml, NOTES.txt, README): document
ENCRYPTION_KEY and its upgrade migration path
- Add { algorithms: ['HS256'] } to all jwt.verify() calls to prevent
algorithm confusion attacks (including the 'none' algorithm)
- Add { algorithm: 'HS256' } to all jwt.sign() calls for consistency
- Reduce OIDC token payload to only { id } (was leaking username, email, role)
- Validate OIDC redirect URI against APP_URL env var when configured
- Add startup warning when JWT_SECRET is auto-generated
https://claude.ai/code/session_01SoQKcF5Rz9Y8Nzo4PzkxY8