Follow-ups deferred from the v1.0.0 release (#604). None of these blocked the release; they surfaced when the beta tree merged to main and the whole diff read as added lines, so the scanners saw this code for the first time.
CI gaps
a11y-smoke.yml has no push trigger. It fires only on workflow_dispatch and pull_request. Anything landing directly on beta (or main) never runs the gate — which is how a critical button-name violation sat on the release candidate through several pre-releases before #604 caught it. Adding push: branches: [main, beta] closes the hole.
Eight workflows declare no permissions: block: a11y-smoke, cli-release, db-integration, docs-release, e2e-smoke, packages-release, release-selfhost, release. The repository default is already read (gh api repos/TestPlanIt/testplanit/actions/permissions/workflow), so this is documentation of an existing guarantee rather than a live exposure — but it silences a recurring CodeQL finding and pins the guarantee against a future change to the repo default.
Code scanning
OfficeDocumentPreview.tsx — incomplete URL scheme check. CodeQL notes the check does not consider data: and vbscript:. This is the one finding from the batch I'd treat as a plausible real bug; it needs a look at whether the URL reaches an href/src.
Polynomial ReDoS on uncontrolled data — lib/llm/adapters/anthropic.adapter.ts and app/api/admin/llm/available-models/pricing.ts (x2). URL-normalisation regexes that degrade on inputs with many / repetitions. The input is an admin-supplied base URL, so the blast radius is small.
Externally-controlled format string — services/forecastService.ts (x2) and services/testRunService.ts. Not investigated; needs triage to confirm or dismiss.
Assessed and dismissed — recorded so they are not re-litigated
utils/stripHtmlTags.ts (double escaping; incomplete multi-character sanitization) — not exploitable. Its output feeds plainText for truncated display and as a sort key. The adjacent HTML path uses DOMPurify.sanitize with an explicit ALLOWED_TAGS allow-list. CodeQL is flagging a plain-text extractor as though it were the sanitizer.
SSRF in app/api/admin/llm/available-models/ (x3) — the route rejects anything but session.user.access === "ADMIN". An administrator pointing TestPlanIt at an LLM endpoint is the feature, not a bypass.
Follow-ups deferred from the v1.0.0 release (#604). None of these blocked the release; they surfaced when the beta tree merged to
mainand the whole diff read as added lines, so the scanners saw this code for the first time.CI gaps
a11y-smoke.ymlhas nopushtrigger. It fires only onworkflow_dispatchandpull_request. Anything landing directly onbeta(ormain) never runs the gate — which is how a criticalbutton-nameviolation sat on the release candidate through several pre-releases before #604 caught it. Addingpush: branches: [main, beta]closes the hole.Eight workflows declare no
permissions:block:a11y-smoke,cli-release,db-integration,docs-release,e2e-smoke,packages-release,release-selfhost,release. The repository default is alreadyread(gh api repos/TestPlanIt/testplanit/actions/permissions/workflow), so this is documentation of an existing guarantee rather than a live exposure — but it silences a recurring CodeQL finding and pins the guarantee against a future change to the repo default.Code scanning
OfficeDocumentPreview.tsx— incomplete URL scheme check. CodeQL notes the check does not considerdata:andvbscript:. This is the one finding from the batch I'd treat as a plausible real bug; it needs a look at whether the URL reaches anhref/src.Polynomial ReDoS on uncontrolled data —
lib/llm/adapters/anthropic.adapter.tsandapp/api/admin/llm/available-models/pricing.ts(x2). URL-normalisation regexes that degrade on inputs with many/repetitions. The input is an admin-supplied base URL, so the blast radius is small.Externally-controlled format string —
services/forecastService.ts(x2) andservices/testRunService.ts. Not investigated; needs triage to confirm or dismiss.Assessed and dismissed — recorded so they are not re-litigated
utils/stripHtmlTags.ts(double escaping; incomplete multi-character sanitization) — not exploitable. Its output feedsplainTextfor truncated display and as a sort key. The adjacent HTML path usesDOMPurify.sanitizewith an explicitALLOWED_TAGSallow-list. CodeQL is flagging a plain-text extractor as though it were the sanitizer.SSRF in
app/api/admin/llm/available-models/(x3) — the route rejects anything butsession.user.access === "ADMIN". An administrator pointing TestPlanIt at an LLM endpoint is the feature, not a bypass.