Skip to content

fix: audit hardening and games draw-offer attribution - #815

Merged
rinchen merged 6 commits into
mainfrom
audit
Aug 7, 2026
Merged

fix: audit hardening and games draw-offer attribution#815
rinchen merged 6 commits into
mainfrom
audit

Conversation

@rinchen

@rinchen rinchen commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

  • Close floating-promise and IPC sender-validation holes (hydrateMeshtasticMessagesFromDb, meshcore:openJsonFile, update:*, app:getProcessUptimeSec, device-connected/device-disconnected), sanitize TAK/updater error surfaces, and use mkdtemp in packaging smoke scripts (scanner also flags mkdirSync).
  • Share must-match helpers that were drifting (mqttUsesTls, Store & Forward parse, Meshtastic 10m dedup window, app_settings key prefixes) and route LoRa runtimes through lastConnectionStorage / gpsSource.
  • Localize overlooked UI strings (Raw Packet Log transport badges, MeshCore type labels, Chat “Unknown” sentinel), add SoftAP/skipDiscoverSelf tests, and correct factual docs mismatches (IPC channel names, CONTRIBUTING/Vitest scope, diagnostics paths).
  • Fix Reticulum games draw-offer UI: gate Accept/Decline and banners on metadata.draw_offered_by so offering a draw shows a waiting state instead of “Your opponent offered a draw.”
  • Hide MeshCore DM UI for Repeater contacts (Message, quick DM, unread, History), mirroring Room exclusion; Sensors stay DM-eligible. Fixes hide Meshcore DM a repeater #809.

Test plan

  • pnpm exec vitest run for IPC security, SoftAP send, ConnectionDriver/skipDiscoverSelf, MQTT TLS, backlog/dedup, insecure-temp scanner
  • pnpm exec vitest run for GamesPanel / ChessBoard / TicTacToeBoard / reticulumGamesMetadata draw-offer cases
  • pnpm exec vitest run for meshcoreUtils / NodeDetailModal / NodeListPanel repeater DM exclusion
  • pnpm run check:i18n (hard pass after fixing RU HTML entities)
  • Pre-commit hook (typecheck, eslint, staged tests, scanners, i18n auto-translate)
  • Pre-push: vitest --changed vs origin/main
  • Manual smoke: Meshtastic message refresh from DB; MeshCore SoftAP dead-bridge send; App Settings updater check still works from packaged UI
  • Manual: in a Reticulum game, offer a draw — you see waiting banner (no Accept); opponent side still gets Accept/Decline
  • Manual: MeshCore tab — Repeater node detail has no Message button; Chat/Sensor still can DM; History does not list Repeater peers

Close floating promise and sender-validation holes, share must-match helpers that were drifting, localize overlooked UI strings, and correct factual docs mismatches from the obvious-wrong audit.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR centralizes shared settings, MQTT TLS, GPS, parsing, and deduplication helpers. It adds IPC sender validation and error sanitization, updates game draw-offer states, improves temporary-directory cleanup, localizes renderer labels, and refreshes documentation.

Changes

IPC and error handling

Layer / File(s) Summary
Shared settings and IPC validation
src/shared/appSettingsKeyPrefixes.ts, src/main/index.ts, src/main/*test.ts
Shared setting prefixes replace local declarations. Additional IPC handlers and device listeners validate senders.
Updater and runtime hardening
src/main/updater.ts, src/main/tak-server-manager.ts, src/renderer/runtime/*, src/renderer/stores/*, src/main/updater.contract.test.ts, src/renderer/runtime/auditHardening.contract.test.ts
Updater and TAK errors are sanitized. Hydration and lazy-import failures are handled and logged.

Reticulum game draw offers

Layer / File(s) Summary
Draw-offer metadata and state
src/shared/games-types.ts, src/renderer/lib/reticulum/reticulumGamesMetadata.ts, src/renderer/components/GamesPanel.tsx, src/renderer/components/games/*
Game sessions identify the offer owner. UI states distinguish opponent offers from local pending offers and preserve legacy behavior.
Draw-offer tests
src/renderer/components/*test.tsx, src/renderer/lib/reticulum/reticulumGamesMetadata.test.ts, src/renderer/stores/reticulumGamesStore.test.ts
Tests cover ownership, completed sessions, controls, banners, outbound metadata, and live updates.

Shared transport and runtime utilities

Layer / File(s) Summary
Shared helpers
src/shared/mqttTls.ts, src/renderer/lib/gpsSource.ts, src/renderer/lib/timeConstants.ts, src/shared/meshtasticTextMessagePayload.ts, src/shared/appSettingsKeyPrefixes.ts
Shared helpers centralize MQTT TLS inference, GPS settings, packet parsing, setting prefixes, and the Meshtastic deduplication window.
Transport and storage integration
src/main/*mqtt*, src/renderer/lib/*, src/renderer/runtime/*, src/renderer/hooks/*
MQTT, packet parsing, GPS persistence, connection address resolution, self-discovery, SoftAP sending, and deduplication use the shared contracts.
Renderer labels
src/renderer/components/ChatPanel.tsx, src/renderer/components/NodeListPanel.tsx, src/renderer/components/RawPacketLogPanel.tsx
MeshCore sentinels and contact types are localized. Meshtastic transport labels use translation keys.

Tooling and documentation

Layer / File(s) Summary
Temporary-directory safety
scripts/check-insecure-temp-files.*, scripts/test-linux-appimage-reticulum-sidecar.mjs, scripts/test-win-nsis-install.mjs
Directory creation is checked for predictable temporary paths. Sidecar and NSIS tests use unique temporary directories and cleanup.
Documentation references
AGENTS.md, CONTRIBUTING.md, docs/*, scripts/check-log-injection.mjs, src/renderer/lib/parseStoredJson.ts
Testing, accessibility, diagnostics, IPC channel, voice channel, and logging references are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request's two main changes: audit hardening and games draw-offer attribution.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rinchen
rinchen marked this pull request as draft August 7, 2026 01:04
Gate Accept/Decline and banners on metadata.draw_offered_by so offering
a draw no longer shows the opponent-offer UI.
@rinchen rinchen changed the title fix: harden IPC/logging gaps and clear audit findings fix: audit hardening and games draw-offer attribution Aug 7, 2026
rinchen added 2 commits August 6, 2026 19:09
Expand helper matrix, legacy/self/opponent GamesPanel regressions,
board inactive/legacy banners, and games.update store preservation.
Lock in shared mqttTls/appSettings prefixes, GPS/last-connection helpers, S&F parse, updater sender checks, and runtime source contracts from the audit fixes.
@rinchen
rinchen marked this pull request as ready for review August 7, 2026 01:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (2)
src/renderer/components/ChatPanel.tsx-2530-2536 (1)

2530-2536: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Limit Unknown localization to MeshCore.

At Line [2536], the condition runs for every protocol. nodeDisplayName in src/renderer/lib/nodeLongNameOrHex.ts Lines [4]-[12] can return a legitimate Unknown name for Meshtastic or Reticulum, so this code replaces that name with common.unknown. Scope the replacement to MeshCore. Add tests for the MeshCore sentinel and a non-MeshCore node named Unknown.

Suggested fix
-                const displaySenderName =
-                  rawSenderName === 'Unknown' ? t('common.unknown') : rawSenderName;
+                const displaySenderName =
+                  protocol === 'meshcore' && rawSenderName === 'Unknown'
+                    ? t('common.unknown')
+                    : rawSenderName;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/components/ChatPanel.tsx` around lines 2530 - 2536, Update the
displaySenderName condition in the sender-name construction to localize the
literal “Unknown” only when protocol is MeshCore; preserve a legitimate
“Unknown” name for Meshtastic and Reticulum. Add tests covering MeshCore
sentinel localization and non-MeshCore nodes named “Unknown”.

Sources: Coding guidelines, Path instructions

scripts/check-insecure-temp-files.test.mjs-65-75 (1)

65-75: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Cover the asynchronous mkdir rule.

The checker now registers both mkdirSync and mkdir in scripts/check-insecure-temp-files.mjs Lines 30-32. This test covers only mkdirSync. Add a behavioral case for fs.mkdir(...).

As per coding guidelines and path instructions, behavioral changes must include a passing test before the task is considered complete.

Suggested regression test
+  it('fails on mkdir to predictable tmpdir path', () => {
+    const result = runCheckOnSnippet(`
+import fs from 'fs';
+import os from 'os';
+import path from 'path';
+const dir = path.join(os.tmpdir(), 'mesh-client-appimage-x64-2');
+fs.mkdir(dir, { recursive: true }, () => {});
+`);
+    expect(result.status).toBe(1);
+    expect(result.stderr).toMatch(/insecure-temporary-file|predictable/);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-insecure-temp-files.test.mjs` around lines 65 - 75, Add a
behavioral test alongside the existing “fails on mkdirSync to predictable tmpdir
path” case that runs a snippet using asynchronous fs.mkdir on a predictable
os.tmpdir()-derived path and asserts the checker exits with status 1 and reports
the insecure-temporary-file or predictable diagnostic. Reuse the existing
runCheckOnSnippet helper and test structure.

Sources: Coding guidelines, Path instructions

🧹 Nitpick comments (1)
src/renderer/hooks/useSendMessage.test.ts (1)

381-383: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the repository console-warning test helper.

Replace vi.spyOn(console, 'warn') with mockConsoleWarn or withMockedConsoleWarn. The renderer test rules prohibit ad-hoc console.warn spies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/hooks/useSendMessage.test.ts` around lines 381 - 383, Update the
test case “SoftAP dead-accepted: marks failed when live reopen yields no handle”
to use the repository’s mockConsoleWarn or withMockedConsoleWarn helper instead
of vi.spyOn(console, 'warn'), preserving the existing warning assertions and
cleanup behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/test-linux-appimage-reticulum-sidecar.mjs`:
- Around line 199-210: The AppImage extraction flow must not delete and recreate
the unique directory returned by mkdtempSync. Update prepareAppImageExtractDir
and its callers around extractAppImageWithUnsquashfs or spawnSync so setup only
creates needed children under extractDir, or is performed atomically, preserving
extraction and cleanup in the original unique directory.

In `@scripts/test-win-nsis-install.mjs`:
- Around line 171-176: Update probe7zExtract to use the existing directory
created by mkdtempSync without removing or recreating it before invoking 7-Zip.
Preserve the unique temporary-directory flow in the surrounding probeDir setup
and retain cleanup in the finally block.
- Around line 184-185: Wrap the installer execution, validation, and
failure-reporting flow using workDir and logPath in a try/finally block, and
remove workDir in finally after all diagnostic logs have been read. Preserve the
existing success and error behavior while ensuring the mkdtemp-created directory
is cleaned up for every outcome.

In `@src/main/tak-server-manager.ts`:
- Around line 79-83: Add a regression test covering the error-handling path that
invokes sanitizeLogMessage, using an error message containing CR or LF
characters. Assert that the sanitized value is used consistently for console
logging, the status.error field, and the emitted error event.

In `@src/renderer/stores/nomadPageViewerStore.ts`:
- Around line 559-560: Replace String(err) with errLikeToLogString(err) in the
pageReadyToast dynamic-import rejection handler in
src/renderer/stores/nomadPageViewerStore.ts:559-560. Update
src/renderer/runtime/auditHardening.contract.test.ts:58-63 to assert that this
handler uses errLikeToLogString(err); both changes target the existing
rejection-handler contract.

---

Other comments:
In `@scripts/check-insecure-temp-files.test.mjs`:
- Around line 65-75: Add a behavioral test alongside the existing “fails on
mkdirSync to predictable tmpdir path” case that runs a snippet using
asynchronous fs.mkdir on a predictable os.tmpdir()-derived path and asserts the
checker exits with status 1 and reports the insecure-temporary-file or
predictable diagnostic. Reuse the existing runCheckOnSnippet helper and test
structure.

In `@src/renderer/components/ChatPanel.tsx`:
- Around line 2530-2536: Update the displaySenderName condition in the
sender-name construction to localize the literal “Unknown” only when protocol is
MeshCore; preserve a legitimate “Unknown” name for Meshtastic and Reticulum. Add
tests covering MeshCore sentinel localization and non-MeshCore nodes named
“Unknown”.

---

Nitpick comments:
In `@src/renderer/hooks/useSendMessage.test.ts`:
- Around line 381-383: Update the test case “SoftAP dead-accepted: marks failed
when live reopen yields no handle” to use the repository’s mockConsoleWarn or
withMockedConsoleWarn helper instead of vi.spyOn(console, 'warn'), preserving
the existing warning assertions and cleanup behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: 72b646eb-0c21-4816-908b-83e239efaa8f

📥 Commits

Reviewing files that changed from the base of the PR and between 3bfe618 and 929fb5a.

⛔ Files ignored due to path filters (16)
  • src/renderer/locales/cs/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/de/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/en/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/es/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/fr/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/id/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/it/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/ja/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/ko/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/nl/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/pl/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/pt-BR/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/ru/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/tr/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/uk/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/zh/translation.json is excluded by !src/renderer/locales/**
📒 Files selected for processing (59)
  • AGENTS.md
  • CONTRIBUTING.md
  • docs/accessibility-checklist.md
  • docs/diagnostics.md
  • docs/reticulum-sidecar-ipc.md
  • scripts/check-insecure-temp-files.mjs
  • scripts/check-insecure-temp-files.test.mjs
  • scripts/check-log-injection.mjs
  • scripts/test-linux-appimage-reticulum-sidecar.mjs
  • scripts/test-win-nsis-install.mjs
  • src/main/database.test.ts
  • src/main/index.contract.test.ts
  • src/main/index.ipc-security.test.ts
  • src/main/index.ts
  • src/main/meshcore-mqtt-adapter.ts
  • src/main/mqtt-manager.ts
  • src/main/tak-server-manager.ts
  • src/main/updater.contract.test.ts
  • src/main/updater.ts
  • src/renderer/components/ChatPanel.tsx
  • src/renderer/components/GamesPanel.test.tsx
  • src/renderer/components/GamesPanel.tsx
  • src/renderer/components/NodeListPanel.tsx
  • src/renderer/components/RawPacketLogPanel.tsx
  • src/renderer/components/games/ChessBoard.test.tsx
  • src/renderer/components/games/ChessBoard.tsx
  • src/renderer/components/games/TicTacToeBoard.test.tsx
  • src/renderer/components/games/TicTacToeBoard.tsx
  • src/renderer/hooks/openMeshCoreTransport.test.ts
  • src/renderer/hooks/useSendMessage.test.ts
  • src/renderer/lib/drivers/ConnectionDriver.test.ts
  • src/renderer/lib/gpsSource.test.ts
  • src/renderer/lib/gpsSource.ts
  • src/renderer/lib/lastConnectionStorage.test.ts
  • src/renderer/lib/lastConnectionStorage.ts
  • src/renderer/lib/meshcoreRepeaterCredentialStorage.ts
  • src/renderer/lib/meshcoreRoomCredentialStorage.ts
  • src/renderer/lib/meshcoreRoomSyncStorage.ts
  • src/renderer/lib/meshtasticBacklogUtils.ts
  • src/renderer/lib/meshtasticMessageDedup.test.ts
  • src/renderer/lib/meshtasticMessageDedup.ts
  • src/renderer/lib/meshtasticRemoteAdminKeyStorage.ts
  • src/renderer/lib/mqttTls.ts
  • src/renderer/lib/parseStoredJson.ts
  • src/renderer/lib/reticulum/reticulumGamesMetadata.test.ts
  • src/renderer/lib/reticulum/reticulumGamesMetadata.ts
  • src/renderer/lib/timeConstants.ts
  • src/renderer/runtime/auditHardening.contract.test.ts
  • src/renderer/runtime/useMeshcoreRuntime.ts
  • src/renderer/runtime/useMeshtasticRuntime.ts
  • src/renderer/stores/nomadPageViewerStore.ts
  • src/renderer/stores/reticulumGamesStore.test.ts
  • src/shared/appSettingsKeyPrefixes.test.ts
  • src/shared/appSettingsKeyPrefixes.ts
  • src/shared/games-types.ts
  • src/shared/meshtasticTextMessagePayload.test.ts
  • src/shared/meshtasticTextMessagePayload.ts
  • src/shared/mqttTls.test.ts
  • src/shared/mqttTls.ts

Comment thread scripts/test-linux-appimage-reticulum-sidecar.mjs
Comment thread scripts/test-win-nsis-install.mjs
Comment thread scripts/test-win-nsis-install.mjs Outdated
Comment on lines +79 to +83
const safe = sanitizeLogMessage(msg);
console.error('[TakServer]', safe);
this._status = { running: false, port: settings.port, clientCount: 0, error: safe };
this.emit('status', this.getStatus());
this.emit('error', msg);
this.emit('error', safe);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Add a regression test for sanitized server errors.

Test an error message that contains CR or LF characters. Verify that the logged value, status error, and emitted error value are sanitized.

As per coding guidelines, “Behavioral changes must include a passing test before the task is considered complete.” As per path instructions, “pair every behavioral change with passing tests.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/tak-server-manager.ts` around lines 79 - 83, Add a regression test
covering the error-handling path that invokes sanitizeLogMessage, using an error
message containing CR or LF characters. Assert that the sanitized value is used
consistently for console logging, the status.error field, and the emitted error
event.

Sources: Coding guidelines, Path instructions

Comment thread src/renderer/stores/nomadPageViewerStore.ts Outdated
rinchen added 2 commits August 6, 2026 19:44
Preserve mkdtemp extract dirs in packaging smokes, localize MeshCore Unknown only, and tighten TAK/nomad error logging tests.
Mirror Room exclusion so Message, quick DM, unread, and History no longer treat repeaters as chat peers. Sensors stay DM-eligible.

Fixes #809
@rinchen
rinchen merged commit 99b2162 into main Aug 7, 2026
12 checks passed
@rinchen
rinchen deleted the audit branch August 7, 2026 02:05
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.

hide Meshcore DM a repeater

1 participant