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 /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,cli1,hash1,hash2,hash3,hash4,ns1,ns2,frame1 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