Files
..

Edge-Net Test Suite

Comprehensive security and persistence tests for the Edge-Net WebSocket relay server.

Test Coverage

Credit Persistence Tests

  1. Local IndexedDB Persistence - Credits earned locally persist across sessions
  2. QDAG Sync - Credits sync to QDAG (Firestore) as source of truth
  3. Refresh Recovery - After browser refresh, QDAG balance is loaded
  4. Pending Credits - Pending credits shown correctly before sync
  5. Double-Sync Prevention - Prevents duplicate credit awards
  6. Rate Limiting - Prevents abuse via message throttling
  7. Cross-Device Sync - Same publicKey = same credits everywhere

Attack Vectors Tested

  1. Task Completion Spoofing - Prevents nodes from completing tasks not assigned to them
  2. Replay Attacks - Prevents completing the same task multiple times
  3. Credit Self-Reporting - Prevents clients from claiming their own credits
  4. Public Key Spoofing - Tests identity verification and credit attribution
  5. Rate Limiting - Validates per-node message throttling
  6. Message Size Limits - Tests protection against oversized payloads
  7. Connection Limits - Validates per-IP connection restrictions
  8. Task Expiration - Tests cleanup of unfinished tasks

Setup

Prerequisites

  1. Node.js 20+
  2. Running Edge-Net relay server (or will start one for tests)

Installation

cd /workspaces/ruvector/examples/edge-net/tests
npm install

Environment Variables

# Optional: Override relay URL (default: ws://localhost:8080)
export RELAY_URL=ws://localhost:8080

Running Tests

Run all security tests

npm test

Run specific test suite

# Security tests
npm run test:security

# Credit persistence tests
npm run test:credits

# All tests (in sequence)
npm run test:all

Manual credit test (interactive)

npm run test:credits-manual

# With custom relay URL
RELAY_URL=wss://edge-net-relay.example.com npm run test:credits-manual

Watch mode (development)

npm run test:watch

Run with coverage

npm test -- --coverage

Test Structure

tests/
├── relay-security.test.ts     # Security test suite (attack vectors)
├── credit-persistence.test.ts # Credit persistence test suite
├── manual-credit-test.cjs     # Manual interactive credit test
├── jest.config.js             # Jest configuration
├── package.json               # Test dependencies
├── tsconfig.json              # TypeScript configuration
└── README.md                  # This file

Credit Persistence Flow

Architecture Overview

[User Completes Task]
        |
        v
[Relay Verifies Assignment]  -----> [REJECT if not assigned]
        |
        v
[QDAG Credits Account (Firestore)]  <-- SOURCE OF TRUTH
        |
        v
[Relay Sends credit_earned + balance]
        |
        v
[Dashboard Updates Local State]
        |
        v
[IndexedDB Saves as Backup]

Reconnection Flow

[Browser Refresh / App Reopen]
        |
        v
[Load from IndexedDB (backup)]
        |
        v
[Connect to Relay with publicKey]
        |
        v
[Request ledger_sync from QDAG]
        |
        v
[QDAG Returns Authoritative Balance]
        |
        v
[Update Local State to Match QDAG]

Key Design Principles

  1. QDAG is Source of Truth - Credits stored in Firestore, keyed by publicKey
  2. Same Key = Same Credits - publicKey links all devices/CLI
  3. Server Awards Credits - Only relay can credit accounts
  4. Client Cannot Self-Report - ledger_update is rejected
  5. Double-Completion Prevented - completedTasks set tracks finished work
  6. Task Assignment Verified - assignedTasks map tracks who works on what

Test Scenarios

1. Task Completion Spoofing

// Scenario: Attacker tries to complete victim's task
const victim = await createTestNode();
const attacker = await createTestNode();

// Task assigned to victim
// Attacker tries to complete it
attacker.ws.send({ type: 'task_complete', taskId: victimTask });

// Expected: Error "Task not assigned to you"

2. Replay Attacks

// Scenario: Node tries to complete same task twice
worker.ws.send({ type: 'task_complete', taskId });  // First time: succeeds
worker.ws.send({ type: 'task_complete', taskId });  // Second time: fails

// Expected: Error "Task already completed"

3. Credit Self-Reporting

// Scenario: Client tries to report their own credits
attacker.ws.send({
  type: 'ledger_update',
  ledger: { earned: 999999999, spent: 0 }
});

// Expected: Error "Credit self-reporting disabled"

4. Public Key Spoofing

// Scenario: Attacker uses victim's public key
const attacker = await createTestNode('attacker', 'victim-public-key');

// Expected: Cannot steal existing credits (assigned at task time)
// Note: Current implementation allows read-only access to balances

Expected Results

All tests should pass:

✓ Task Completion Spoofing
  ✓ should reject task completion from node not assigned
  ✓ should only allow assigned worker to complete task

✓ Replay Attacks
  ✓ should reject duplicate task completion

✓ Credit Self-Reporting
  ✓ should reject ledger_update messages
  ✓ should only credit via verified completions

✓ Public Key Spoofing
  ✓ should not allow stealing credits via key spoofing
  ✓ should maintain separate ledgers

✓ Rate Limiting
  ✓ should enforce rate limits per node

✓ Message Size Limits
  ✓ should reject oversized messages

✓ Connection Limits
  ✓ should limit connections per IP

✓ Combined Attack Scenario
  ✓ should defend against combined attacks

Known Issues / Limitations

Current Implementation Gaps

  1. No cryptographic signature verification - Public key ownership not proven

    • Impact: Medium (cannot verify identity)
    • Status: Documented in security audit
    • Recommendation: Implement Ed25519 signature validation
  2. Unbounded completed tasks set - Memory leak over time

    • Impact: Low (development only)
    • Status: Needs cleanup implementation
    • Recommendation: Periodic cleanup of old tasks
  3. Read-only public key spoofing - Can check another user's balance

    • Impact: Low (privacy leak only)
    • Status: By design (multi-device support)
    • Recommendation: Rate-limit balance queries

Security Audit

See comprehensive security audit report:

  • Report: /workspaces/ruvector/examples/edge-net/docs/SECURITY_AUDIT_REPORT.md
  • Date: 2026-01-03
  • Rating: GOOD (with recommendations)

Debugging Failed Tests

Relay server not running

# Start relay server in separate terminal
cd /workspaces/ruvector/examples/edge-net/relay
npm start

Connection timeout errors

# Check if relay is accessible
curl http://localhost:8080/health

# Expected: {"status":"healthy","nodes":0,"uptime":...}

Firestore errors

# Tests use in-memory ledger cache
# Firestore failures won't break tests, but will log errors
# Set up Firestore credentials for full integration testing
export GOOGLE_CLOUD_PROJECT=your-project-id
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

Contributing

When adding new security tests:

  1. Add test to appropriate describe() block
  2. Use helper functions (createTestNode, waitForMessage, etc.)
  3. Always clean up connections in finally blocks
  4. Document the attack vector being tested
  5. Update this README with new test scenarios

Resources