Sonar's S2871 flags a bare .sort(), which orders by UTF-16 code unit rather than
by value. All 30 remaining sites turned out to sort ASCII strings — ISO dates,
currency and country codes, setting keys, route names, OAuth scopes, zip entry
names — where that order is already the intended one. Each says so explicitly
now, with a comparator that reproduces it exactly rather than a localeCompare
that would quietly reorder things.
That mattered more than it sounds. Several of these feed something other than a
display: useAtlas joins the sorted codes into the signature that decides whether
to rebuild the region layer, daysSlice re-pins sorted dates positionally onto
reordered days, and three timeline views pick a day's colour by indexOf into the
sorted array. Switching any of them to a locale collator would have been a silent
regression.
Two smaller ones came with it. The .map(placeBtn) calls in FileManagerAssignModal
pass the index as a second argument, which placeBtn declares and uses for its
React key — correct already, now written out as .map((p, i) => placeBtn(p, i)).
And budgetSlice's optimistic paid-toggle stored the raw boolean behind
`paid as unknown as number`, while the schema declares z.number() and both the
REST response and the WebSocket event carry 0/1. So the optimistic state and the
confirmed state disagreed on type until a refresh. It normalises now, and the two
tests that asserted `true` assert the contract.
Four `| 0` truncations are listed in sonar-project.properties instead of changed.
Two are a djb2 string hash where the 32-bit wrap is the algorithm. The other two
look like plain truncation of a row id, but `| 0` is also coercing undefined to
0 there — the settlement flows the API returns carry no user_id, so that is the
normal path, and Math.trunc returns NaN and crashes the lookup.
* fix(security): mint idempotency keys properly, and resolve the extractor by path
Three findings out of a sweep of every open Sonar security issue. The other 49
turned out to be false positives and are documented in sonar-project.properties
rather than changed — each entry scoped to a file, never a whole rule, so a
future Math.random() on an auth path still fails.
The idempotency key is the one that can lose data. Both producers fell back to
Math.random when crypto.randomUUID was missing, and randomUUID needs a secure
context — TREK's own quickstart is http://localhost:3000 and LAN installs run on
http://192.168.x.x, so on the deployments the README documents the fallback is
what actually runs. The axios one was
`Math.random().toString(36).slice(2)`, which is not length-stable: 0.5 yields a
single character and 0 yields the empty string, which the interceptor reads as
"no key" and skips deduplication for entirely. Two writes sharing a key collapse
into one — the second gets the first one's cached response and the edit is gone
with no error anywhere. Both now come from utils/randomId, which has the
getRandomValues rung that works in insecure contexts too.
The extractor probe stored the bare binary name, so every extraction re-resolved
it through whatever PATH held at that moment. The official image sets
KITINERARY_EXTRACTOR_PATH and never reaches that branch, but a bare-metal install
without it ran an unqualified name from a PATH it does not control. It now walks
the search path itself and keeps the absolute path of the file it verified;
execFileSync also drops the /bin/sh hop the old probe needed. PATH comes through
app-config because process.env is walled off in src/.
* fix: four tests and sorts that were not doing what they claimed
Sonar reported 43 high-impact reliability issues. Reading all of them, four are
real; the rest are cosmetic or would be made worse by the suggested change.
budget.test.ts hand-built `paid: true`, but both emitters send `paid: body.paid
? 1 : 0` and the handler stores the value verbatim — the test asserted a payload
the server never sends. The line below it already used a number.
admin.controller.test.ts had a `.resolves` assertion with no await. The promise
was dropped, so that line could not fail the test.
The two to-do category lists sorted with a bare .sort(), which is byte order:
every accented name files behind the whole ASCII range, so in a German trip
"Übernachtung" lands after "Zelt". TREK ships 23 locales, so this is real data.
One side effect worth knowing: katColor picks a category's dot colour by its
index, so re-sorting can move which category shows which colour — it already
moves whenever a category is added, and the colour carries no meaning.
Deliberately not changed: the two `| 0` truncations (Math.trunc is not
equivalent — `| 0` wraps at 32 bits and these carry millisecond timestamps), the
two .map() callbacks whose second parameter is declared and used, and the sha1
in thumbnail.service (its own comment says a swap would orphan every existing
thumbnail).
* refactor: give every button a type, and prefer the Number statics
Two mechanical sweeps, no behaviour change intended in either.
A <button> with no type attribute is type="submit" per the HTML spec. Outside a
form that does nothing today, but it turns into a surprise the moment the markup
is wrapped in one, which is what Sonar's S9011 is warning about. 870 buttons get
an explicit type="button". Two things were left alone on purpose: buttons that
take a {...spread}, because the spread may carry a type and the merge order
decides which wins, and anything inside a <form> — there the answer is a
judgement call, and checking all eleven form files found every real submit button
already carrying type="submit".
The Number sweep covers only the three globals that are exactly equivalent to
their Number twin: parseInt, parseFloat and NaN. isNaN is NOT in it — the global
coerces its argument, so isNaN('abc') is true where Number.isNaN('abc') is false,
and renaming it would change what the code does. It reaches server and plugin-sdk
as well as the client; both copies of egress-policy.ts got the identical edit, so
the mirror the SDK's parity test guards stays intact.
Adds new and expanded test suites across client and server to cover the
OAuth 2.1 scope system, MCP session manager, collab service, unified
memories helpers, OIDC service, budget slice, and OAuth authorize page.
Also extends SonarQube coverage exclusions to include bootstrapping files
(migrations, scheduler, main.tsx, types.ts) that are not meaningfully
testable.
* 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