Reported against 2.2.1 by someone reading the source. Every finding held.
**Sign-in had no ceiling.** Failures were logged with the caller's address
and nothing more. Two counters now — one per address, one per account —
because the two attacks look different: one source working through many
accounts is caught by the first, many sources working on one account by the
second, and behind a proxy only the second still means anything.
The count is kept in the process that serves the request. PLANKA needs no
Redis and the stock deployment is one container; run several and each keeps
its own count, which multiplies the ceiling by their number. That trade is
written where the limits are configured.
**The second factor could be guessed at leisure.** Six digits, and a
pending token that stayed valid for its full ten minutes however many codes
were wrong. Wrong codes are now counted on the session row — in the
database, so the count survives a restart and holds across every process —
and when the budget is spent the session is destroyed. After that even the
right code is refused and the login starts over from the password.
**Avatars, background images and favicons** checked the token's signature
and nothing else, so a revoked session, a deactivated account or a changed
password all kept working there for as long as the signature lasted, which
is a year by default. The five checks the API makes now live in one helper
that both use, rather than the shortened copy that had drifted from it.
**A link attachment's favicon** was fetched from wherever the URL pointed.
Storing a link is harmless — it is a string the user typed — but fetching
its icon is a request the server makes to an address the user chose, and
whether an icon came back reported on what is reachable from inside the
network. Server-side fetches now refuse private, loopback and link-local
addresses, `169.254.169.254` among them. The attachment is still created:
linking to an internal wiki is a legitimate thing to do, and it was the
server's own request that had to stop.
**The signing key.** Our own compose file ships `notsecretkey`, and it is
printed in the documentation — so on any instance that copied it, anyone can
sign a token for any account. PLANKA now says so on every start, and keeps
saying it, along with a key that is missing or shorter than 32 characters.
The placeholder carries the warning inline, where it is copied from.
**The backup script** wrote password hashes, live sessions, TOTP secrets and
SMTP credentials to an unencrypted archive. `BACKUP_PASSPHRASE` now encrypts
it, and without one the script says what it just put on disk. It also says
what it is — an example for the stock compose stack, not a backup concept —
and names the window between the database dump and the file copy, which no
ordering closes.
Adds three new environment variables that give instance operators control
over Apprise notification services, addressing GDPR and compliance concerns
around third-party data transfers.
## New environment variables
- APPRISE_ENABLED (default: true)
Set to "false" to globally prevent users from creating Apprise notification
services and to silently skip sending any Apprise notifications. Existing
services in the database are preserved but not used while disabled.
- APPRISE_ALLOWED_SCHEMAS (default: empty — no restriction)
Comma-separated allowlist of Apprise URL schemas (e.g. "slack,tgram").
When set, only services whose URL schema appears in this list can be
created or will be sent. Takes priority over APPRISE_BLOCKED_SCHEMAS.
Uses Apprise's internal schema names (e.g. "tgram" for Telegram).
- APPRISE_BLOCKED_SCHEMAS (default: empty — built-in list applies)
Comma-separated blocklist of Apprise URL schemas (e.g. "discord,slack").
When set, replaces the built-in blocked list entirely. When empty, the
built-in list (syslog, dbus, kde, qt, glib, gnome, macosx, windows)
continues to apply as a fallback.
## Enforcement
Validation happens at two layers:
1. API layer (create endpoints for user and board notification services)
Returns HTTP 403 when Apprise is disabled, HTTP 422 when the URL schema
is not permitted. This gives users immediate feedback in the UI.
2. Python send layer (send_notifications.py)
The schema config is passed as a JSON argument so the same allow/block
rules apply at send time, guarding against services that were created
before the config was tightened.
## Files changed
- server/config/custom.js — parse new env vars via existing envToArray helper
- server/utils/send_notifications.py — accept schemaConfig as argv[4]; replace
hard-coded BLOCKED_SCHEMAS_SET with dynamic allowed/blocked resolution
- server/api/helpers/utils/send-notifications.js — short-circuit when disabled;
pass schemaConfig JSON to the Python script
- server/api/helpers/notification-services/create-one-in-{user,board}.js — add
appriseDisabled and schemaNotAllowed exits with schema validation logic
- server/api/controllers/notification-services/create-in-{user,board}.js — wire
new exits to forbidden (403) and unprocessableEntity (422) responses
- server/.env.sample — document all three new variables with comments
* feat: Make logfile location customizable
It may be desirable to log to a more standard location (e.g. in /var/log/),
or in some cases to turn logging to file off. To support these, use a
custom config property to determine the location of the output log file,
and default to the previous location if it is unset.
* feat: Support alternate storage locations for uploaded files
This involves a couple primary changes:
1) to make Sails' temporary file-upload directory a configurable location
by using a common file-upload-receiving helper;
2) to create custom static routes for the file-upload locations, so they
can be outside the application's public directory; and
3) to use the file-uploading handler everywhere that receives files, so
config for the helper is applied to all file uploads consistently.
This is sufficient to allow the application directory to be deployed read-
only, with writable storage used for file uploads. The new config property
for Sails' temporary upload directory, combined with the existing settings
for user-avatar and background-image locations are sufficient to handle
uploads; the new custom routes handle serving those files from external
locations.
The default behavior of the application should be unchanged, with files
uploaded to, and served from, the public directory if the relevant
config properties aren't set to other values.