Skip to content

feat: traceroute request tracking and auth documentation#4

Merged
Yeraze merged 3 commits into
mainfrom
feat/traceroute-request-tracking-auth-docs
Sep 27, 2025
Merged

feat: traceroute request tracking and auth documentation#4
Yeraze merged 3 commits into
mainfrom
feat/traceroute-request-tracking-auth-docs

Conversation

@Yeraze

@Yeraze Yeraze commented Sep 27, 2025

Copy link
Copy Markdown
Owner

Summary

  • Add screenshot to documentation
  • Implement traceroute request time tracking to fix scheduling issues
  • Document authentication recommendations in README

Changes

Traceroute Request Tracking

Previously, the automatic traceroute scheduler would get stuck on nodes that never complete traceroutes (offline nodes, unreachable nodes, etc.), preventing other nodes from getting refreshed traceroutes. This PR fixes the issue by tracking when traceroutes are requested rather than when they complete.

  • Added lastTracerouteRequest field to nodes database table
  • Updated getNodeNeedingTraceroute() to prioritize nodes by request time instead of completion time
  • Created recordTracerouteRequest() method to update timestamp when traceroute is initiated
  • Integrated recording into sendTraceroute() method

This ensures all nodes get scheduled for traceroutes in a fair rotation, regardless of whether they successfully respond.

Authentication Documentation

Added clear documentation that MeshMonitor has no internal authentication and should be proxied behind an auth provider:

  • Listed recommended solutions (Authentik, Pocketbase, Authelia, nginx)
  • Provided nginx basic auth example
  • Referenced Authentik documentation for more complex setups

Documentation

  • Added screenshot image to docs folder

Test plan

  • Built and deployed with Docker
  • Verify database migration adds new column
  • Verify traceroute scheduler cycles through all nodes
  • Verify nodes that don't respond don't block the queue

🤖 Generated with Claude Code

Yeraze and others added 2 commits September 27, 2025 10:10
- Add lastTracerouteRequest field to nodes table
- Update getNodeNeedingTraceroute to prioritize by request time instead of completion
- Record timestamp when traceroute is initiated via recordTracerouteRequest()
- Prevents nodes that never complete traceroutes from blocking scheduler
- Ensures all nodes get refreshed traceroutes based on when they were last requested

Also add Authentication section to README documenting lack of internal auth
and recommending proxy solutions like Authentik, Pocketbase, or nginx.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude

claude Bot commented Sep 27, 2025

Copy link
Copy Markdown
Contributor

Claude finished @Yeraze's task —— View job


PR Review Complete ✅

I've thoroughly reviewed this pull request implementing traceroute request tracking and authentication documentation. Here's my comprehensive feedback:

Progress:

  • Read repository guidelines (CLAUDE.md)
  • Analyze database schema changes
  • Review traceroute scheduling logic
  • Examine authentication documentation
  • Provide comprehensive feedback

Code Quality & Best Practices

Strong Points:

  • Clean separation of concerns between database layer and business logic
  • Proper error handling in migration code
  • Good use of TypeScript interfaces and type safety
  • Consistent code style throughout the changes
  • Appropriate use of transactions and prepared statements

Database Migration Implementation:

  • The migration for lastTracerouteRequest in database.ts:301-308 follows the established pattern correctly
  • Proper error handling with duplicate column check
  • Uses INTEGER type which is appropriate for timestamps

🐛 Potential Issues

Minor Issues Found:

  1. Missing Index (database.ts:313-328): The new lastTracerouteRequest field should have an index for performance since it's used in ORDER BY clauses in getNodeNeedingTraceroute().

    Recommendation: Add this index:

    CREATE INDEX IF NOT EXISTS idx_nodes_lastTracerouteRequest ON nodes(lastTracerouteRequest);
  2. Missing Update Query (database.ts:346-422): The upsertNode() method doesn't include lastTracerouteRequest in either the INSERT or UPDATE statements, meaning manual updates to this field could be overwritten.

    Impact: Low - the field is only updated via recordTracerouteRequest(), but this creates inconsistency.


Performance Considerations

Positive:

  • The new scheduling logic (database.ts:836-860) is efficient with proper LIMIT 1 usage
  • Two-tier approach (NULL first, then oldest) prevents expensive full table scans
  • Uses appropriate ASC ordering for finding oldest requests

Recommendations:

  • Add the missing index mentioned above for optimal query performance
  • Consider adding a composite index on (nodeNum, lastTracerouteRequest) if this becomes a bottleneck

🔒 Security Review

Authentication Documentation (README.md:134-160):

  • ✅ Clearly states no internal authentication
  • ✅ Provides appropriate security recommendations
  • ✅ Includes practical examples (nginx basic auth, Authentik)
  • ✅ Links to external documentation appropriately

Code Security:

  • ✅ No SQL injection risks (uses prepared statements)
  • ✅ No sensitive data exposure
  • ✅ Proper input validation in place

🧪 Test Coverage Concerns

Missing Test Areas:

  • No tests for the new getNodeNeedingTraceroute() scheduling logic
  • No tests for recordTracerouteRequest() method
  • No verification that migration handles existing data correctly

Recommendations:

  • Add unit tests for the new scheduling algorithm edge cases
  • Test that nodes with NULL lastTracerouteRequest are prioritized correctly
  • Verify the migration doesn't break existing functionality

💡 Suggestions for Improvement

  1. Add Logging Enhancement (database.ts:862-868):

    recordTracerouteRequest(nodeNum: number): void {
      const now = Date.now();
      const stmt = this.db.prepare(`
        UPDATE nodes SET lastTracerouteRequest = ? WHERE nodeNum = ?
      `);
      const result = stmt.run(now, nodeNum);
      if (result.changes === 0) {
        console.warn(`⚠️ Failed to record traceroute request for node ${nodeNum} - node not found`);
      }
    }
  2. Consider Rate Limiting: The current implementation doesn't prevent rapid traceroute requests to the same node. Consider adding a minimum interval check.

  3. Documentation Enhancement: Add inline comments explaining the two-tier scheduling logic for future maintainers.


📊 Overall Assessment

This is a well-implemented solution that effectively addresses the traceroute scheduling problem. The approach of tracking request time instead of completion time is sound and will prevent nodes from getting stuck in the queue.

Risk Level: 🟢 Low - Changes are isolated and follow existing patterns
Readiness:Ready to merge with minor improvements recommended

The authentication documentation is comprehensive and provides appropriate security guidance for production deployments.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude

claude Bot commented Sep 27, 2025

Copy link
Copy Markdown
Contributor

Claude finished @Yeraze's task —— View job


PR Review Complete ✅

I've completed the code review for this traceroute request tracking and authentication documentation PR. Overall, this is a solid implementation that addresses the described scheduling issue effectively.

Review Summary:

  • Code quality and best practices
  • Potential bugs or issues
  • Performance considerations
  • Security concerns
  • Test coverage

Code Quality & Best Practices ✅

Database Changes (src/services/database.ts)

Excellent implementation:

  • ✅ Clean database migration in migrateSchema() with proper error handling
  • ✅ New lastTracerouteRequest field properly added to DbNode interface
  • ✅ Migration uses safe ALTER TABLE with duplicate column detection
  • recordTracerouteRequest() method is simple and focused
  • getNodeNeedingTraceroute() logic is well-structured with clear priority:
    1. Nodes never requested (fair initial distribution)
    2. Nodes with oldest request time (fair rotation)

Traceroute Integration (src/server/meshtasticManager.ts)

Properly integrated:

  • recordTracerouteRequest() called immediately after sending request in sendTraceroute() method (line 2351)
  • ✅ Placement is correct - records the request regardless of whether the target responds
  • ✅ Auto-traceroute scheduler uses the updated logic (line 390)

Documentation (README.md)

Comprehensive authentication guidance:

  • ✅ Clear statement that MeshMonitor has no internal auth
  • ✅ Lists appropriate auth solutions (Authentik, Pocketbase, Authelia, nginx)
  • ✅ Includes practical nginx basic auth example
  • ✅ References external documentation for complex setups

Potential Issues & Improvements 🔍

Minor Issues

  1. Database index optimization: Consider adding an index on lastTracerouteRequest for better performance:

    CREATE INDEX IF NOT EXISTS idx_nodes_lastTracerouteRequest ON nodes(lastTracerouteRequest);
  2. Error handling: The recordTracerouteRequest() call in sendTraceroute() should ideally have error handling to prevent failure from affecting the main traceroute flow.

Suggestions for Future Enhancements

  1. Configuration: Consider making the traceroute interval configurable
  2. Metrics: Add logging/metrics for traceroute request success/failure rates
  3. Admin UI: Could add an admin interface to view traceroute scheduling status

Performance Considerations ⚡

Current implementation is performant:

  • ✅ Database queries are efficient with proper ordering
  • getNodeNeedingTraceroute() uses LIMIT 1 to avoid unnecessary data transfer
  • ✅ Timestamps are stored as integers (efficient storage and comparison)
  • ✅ Migration is non-blocking and safe for existing deployments

Recommended optimization:

  • Add the suggested index for lastTracerouteRequest to improve query performance as the node count grows

Security Considerations 🔒

No security issues identified:

  • ✅ No SQL injection vulnerabilities (uses prepared statements)
  • ✅ Authentication documentation properly explains security posture
  • ✅ No sensitive data exposure in new fields
  • ✅ Database operations follow existing patterns

Authentication documentation is appropriate:

  • ✅ Honest about lack of internal auth
  • ✅ Provides practical security recommendations
  • ✅ Nginx example follows security best practices

Test Coverage 🧪

Areas needing attention:

  • ⚠️ No visible unit tests for new recordTracerouteRequest() method
  • ⚠️ No integration tests for the new scheduling behavior
  • ⚠️ Database migration testing could be added

Recommended tests:

  1. Test that getNodeNeedingTraceroute() properly prioritizes never-requested nodes
  2. Test fair rotation after all nodes have been requested
  3. Test that recordTracerouteRequest() updates timestamps correctly
  4. Integration test verifying the scheduler works with stuck/offline nodes

Overall Assessment 🎯

This is a well-implemented fix that addresses the core issue described in the PR:

  • Problem solved: Stuck offline nodes no longer block the traceroute queue
  • Fair scheduling: All nodes get traceroute requests in rotation
  • Clean implementation: Minimal, focused changes with proper database patterns
  • Good documentation: Clear auth guidance for operators

Recommendation: APPROVE

The implementation is solid, follows good practices, and solves the stated problem effectively. The suggested improvements are minor optimizations that could be addressed in future PRs.

@Yeraze Yeraze merged commit 0210a68 into main Sep 27, 2025
1 check passed
@Yeraze Yeraze deleted the feat/traceroute-request-tracking-auth-docs branch September 27, 2025 14:19
Yeraze added a commit that referenced this pull request Oct 12, 2025
TheWISPRer pushed a commit to TheWISPRer/meshmonitor that referenced this pull request Jun 20, 2026
- Mark messages played from the delivery-success callback, not at enqueue:
  handleCommand returns { responses, playOnDelivery }; a dropped body DM now
  leaves the message pending instead of losing it. (Yeraze#1)
- Wire purgeExpired into databaseMaintenanceService so expired rows are
  reclaimed daily (purgeExpired returns a count). (Yeraze#2)
- Count the per-recipient cap across the recipient's identity forms via an
  injected node resolver, so it can't be bypassed by addressing one node by
  several name forms. (Yeraze#3)
- Mailbox bypasses the per-node cooldown (interactive flow). (Yeraze#4)
- inbox play <sender> filter matches !hex/node-num forms too. (Yeraze#5)
- Non-DM commands return [] (no unsolicited DM). (Yeraze#6)
- inbox delete returns the same response for not-yours vs non-existent ids
  (no id enumeration). (Yeraze#7)
- Wrap the mailbox dispatch in try/catch like the script branch. (Yeraze#8)
- Remove the command-prefix tolerance: canonical msg/inbox only. (Yeraze#9)
- Use shared nodeIdHex (unsigned coerce) for the mailbox log target. (Yeraze#10)

Docs + tests updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Yeraze added a commit that referenced this pull request Jun 20, 2026
…gaps (#3578)

Addresses Claude Code Review findings on PR #3578:
- Sanitize device-controlled message before logging/forwarding: strip control
  chars (log-injection defense) and bound length to 500 (#9, #10).
- Widen protected-cap regex to {1,8} hex digits so a short node id still
  reconciles (#4).
- Exclude clientNotification from the unknown-FromRadio debug JSON.stringify
  dump (#3).
- Drop the redundant `&& this.sourceId` guard (always set) and comment why the
  toast still fires when the DB revert fails (#2, #5).
- Frontend: name the level magic numbers and note they mirror the backend
  NOTIFICATION_LEVEL (#1).
- Tests: sanitizer (control chars/whitespace/truncation/empty), short-hex-id
  parse, and the DB-revert-failure path (#6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
Yeraze added a commit that referenced this pull request Jun 20, 2026
…8 favorite/ignore cap (#3548) (#3578)

* feat(notifications): surface device ClientNotifications + handle firmware 2.8 favorite/ignore cap (#3548)

MeshMonitor decoded FromRadio.ClientNotification (mesh.proto field 16) and
dropped it. Surface these device-originated warnings/errors as toasts, and
handle the firmware 2.8 protected-node-cap refusal for Set Favorite / Ignore.

Backend:
- clientNotificationPolicy.ts (pure/testable): suppression patterns (recurring
  power-save "sleeping for N interval" INFO + key-verification variants + empty
  messages), the 2.8 protected-node-cap refusal parser, level->severity mapping,
  and a per-source ToastThrottle that dedupes identical messages within a window.
- meshtasticProtobufService.ts: add the clientNotification dispatch branch (was
  falling through to the generic catch-all).
- dataEventEmitter.ts: client-notification event type + emitClientNotification().
- meshtasticManager.ts: handleClientNotification() reverts the optimistic
  favorite/ignore flag and re-broadcasts the node when the device refuses at the
  protected-node cap, then applies the suppression/throttle policy and emits the
  toast event. Source-scoped throughout.

Frontend:
- DeviceNotificationToaster.tsx: listens for client-notification inside
  ToastProvider, maps level->severity, shows the toast. Wired into App.tsx.
- WS forwarding + per-source room filtering needed no changes.

Scope: the 2.8 NodeDB warm-tier restructure and the on-disk snr_q4 field do NOT
affect the over-the-air wire MeshMonitor reads. OTA NodeInfo.snr stays a float in
dB; no protobuf/decode change. A regression test guards this. The cap-refusal
warning is only emitted by firmware for the locally-connected node (from == 0).

Tests (20 new, full suite green): policy unit tests, manager handler tests
(reconciliation/suppression/throttle, per-source), and protobuf dispatch + SNR
float guard.

Docs: FAQ (node count + blocked-node 2.8 behavior; device-notification toasts),
IgnoredNodesSection inline help, CHANGELOG, and the support plan dev-note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4

* address review: sanitize device notification text, robustness + test gaps (#3578)

Addresses Claude Code Review findings on PR #3578:
- Sanitize device-controlled message before logging/forwarding: strip control
  chars (log-injection defense) and bound length to 500 (#9, #10).
- Widen protected-cap regex to {1,8} hex digits so a short node id still
  reconciles (#4).
- Exclude clientNotification from the unknown-FromRadio debug JSON.stringify
  dump (#3).
- Drop the redundant `&& this.sourceId` guard (always set) and comment why the
  toast still fires when the DB revert fails (#2, #5).
- Frontend: name the level magic numbers and note they mirror the backend
  NOTIFICATION_LEVEL (#1).
- Tests: sanitizer (control chars/whitespace/truncation/empty), short-hex-id
  parse, and the DB-revert-failure path (#6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TheWISPRer pushed a commit to TheWISPRer/meshmonitor that referenced this pull request Jun 21, 2026
- Mark messages played from the delivery-success callback, not at enqueue:
  handleCommand returns { responses, playOnDelivery }; a dropped body DM now
  leaves the message pending instead of losing it. (Yeraze#1)
- Wire purgeExpired into databaseMaintenanceService so expired rows are
  reclaimed daily (purgeExpired returns a count). (Yeraze#2)
- Count the per-recipient cap across the recipient's identity forms via an
  injected node resolver, so it can't be bypassed by addressing one node by
  several name forms. (Yeraze#3)
- Mailbox bypasses the per-node cooldown (interactive flow). (Yeraze#4)
- inbox play <sender> filter matches !hex/node-num forms too. (Yeraze#5)
- Non-DM commands return [] (no unsolicited DM). (Yeraze#6)
- inbox delete returns the same response for not-yours vs non-existent ids
  (no id enumeration). (Yeraze#7)
- Wrap the mailbox dispatch in try/catch like the script branch. (Yeraze#8)
- Remove the command-prefix tolerance: canonical msg/inbox only. (Yeraze#9)
- Use shared nodeIdHex (unsigned coerce) for the mailbox log target. (Yeraze#10)

Docs + tests updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Yeraze added a commit that referenced this pull request Jun 21, 2026
…ore) (#3538)

* feat: add Dead Drop / Mailbox auto-responder (async message store)

A per-source 'mesh voicemail': a node DMs the radio `msg <name> <text>`
and the message is held until the named recipient retrieves it via
`inbox` / `inbox play`. Implemented as a new auto-responder
responseType ('mailbox'), reusing the existing DM-gating, per-node
cooldown, param extraction, and per-source scoping.

- DB: dead_drop_messages table (SQLite/PostgreSQL/MySQL) + migration 092
- Repository (Drizzle-only) + DatabaseService.deadDrop accessor
- DeadDropService: command brain (store/inbox/play[/sender]/delete/clear,
  180-byte cap, per-recipient & per-sender caps, 7-day expiry, batch play)
- meshtasticManager: 'mailbox' responseType dispatch branch
- UI: 'Mailbox' response type option + guidance in the auto-responder editor
- Tests: 34 (migration registry, repository perSource, service)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): add Mailbox option to the Add-Trigger form select

The Mailbox response type was only added to the per-trigger edit view
(TriggerItem); the separate Add-Trigger form in AutoResponderSection had
its own type <select> (Text/HTTP/Script) that was missed, so new mailbox
triggers couldn't be created from the UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): don't require response text to add a Mailbox trigger

The Add button's disabled gate required a non-empty response field for
all types; Mailbox has no response, so the button stayed greyed out.
Exempt mailbox from the response-required check (matches validateResponse).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): accept mailbox responseType in settings save validation

The settings-save validator required a non-empty response for every
trigger and only allowed text/http/script responseTypes, so saving a
Mailbox trigger failed with a generic 400. Exempt mailbox from the
response-required check and add it to the responseType allowlist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(dead-drop): settings-save accepts mailbox triggers without response text

Regression coverage for the mailbox responseType in the autoResponderTriggers
validator: a mailbox trigger with empty response now saves (200), while
non-mailbox empty responses and unknown responseTypes still 400.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(dead-drop): tolerate optional command keyword prefix

The mailbox service parsed hardcoded msg/inbox, but a trigger configured with
prefixed keywords (e.g. betamsg/betainbox, to coexist with another responder
already using msg/inbox) would fire the handler yet fall through to help. Strip
an optional non-space prefix from the leading verb so prefixed keywords parse
correctly; no-op for plain msg/inbox. Caught by live over-the-air testing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(dead-drop): add Mailbox feature docs + live testing brief

- automation.md: Mailbox response type + 'Mailbox (Dead Drop)' section
  (commands, recipient matching, delivery format, limits, command-prefix
  coexistence, configuration).
- dev-notes/DEAD_DROP_TESTING.md: architecture, automated coverage, and the
  over-the-air validation results (ALTO MF / ALTO LF / ZN Office).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): register dead_drop_messages in migrate-db table lists

The cross-database migrate-db CLI tracks every schema table in TABLE_ORDER /
SKIP_TABLES; migrationTables.test.ts fails if a new schema table isn't listed.
Add dead_drop_messages to TABLE_ORDER and SOURCE_SCOPED_TABLES (it carries a
sourceId, like auto_favorite_targets). Caught by the full Vitest suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(dead-drop): address PR review feedback

- Mark messages played from the delivery-success callback, not at enqueue:
  handleCommand returns { responses, playOnDelivery }; a dropped body DM now
  leaves the message pending instead of losing it. (#1)
- Wire purgeExpired into databaseMaintenanceService so expired rows are
  reclaimed daily (purgeExpired returns a count). (#2)
- Count the per-recipient cap across the recipient's identity forms via an
  injected node resolver, so it can't be bypassed by addressing one node by
  several name forms. (#3)
- Mailbox bypasses the per-node cooldown (interactive flow). (#4)
- inbox play <sender> filter matches !hex/node-num forms too. (#5)
- Non-DM commands return [] (no unsolicited DM). (#6)
- inbox delete returns the same response for not-yours vs non-existent ids
  (no id enumeration). (#7)
- Wrap the mailbox dispatch in try/catch like the script branch. (#8)
- Remove the command-prefix tolerance: canonical msg/inbox only. (#9)
- Use shared nodeIdHex (unsigned coerce) for the mailbox log target. (#10)

Docs + tests updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(dead-drop): recommend enabling Verify response on the mailbox trigger

Played-state is committed from the delivery-success callback; with Verify
response off (maxAttempts=1) a single unacked send could mark a voicemail
played on transmit. Document enabling Verify response so undelivered bodies
resurface. (PR #3538 review follow-up)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: chrisn <chrisn@DebDev1.corp.tlclocal.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Randall Hand <randall.hand@gmail.com>
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