Skip to content

fix(security): RE2 for user-supplied regexes; resolve 4 CodeQL alerts (#152/153/155/156)#3544

Merged
Yeraze merged 2 commits into
mainfrom
fix/codeql-security-alerts
Jun 18, 2026
Merged

fix(security): RE2 for user-supplied regexes; resolve 4 CodeQL alerts (#152/153/155/156)#3544
Yeraze merged 2 commits into
mainfrom
fix/codeql-security-alerts

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Summary

Reviews and resolves the four open CodeQL code-scanning alerts. Three are intentional features gated behind requirePermission('settings','write'); one is a frontend false positive. None were reachable by an unauthenticated user.

# Sev Rule Resolution
153 high js/regex-injection (name-filter) Code fix — RE2
155 high js/regex-injection (auto-responder) Code fix — RE2
152 high js/user-controlled-bypass (App.tsx) Dismissed — false positive
156 med js/http-to-file-access (script import) Dismissed — by design

The fix (#153, #155)

Compile all user/admin-supplied regexes with RE2, a linear-time engine immune to catastrophic backtracking (ReDoS). The prior heuristic guards (length caps + nested-quantifier checks) reduced but couldn't eliminate the risk, and CodeQL correctly kept flagging the user → RegExp dataflow. CodeQL recognizes RE2 as a safe sink, so these alerts close on the next scan.

  • New src/utils/safeRegex.tscompileUserRegex(pattern, flags) wraps RE2 and returns a RegExp-compatible matcher (test/exec/match/replace). RE2 rejects backreferences/lookaround (the constructs that enable ReDoS) — the intended trade-off; callers keep their existing try/catch.
  • Converted every user-regex site — validation and execution:
    • server.ts — name-filter validation (2 sites).
    • services/database.ts — the name-filter execution sites that actually match node names (traceroute + localstats union filters). These are the real ReDoS surface; CodeQL flagged only the validation because the taint flow is broken by the settings store.
    • routes/scriptRoutes.ts + meshtasticManager.ts — auto-responder trigger and auto-acknowledge matching.
  • Dockerfile — add build-base python3 to the builder stage so the native re2 compiles from source where no prebuilt binary exists (Alpine/musl arm). Builder-only; not in the runtime image.

Dismissals (already applied via the code-scanning API, with justifications)

Testing

  • New safeRegex suite: asserts linear-time behaviour on the classic (a+)+$ ReDoS pattern (<100ms vs multi-second on native RegExp) and rejection of backreference/lookaround.
  • Existing auto-responder / auto-ack / localstats-filter suites pass unchanged (behaviour preserved; 140+ regex-path tests).
  • Full Vitest suite green (0 failures, 2321 suites); tsc clean.

Note on the native dependency

re2 is a native module. The project already ships native deps (better-sqlite3, bcrypt), and the builder stage now carries the toolchain, so multi-arch Alpine builds compile it where prebuilts are absent. The CI Docker build + system tests exercise this.

🤖 Generated with Claude Code

…3543)

Resolves the two high-severity CodeQL js/regex-injection alerts (#153, #155) by
compiling all user/admin-supplied regular expressions with RE2, a linear-time
engine immune to catastrophic backtracking. The previous heuristic guards
(length caps + nested-quantifier checks) reduced but could not eliminate the
risk, and CodeQL (correctly) kept flagging the user→RegExp dataflow.

- New src/utils/safeRegex.ts: compileUserRegex(pattern, flags) wraps RE2 and
  returns a RegExp-compatible matcher (test/exec/match/replace). RE2 rejects
  backreferences and lookaround — the intended trade-off — so callers keep their
  existing try/catch to treat unsupported patterns as "invalid regex".
- Convert every user-supplied regex site, validation AND execution:
  - server.ts: remote(-localstats) name-filter validation (2 sites).
  - services/database.ts: the name-filter execution sites that actually match
    node names (traceroute + localstats union filters) — the real ReDoS surface
    CodeQL didn't reach through the settings store.
  - routes/scriptRoutes.ts + meshtasticManager.ts: auto-responder trigger and
    auto-acknowledge pattern matching.
- Dockerfile: add build-base + python3 to the builder stage so re2 (native) can
  compile from source where no prebuilt exists (Alpine/musl arm). Builder stage
  only — not in the runtime image.

Tests: new safeRegex suite asserts linear-time behaviour on the classic (a+)+$
ReDoS pattern and rejection of backreference/lookaround. Full suite green.

The two non-regex alerts are handled separately: #152 (frontend routing, not a
security gate) dismissed as false-positive; #156 (authenticated admin script
import, path-sanitized) dismissed as by-design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: fix(security): RE2 for user-supplied regexes

Todo List:

  • Gather context and understand the PR
  • Read new files (safeRegex.ts, safeRegex.test.ts)
  • Review modified files (server.ts, database.ts, meshtasticManager.ts, scriptRoutes.ts, Dockerfile)
  • Analyze test coverage
  • Provide comprehensive review feedback

Review Summary

This is a solid, well-scoped security fix. The approach is correct and the changes are conservative. A few observations worth addressing:

Issues

1. autoAckRegex.ts and settingsRoutes.ts still use new RegExp() — inconsistency

src/server/utils/autoAckRegex.ts:69 still calls new RegExp(pattern, 'i') at execution time:

return { regex: new RegExp(pattern, 'i') };

And src/server/routes/settingsRoutes.ts:215 uses new RegExp(pattern, 'i') for validation:

new RegExp(pattern, 'i');

These are the autoAckRegex validation/compilation code paths that meshtasticManager.ts bypasses (the manager now uses compileUserRegex via the cache, so at execution time it's safe). But:

  • compileAutoAckRegex in autoAckRegex.ts is exported and may be called directly — callers get a native RegExp not a RE2 instance
  • The heuristic filter in both files (charset allowlist + DANGEROUS_SHAPE_RE) reduces but doesn't eliminate the risk for the compile step in compileAutoAckRegex, which uses native new RegExp
  • CodeQL may or may not flag these depending on taint flow, but they represent residual surface if compileAutoAckRegex is ever called outside the manager

Recommendation: Update autoAckRegex.ts:69 to use compileUserRegex and settingsRoutes.ts:215 to attempt compileUserRegex for validation. The existing charset allowlist can remain as a fast pre-rejection step.


2. safeRegex.tsas unknown as RegExp cast is fragile

return new RE2(pattern, flags) as unknown as RegExp;

The RE2 class implements test, exec, etc., but it is not a true RegExp subclass. The double cast hides this. If any downstream code passes the returned value to something that does instanceof RegExp or accesses RegExp-specific internals (e.g. source, flags, lastIndex), it will silently fail or return unexpected values.

Recommendation: Either keep the as unknown as RegExp cast (acceptable given the explicit comment in the file) but add a note that instanceof RegExp checks will return false, or return a union type RE2 | RegExp so TypeScript surfaces incompatibilities at call sites. At minimum, the test suite should assert that re.source and re.flags return expected values to document the behaviour.


Minor Observations

3. src/utils/safeRegex.ts — no input validation before RE2 construction

Unlike the autoAckRegex.ts path, compileUserRegex applies no length cap or charset filter before handing the pattern to RE2. This is fine for security (RE2 is the mitigation), but a very long pattern could consume CPU during compilation. The existing call sites do have length checks upstream (e.g. scriptRoutes.ts:421 caps at 2000 chars), but compileUserRegex itself is silent on this. Worth documenting that length validation is the caller's responsibility.

4. Dockerfilebuild-base python3 in builder is correct, but python3 was already added

The runtime stage (FROM node:24.15.0-alpine3.22 at line 48) already installs python3 via apk add. The builder now adds python3 too (line 11). This is harmless — they're independent stages — but the comment could clarify it's specifically build-base (the C compiler toolchain) that's new, not python3.

5. Test: (a+)+$ ReDoS timing test

The test at safeRegex.test.ts:19-27 asserts elapsed < 100ms. This is correct in intent but timing assertions in unit tests can be flaky in resource-constrained CI environments (e.g. under load or on slow runners). Consider increasing the threshold to 500ms or using a larger repeat(100) input to make the native RegExp case definitively slow while keeping RE2 well under the cap.


What's Done Well

  • Correct, comprehensive conversion. All user-regex execution sites (validation and execution) are updated to use RE2. The PR author correctly identified that CodeQL flags validation but the real risk is execution — and both are fixed.
  • Dockerfile is correct. build-base python3 in the builder stage only is the right pattern; the runtime image stays lean.
  • Test coverage is good. The test suite covers: basic matching, capture groups, ReDoS immunity (timing), invalid patterns, and unsupported RE2 constructs (backreferences, lookahead).
  • Drop-in interface maintained. RE2 exposes test/exec/match/replace, so call-site changes are zero.

Summary

The two issues worth fixing before merge:

  1. autoAckRegex.ts:69 — replace new RegExp(pattern, 'i') with compileUserRegex(pattern, 'i') (and update the import)
  2. settingsRoutes.ts:215 — replace new RegExp(pattern, 'i') with compileUserRegex(pattern, 'i') for consistent validation

Everything else is optional polish. The core security fix is sound.

…cs (#3544)

Claude review feedback on the RE2 security PR:

1. autoAckRegex.ts:69 — convert the MeshCore auto-ack pattern compile to
   compileUserRegex (the charset allowlist/length/shape guards stay as a fast
   pre-rejection). Residual ReDoS surface CodeQL may not flag through the
   barrier, now closed for consistency.
2. settingsRoutes.ts:215 — convert the regex-validation compile to
   compileUserRegex so stored patterns are guaranteed RE2-compatible.
3. safeRegex.ts — document that the result is not `instanceof RegExp` and that
   length bounding stays the caller's responsibility; add a test asserting
   `.source`/`.flags` behave.
4. safeRegex.test.ts — raise the ReDoS timing threshold to 1000ms and use a
   larger input, so the assertion can't flake on loaded CI runners.
5. Dockerfile — clarify the builder comment (build-base is the new compiler;
   python3 is node-gyp's other requirement).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Yeraze

Yeraze commented Jun 18, 2026

Copy link
Copy Markdown
Owner Author

Thanks for the thorough review — addressed in 3e933426:

Note: the prior CI run had two transient failures unrelated to this change — Test Suite (24.x) hit a vitest worker-teardown race (EnvironmentTeardownError; all 463 files passed) and ClamAV Scan hit npm install ECONNRESET. This push re-runs them clean.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review: fix(security): RE2 for user-supplied regexes

Todo List:

  • Gather context and understand the PR
  • Read updated files (safeRegex.ts, safeRegex.test.ts, autoAckRegex.ts, settingsRoutes.ts)
  • Review remaining modified files
  • Verify all previous review items addressed
  • Provide updated review feedback

Review of Updated Changes

All five items from the previous review have been addressed. This is a clean, well-scoped security fix ready to merge. A few minor observations on the current state:

What Was Addressed

#1 autoAckRegex.ts:69compileUserRegex is now used at autoAckRegex.ts:71. The charset/length/shape guards remain as a fast pre-check — the layered approach is sound.

#2 settingsRoutes.ts:215 — Updated to compileUserRegex(pattern, 'i') for consistent RE2-compatible validation.

#3 instanceof RegExp cast — Well-documented in safeRegex.ts with explicit caveats about instanceof, lastIndex, and length bounding as caller responsibility. The new test at safeRegex.test.ts:32-38 asserts .source and .flags behave as expected.

#4 ReDoS timing test — Threshold raised to 1000ms at safeRegex.test.ts:29 with a note about CI flakiness.

#5 Dockerfile comment — Comment at line 10 now clearly distinguishes build-base (the new C/C++ compiler toolchain) from python3 (which existed before).

Minor Observations (non-blocking)

new RegExp in test filesmeshtasticManager.autoack-channels.test.ts lines 286, 317, 347, 377, 422, 432, 448, 461 still use new RegExp(autoAckRegex, 'i') for inline assertions. This is acceptable in tests since the values are hardcoded strings like '^(test|ping)', not user-supplied patterns.

new RegExp in meshtasticManager.ts:9964,10183,10306 — These three remaining instances use the pattern `\\{${key}\\}` where key is a param name from admin-defined trigger templates (e.g. {location}). The key is a known extracted identifier, not user-controlled at runtime, so these are safe template-substitution usages and don't need RE2.

src/components/auto-responder/utils.ts:269,291 — These use new RegExp in frontend utility code that processes auto-responder patterns. Since this is frontend-only (preview/display logic, not execution), it carries no server-side ReDoS risk. Worth noting in case CodeQL scans frontend code and flags it.

src/server/mqttPacketFilter.ts:325new RegExp('^' + expanded + '$') constructs a regex from an expanded MQTT topic pattern. This doesn't appear to be user-supplied input (MQTT topic filtering from config), but worth a review to confirm the expanded value is sanitized before this call.

Summary

The core fix is correct and comprehensive. All user-regex execution paths — validation and runtime matching — now go through RE2. The documentation, test coverage, and Dockerfile are all in good shape. The remaining new RegExp instances in the codebase are either test-only, template substitution with bounded keys, or frontend display code — none are ReDoS-risky user-controlled paths.

Verdict: Approved — no blocking issues remain.

@Yeraze Yeraze merged commit 7c2bf44 into main Jun 18, 2026
21 checks passed
@Yeraze Yeraze deleted the fix/codeql-security-alerts branch June 18, 2026 22:05
@Yeraze Yeraze mentioned this pull request Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant