v5.15.1 5 March 2026 Fix

Security & Code Quality — Lint Remediation Pass 1

Why This Matters

SpeyBooks uses a domain-specific linter (lint.py) that enforces architectural boundaries standard AST linters cannot catch — RLS context enforcement, monetary calculation boundaries, hardcoded secret detection, and type safety. The linter is a build gate: any ERROR level finding blocks the path to production.

This release resolves the first two classes of findings identified after Milestone 2 deployment: a genuine misclassification in the log retention job, and a set of false positives in documentation fixture files and the health check probe. Both categories required careful diagnosis before resolution — suppressing a genuine finding is a security regression; fixing a false positive by changing legitimate code is unnecessary churn.


BE002 — Log Retention Job

Finding

The linter flagged api-log-retention.ts for direct database pool access, which in the API server context bypasses the RLS session variable set on request.dbClient. This is a genuine and important rule — any query running outside the tenant-scoped client can read or write across tenant boundaries.

Resolution

This is a legitimate false positive for this specific file. The log retention job is a standalone cron process that runs entirely outside the API server. It has no Fastify request context, no tenant session, and no request.dbClient to use. The api_request_log table it operates on is an administrative table with no organisation_id column and no tenant-scoped RLS policy — it is explicitly not a tenant-scoped table.

The suppression directive was already present in the file but positioned incorrectly — on the line above the while loop rather than directly above the pool.query() call. The linter’s preceding-line suppression checks lines[line_index - 1] exactly, so placement matters. The directive was repositioned to the line immediately preceding the flagged call.

A secondary improvement was made at the same time: the RETENTION_DAYS constant was being interpolated directly into the SQL string (INTERVAL '${RETENTION_DAYS} days'). While not a security risk — RETENTION_DAYS is a hardcoded integer constant — string interpolation in SQL is a pattern worth eliminating categorically. The interval is now fully parameterised using PostgreSQL’s ($1 || ' days')::INTERVAL cast.


UN001 — Documentation Fixtures and Health Probe

Finding

Five UN001 findings across three files: three whsec_... webhook signing secret examples in webhook-endpoints.doc.ts, one TOTP demo secret in auth.doc.ts, and synthetic probe credentials in health.ts.

Resolution

All five are confirmed false positives. The UN001 rule correctly detects the pattern of a secret-named field assigned a non-trivial string value — this is exactly the right heuristic for catching real hardcoded secrets. These specific instances are not real secrets:

  • The whsec_... values in webhook-endpoints.doc.ts are documentation examples showing the format of a webhook signing secret in curl examples and response fixtures. The repeating hex pattern makes their illustrative intent unambiguous.
  • JBSWY3DPEHPK3PXP in auth.doc.ts is the canonical TOTP demonstration secret used in virtually every TOTP library’s own documentation and test suite.
  • health@check.internal and health-check in health.ts are synthetic probe credentials whose entire purpose is to be rejected by the authentication service — a 400 or 401 response proves the auth stack is processing requests. A real credential would defeat the purpose of the probe.

webhook-endpoints.doc.ts and auth.doc.ts received file-level suppressions (// lint-ignore-file UN001) since every example value in a documentation fixture file is by definition not a production secret. health.ts received a line-level suppression positioned directly above the flagged body: line.


Linter Integrity

These resolutions follow the principle that suppression directives are documentation, not escape hatches. Each suppression carries a reason that explains why the finding is a false positive and what property makes the suppressed code safe. A future developer reading the suppression understands immediately why it exists and can verify the reasoning has not been invalidated by subsequent changes.

The linter rule itself is correct in all five cases — the pattern it detects is a genuine risk pattern. The suppression acknowledges the rule, not overrides it.


Operational Impact

  • BE002 error count: 1 → 0
  • UN001 error count: 5 → 0
  • Total backend errors: 125 → 120
  • Log retention SQL fully parameterised — no string interpolation in query
  • All suppressions carry explicit documented reasons

Files Changed

Backend:

  • api/src/jobs/api-log-retention.ts — lint-ignore BE002 repositioned to correct line, RETENTION_DAYS parameterised via ($1 || ' days')::INTERVAL
  • api/src/routes/health.ts — lint-ignore UN001 added directly above flagged body line, stray suppression comments from earlier attempts removed, missing closing brace restored
  • api/src/routes/webhook-endpoints.doc.ts — lint-ignore-file UN001 added
  • api/src/routes/auth.doc.ts — lint-ignore-file UN001 added

Remaining 120 backend errors are BE005/BE006 — any type usage and type assertions. These are resolved structurally by Milestone 5 (schema-derived categorical boundary and generated types). Manual remediation before that milestone would mean doing the work twice.