Project background uploads (and migrated backgrounds) are decoded through
imageutils.Decode, which also covers the blur hash computed from the
decoded image. A busy server answers 503. Drops the unused CreateBlurHash,
which decoded without any bound.
Uploaded avatars are decoded through imageutils.Decode on upload and when
resizing per size, so a busy server answers 503 instead of decoding in
parallel. The old avatar file is removed only after the new one decoded,
since a storage delete is not rolled back with the session.
Colliding non-CRUD routes got a method suffix in registration order, per
version, so one key granted different actions on v1 and v2: a read-only
projects.background scope deleted backgrounds through v2, and bucket
create/update keys swapped meaning (GHSA-j5v7-qcwp-5g32). Keys now derive
from the full v1+v2 route set and carry the action (read/create/update/
delete) once a subkey has more than one. Marking all notifications as
read gets its own key on both versions instead of overwriting update.
A cross-site <form> needs no CORS preflight, and v2 decoded form bodies
API-wide, so a form POST to /login or the OIDC callback minted a session
in the victim's browser (GHSA-frf4-3prf-3vch). Form decoding is now
scoped to /oauth/token, and both API groups answer 415 elsewhere.
Address review comment: v1 fell back to the original when a preview was asked for. An empty or invalid preview_size now fails with 422 (4039), an attachment without a preview with 422 (4038).
Address review comment: previews moved to their own route, so the download route no longer takes preview_size.
BREAKING CHANGE: the v2 attachment download route ignores preview_size. Request previews from GET /tasks/{task}/attachments/{attachment}/preview?size= instead.
Address review comment: GET /tasks/{task}/attachments/{attachment}/preview?size= never returns the original. A missing or invalid size, or an attachment with no preview, fails with 422.
Address review comment: v2 answered a preview request with the original when no preview could be made. It now fails with 422 (code 4038) and the client decides whether to fetch the original. v1 keeps the fallback.
A PNG preview of a photo is several times the size of a JPEG. Previews
with any transparency stay PNG, since JPEG has no alpha channel. The
format is decided in encodePreview alone; the Content-Type of a
response follows from the preview's bytes.
Each submitted property was echoed back with its full namespace URI, so a
namespace declared once on the root and reused by many short elements
amplified a ~22MB body into terabytes of buffered response.
Cap the PROPPATCH body at 64KB, the property count at 100 and the
namespace length at 256 bytes, and dedupe repeated properties.
A bulk update with no fields fell through to the single-task path and
replaced every task wholesale with values, clearing description, dates,
priority and more on all of them. Reject it with a new
ErrBulkTaskFieldsRequired (4036) and document the accepted bulk fields.
Fixes#4109
The length-only check let through 7 character values without a leading #
and multi-byte strings, which do not fit the varchar(6) column after
normalizing. Tasks, labels and projects now only accept an optional #
followed by 3 or 6 hex digits.
Create, bulk create and duplicate responses now carry the task's stored
position in every view, so clients can place the new card without
refetching the board.
The socket only checked the token's signature and expiry. A token of a
deleted session or of a disabled user still authenticated, and an open
socket kept receiving notifications after logout, session revocation,
password change or account deletion.
Auth now requires the token's session to exist for that user and the
user to be active; the connection registers first and is checked once,
so a concurrent revocation can't slip in between. Transient errors close
with try-again-later instead of invalid_token. Each socket closes when
its token expires, which bounds exposure even on nodes that miss a
revocation event.
Deleting sessions dispatches SessionsRevokedEvent on commit, every caller
flushes it after committing, and the hub closes the matching sockets.
Disabling or locking a user and deleting a user revoke their sessions,
also from the CLI.
Webhooks kept delivering task and project data to their creator after the
creator was removed from the project.
When a user can no longer read a project, or any project under it, their
webhooks there are deleted together with their task assignments and
subscriptions. This runs in the request's transaction for every explicit
removal: unsharing a user or team, removing a team member (including
external team sync, limited to teams the user was in), deleting a team
and reassigning a project's owner. Removing a user who is not a team
member is a no-op. It replaces the async listener that only cleaned up
after team member removal. Deleting a user deletes all webhooks they
created. Access is resolved in one batch per user and task data is
deleted via subquery, so large projects stay within bind limits.
As a backstop for access lost indirectly, e.g. by moving a project to
another parent, webhook delivery skips webhooks whose creator is not
active or can no longer read the event's project. A downgrade to read
keeps the webhook. Link shares are left alone by design.
Any user pointing their default at a foreign project blocked its owner from archiving or deleting it. Deleting a project now clears every user's default that points at it, not only the owner's.
default_project_id accepted any id. Pointing it at a foreign project blocked the owner from archiving or deleting that project. It must now be 0 or a writable, non-archived project of the user; an unchanged value is not re-checked so a default the user lost access to doesn't block saving other settings.
The delete permission check read the share's tier from the URL-bound
struct, which is always the zero value (read), so a write member could
delete any link share, including admin ones.
Creating, changing or deleting link shares now requires project admin,
whatever the share's tier. Listing and reading by ID already did. Shares
created by write members stay valid. Deleting a share that does not exist
in the project in the path now returns 404.
Changelog: Creating, changing or deleting link shares now requires project admin.
Every 429 from the rate limiters now carries Retry-After so clients know
when the window ends. The basic-auth limiter keys on aligned buckets, so
its value comes from the bucket end. Retry-After isn't CORS-safelisted,
so the CORS config exposes it for the desktop app and frontends on
another origin.
- Owners can manage bots in any status (disabled or locked); unowned,
missing and non-bot ids all return 404 so existence never leaks.
- PUT /api/v2/user/bots/{bot} requires status, so a partial body can no
longer re-enable a disabled bot. A bot_status validator tag limits it
to active/disabled.
- BotUser.Status is a pointer: the v1 update route keeps the current
status when the body omits it instead of unlocking the bot.
- Owner status changes dispatch bot.status.changed for the audit log.
- Regenerate the frontend client and adapt the bot update callers.
Refs #3999