Reuse the board-scoped integration lookup before deleting, after board-admin
authorization. Missing, wrong-board and already deleted integrations now return
the existing JSON 404 response instead of acknowledging an unmatched selector.
Extend route regressions for deletion, authorization and database errors. Add
live API and browser coverage for rejected and successful deletions, persisted
state and retention of other webhooks in settings.
The daily scan keeps one baseline per stored file and reports every baseline
whose file is gone as a StorageBleed. Nothing ever removed a baseline, so
every attachment purged by retention and every avatar deleted was reported
missing on every run from then on - one finding folded 1,164 times.
The baseline is now forgotten where WeKan deletes or moves a local file:
- Attachments and Avatars onAfterRemove, for every version path. This is
the retention purge's path: Meteor-Files unlinks the version paths itself
after that hook, without going through the store strategy, so a fix in
the strategy's unlink() alone would not have covered it;
- the filesystem strategy's unlink() (moving a file to GridFS or cloud);
- the filesystem strategy's rename(), for the old path.
A file that vanishes any other way keeps its baseline and is still reported.
The removal is fire-and-forget and can never break a delete.
Baselines already orphaned before this fix stay reported until removed, as
described in the issue.
Thanks to Raymond-Voogt and xet7 !
Fixes#6898.
The recurring-checklist reset cron queries
resetInterval: { $exists: true, $nin: [null, 'none'] } on every tick, and with
no index that read the whole checklists collection (about 45,000 documents on
the reporting instance), reported as db.collection-scan on Admin Panel ->
Problems. A partial index on resetInterval, like the one #6825 added for
recurring cards, holds only the recurring checklists.
Thanks to Raymond-Voogt and xet7 !
Fixes#6899.
PUT /api/users/:userId with action takeOwnership called the async
board.changeOwnership() without await: the reply was sent before the board
was changed, and a failed update was an unhandled rejection behind a 200.
Boards.removeMember tested `!ReactiveCache.getUser(memberId)`. On the server
that lookup is async and returns a Promise, which is always truthy, so the
#5330 branch that hard-removes a member whose account was deleted never ran
on the server; such a member was only deactivated and lingered.
Tests pin both, and fail the tree for any un-awaited changeOwnership() or
negated un-awaited ReactiveCache lookup in server/ and models/.
Thanks to xet7 !
Save supported custom-field properties in one board-scoped update so schema validation rejects the entire change before any accompanying fields persist. Preserve the existing authorization, field allowlist and response behavior.
Add handler regressions and live database/browser coverage for rejected settings and types, accepted updates, false values, omitted fields and board scoping.
Match the target user and administrator flag within the same board member, following the existing membership query pattern. Leave boards where the target is only a normal member unchanged.
Cover selection, successful transfers, unchanged unrelated memberships, repeated requests, authorization, and retained browser access.
In the v13.09 release the Snap Store failed snap-auth-check's first two
install attempts and then let the third hang with no output, until the job's
10-minute timeout cancelled it - the retry added for v13.07 never got its
remaining turns. Each `snap install snapcraft` in release-all.yml now runs
under `timeout 180`, and an attempt cut short is aborted (`snap abort
--last=install`) so the next one does not meet snapd's change still in
progress. snap-auth-check's job timeout is 25 minutes, enough for five
attempts 30 seconds apart and the whoami after them.
tests/snapAuthPreflight.test.cjs requires every install in the workflows to
be bounded and followed by the abort, and fails when snap-auth-check's job
timeout cannot hold all five attempts.
Thanks to xet7 !
The v13.08 release stopped in snap-auth-check before it asked the Snap Store
anything: "cannot install system snap snapd: Post
https://api.snapcraft.io/v2/snaps/refresh: ... Client.Timeout exceeded while
awaiting headers". The job installed snapcraft once, while the snap build
job already retried five times, 30 seconds apart. It retries the same way now,
and when every attempt fails it says the store did not answer - not that
SNAP_AUTH is wrong.
tests/snapAuthPreflight.test.cjs fails on any snapcraft install in any
workflow without the five-attempt retry, and on the outage being reported as
a credential problem.
Thanks to xet7 !
"dotted checkbox writes emit false once and removing a valued field emits an
unset" read the card's activities with an unsorted find and expected them in
insertion order. On FerretDB with the pushdown of plain equality, that find is
served by activities' { cardId: 1, createdAt: -1 } index and returns the newest
first, so it failed every time against Weddy, which creates WeKan's indexes,
with ['unsetCustomField', 'setCustomField']. The order of an unsorted find is
no contract on MongoDB either once an index serves it; what the test pins is
one set and one unset, so it now compares the activity types sorted.
Thanks to xet7 !
Code scanning alert #604 (py/clear-text-storage-sensitive-data, CWE-312):
download_github_security.py wrote a secret scanning alert's
secret_type_display_name into SUMMARY.md. That is the secret's type, not the
secret, but SUMMARY.md is the one file that is never redacted and the one most
likely to be pasted somewhere, so it now says only "Secret scanning alert" and
points at secret-scanning/, where the type still is. When
--include-secret-values keeps the secrets, the files holding them are created
readable by their owner only.
tests/securityDownloadScript.test.cjs checks the summary of a redacted and of a
kept download for the secret, its type and its type name, the 0600 mode of
kept secrets, and that describe() reads no field of a secret scanning alert
and every secret scanning write passes the private flag.
Thanks to GitHub CodeQL and xet7 !
The v13.07 single files for armhf, armv6 and armv7 all stopped on "The
bundle's Node.js is v26.13.0 but this is v26.13.1: a single executable must
be built by the same Node.js version it embeds." A 32-bit target's SEA blob is
written by a 32-bit Node.js, and the workflow borrowed the i386 bundle's.
Every bundle takes the newest Node.js of the pinned major its platform has, so
i386 had v26.13.1 from wekan/node-patches while 32-bit ARM still had v26.13.0.
The host is now resolved with releases/resolve-node-source.sh at the target
bundle's exact version, checked against its published SHA256, and refused
unless it reports that version. libatomic1:i386, which the bundle used to
supply in node-runtime/, comes from apt, so HOST_LIBS is gone.
tests/singleFile.test.cjs pins the exact version, the checksum before the
first run, and that the host is never taken from another platform's bundle.
Thanks to xet7 !
Read parentId from the request body so single-card creation validates and persists the requested parent, matching the bulk endpoint. Cover persistence, subtask rendering, empty parents and rejected creation requests.
The problem streams were append-only and Acknowledge only reset the "new"
count, so a problem fixed long ago stayed in the table and buried the live
ones. Clear, beside Acknowledge, calls the admin-only clearEventLog, which
deletes the checked streams' events up to now and stamps the
acknowledgement; anything that happens after the click shows as new again,
and an unknown stream clears nothing. A daily sweep, claimed once per
installation, deletes events older than EVENTLOG_RETENTION_DAYS (default 90,
0 keeps them forever); a recurring problem's folded row keeps a recent date,
so it stays. The setting is listed on every platform, and clear-problems is
translated to every language.
Thanks to Raymond-Voogt and xet7 !
Fixes#6893,
A board admin can refuse a role write access in one list (#4527,
roleExceptions). The attachment API's upload, delete, copy and move, over
REST and DDP, asked only for board write access and mayWriteCardAttachment,
which allows every card to a member who is not assigned-only, so a normal
member the list refused still added and removed files on that list's cards.
Every attachment write that names a card now also asks mayWriteInCardList
with that card, and a move asks both the card it takes the file from and the
card it puts it on. The card's own board decides. As for the REST card
routes, only a list's refusal counts. A refusal is recorded in Admin Panel ->
Problems as ListLockAttachBleed (authz.list-locked-attachment, medium).
tests/listLockAttachments.test.cjs runs the guard on the reported case and
the negative cases, and fails on any mayWriteCardAttachment call in the tree
without the list check beside it; list-lock-attachments.e2e.js replays the
writes against a running server.
Thanks to Furkan Derse and xet7 !
On a large installation the database-problems page filled with collection
scans of queries WeKan makes constantly but never indexed: a user looked
up by services.ldap.id at every LDAP login and sync pass (13,000+ users,
~430 ms per login), cards by assignees or members across every board for
My Cards and the filters (1.1M cards, 1-3.7 s, dozens of times a minute),
and changeHistory by entity and by integrity hash (120,000+ rows, and
growing). Each now has an index, created at startup with ensureIndex like
every other one: services.ldap.id sparse, assignees and members both so
their $or uses an index on each branch, and changeHistory's
{ entityType, entityId, createdAt } and { integrityHash }.
Thanks to Raymond-Voogt and xet7 !
Fixes#6892.
A card's Spent time is stored in decimal hours, and a Pomodoro or Flowtime
session adds its duration to it, so a short session left a badge such as
0.034722222222222224. The badge shows days, hours, minutes and seconds now,
the leading zero units left out (12h34m56s, 2m5s), and the tooltip that
names hours rounds them to two decimals. The stored value and the edit
popup keep their hours. models/lib/spentTimeFormat.js, with a unit test
and a UI test.
Thanks to brlin-tw and xet7 !
Fixes#6890.
Wait for Accounts.destroyToken to finish so deletion completes before a success response and rejected database writes reach the existing error handler.
Add regression coverage for delayed and failed deletion, authorization, existing token input behavior, session isolation and browser logout.
releases/sec/download-github-security.sh ORGANIZATION saves, for every
repository of the organization (or the ones named with --repo), its
Dependabot, code scanning, secret scanning and security advisory alerts
to .tools/log/sec/<organization>/<repository>/<YYYY-MM-DD_HH-MM-SS>/,
a directory per kind: alerts.json, a file per alert, an open code
scanning alert's instances, and SUMMARY.md with every open alert and its
link. It runs the GitHub CLI outside the editor's sandbox, where gh and
its login are, so the alerts can then be read and fixed from the files
inside it. A kind GitHub does not answer - not enabled, or not visible
to the account - gets status.txt with the reason and the run goes on;
archived repositories and forks are skipped unless asked for; the secret
a secret scanning alert found is redacted unless --include-secret-values
is given. A name that is not a GitHub name is refused before it becomes
a path or an API address. .tools/ keeps the downloads out of git.
tests/securityDownloadScript.test.cjs runs it against a stand-in gh: the
layout, both pages of a paginated answer, the summary, a disabled kind,
the redaction everywhere in the download and, as negatives, bad names,
no gh and gh signed out writing nothing. buildScriptParity knows why
build.sh has no menu entry for it.
Thanks to xet7 !
fast-xml-parser <= 5.11.2, with its stopNodes option, rescanned the rest
of the input from every "<" of a stop node's body when no ">" followed,
so n such fragments cost O(n^2) CPU before it gave up (CWE-407). The
Azure and Google Cloud storage clients bring it in; each allows 5.x, so
the lock moves to 5.12.0 - which stops at the first failed scan - with
is-unsafe 2.0.3 and strnum 2.5.1, and no override.
tests/dependencySecurityUpdates.test.cjs pins that no tracked lockfile
resolves a version below 5.12.0, with a negative that the finder flags
5.11.2 at any depth, and runs the alert's input: 40,000 unterminated
fragments in a stop node are refused within two seconds. On 5.11.2 that
input grows quadratically (20,000 fragments took 1.2 s here, so 40,000
take about 5 s); on 5.12.0 it takes about a millisecond. A dependency
inside a storage client is not an attempt anybody makes, so there is
nothing to record in Admin Panel -> Problems.
Thanks to dependabot and xet7 !
The fix for #601 passed the paths to `bash -c` as arguments ("$1",
"$2") instead of pasting them into the script. That is safe in itself,
but it still hands a temporary or checkout path to a shell, and CodeQL
flagged js/shell-command-injection-from-environment again on the two
umask tests in tests/releaseDownloads.test.cjs. Those tests no longer
use a shell: fetch.sh runs as bash's program with its arguments, the
umask is set on the test's own process around the call and put back,
and `chmod +x` is fs.chmodSync. tests/macosSandbox.test.cjs, fixed the
same way for #601, now runs ls and touch as programs of sandbox-exec,
each path an argument.
The guard (tests/testShellPathsFromEnv.test.cjs) now also refuses any
value given to a shell's -c script as an argument - spawned or as a
wrapper's program - and the one other test that did so,
tests/dbConformanceWiring.test.cjs, passes its value in the environment.
Run against the files as they were, it names all three; its pattern
takes a backslash only as an escape, so a long file cannot make it
backtrack.
The findings were in tests, not in WeKan, so there is no attempt to
show in Admin Panel -> Problems.
Thanks to GitHub CodeQL and xet7 !
CodeQL flagged js/shell-command-injection-from-environment (CWE-78/88) in
tests/npmRetry.test.cjs: its bash -c script set
node_bin=${JSON.stringify(prog)} and ran bash ${JSON.stringify(script)},
pasting a path under the temporary directory and one under the checkout
into the script text. JSON's quotes are not the shell's: a path with $(,
a backtick or $ would change the command. The paths now go in through
the environment ("$NODE_BIN", "$NPM_RETRY_SCRIPT"), so the script is a
constant - and NODE_BIN is not node_bin, so the test still shows the
unbound variable it exists to show.
The guard written for alerts #549 and #550 (tests/testShellPathsFromEnv
.test.cjs) only knew `source "${path}"`. It now refuses any value quoted
into a -c script - "${...}" or ${JSON.stringify(...)} - across the tests,
while a script built from the test's own snippets stays allowed; with
the fixes taken away it names every offender. It found the same fault in
two more places, fixed as arguments ("$1", "$2"):
tests/releaseDownloads.test.cjs, which quoted fetch.sh and a temporary
file into its umask tests, and tests/macosSandbox.test.cjs, which quoted
the home and temporary paths into its sandbox probe.
The finding was in tests, not in WeKan, so there is no attempt to show
in Admin Panel -> Problems.
Thanks to GitHub CodeQL and xet7 !
#6889 guarded the removal halves of the cardMembers and cardAssignees
hooks, which read user.username after a lookup that finds nobody for a
deleted account. The joining halves had the same read: the REST route
checks the user before adding, but a client $addToSet, a copied or
imported card, or a rule can still add such an id, and the hook threw.
Both now record the join with the id and no username, as a removal does.
tests/cardMemberRemoval.test.cjs runs the real hooks: adding a member
and an assignee whose account is gone records the join without
throwing; and, tree-wide, no `const x = user.field;` follows a
ReactiveCache.getUser() lookup without a guard.
Thanks to AhmedLukman and xet7 !
#6888 makes the REST card PUT write its isOverTime parameter to the
schema's isOvertime field. Every overtime set through the API before it
- since b895e61237 - was stored under isOverTime, which the UI and every
reader ignore: those cards still show no overtime.
A schema upgrade step, card-overtime-field, carries that over on the
next start: isOverTime becomes isOvertime where the card has none, the
value the UI already shows wins where it has both, and the stray field
is removed. Two updateMany batches and an $unset, version-gated and
idempotent like the steps beside it.
tests/schemaUpgradeSteps.test.cjs pins the fold, the precedence and a
second pass finding nothing; as negatives, no work on cards without the
field, and - tree-wide - no server code writing isOverTime into a card.
Thanks to AhmedLukman and xet7 !
The Cards.before.update timing hook read modifier.$set[field] for any
update whose first field was a date, but unsetDue/unsetStart/unsetEnd/
unsetReceived send only a $unset. Without a $set the hook threw a
TypeError; with one beside the $unset (a stamped modifiedAt) it wrote an
a-dueAt with timeValue: undefined, which counted as a due date change
(#6081), matched the "date set or changed" rule triggers (#2474), and
showed in the activity feed as an empty date.
server/lib/timingChangeActivity.js now decides it: a date set records
timeValue, a date cleared records timeOldValue alone - the same record a
rule's removeDate writes, and the one models/lib/boardTimeline.js rewinds
- and clearing a date the card does not have records nothing. The rules
skip a cleared date before matching triggers, as server/triggersDef.js
already said they should, and the feed shows only the date it had.
tests/cardTimingActivity.test.cjs runs the real hook: a set, a $unset
alone and a $unset beside a $set (both used to fail), and clearing
nothing; and, tree-wide, no modifier.$set[...] read that assumes the $set
is there. The two guards that pinned the old inline list and its
modifier.$set[action] read now pin the helper instead.
Thanks to xet7 !
Allow card assignment removal when the target user no longer exists. Keep normal membership activity and no-op behavior intact, with unit and REST/browser regression coverage.
Thanks to AhmedLukman and xet7 !
From 3.6-rc.1. `meteor update --release METEOR@3.6` refused it: WeKan's
forks of six Meteor packages in packages/ (allow-deny, ddp-server,
dynamic-import, facebook-oauth, google-oauth, shell-server) declared
3.6-rc.1's versions with a _1 wrap number, and a local package
overrides the release's, so ~3.6.0 could not be met. Meteor 3.6 changed
those six packages only in their version line (compared on GitHub,
release/METEOR@3.6-rc.1...release/METEOR@3.6), so each fork now
declares 3.6's version with its _1 wrap number; .meteor/packages drops
the rc pins, and .meteor/versions, @meteorjs/rspack 3.0.0 and the
lockfile are what meteor update wrote. webapp is 2.4.0.
The Dockerfile's METEOR_RELEASE is METEOR@3.6. The Bun build's patch
applies unchanged to Meteor 3.6, which still has no Bun ESM loader of
its own: it is meteor-3.6.patch, checked against 3.6's commit
e730097c. tests/meteor353SecurityFixes.test.cjs is moved to 3.6 by
decision, after that check. WeKan builds with Meteor 3.6, and the node
suites pass (2162 run, 0 failed).
Thanks to xet7 !
v13.04's four Android bundle jobs all died with "environment: node:
unbound variable". v13.04 sent the Android npm installs through
releases/npm-retry.sh as `npm-retry.sh npm install`, and npm there is
build-bundle.sh's function npm() { "$node" "$npm_cli" ...; }.
npm-retry.sh is a new bash with set -u: export -f carried the function
into it, but not $node or $npm_cli, so the function failed before npm
ran - and the retry wrapper rightly called it not a network error.
The two installs now give npm-retry.sh the program itself, "$node"
"$npm_cli" install, still retried on a network error as before.
tests/npmRetry.test.cjs fails when any releases/ script gives
npm-retry.sh one of its own shell functions (it fails on the old two
lines), and reproduces the failure: an exported function without its
variable fails in npm-retry.sh, the program it runs works.
Thanks to xet7 !
Opens the Upcoming section. The fix is covered by a positive and a
negative check in tests/superProductivityFormat.test.cjs, which fails
on the old formatter; it is a pure formatter behind the REST export,
so no browser test was added, and WeKan's own server was not run here.
Thanks to xet7 !
A card whose list is archived is collected with listTitle '', and the
Super Productivity formatter tagged such a card with tagFor(''), which
is undefined - the export then threw "Cannot read properties of
undefined (reading 'taskIds')" and the whole board's export failed.
The list with no title is now "Untitled", its tag and its panel alike,
so the panel shows its cards and every task's tags exist. The test
fails with that error on the old formatter.
Found while checking Weddy's Super Productivity export against
WeKan's formatter.
Thanks to xet7 !
v13.03's build-sandstorm died in releases/sandstorm-slim-spk.sh with
"npm error 504 Gateway Time-out - GET https://github.com/meteor/
node-source-map-support/tarball/81bce1f9..." - the same github.com outage
releases/npm-retry.sh was written for in v10.86. That install, and the
Android bundle's programs/server and sharp installs, ran npm bare, because
tests/npmRetry.test.cjs read only release-all.yml and never the scripts it
runs. All three now go through npm-retry.sh: a transient 5xx or socket
error is retried with backoff, a real npm error still fails at once.
tests/npmRetry.test.cjs now follows every releases/ script a workflow runs,
and the scripts those run, and fails on a bare npm install or ci in any of
them; it fails on the old lines. tests/androidBundles.test.cjs pins the same
installs in the same order, now through the retry.
Thanks to xet7 !
The Upcoming release fixes the CRITICAL SECURITY ISSUE of ListLockBleed
(GHSA-4734-8vrg-8j3w): a list that refuses a role write access refuses that
role's checklist and checklist-item REST writes too.
tests/securityRegressionCoverage.test.cjs names its guards
(tests/listPermissions.test.cjs and
tests/playwright/specs/list-lock-checklists.e2e.js) and counts 147.
Thanks to tsurutanmen and xet7 !
GHSA-4734-8vrg-8j3w. A board admin can refuse a role write access in one
list (#4527, list.roleExceptions). The card and comment REST routes and the
DDP checklist writes applied that refusal; the six checklist and
checklist-item REST routes asked only the board, so a member the list
refused still created, renamed, ticked and deleted its cards' checklists
and items with an API token.
They now call assertListNotRefused with the card's list, as the card routes
do, and answer 403. The refusal is recorded in Admin Panel -> Problems as
ListLockBleed (authz.list-locked, medium: recorded, never disables the
account): no screen offers such a write, so it is the lock being stepped
round. The card routes' refusals are recorded through the same gate.
tests/listPermissions.test.cjs scans every REST write route of the tree that
names a card and fails on one that does not ask the list - not only the
per-list card routes it checked before - naming the three that follow their
own rule (a comment's edit and deletion, dependency lines, a bot run's
report), and checks the refusal is recorded. It fails on the old routes.
tests/playwright/specs/list-lock-checklists.e2e.js refuses all six writes in
a locked list with 403, leaves the checklist and item untouched, records the
attempt, and still lets the same member write in an open list and the board
admin in the locked one.
Thanks to tsurutanmen and xet7 !
The Hall of Fame published UnassignedArchiveBleed (GHSA-6xqf-jh8x-4wxc), so
tests/securityRegressionCoverage.test.cjs failed: the companion list had a
name the inventory did not. It is in GUARDED now, with the two suites that
pin the fix, and those suites name it.
Thanks to tsurutanmen and xet7 !
v13.02's snap-native amd64 and both amd64 snap-variants failed within eight
minutes of each other with "Failed to refresh package list: failed to run
apt update" while snapcraft installed build-packages, and the arm64 snaps,
served by ports.ubuntu.com, built. releases/snap-refresh-managed-apt.py
recovered only a stale .deb 404 and refused this, so the one guarded retry
never ran.
It now also recognises a failed apt update in the managed project instance,
retries apt-get update there with a 30/60/120/240 s backoff until the archive
answers, and only then lets the Snapcraft retry run; an outage that outlasts
the backoff stays fatal. It prints apt's own E:/W:/Err: lines from the
Snapcraft log first, so a failing job says why apt failed. Anything else is
still refused.
Thanks to xet7 !
Keep accepting the isOverTime request parameter and write its validated value to the isOvertime schema field used by card readers and the UI.
Correct the handler regression and cover JSON/form edits, persistence, API reads, reactive display, reloads and rejected requests.
2026-11-06 19:04:30 +03:00
378 changed files with 6139 additions and 1460 deletions
[ "$rc" = 0 ] || { echo "::error::$TARGET needs an i386 Node.js $NODE_VERSION to build its single file, and none of the three Node.js sources has one."; exit 1; }
node_url="$(printf '%s\n' "$src" | sed -n 's/^node_url=//p')"
node_kind="$(printf '%s\n' "$src" | sed -n 's/^node_kind=//p')"
node_member="$(printf '%s\n' "$src" | sed -n 's/^node_member=//p')"
node_sha256="$(printf '%s\n' "$src" | sed -n 's/^node_sha256=//p')"
@@ -10,7 +10,7 @@ This is without container (without Docker or Snap).
Right click and download files 1-4:
1. [wekan-13.02-win64.zip](https://github.com/wekan/wekan/releases/download/v13.02/wekan-13.02-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
1. [wekan-13.11-win64.zip](https://github.com/wekan/wekan/releases/download/v13.11/wekan-13.11-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
"databaseReportTitle":"Leqa ni kato ni itukutuku",
"new-problems":"leqa vou",
"acknowledge":"Vakadinadinataka",
"clear-problems":"Vakasavasavataka",
"rulesReportTitle":"iTukutuku ni Lawa",
"boardsReportTitle":"iTukutuku ni Vola",
"cardsReportTitle":"iTukutuku ni Kadi",
Loaded 100 of 378 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.