mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
1ad2e92142
The bulk of what SonarQube still had open on the reliability side, read site by
site rather than swept. Two clusters are worth calling out because they are
defects, not lint.
**Four regexes could be made to hang the process.** The worst is
`/.+@.+\..+/` in the forgot-password path: three unbounded `.+` that all
overlap, unanchored, on an endpoint that needs no login and whose schema is a
bare z.string() under a 100kb body cap. A thousand '@' characters had not
finished after 100 seconds — one request pins the event loop, and Node has one.
EMAIL_REGEX in auth.helpers is the same shape on register and profile update
(310ms for 8kb). The two tag strippers over an uploaded KML description and over
Commons metadata took 0.52s on a 16k run of '<'. Each rewrite was proven
equivalent by brute force against the original — millions of strings for the
coordinate patterns, 671,876 for the tag stripper — because a subtly different
regex on a parsing path is worse than the finding.
`<[^>]+>` could not be fixed in the pattern: `[^>]` matches `<` too, so the only
unambiguous form changes what happens to malformed markup. It is now a scan in
common/stripHtmlTags.ts, quirks included — `<>` is left alone, `<a<b>` is one
tag — with the equivalence pinned by a test that compares against the old regex
over every string up to length six.
**And roughly 500 places could only be reached with a mouse.** Clickable divs
became real buttons where the markup allowed (52 of them), took
role + tabIndex + Enter/Space where they had to stay divs, and modal backdrops
took role="presentation" — the honest role, since a backdrop is a mouse shortcut
for a Cancel control that is already in the tab order, and making it a button
plants a nameless full-screen tab stop around its own contents.
Alongside those: `.sort()` calls that now say the order they always had, `{n &&
<X/>}` guards that rendered a literal "0" for a zero-byte file and for a place at
latitude 0, an optimistic budget write that stored a boolean where the contract
and both transports use 0/1, and the Number statics.
sonar-project.properties documents the six that were deliberately left: two wiki
patterns with no overlapping quantifier pair whose "fix" would stop parsing
`[see [1]](url)`, four tar header fields bounded at 12-155 bytes by the format,
two djb2 hashes where the 32-bit wrap is the algorithm, two `| 0` that are also
coercing undefined to 0 on a path where the API sends no user_id, and the MCP
session maps Sonar reads as empty because they are filled from another module.
228 lines
14 KiB
Properties
228 lines
14 KiB
Properties
sonar.projectKey=TREK
|
|
sonar.projectName=TREK
|
|
sonar.sourceEncoding=UTF-8
|
|
|
|
# Sources — one entry per workspace package (plugin-sdk is standalone but lives in-repo)
|
|
sonar.sources=client/src,server/src,shared/src,plugin-sdk/src
|
|
|
|
# Tests — dedicated test dirs per package, plus co-located client tests in src
|
|
# and co-located shared schema/sanitize/i18n specs in shared/src
|
|
sonar.tests=server/tests,client/tests,client/e2e,client/src,shared/src,plugin-sdk/test
|
|
sonar.test.inclusions=\
|
|
server/tests/**/*.test.ts,\
|
|
client/tests/**/*.test.ts,\
|
|
client/tests/**/*.test.tsx,\
|
|
client/e2e/**/*.spec.ts,\
|
|
client/src/**/*.test.ts,\
|
|
client/src/**/*.test.tsx,\
|
|
shared/src/**/*.spec.ts,\
|
|
plugin-sdk/test/**/*.test.ts
|
|
|
|
# Keep generated output and test files out of source analysis
|
|
sonar.exclusions=\
|
|
**/node_modules/**,\
|
|
**/dist/**,\
|
|
**/build/**,\
|
|
**/coverage/**,\
|
|
client/dev-dist/**,\
|
|
**/*.test.ts,\
|
|
**/*.test.tsx,\
|
|
**/*.spec.ts,\
|
|
**/*.d.ts
|
|
|
|
# Coverage — lcov paths relative to repo root, one per package
|
|
# (each package's vitest config emits lcov to <pkg>/coverage/).
|
|
sonar.javascript.lcov.reportPaths=\
|
|
server/coverage/lcov.info,\
|
|
client/coverage/lcov.info,\
|
|
shared/coverage/lcov.info,\
|
|
plugin-sdk/coverage/lcov.info
|
|
|
|
# Infrastructure/bootstrap files plus generated/data files the vitest configs
|
|
# exclude from instrumentation (a file absent from lcov would otherwise count
|
|
# as 0% covered here — keep these in sync with each package's vitest.config.ts)
|
|
sonar.coverage.exclusions=\
|
|
server/src/index.ts,\
|
|
server/src/config.ts,\
|
|
server/src/db/database.ts,\
|
|
server/src/db/migrations.ts,\
|
|
server/src/db/seeds.ts,\
|
|
server/src/demo/**,\
|
|
server/src/nest/plugins/runtime/plugin-host-entry.ts,\
|
|
client/src/main.tsx,\
|
|
client/src/types.ts,\
|
|
shared/src/plugin-permissions.ts,\
|
|
shared/src/i18n/*/**,\
|
|
plugin-sdk/src/generated/**,\
|
|
plugin-sdk/src/lucide-icon-names.ts
|
|
|
|
# Locale files are large near-identical literal objects — keep them out of duplication metrics
|
|
sonar.cpd.exclusions=shared/src/i18n/**
|
|
|
|
# Locale files are UI copy — strings like "Password", "API key", "token" are labels,
|
|
# not credentials, and the secret detectors false-positive on them constantly.
|
|
# Ignore the secrets analyzer and the hard-coded-credential rules on i18n only.
|
|
sonar.issue.ignore.multicriteria=i18nsec1,i18nsec2,i18nsec3,rand1,rand2,rand3,rand4,rand5,rand6,cli1,hash1,hash2,hash3,hash4,hash5,hash6,hash7,hash8,ns1,ns2,frame1,empty1,empty2,canvas1,wiki1,tar1
|
|
sonar.issue.ignore.multicriteria.i18nsec1.ruleKey=secrets:*
|
|
sonar.issue.ignore.multicriteria.i18nsec1.resourceKey=shared/src/i18n/**
|
|
sonar.issue.ignore.multicriteria.i18nsec2.ruleKey=typescript:S2068
|
|
sonar.issue.ignore.multicriteria.i18nsec2.resourceKey=shared/src/i18n/**
|
|
sonar.issue.ignore.multicriteria.i18nsec3.ruleKey=typescript:S6418
|
|
sonar.issue.ignore.multicriteria.i18nsec3.resourceKey=shared/src/i18n/**
|
|
|
|
# S2245 (Math.random in a security context) — scoped per file, never globally, so
|
|
# a future Math.random() on an auth, token or crypto path still fails the gate.
|
|
# The two places where the value DID reach something that matters — the two
|
|
# idempotency-key producers — were fixed instead of listed here; they now share
|
|
# client/src/utils/randomId.ts.
|
|
#
|
|
# Studio + studioStore: element and spread ids inside a photo-book document. The
|
|
# whole document is stored and fetched as one blob scoped by journey id, so an
|
|
# element id is never a URL, a lookup key or an authorization subject. Worst case
|
|
# of a collision is documented in studioStore itself: two elements would share a
|
|
# selection.
|
|
sonar.issue.ignore.multicriteria.rand1.ruleKey=typescript:S2245
|
|
sonar.issue.ignore.multicriteria.rand1.resourceKey=client/src/components/Studio/**
|
|
sonar.issue.ignore.multicriteria.rand2.ruleKey=typescript:S2245
|
|
sonar.issue.ignore.multicriteria.rand2.resourceKey=client/src/store/studioStore.ts
|
|
# LoginWorld: canvas animation parameters — arc endpoints, flight speed, spawn
|
|
# delays. Nothing crosses a trust boundary; nothing leaves the browser.
|
|
sonar.issue.ignore.multicriteria.rand3.ruleKey=typescript:S2245
|
|
sonar.issue.ignore.multicriteria.rand3.resourceKey=client/src/pages/login/LoginWorld.tsx
|
|
# placesSession: a Google Places session token. It is a BILLING correlator, not a
|
|
# credential — Google groups requests by (API key, token) for pricing. Suggestions
|
|
# derive from the typed input, never from the token, so a guessed value cannot
|
|
# pull another user's results. The Math.random rung is also the third fallback,
|
|
# reached only where Web Crypto is absent entirely.
|
|
sonar.issue.ignore.multicriteria.rand4.ruleKey=typescript:S2245
|
|
sonar.issue.ignore.multicriteria.rand4.resourceKey=client/src/utils/placesSession.ts
|
|
# Cost-split rows: React list keys for ephemeral ticket rows, dropped before the
|
|
# payload is serialised (see writeTicketItems) so they never reach the API or DB.
|
|
sonar.issue.ignore.multicriteria.rand5.ruleKey=typescript:S2245
|
|
sonar.issue.ignore.multicriteria.rand5.resourceKey=client/src/components/Budget/CostsPanel.tsx
|
|
sonar.issue.ignore.multicriteria.cli1.ruleKey=typescript:S2245
|
|
sonar.issue.ignore.multicriteria.cli1.resourceKey=client/src/mobile/screens/trip/sheets/MCostSheet.tsx
|
|
|
|
# S4790 (weak hash) — every one of these derives a cache key or a filename, never
|
|
# a token, signature or integrity check, and the digest never reaches a client:
|
|
# the public URLs carry the plain placeId/photoId. Every genuine security hash in
|
|
# the server is already sha256 (backup codes, tokens, PKCE, the plugin audit
|
|
# chain). Swapping these would only orphan existing cached files — thumbnail.ts
|
|
# says so in its own comment.
|
|
sonar.issue.ignore.multicriteria.hash1.ruleKey=typescript:S4790
|
|
sonar.issue.ignore.multicriteria.hash1.resourceKey=server/src/nest/memories/**
|
|
sonar.issue.ignore.multicriteria.hash2.ruleKey=typescript:S4790
|
|
sonar.issue.ignore.multicriteria.hash2.resourceKey=server/src/nest/place-photos/**
|
|
sonar.issue.ignore.multicriteria.hash3.ruleKey=typescript:S4790
|
|
sonar.issue.ignore.multicriteria.hash3.resourceKey=server/src/nest/place-enrichment/**
|
|
|
|
# S4036 (OS command via PATH) — the plugin SDK is a CLI a plugin author runs on
|
|
# their own machine, and reaching git/npm through the developer's own PATH is the
|
|
# point of it. Every invocation passes an argv array, so nothing is shell-parsed.
|
|
# The one server-side site was NOT suppressed: it now resolves its binary to an
|
|
# absolute path (see kitinerary-extractor.service.ts).
|
|
sonar.issue.ignore.multicriteria.hash4.ruleKey=typescript:S4036
|
|
sonar.issue.ignore.multicriteria.hash4.resourceKey=plugin-sdk/src/cli/**
|
|
|
|
# S5332 (clear-text protocol) — neither site opens a connection.
|
|
# gpx-export: the GPX and XSI namespace URIs plus the schemaLocation pair. A
|
|
# namespace is an exact identifier fixed by the schema, so rewriting it to
|
|
# https:// would produce GPX that other tools reject.
|
|
sonar.issue.ignore.multicriteria.ns1.ruleKey=typescript:S5332
|
|
sonar.issue.ignore.multicriteria.ns1.resourceKey=server/src/nest/places/gpx-export.helpers.ts
|
|
# host-surface: 'http://x.invalid' is a throwaway base handed to new URL() so a
|
|
# relative path can be parsed — the very next line rejects the value if it
|
|
# resolved anywhere else. The string is part of the guard, not a destination, and
|
|
# .invalid is reserved by RFC 2606 precisely so it can never resolve.
|
|
sonar.issue.ignore.multicriteria.ns2.ruleKey=typescript:S5332
|
|
sonar.issue.ignore.multicriteria.ns2.resourceKey=server/src/nest/plugins/host/rpc/host-surface.rpc.ts
|
|
|
|
# S2819 (postMessage target origin) — the plugin frame is embedded with
|
|
# sandbox="allow-scripts allow-forms" and deliberately WITHOUT allow-same-origin,
|
|
# so the document inside has an opaque origin. There is no origin string that
|
|
# would match it: '*' is the only value that works, which makes the rule
|
|
# unsatisfiable here rather than unsatisfied. The receiving side authenticates on
|
|
# event.source instead.
|
|
sonar.issue.ignore.multicriteria.frame1.ruleKey=typescript:S2819
|
|
sonar.issue.ignore.multicriteria.frame1.resourceKey=client/src/components/Plugins/PluginFrame.tsx
|
|
# randomId is the module that MINTS idempotency keys, and its first two rungs are
|
|
# crypto.randomUUID and crypto.getRandomValues. The Math.random line is the third,
|
|
# reached only by a runtime with no Web Crypto at all, and the value it produces is
|
|
# a deduplication key the server scopes per (user, method, path) — not a token, not
|
|
# a secret. Replacing it is not possible: there is no stronger primitive left at
|
|
# that point, and deleting the rung would leave such a runtime with no key.
|
|
sonar.issue.ignore.multicriteria.rand6.ruleKey=typescript:S2245
|
|
sonar.issue.ignore.multicriteria.rand6.resourceKey=client/src/utils/randomId.ts
|
|
|
|
# S7767 (bitwise truncation) — Math.trunc is NOT equivalent to `| 0`, and all four
|
|
# sites depend on a difference the rule does not model.
|
|
#
|
|
# packingListPanel.helpers and todoListModel hold the same djb2-variant string
|
|
# hash, `h = ((h << 5) - h + chr) | 0`, whose only job is to stay bounded while it
|
|
# folds a category name into a colour index. Math.trunc would let h grow past 2^53,
|
|
# lose precision, and hand out different colours for the same names.
|
|
#
|
|
# BudgetPanel.helpers and TravelerPicker look like plain truncation of a user row
|
|
# id, but `| 0` is also coercing there: it turns undefined and NaN into 0, while
|
|
# Math.trunc returns NaN and the array lookup then yields undefined. That is not
|
|
# theoretical — the settlement `flows` the API returns carry a username and an
|
|
# avatar but no user_id, so colorForUserId(undefined) is the normal path for that
|
|
# section. Swapping it crashed two BudgetPanel tests on `.gradient` of undefined.
|
|
sonar.issue.ignore.multicriteria.hash5.ruleKey=typescript:S7767
|
|
sonar.issue.ignore.multicriteria.hash5.resourceKey=client/src/components/Packing/packingListPanel.helpers.ts
|
|
sonar.issue.ignore.multicriteria.hash6.ruleKey=typescript:S7767
|
|
sonar.issue.ignore.multicriteria.hash6.resourceKey=client/src/components/Todo/todoListModel.ts
|
|
sonar.issue.ignore.multicriteria.hash7.ruleKey=typescript:S7767
|
|
sonar.issue.ignore.multicriteria.hash7.resourceKey=client/src/components/Budget/BudgetPanel.helpers.ts
|
|
sonar.issue.ignore.multicriteria.hash8.ruleKey=typescript:S7767
|
|
sonar.issue.ignore.multicriteria.hash8.resourceKey=client/src/components/Planner/TravelerPicker.tsx
|
|
|
|
# S4158 (empty collection accessed) — two places where the analyser cannot see
|
|
# who fills the collection, and where acting on it would delete working code.
|
|
#
|
|
# mcp/sessionManager declares `export const sessions = new Map()` and never
|
|
# writes to it in that file; every insert comes from
|
|
# nest/mcp-transport/mcp-transport.service.ts. The four findings are the eviction
|
|
# loop, the two revocation loops and the per-user cap scan — so "removing the
|
|
# dead code" would leak every MCP session and leave a revoked OAuth client with
|
|
# live ones. It is only ever empty at process start.
|
|
sonar.issue.ignore.multicriteria.empty1.ruleKey=typescript:S4158
|
|
sonar.issue.ignore.multicriteria.empty1.resourceKey=server/src/mcp/sessionManager.ts
|
|
# client/src/managed is a build-time seam: the arrays are empty in this repo and
|
|
# in every build of it, and an operator shipping extra screens replaces the file.
|
|
# Its header comment makes reading them unconditionally a rule, so the public
|
|
# build takes the same code path as a managed one.
|
|
sonar.issue.ignore.multicriteria.empty2.ruleKey=typescript:S4158
|
|
sonar.issue.ignore.multicriteria.empty2.resourceKey=client/src/managed/**
|
|
|
|
# S8786 (non-linear backtracking) on the wiki renderer — the two link patterns
|
|
# have NO overlapping quantifier pair: each `[^\]]+` / `[^)\s]+` is followed by a
|
|
# literal its own class excludes, and they are separated by `](`. What is
|
|
# quadratic is only the unanchored global scan over a long run of `[`, and the
|
|
# way to stop that is to exclude `[` from the link-text class — which would stop
|
|
# `[see [1]](url)` being rewritten at all. The subject is not user input either:
|
|
# the content is the `wiki/**` directory shipped with the app, the same files CI
|
|
# mirrors to the public GitHub wiki. Changing link parsing to satisfy a rule
|
|
# about untrusted input, on input that is not untrusted, is the wrong trade.
|
|
sonar.issue.ignore.multicriteria.wiki1.ruleKey=typescript:S8786
|
|
sonar.issue.ignore.multicriteria.wiki1.resourceKey=server/src/nest/help/wiki.ts
|
|
# S8786 on the tar reader — `/\0.*$/` trims the NUL padding out of a header
|
|
# field, and it does backtrack, but the subject is a fixed-size slice the tar
|
|
# format defines: 100 bytes for the name, 155 for the prefix, 12 for the size.
|
|
# The quadratic factor is bounded at ~10^4 character comparisons per member and
|
|
# an attacker cannot grow it. The rewrite that would clear the rule
|
|
# (`\0[^\0\n\r]*$`) stops matching NUL runs, which changes what gets truncated on
|
|
# the path-traversal guard — not a trade worth making for a lint finding. The two
|
|
# tag strippers that WERE reachable (uploaded KML, Commons metadata) were fixed
|
|
# instead, in common/stripHtmlTags.ts.
|
|
sonar.issue.ignore.multicriteria.tar1.ruleKey=typescript:S8786
|
|
sonar.issue.ignore.multicriteria.tar1.resourceKey=server/src/nest/plugins/install/safe-extract.ts
|
|
|
|
# S6825 on LoginWorld — the element is a decorative animated background:
|
|
# <canvas aria-hidden pointer-events:none>, no tabIndex, no children, no role.
|
|
# aria-hidden is the correct markup for it. Both ways to satisfy the rule make
|
|
# things worse: dropping aria-hidden exposes a contentless canvas to a screen
|
|
# reader, and adding tabIndex={-1} makes it focusable for the linter's benefit.
|
|
sonar.issue.ignore.multicriteria.canvas1.ruleKey=typescript:S6825
|
|
sonar.issue.ignore.multicriteria.canvas1.resourceKey=client/src/pages/login/LoginWorld.tsx
|