Added comprehensive documentation of discovered differences between CLI and ROOM contact types when using the add_pending command. Key findings from real-world testing (2025-12-29): CLI Contacts (flexible matching): - ✅ Full name works: "StNMobile T1000e" - ✅ Name prefix works: "StN" - ✅ Public key prefix works: "2ce5514" - ✅ Full public key works ROOM Contacts (strict matching): - ❌ Full name fails: "TK room cwiczebny🔆" (UTF-8 issues) - ❌ Name prefix fails: "TK room" - ❌ Public key prefix fails: "b3fec489" - ✅ ONLY full public key works Root cause: meshcli uses different matching logic for different contact types, likely to prevent accidental approval of group rooms which have different security/privacy implications. Recommendation: UI should always send full public_key (not name) when calling POST /add_pending to ensure compatibility with all contact types. Updated sections: - Selector formats with CLI/ROOM compatibility notes - New "Important Discovery: Contact Type Differences" section - Updated test commands with real-world results - JavaScript code examples for UI implementation - Best practices for UI design This documentation will guide the next phase: UI implementation for pending contact management. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
15 KiB
Pending Contacts API - Technical Notes
Overview
This document describes the implementation of pending contacts management API in meshcore-bridge. This feature enables manual approval of new contacts when manual_add_contacts mode is enabled in meshcli.
Branch: dev-2
Status: Implemented ✅ (API only, no UI yet)
Date: 2025-12-29
Problem Statement
When meshcli runs with manual_add_contacts on, new contacts attempting to connect are placed in a "pending" state instead of being automatically added to the contacts list. This provides security benefits:
- Control over network access - Only approved contacts can communicate with the node
- Prevention of spam/unwanted contacts - Filter out random nodes attempting connection
- Explicit trust model - User decides who to trust on the mesh network
However, managing pending contacts required manual meshcli commands, which was inconvenient for web interface users.
Solution
Added two new HTTP endpoints to meshcore-bridge for programmatic pending contact management:
GET /pending_contacts- List all contacts awaiting approvalPOST /add_pending- Approve and add a specific pending contact
Both endpoints use the existing persistent meshcli session architecture (no new processes spawned).
Implementation Details
1. Session Initialization
Modified _init_session_settings() in meshcore-bridge/bridge.py:122-136 to enable manual contact approval:
def _init_session_settings(self):
"""Configure meshcli session for advert logging, message subscription, and manual contact approval"""
if self.process and self.process.stdin:
self.process.stdin.write('set json_log_rx on\n')
self.process.stdin.write('set print_adverts on\n')
self.process.stdin.write('set manual_add_contacts on\n') # NEW
self.process.stdin.write('msgs_subscribe\n')
self.process.stdin.flush()
Why in init?
- Persistent setting - applies to entire session lifetime
- No need to re-enable after watchdog restart
- Consistent behavior across all API calls
2. GET /pending_contacts Endpoint
Location: meshcore-bridge/bridge.py:499-565
Purpose: Retrieve list of contacts awaiting manual approval
Request:
GET /pending_contacts HTTP/1.1
Host: meshcore-bridge:5001
Response (success):
{
"success": true,
"pending": [
{
"name": "Skyllancer",
"public_key": "f9ef123abc456..."
},
{
"name": "KRA Reksio mob2🐕",
"public_key": "41d5789def012..."
}
],
"raw_stdout": "Skyllancer: f9ef123abc456...\nKRA Reksio mob2🐕: 41d5789def012..."
}
Response (no pending contacts):
{
"success": true,
"pending": [],
"raw_stdout": ""
}
Response (error):
{
"success": false,
"error": "meshcli session not initialized",
"pending": []
}
Implementation Notes:
- Executes
pending_contactscommand viaMeshCLISession.execute_command() - Parses meshcli output format:
"ContactName: <hex_public_key>" - Removes spaces from public key hex (meshcli may insert spaces for readability)
- Only parses lines containing colon
: - Trims whitespace from contact names
- Returns empty array if no pending contacts exist
- Includes
raw_stdoutfor debugging/troubleshooting
Parsing Logic:
for line in stdout.split('\n'):
line = line.strip()
if ':' in line:
parts = line.split(':', 1)
if len(parts) == 2:
name = parts[0].strip()
public_key = parts[1].strip().replace(' ', '')
if name and public_key:
pending.append({'name': name, 'public_key': public_key})
3. POST /add_pending Endpoint
Location: meshcore-bridge/bridge.py:568-629
Purpose: Approve and add a pending contact to the contacts list
Request:
POST /add_pending HTTP/1.1
Host: meshcore-bridge:5001
Content-Type: application/json
{
"selector": "Skyllancer"
}
Selector formats supported (by meshcli):
- Full contact name:
"Skyllancer"(works for CLI contacts) - Public key prefix:
"f9ef123"(works for CLI contacts, may not work for ROOM) - Full public key:
"f9ef123abc456..."(RECOMMENDED - works for all contact types)
Response (success):
{
"success": true,
"stdout": "Contact added successfully",
"stderr": "",
"returncode": 0
}
Response (validation error):
{
"success": false,
"stdout": "",
"stderr": "selector must be a non-empty string",
"returncode": -1
}
Implementation Notes:
- Validates
selectoris non-empty string - Trims whitespace from selector before execution
- Executes
add_pending <selector>command via persistent session - Returns full command result (stdout, stderr, returncode)
- Uses default timeout (10 seconds)
Validation:
if not isinstance(selector, str) or not selector.strip():
return jsonify({
'success': False,
'stderr': 'selector must be a non-empty string',
'returncode': -1
}), 400
Important Discovery: Contact Type Differences
🔍 Testing revealed different behavior for different contact types:
CLI Contacts (Clients)
Examples: StNMobile T1000e, ML2056, olekstomek
✅ All selector formats work:
- Full name:
"StNMobile T1000e"→ SUCCESS - Name prefix:
"StN"→ SUCCESS - Public key prefix:
"2ce5514"→ SUCCESS - Full public key:
"2ce5514826d39a44d23eb8eb9539676b57cae234528573c45d44bdd6ed01eeb5"→ SUCCESS
ROOM Contacts (Group Rooms)
Examples: TK room cwiczebny🔆
❌ Name-based selectors DO NOT work:
- Full name:
"TK room cwiczebny🔆"→ FAILED (also UTF-8 encoding issues in shell) - Name prefix:
"TK room"→ FAILED
❌ Public key prefix DOES NOT work:
- Prefix:
"b3fec489"→ FAILED
✅ Only FULL public key works:
- Full public key:
"b3fec489e1ee2d0277bd16de957253c8f5aa44a721df722224bbdc1edc5829b6"→ SUCCESS
Root Cause Analysis
The add_pending command in meshcli appears to use different matching logic for different contact types:
- CLI contacts: Flexible matching - accepts name prefix, key prefix, or full key
- ROOM contacts: Strict matching - requires exact full public key
This may be intentional behavior to prevent accidental approval of group rooms, which have different security/privacy implications than individual client contacts.
Recommendation for UI Implementation
Always use full public key for add_pending calls:
// Good - works for all contact types
fetch('/add_pending', {
method: 'POST',
body: JSON.stringify({
selector: pendingContact.public_key // Full key from GET /pending_contacts
})
})
// Bad - may fail for ROOM contacts
fetch('/add_pending', {
method: 'POST',
body: JSON.stringify({
selector: pendingContact.name // Won't work for ROOMs
})
})
UI Design Consideration:
Since GET /pending_contacts already returns both name and public_key, the UI should:
- Display human-readable name for user
- Send full public_key to API (not name)
- This ensures compatibility with all contact types
Example UI flow:
User sees: "TK room cwiczebny🔆" [Approve button]
User clicks: Approve
Frontend sends: {"selector": "b3fec489e1ee...5829b6"} ← Full public key
Testing
Prerequisites
The bridge container must have:
manual_add_contacts onenabled (automatic in session init)- Pending contacts available (requires other nodes trying to connect)
Test Commands
From host or inside mc-webui container:
# List pending contacts
curl -s http://meshcore-bridge:5001/pending_contacts | jq
# Add a CLI contact by name (works for CLI, not ROOM)
curl -s -X POST http://meshcore-bridge:5001/add_pending \
-H 'Content-Type: application/json' \
-d '{"selector":"ML2056"}' | jq
# Add by full public key (RECOMMENDED - works for all types)
curl -s -X POST http://meshcore-bridge:5001/add_pending \
-H 'Content-Type: application/json' \
-d '{"selector":"b3fec489e1ee2d0277bd16de957253c8f5aa44a721df722224bbdc1edc5829b6"}' | jq
Real-world test results (2025-12-29):
-
CLI contacts - flexible matching:
# All worked successfully curl -d '{"selector":"StNMobile T1000e"}' → ✅ SUCCESS curl -d '{"selector":"ML2056"}' → ✅ SUCCESS curl -d '{"selector":"2ce5514"}' → ✅ SUCCESS (prefix) -
ROOM contact - strict matching:
# Failed attempts curl -d '{"selector":"TK room cwiczebny🔆"}' → ❌ FAILED (UTF-8 encoding) curl -d '{"selector":"TK room"}' → ❌ FAILED (name prefix) curl -d '{"selector":"b3fec489"}' → ❌ FAILED (key prefix) # Success curl -d '{"selector":"b3fec489e1ee...5829b6"}' → ✅ SUCCESS (full key)
Expected workflow:
- New node attempts connection
GET /pending_contactsshows the node in pending list withnameandpublic_keyPOST /add_pendingwith fullpublic_key(not name!) approves the contactGET /pending_contactsno longer shows the approved contact (moved to regular contacts)
Best Practice: Always use full public_key from the GET /pending_contacts response to ensure compatibility with all contact types (CLI, ROOM, REP, SENS).
Architecture Benefits
Reuses Persistent Session
- No new subprocess spawning
- Uses existing command queue (FIFO serialization)
- Same event-based synchronization mechanism
- Consistent error handling with other endpoints
Thread-safe
- Commands queued through
queue.Queue() - Protected by
pending_lockduring response handling - No race conditions with other CLI commands
Consistent with /cli Endpoint
- Same request/response format
- Same timeout handling
- Same error reporting structure
Future Work
UI Integration (Next Phase)
Planned features:
- Pending Contacts Badge - Notification icon showing count of pending contacts
- Pending Contacts Modal - List view with approve/reject buttons
- Auto-refresh - Poll
/pending_contactsevery 30-60 seconds - Notifications - Toast when new pending contacts appear
UI Mockup:
┌─────────────────────────────────────┐
│ Pending Contact Requests [X] │
├─────────────────────────────────────┤
│ Skyllancer │
│ f9ef123abc... │
│ [Approve] [Reject] │
├─────────────────────────────────────┤
│ KRA Reksio mob2🐕 │
│ 41d5789def... │
│ [Approve] [Reject] │
└─────────────────────────────────────┘
Additional API Endpoints (Consideration)
POST /reject_pending - Reject a pending contact
{
"selector": "Skyllancer"
}
DELETE /pending_contacts - Flush all pending contacts
- Executes
flush_pendingmeshcli command - Useful for mass-reject after scanning for unwanted connections
Settings Integration
Add UI toggle in Settings modal:
☑ Manual Contact Approval
Require explicit approval for new contacts
This would execute:
# Enable
curl -X POST /cli -d '{"args":["set","manual_add_contacts","on"]}'
# Disable
curl -X POST /cli -d '{"args":["set","manual_add_contacts","off"]}'
Security Considerations
Why Manual Contact Approval?
- DoS Prevention - Prevents flooding with fake contact requests
- Network Privacy - Control who can see your node
- Trust Model - Explicit approval creates stronger trust relationships
- Spam Filtering - Reject unwanted contact attempts
Trade-offs
Pros:
- Enhanced security and privacy
- User controls network access
- Prevents automatic addition of random nodes
Cons:
- Requires user interaction for every new contact
- May miss legitimate contacts if user doesn't check pending list regularly
- Additional management overhead
Recommendation
Use manual approval when:
- Running on public/shared networks
- Privacy is a concern
- Network has history of spam/unwanted contacts
- Small, controlled mesh network
Use auto-approval (default) when:
- Private/trusted network environment
- Ease of use is priority
- Large mesh network where manual approval is impractical
Meshcli Command Reference
pending_contacts
meshcli> pending_contacts
Skyllancer: f9ef123abc456def789012345678901234567890abcdef
KRA Reksio mob2🐕: 41d5789def012345678901234567890abcdefabcdef012
add_pending
# By name
meshcli> add_pending Skyllancer
Contact added successfully
# By public key prefix (first few chars)
meshcli> add_pending f9ef123
Contact added successfully
# By full public key
meshcli> add_pending f9ef123abc456def789012345678901234567890abcdef
Contact added successfully
flush_pending
meshcli> flush_pending
All pending contacts removed
Manual mode toggle
# Enable manual approval
meshcli> set manual_add_contacts on
manual_add_contacts set to on
# Disable (auto-approve)
meshcli> set manual_add_contacts off
manual_add_contacts set to off
# Check current setting
meshcli> get manual_add_contacts
manual_add_contacts: on
Deployment
Files Modified
-
meshcore-bridge/bridge.py
- Added
manual_add_contacts onto session init (line 131) - Added
GET /pending_contactsendpoint (lines 499-565) - Added
POST /add_pendingendpoint (lines 568-629)
- Added
-
Dockerfile
- Added
curlpackage for testing (line 7)
- Added
-
README.md
- Added "Testing Bridge API" section (lines 362-406)
- Documented endpoints with examples
Commit
Branch: dev-2
Commit: 815adb5 - "feat(bridge): Add pending contacts management API endpoints"
Deployment Steps
# On server
cd ~/mc-webui
git fetch
git checkout dev-2
git pull origin dev-2
docker compose up -d --build
Verification
# Check logs for successful init
docker compose logs meshcore-bridge | grep manual_add_contacts
# Should see: "Session settings applied: ... manual_add_contacts=on ..."
# Test endpoint
docker exec mc-webui curl -s http://meshcore-bridge:5001/pending_contacts | jq
References
- meshcore-cli documentation: technotes/meshcore-cli.md
- Persistent session architecture: technotes/persistent-meshcli-session.md
- Meshcli command reference:
meshcli -h | grep pendingormeshcli> ? pending_contacts
Author: Claude Code (Anthropic) Date: 2025-12-29 Status: API Implemented ✅ | UI Pending ⏳