v0.9.6: InfoNet hashchain, Wormhole gate encryption, mesh reputation, 16 community contributors

Gate messages now propagate via the Infonet hashchain as encrypted blobs — every node syncs them
through normal chain sync while only Gate members with MLS keys can decrypt. Added mesh reputation
system, peer push workers, voluntary Wormhole opt-in for node participation, fork recovery,
killwormhole scripts, obfuscated terminology, and hardened the self-updater to protect encryption
keys and chain state during updates.

New features: Shodan search, train tracking, Sentinel Hub imagery, 8 new intelligence layers,
CCTV expansion to 11,000+ cameras across 6 countries, Mesh Terminal CLI, prediction markets,
desktop-shell scaffold, and comprehensive mesh test suite (215 frontend + backend tests passing).

Community contributors: @wa1id, @AlborzNazari, @adust09, @Xpirix, @imqdcr, @csysp, @suranyami,
@chr0n1x, @johan-martensson, @singularfailure, @smithbh, @OrfeoTerkuci, @deuza, @tm-const,
@Elhard1, @ttulttul
This commit is contained in:
anoracleofra-code
2026-03-26 05:58:04 -06:00
parent d363013742
commit 668ce16dc7
363 changed files with 170430 additions and 23203 deletions
@@ -0,0 +1,233 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { NextRequest } from 'next/server';
import { GET as proxyGet } from '@/app/api/[...path]/route';
import {
DELETE as deleteAdminSession,
GET as getAdminSession,
POST as postAdminSession,
} from '@/app/api/admin/session/route';
function extractSessionCookie(setCookie: string): string {
return setCookie.split(';')[0] || '';
}
describe('admin/session boundary hardening', () => {
const originalAdminKey = process.env.ADMIN_KEY;
const originalBackendUrl = process.env.BACKEND_URL;
beforeEach(() => {
process.env.ADMIN_KEY = 'top-secret';
process.env.BACKEND_URL = 'http://127.0.0.1:8000';
vi.restoreAllMocks();
});
afterEach(() => {
process.env.ADMIN_KEY = originalAdminKey;
process.env.BACKEND_URL = originalBackendUrl;
vi.restoreAllMocks();
});
it('rejects invalid admin keys before minting a session', async () => {
const req = new NextRequest('http://localhost/api/admin/session', {
method: 'POST',
body: JSON.stringify({ adminKey: 'wrong-key' }),
headers: { 'Content-Type': 'application/json' },
});
const res = await postAdminSession(req);
const body = await res.json();
expect(res.status).toBe(403);
expect(body.ok).toBe(false);
expect(body.detail).toBe('Invalid admin key');
expect(res.headers.get('set-cookie')).toBeNull();
});
it('accepts a verified admin key and reports the minted session as present', async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const req = new NextRequest('http://localhost/api/admin/session', {
method: 'POST',
body: JSON.stringify({ adminKey: 'top-secret' }),
headers: { 'Content-Type': 'application/json' },
});
const res = await postAdminSession(req);
const cookie = extractSessionCookie(res.headers.get('set-cookie') || '');
expect(res.status).toBe(200);
expect(cookie).toContain('sb_admin_session=');
expect(res.headers.get('cache-control')).toContain('no-store');
expect(fetchMock).toHaveBeenCalledTimes(1);
const getReq = new NextRequest('http://localhost/api/admin/session', {
method: 'GET',
headers: { cookie },
});
const getRes = await getAdminSession(getReq);
const getBody = await getRes.json();
expect(getBody.ok).toBe(true);
expect(getBody.hasSession).toBe(true);
expect(getRes.headers.get('cache-control')).toContain('no-store');
const deleteReq = new NextRequest('http://localhost/api/admin/session', {
method: 'DELETE',
headers: { cookie },
});
const deleteRes = await deleteAdminSession(deleteReq);
expect(deleteRes.status).toBe(200);
expect(deleteRes.headers.get('cache-control')).toContain('no-store');
});
it('invalidates the previous admin session token when a new one is minted', async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const firstReq = new NextRequest('http://localhost/api/admin/session', {
method: 'POST',
body: JSON.stringify({ adminKey: 'top-secret' }),
headers: { 'Content-Type': 'application/json' },
});
const firstRes = await postAdminSession(firstReq);
const firstCookie = extractSessionCookie(firstRes.headers.get('set-cookie') || '');
const secondReq = new NextRequest('http://localhost/api/admin/session', {
method: 'POST',
body: JSON.stringify({ adminKey: 'top-secret' }),
headers: {
'Content-Type': 'application/json',
cookie: firstCookie,
},
});
const secondRes = await postAdminSession(secondReq);
const secondCookie = extractSessionCookie(secondRes.headers.get('set-cookie') || '');
expect(secondCookie).toContain('sb_admin_session=');
expect(secondCookie).not.toBe(firstCookie);
const oldSessionCheck = await getAdminSession(
new NextRequest('http://localhost/api/admin/session', {
method: 'GET',
headers: { cookie: firstCookie },
}),
);
const oldBody = await oldSessionCheck.json();
expect(oldBody.hasSession).toBe(false);
const newSessionCheck = await getAdminSession(
new NextRequest('http://localhost/api/admin/session', {
method: 'GET',
headers: { cookie: secondCookie },
}),
);
const newBody = await newSessionCheck.json();
expect(newBody.hasSession).toBe(true);
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it('rejects session minting when frontend admin key is set but backend has no configured admin key', async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ detail: 'Forbidden — admin key not configured' }), {
status: 403,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const req = new NextRequest('http://localhost/api/admin/session', {
method: 'POST',
body: JSON.stringify({ adminKey: 'top-secret' }),
headers: { 'Content-Type': 'application/json' },
});
const res = await postAdminSession(req);
const body = await res.json();
expect(res.status).toBe(403);
expect(body.ok).toBe(false);
expect(body.detail).toBe('Forbidden — admin key not configured');
expect(res.headers.get('set-cookie')).toBeNull();
});
it('does not forward raw x-admin-key headers through the sensitive proxy path', async () => {
process.env.ADMIN_KEY = '';
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const req = new NextRequest('http://localhost/api/settings/api-keys', {
method: 'GET',
headers: { 'x-admin-key': 'browser-supplied-key' },
});
const res = await proxyGet(req, { params: Promise.resolve({ path: ['settings', 'api-keys'] }) });
const body = await res.json();
expect(res.status).toBe(200);
expect(body.ok).toBe(true);
expect(res.headers.get('cache-control')).toContain('no-store');
const forwarded = fetchMock.mock.calls[0]?.[1];
const forwardedHeaders = new Headers((forwarded as RequestInit | undefined)?.headers);
expect(forwardedHeaders.get('X-Admin-Key')).toBeNull();
});
it('forwards the minted admin session to sensitive proxy paths and preserves upstream errors', async () => {
const verifyMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', verifyMock);
const sessionReq = new NextRequest('http://localhost/api/admin/session', {
method: 'POST',
body: JSON.stringify({ adminKey: 'top-secret' }),
headers: { 'Content-Type': 'application/json' },
});
const sessionRes = await postAdminSession(sessionReq);
const cookie = extractSessionCookie(sessionRes.headers.get('set-cookie') || '');
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ detail: 'Forbidden upstream' }), {
status: 403,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
const req = new NextRequest('http://localhost/api/wormhole/identity', {
method: 'GET',
headers: { cookie },
});
const res = await proxyGet(req, { params: Promise.resolve({ path: ['wormhole', 'identity'] }) });
const body = await res.json();
expect(res.status).toBe(403);
expect(body.detail).toBe('Forbidden upstream');
expect(res.headers.get('cache-control')).toContain('no-store');
const forwarded = fetchMock.mock.calls[0]?.[1];
const forwardedHeaders = new Headers((forwarded as RequestInit | undefined)?.headers);
expect(forwardedHeaders.get('X-Admin-Key')).toBe('top-secret');
});
});
@@ -0,0 +1,57 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const primeAdminSession = vi.fn();
const localControlFetch = vi.fn();
const hasLocalControlBridge = vi.fn();
const canInvokeLocalControl = vi.fn();
vi.mock('@/lib/adminSession', () => ({
primeAdminSession,
}));
vi.mock('@/lib/localControlTransport', () => ({
localControlFetch,
hasLocalControlBridge,
canInvokeLocalControl,
}));
describe('controlPlane native boundary', () => {
beforeEach(() => {
vi.resetModules();
primeAdminSession.mockReset();
localControlFetch.mockReset();
hasLocalControlBridge.mockReset();
canInvokeLocalControl.mockReset();
localControlFetch.mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
});
it('skips browser admin-session priming when a native bridge can invoke the request', async () => {
hasLocalControlBridge.mockReturnValue(true);
canInvokeLocalControl.mockReturnValue(true);
const mod = await import('@/lib/controlPlane');
await mod.controlPlaneFetch('/api/wormhole/gate/message/compose', {
method: 'POST',
body: JSON.stringify({ gate_id: 'infonet', plaintext: 'hello' }),
});
expect(primeAdminSession).not.toHaveBeenCalled();
expect(localControlFetch).toHaveBeenCalledTimes(1);
});
it('still primes browser admin-session when no native invoke path exists', async () => {
hasLocalControlBridge.mockReturnValue(false);
canInvokeLocalControl.mockReturnValue(false);
const mod = await import('@/lib/controlPlane');
await mod.controlPlaneFetch('/api/wormhole/identity');
expect(primeAdminSession).toHaveBeenCalledTimes(1);
expect(localControlFetch).toHaveBeenCalledTimes(1);
});
});
@@ -0,0 +1,52 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
getDesktopNativeControlAuditReport,
installDesktopControlBridge,
} from '@/lib/desktopBridge';
describe('desktopBridge native audit access', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('returns the runtime audit report when available', () => {
Object.defineProperty(globalThis, 'window', {
value: {},
configurable: true,
writable: true,
});
installDesktopControlBridge({
invokeLocalControl: vi.fn(),
getNativeControlAuditReport: vi.fn(() => ({
totalEvents: 2,
totalRecorded: 2,
recent: [],
byOutcome: { allowed: 2 },
})),
});
expect(getDesktopNativeControlAuditReport(5)).toEqual(
expect.objectContaining({
totalEvents: 2,
totalRecorded: 2,
byOutcome: { allowed: 2 },
}),
);
});
it('returns null when no runtime audit report is exposed', () => {
Object.defineProperty(globalThis, 'window', {
value: {},
configurable: true,
writable: true,
});
installDesktopControlBridge({
invokeLocalControl: vi.fn(),
});
expect(getDesktopNativeControlAuditReport(5)).toBeNull();
});
});
@@ -0,0 +1,94 @@
import { describe, expect, it } from 'vitest';
import {
describeNativeControlError,
extractGateTargetRef,
} from '../../lib/desktopControlContract';
describe('extractGateTargetRef', () => {
it('extracts gate_id from gate key rotation payload', () => {
expect(
extractGateTargetRef('wormhole.gate.key.rotate', { gate_id: 'infonet', reason: 'test' }),
).toBe('infonet');
});
it('extracts gate_id from gate message compose payload', () => {
expect(
extractGateTargetRef('wormhole.gate.message.compose', { gate_id: 'ops', plaintext: 'hi' }),
).toBe('ops');
});
it('extracts gate_id from gate proof payload', () => {
expect(extractGateTargetRef('wormhole.gate.proof', { gate_id: 'alpha' })).toBe('alpha');
});
it('extracts gate_id from gate message post payload', () => {
expect(
extractGateTargetRef('wormhole.gate.message.post', { gate_id: 'ops', plaintext: 'hi' }),
).toBe('ops');
});
it('extracts gate_id from gate persona list payload', () => {
expect(
extractGateTargetRef('wormhole.gate.personas.get', { gate_id: 'alpha' }),
).toBe('alpha');
});
it('returns undefined for non-gate commands', () => {
expect(extractGateTargetRef('wormhole.status', undefined)).toBeUndefined();
expect(extractGateTargetRef('settings.news.get', undefined)).toBeUndefined();
});
it('returns undefined when payload has no gate_id', () => {
expect(extractGateTargetRef('wormhole.gate.key.rotate', { reason: 'test' })).toBeUndefined();
expect(extractGateTargetRef('wormhole.gate.key.rotate', null)).toBeUndefined();
expect(extractGateTargetRef('wormhole.gate.key.rotate', 'not-an-object')).toBeUndefined();
});
it('returns undefined when gate_id is empty string', () => {
expect(extractGateTargetRef('wormhole.gate.key.get', { gate_id: '' })).toBeUndefined();
});
});
describe('describeNativeControlError', () => {
it('describes profile mismatch errors', () => {
const err = new Error('native_control_profile_mismatch:settings_only:wormhole_gate_key');
const msg = describeNativeControlError(err);
expect(msg).toContain('Denied');
expect(msg).toContain('session profile');
});
it('describes capability denied errors', () => {
const err = new Error('native_control_capability_denied:wormhole_gate_key');
const msg = describeNativeControlError(err);
expect(msg).toContain('Denied');
expect(msg).toContain('capability');
});
it('describes capability mismatch errors', () => {
const err = new Error('native_control_capability_mismatch:wormhole_gate_content:wormhole_gate_key');
const msg = describeNativeControlError(err);
expect(msg).toContain('Denied');
expect(msg).toContain('capability');
});
it('describes shim enforcement inactivity errors', () => {
const err = new Error('desktop_runtime_shim_enforcement_inactive');
const msg = describeNativeControlError(err);
expect(msg).toContain('Denied');
expect(msg).toContain('native runtime');
});
it('returns null for unrelated errors', () => {
expect(describeNativeControlError(new Error('network_error'))).toBeNull();
expect(describeNativeControlError('some string')).toBeNull();
expect(describeNativeControlError(null)).toBeNull();
expect(describeNativeControlError(undefined)).toBeNull();
});
it('handles plain string errors', () => {
expect(
describeNativeControlError('native_control_profile_mismatch:foo'),
).toContain('Denied');
});
});
@@ -0,0 +1,104 @@
import { describe, expect, it } from 'vitest';
import {
commandToHttpRequest,
httpRequestToInvokeRequest,
} from '@/lib/desktopControlRouting';
describe('desktopControlRouting', () => {
it('maps invoke commands to HTTP requests', () => {
expect(commandToHttpRequest('wormhole.connect')).toEqual({
path: '/api/wormhole/connect',
method: 'POST',
});
expect(commandToHttpRequest('wormhole.gate.key.get', { gate_id: 'infonet' })).toEqual({
path: '/api/wormhole/gate/infonet/key',
method: 'GET',
});
expect(commandToHttpRequest('settings.news.reset')).toEqual({
path: '/api/settings/news-feeds/reset',
method: 'POST',
});
expect(commandToHttpRequest('wormhole.gate.proof', { gate_id: 'infonet' })).toEqual({
path: '/api/wormhole/gate/proof',
method: 'POST',
payload: { gate_id: 'infonet' },
});
expect(
commandToHttpRequest('wormhole.gate.message.post', {
gate_id: 'ops',
plaintext: 'hello',
}),
).toEqual({
path: '/api/wormhole/gate/message/post',
method: 'POST',
payload: { gate_id: 'ops', plaintext: 'hello' },
});
});
it('maps HTTP settings writes back to invoke requests', () => {
expect(
httpRequestToInvokeRequest(
'/api/settings/privacy-profile',
'PUT',
JSON.stringify({ profile: 'high' }),
),
).toEqual({
command: 'settings.privacy.set',
payload: { profile: 'high' },
});
expect(
httpRequestToInvokeRequest(
'/api/wormhole/gate/key/rotate',
'POST',
JSON.stringify({ gate_id: 'infonet', reason: 'operator_reset' }),
),
).toEqual({
command: 'wormhole.gate.key.rotate',
payload: { gate_id: 'infonet', reason: 'operator_reset' },
});
expect(
httpRequestToInvokeRequest(
'/api/wormhole/gate/proof',
'POST',
JSON.stringify({ gate_id: 'infonet' }),
),
).toEqual({
command: 'wormhole.gate.proof',
payload: { gate_id: 'infonet' },
});
expect(
httpRequestToInvokeRequest(
'/api/wormhole/gate/messages/decrypt',
'POST',
JSON.stringify({
messages: [
{
gate_id: 'infonet',
epoch: 3,
ciphertext: 'ct',
nonce: 'n',
sender_ref: 'ref',
},
],
}),
),
).toEqual({
command: 'wormhole.gate.messages.decrypt',
payload: {
messages: [
{
gate_id: 'infonet',
epoch: 3,
ciphertext: 'ct',
nonce: 'n',
sender_ref: 'ref',
},
],
},
});
});
it('returns null for unsupported paths', () => {
expect(httpRequestToInvokeRequest('/api/mesh/status', 'GET')).toBeNull();
});
});
@@ -0,0 +1,60 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createHttpBackedDesktopRuntime } from '@/lib/desktopRuntimeShim';
describe('desktopRuntimeShim enforcement guard', () => {
const fetchMock = vi.fn();
const warnMock = vi.spyOn(console, 'warn').mockImplementation(() => {});
beforeEach(() => {
fetchMock.mockReset();
fetchMock.mockResolvedValue(
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('refuses strictly enforced commands in the HTTP-backed shim', async () => {
const runtime = createHttpBackedDesktopRuntime();
await expect(
runtime.invokeLocalControl?.(
'wormhole.gate.key.rotate',
{ gate_id: 'infonet', reason: 'operator_reset' },
{
capability: 'wormhole_gate_key',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
},
),
).rejects.toThrow('desktop_runtime_shim_enforcement_inactive');
expect(fetchMock).not.toHaveBeenCalled();
expect(warnMock).toHaveBeenCalledWith(
'[desktop-shim] strict native session-profile enforcement is unavailable in the HTTP-backed shim',
expect.objectContaining({
command: 'wormhole.gate.key.rotate',
sessionProfileHint: 'gate_operator',
}),
);
expect(runtime.getNativeControlAuditReport?.(5)).toEqual(
expect.objectContaining({
totalEvents: 1,
totalRecorded: 1,
byOutcome: expect.objectContaining({ shim_refused: 1 }),
lastDenied: expect.objectContaining({
command: 'wormhole.gate.key.rotate',
targetRef: 'infonet',
outcome: 'shim_refused',
}),
}),
);
});
});
@@ -0,0 +1,82 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
describe('localControlTransport capability metadata', () => {
beforeEach(() => {
vi.resetModules();
});
it('attaches capability intent metadata when invoking the native bridge', async () => {
const invoke = vi.fn(async () => ({ ok: true }));
Object.defineProperty(globalThis, 'window', {
value: {
__SHADOWBROKER_LOCAL_CONTROL__: {
invoke,
},
},
configurable: true,
writable: true,
});
const mod = await import('@/lib/localControlTransport');
await mod.localControlFetch('/api/wormhole/gate/key/rotate', {
method: 'POST',
capabilityIntent: 'wormhole_gate_key',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
body: JSON.stringify({ gate_id: 'infonet', reason: 'operator_reset' }),
});
expect(invoke).toHaveBeenCalledWith({
command: 'wormhole.gate.key.rotate',
payload: { gate_id: 'infonet', reason: 'operator_reset' },
meta: {
capability: 'wormhole_gate_key',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
},
});
});
it('falls back to plain fetch when the HTTP-backed shim refuses strict enforcement', async () => {
const invoke = vi.fn(async () => {
throw new Error('desktop_runtime_shim_enforcement_inactive');
});
const fetchMock = vi.fn(async () =>
new Response(JSON.stringify({ ok: true, gate_id: 'infonet' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
vi.stubGlobal('fetch', fetchMock);
Object.defineProperty(globalThis, 'window', {
value: {
__SHADOWBROKER_LOCAL_CONTROL__: {
invoke,
},
},
configurable: true,
writable: true,
});
const mod = await import('@/lib/localControlTransport');
const res = await mod.localControlFetch('/api/wormhole/gate/proof', {
method: 'POST',
capabilityIntent: 'wormhole_gate_content',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ gate_id: 'infonet' }),
});
const data = await res.json();
expect(invoke).toHaveBeenCalledOnce();
expect(fetchMock).toHaveBeenCalledWith(
'/api/wormhole/gate/proof',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({ gate_id: 'infonet' }),
}),
);
expect(data).toEqual({ ok: true, gate_id: 'infonet' });
});
});
@@ -0,0 +1,169 @@
import { describe, expect, it, vi } from 'vitest';
import { createNativeControlRouter } from '../../../../desktop-shell/src/nativeControlRouter';
describe('nativeControlRouter capability scaffolding', () => {
it('rejects mismatched capability intent', async () => {
const exec = async <T = unknown>(): Promise<T> => ({ ok: true } as T);
const router = createNativeControlRouter(
{
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
},
exec,
);
await expect(
router.invoke(
'wormhole.gate.key.rotate',
{ gate_id: 'infonet', reason: 'operator_reset' },
{ capability: 'wormhole_gate_content' },
),
).rejects.toThrow('native_control_capability_mismatch');
});
it('rejects commands outside the allowed native capability set', async () => {
const exec = async <T = unknown>(): Promise<T> => ({ ok: true } as T);
const router = createNativeControlRouter(
{
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
allowedCapabilities: ['wormhole_gate_content'],
},
exec,
);
await expect(
router.invoke(
'wormhole.gate.key.rotate',
{ gate_id: 'infonet', reason: 'operator_reset' },
{ capability: 'wormhole_gate_key' },
),
).rejects.toThrow('native_control_capability_denied');
});
it('audits session-profile mismatch without denying by default', async () => {
const auditControlUse = vi.fn();
const exec = async <T = unknown>(): Promise<T> => ({ ok: true } as T);
const router = createNativeControlRouter(
{
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
sessionProfile: 'settings_only',
auditControlUse,
},
exec,
);
const result = await router.invoke(
'wormhole.gate.key.rotate',
{ gate_id: 'infonet', reason: 'operator_reset' },
{ capability: 'wormhole_gate_key', sessionProfileHint: 'gate_operator' },
);
expect(result).toEqual({ ok: true });
expect(auditControlUse).toHaveBeenCalledWith(
expect.objectContaining({
command: 'wormhole.gate.key.rotate',
expectedCapability: 'wormhole_gate_key',
targetRef: 'infonet',
sessionProfile: 'settings_only',
sessionProfileHint: 'gate_operator',
profileAllows: false,
enforced: false,
outcome: 'profile_warn',
}),
);
});
it('includes targetRef in audit events for gate commands', async () => {
const auditControlUse = vi.fn();
const exec = async <T = unknown>(): Promise<T> => ({ ok: true } as T);
const router = createNativeControlRouter(
{
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
auditControlUse,
},
exec,
);
await router.invoke(
'wormhole.gate.message.compose',
{ gate_id: 'ops-room', plaintext: 'hello' },
{ capability: 'wormhole_gate_content' },
);
expect(auditControlUse).toHaveBeenCalledWith(
expect.objectContaining({
command: 'wormhole.gate.message.compose',
targetRef: 'ops-room',
outcome: 'allowed',
}),
);
});
it('omits targetRef for non-gate commands', async () => {
const auditControlUse = vi.fn();
const exec = async <T = unknown>(): Promise<T> => ({ ok: true } as T);
const router = createNativeControlRouter(
{
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
auditControlUse,
},
exec,
);
await router.invoke('wormhole.status', undefined);
const event = auditControlUse.mock.calls[0][0];
expect(event.command).toBe('wormhole.status');
expect(event.targetRef).toBeUndefined();
});
it('can enforce session-profile mismatch when explicitly enabled', async () => {
const exec = async <T = unknown>(): Promise<T> => ({ ok: true } as T);
const router = createNativeControlRouter(
{
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
sessionProfile: 'settings_only',
enforceSessionProfile: true,
},
exec,
);
await expect(
router.invoke(
'wormhole.gate.key.rotate',
{ gate_id: 'infonet', reason: 'operator_reset' },
{ capability: 'wormhole_gate_key', sessionProfileHint: 'gate_operator' },
),
).rejects.toThrow('native_control_profile_mismatch');
});
it('can enforce a hinted session profile for a narrow gate-key command', async () => {
const exec = async <T = unknown>(): Promise<T> => ({ ok: true } as T);
const router = createNativeControlRouter(
{
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
sessionProfile: 'settings_only',
},
exec,
);
await expect(
router.invoke(
'wormhole.gate.key.rotate',
{ gate_id: 'infonet', reason: 'operator_reset' },
{
capability: 'wormhole_gate_key',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
},
),
).rejects.toThrow('native_control_profile_mismatch');
});
});
@@ -0,0 +1,163 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { createRuntimeBridge } from '../../../../desktop-shell/src/runtimeBridge';
describe('runtimeBridge session profile routing', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('uses the invocation session profile hint when the runtime context is unscoped', async () => {
const auditControlUse = vi.fn();
vi.stubGlobal(
'fetch',
vi.fn(async () =>
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
),
);
const runtime = createRuntimeBridge({
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
auditControlUse,
});
await runtime.invokeLocalControl(
'wormhole.gate.key.rotate',
{ gate_id: 'infonet', reason: 'operator_reset' },
{
capability: 'wormhole_gate_key',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
},
);
expect(auditControlUse).toHaveBeenCalledWith(
expect.objectContaining({
command: 'wormhole.gate.key.rotate',
targetRef: 'infonet',
sessionProfile: 'gate_operator',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
profileAllows: true,
outcome: 'allowed',
}),
);
const report = runtime.getNativeControlAuditReport?.(5);
expect(report).toEqual(
expect.objectContaining({
totalEvents: 1,
totalRecorded: 1,
byOutcome: expect.objectContaining({ allowed: 1 }),
}),
);
expect(report?.recent[0]).toEqual(
expect.objectContaining({
command: 'wormhole.gate.key.rotate',
targetRef: 'infonet',
sessionProfile: 'gate_operator',
outcome: 'allowed',
}),
);
});
it('preserves an explicitly scoped runtime session profile over the invocation hint', async () => {
const auditControlUse = vi.fn();
vi.stubGlobal(
'fetch',
vi.fn(async () =>
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
),
);
const runtime = createRuntimeBridge({
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
sessionProfile: 'settings_only',
auditControlUse,
});
await runtime.invokeLocalControl(
'wormhole.gate.key.rotate',
{ gate_id: 'infonet', reason: 'operator_reset' },
{
capability: 'wormhole_gate_key',
sessionProfileHint: 'gate_operator',
},
);
expect(auditControlUse).toHaveBeenCalledWith(
expect.objectContaining({
command: 'wormhole.gate.key.rotate',
sessionProfile: 'settings_only',
sessionProfileHint: 'gate_operator',
profileAllows: false,
outcome: 'profile_warn',
}),
);
const report = runtime.getNativeControlAuditReport?.(5);
expect(report).toEqual(
expect.objectContaining({
totalEvents: 1,
totalRecorded: 1,
byOutcome: expect.objectContaining({ profile_warn: 1 }),
lastProfileMismatch: expect.objectContaining({
command: 'wormhole.gate.key.rotate',
sessionProfile: 'settings_only',
outcome: 'profile_warn',
}),
}),
);
});
it('denies a strictly hinted gate-key command when the runtime is pinned to another profile', async () => {
vi.stubGlobal(
'fetch',
vi.fn(async () =>
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
),
);
const runtime = createRuntimeBridge({
backendBaseUrl: 'http://127.0.0.1:8000',
wormholeBaseUrl: 'http://127.0.0.1:8787',
sessionProfile: 'settings_only',
});
await expect(
runtime.invokeLocalControl(
'wormhole.gate.key.rotate',
{ gate_id: 'infonet', reason: 'operator_reset' },
{
capability: 'wormhole_gate_key',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
},
),
).rejects.toThrow('native_control_profile_mismatch');
const report = runtime.getNativeControlAuditReport?.(5);
expect(report).toEqual(
expect.objectContaining({
totalEvents: 1,
totalRecorded: 1,
byOutcome: expect.objectContaining({ profile_denied: 1 }),
lastDenied: expect.objectContaining({
command: 'wormhole.gate.key.rotate',
outcome: 'profile_denied',
}),
}),
);
});
});
+457 -223
View File
@@ -1,10 +1,33 @@
import { describe, it, expect } from 'vitest';
import {
buildEarthquakesGeoJSON, buildJammingGeoJSON, buildCctvGeoJSON, buildKiwisdrGeoJSON,
buildFirmsGeoJSON, buildInternetOutagesGeoJSON, buildDataCentersGeoJSON,
buildGdeltGeoJSON, buildLiveuaGeoJSON, buildFrontlineGeoJSON, buildMilitaryBasesGeoJSON
buildEarthquakesGeoJSON,
buildJammingGeoJSON,
buildCctvGeoJSON,
buildKiwisdrGeoJSON,
buildFirmsGeoJSON,
buildInternetOutagesGeoJSON,
buildDataCentersGeoJSON,
buildGdeltGeoJSON,
buildLiveuaGeoJSON,
buildFrontlineGeoJSON,
buildScannerGeoJSON,
buildMilitaryBasesGeoJSON,
buildTrainsGeoJSON,
} from '@/components/map/geoJSONBuilders';
import type { Earthquake, GPSJammingZone, FireHotspot, InternetOutage, DataCenter, GDELTIncident, LiveUAmapIncident, CCTVCamera, KiwiSDR, MilitaryBase } from '@/types/dashboard';
import type {
Earthquake,
GPSJammingZone,
FireHotspot,
InternetOutage,
DataCenter,
GDELTIncident,
LiveUAmapIncident,
CCTVCamera,
KiwiSDR,
Scanner,
MilitaryBase,
Train,
} from '@/types/dashboard';
// ─── Military Bases ────────────────────────────────────────────────────────
@@ -34,289 +57,500 @@ describe('buildMilitaryBasesGeoJSON', () => {
// ─── Earthquakes ────────────────────────────────────────────────────────────
describe('buildEarthquakesGeoJSON', () => {
it('returns null for empty/undefined input', () => {
expect(buildEarthquakesGeoJSON(undefined)).toBeNull();
expect(buildEarthquakesGeoJSON([])).toBeNull();
});
it('returns null for empty/undefined input', () => {
expect(buildEarthquakesGeoJSON(undefined)).toBeNull();
expect(buildEarthquakesGeoJSON([])).toBeNull();
});
it('builds valid FeatureCollection from earthquake data', () => {
const earthquakes: Earthquake[] = [
{ id: 'eq1', mag: 5.2, lat: 35.0, lng: 139.0, place: 'Japan' },
{ id: 'eq2', mag: 3.1, lat: 40.0, lng: -120.0, place: 'California', title: 'Test Title' },
];
const result = buildEarthquakesGeoJSON(earthquakes);
expect(result).not.toBeNull();
expect(result!.type).toBe('FeatureCollection');
expect(result!.features).toHaveLength(2);
it('builds valid FeatureCollection from earthquake data', () => {
const earthquakes: Earthquake[] = [
{ id: 'eq1', mag: 5.2, lat: 35.0, lng: 139.0, place: 'Japan' },
{ id: 'eq2', mag: 3.1, lat: 40.0, lng: -120.0, place: 'California', title: 'Test Title' },
];
const result = buildEarthquakesGeoJSON(earthquakes);
expect(result).not.toBeNull();
expect(result!.type).toBe('FeatureCollection');
expect(result!.features).toHaveLength(2);
const f0 = result!.features[0];
expect(f0.geometry).toEqual({ type: 'Point', coordinates: [139.0, 35.0] });
expect(f0.properties?.type).toBe('earthquake');
expect(f0.properties?.name).toContain('M5.2');
expect(f0.properties?.name).toContain('Japan');
});
const f0 = result!.features[0];
expect(f0.geometry).toEqual({ type: 'Point', coordinates: [139.0, 35.0] });
expect(f0.properties?.type).toBe('earthquake');
expect(f0.properties?.name).toContain('M5.2');
expect(f0.properties?.name).toContain('Japan');
});
it('filters out entries with null lat/lng', () => {
const earthquakes = [
{ id: 'eq1', mag: 5.0, lat: null as any, lng: 10.0, place: 'X' },
{ id: 'eq2', mag: 3.0, lat: 20.0, lng: 30.0, place: 'Y' },
];
const result = buildEarthquakesGeoJSON(earthquakes);
expect(result!.features).toHaveLength(1);
});
it('filters out entries with null lat/lng', () => {
const earthquakes = [
{ id: 'eq1', mag: 5.0, lat: null as any, lng: 10.0, place: 'X' },
{ id: 'eq2', mag: 3.0, lat: 20.0, lng: 30.0, place: 'Y' },
];
const result = buildEarthquakesGeoJSON(earthquakes);
expect(result!.features).toHaveLength(1);
});
it('includes title when present', () => {
const earthquakes: Earthquake[] = [
{ id: 'eq1', mag: 4.0, lat: 10.0, lng: 20.0, place: 'Test', title: 'Big One' },
];
const result = buildEarthquakesGeoJSON(earthquakes);
expect(result!.features[0].properties?.title).toBe('Big One');
});
it('includes title when present', () => {
const earthquakes: Earthquake[] = [
{ id: 'eq1', mag: 4.0, lat: 10.0, lng: 20.0, place: 'Test', title: 'Big One' },
];
const result = buildEarthquakesGeoJSON(earthquakes);
expect(result!.features[0].properties?.title).toBe('Big One');
});
});
// ─── GPS Jamming ────────────────────────────────────────────────────────────
describe('buildJammingGeoJSON', () => {
it('returns null for empty input', () => {
expect(buildJammingGeoJSON(undefined)).toBeNull();
expect(buildJammingGeoJSON([])).toBeNull();
});
it('returns null for empty input', () => {
expect(buildJammingGeoJSON(undefined)).toBeNull();
expect(buildJammingGeoJSON([])).toBeNull();
});
it('builds polygon features with correct opacity mapping', () => {
const zones: GPSJammingZone[] = [
{ lat: 50, lng: 30, severity: 'high', ratio: 0.8, degraded: 100, total: 125 },
{ lat: 45, lng: 35, severity: 'medium', ratio: 0.5, degraded: 50, total: 100 },
{ lat: 40, lng: 25, severity: 'low', ratio: 0.2, degraded: 20, total: 100 },
];
const result = buildJammingGeoJSON(zones);
expect(result!.features).toHaveLength(3);
expect(result!.features[0].properties?.opacity).toBe(0.45);
expect(result!.features[1].properties?.opacity).toBe(0.3);
expect(result!.features[2].properties?.opacity).toBe(0.18);
});
it('builds polygon features with correct opacity mapping', () => {
const zones: GPSJammingZone[] = [
{ lat: 50, lng: 30, severity: 'high', ratio: 0.8, degraded: 100, total: 125 },
{ lat: 45, lng: 35, severity: 'medium', ratio: 0.5, degraded: 50, total: 100 },
{ lat: 40, lng: 25, severity: 'low', ratio: 0.2, degraded: 20, total: 100 },
];
const result = buildJammingGeoJSON(zones);
expect(result!.features).toHaveLength(3);
expect(result!.features[0].properties?.opacity).toBe(0.45);
expect(result!.features[1].properties?.opacity).toBe(0.3);
expect(result!.features[2].properties?.opacity).toBe(0.18);
});
it('builds correct 1°×1° polygon geometry', () => {
const zones: GPSJammingZone[] = [
{ lat: 50, lng: 30, severity: 'high', ratio: 0.8, degraded: 100, total: 125 },
];
const result = buildJammingGeoJSON(zones);
const geom = result!.features[0].geometry;
expect(geom.type).toBe('Polygon');
if (geom.type === 'Polygon') {
const ring = geom.coordinates[0];
expect(ring).toHaveLength(5); // Closed ring
expect(ring[0]).toEqual([29.5, 49.5]);
expect(ring[2]).toEqual([30.5, 50.5]);
}
});
it('builds correct 1°×1° polygon geometry', () => {
const zones: GPSJammingZone[] = [
{ lat: 50, lng: 30, severity: 'high', ratio: 0.8, degraded: 100, total: 125 },
];
const result = buildJammingGeoJSON(zones);
const geom = result!.features[0].geometry;
expect(geom.type).toBe('Polygon');
if (geom.type === 'Polygon') {
const ring = geom.coordinates[0];
expect(ring).toHaveLength(5); // Closed ring
expect(ring[0]).toEqual([29.5, 49.5]);
expect(ring[2]).toEqual([30.5, 50.5]);
}
});
});
// ─── CCTV ───────────────────────────────────────────────────────────────────
describe('buildCctvGeoJSON', () => {
it('returns null for empty input', () => {
expect(buildCctvGeoJSON(undefined)).toBeNull();
});
it('returns null for empty input', () => {
expect(buildCctvGeoJSON(undefined)).toBeNull();
});
it('builds features from camera data', () => {
const cameras: CCTVCamera[] = [
{ id: 'cam1', lat: 40.7, lon: -74.0, direction_facing: 'North', source_agency: 'DOT' },
];
const result = buildCctvGeoJSON(cameras);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.type).toBe('cctv');
expect(result!.features[0].properties?.name).toBe('North');
});
it('builds features from camera data', () => {
const cameras: CCTVCamera[] = [
{ id: 'cam1', lat: 40.7, lon: -74.0, direction_facing: 'North', source_agency: 'DOT' },
];
const result = buildCctvGeoJSON(cameras);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.type).toBe('cctv');
expect(result!.features[0].properties?.name).toBe('North');
});
it('respects inView filter', () => {
const cameras: CCTVCamera[] = [
{ id: 'cam1', lat: 40.7, lon: -74.0 },
{ id: 'cam2', lat: 10.0, lon: 20.0 },
];
const inView = (lat: number, _lng: number) => lat > 30;
const result = buildCctvGeoJSON(cameras, inView);
expect(result!.features).toHaveLength(1);
});
it('respects inView filter', () => {
const cameras: CCTVCamera[] = [
{ id: 'cam1', lat: 40.7, lon: -74.0 },
{ id: 'cam2', lat: 10.0, lon: 20.0 },
];
const inView = (lat: number, _lng: number) => lat > 30;
const result = buildCctvGeoJSON(cameras, inView);
expect(result!.features).toHaveLength(1);
});
});
// ─── KiwiSDR ────────────────────────────────────────────────────────────────
describe('buildKiwisdrGeoJSON', () => {
it('returns null for empty input', () => {
expect(buildKiwisdrGeoJSON(undefined)).toBeNull();
});
it('returns null for empty input', () => {
expect(buildKiwisdrGeoJSON(undefined)).toBeNull();
});
it('builds features with SDR properties', () => {
const receivers: KiwiSDR[] = [
{ lat: 52.0, lon: 13.0, name: 'Berlin SDR', url: 'http://test.com', users: 3, users_max: 8, bands: 'HF', antenna: 'Long Wire', location: 'Berlin' },
];
const result = buildKiwisdrGeoJSON(receivers);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.name).toBe('Berlin SDR');
expect(result!.features[0].properties?.users).toBe(3);
});
it('builds features with SDR properties', () => {
const receivers: KiwiSDR[] = [
{
lat: 52.0,
lon: 13.0,
name: 'Berlin SDR',
url: 'http://test.com',
users: 3,
users_max: 8,
bands: 'HF',
antenna: 'Long Wire',
location: 'Berlin',
},
];
const result = buildKiwisdrGeoJSON(receivers);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.name).toBe('Berlin SDR');
expect(result!.features[0].properties?.users).toBe(3);
});
});
// ─── FIRMS Fires ────────────────────────────────────────────────────────────
describe('buildFirmsGeoJSON', () => {
it('returns null for empty input', () => {
expect(buildFirmsGeoJSON(undefined)).toBeNull();
});
it('returns null for empty input', () => {
expect(buildFirmsGeoJSON(undefined)).toBeNull();
});
it('classifies fires by FRP thresholds', () => {
const fires: FireHotspot[] = [
{ lat: 10, lng: 20, frp: 150, brightness: 400, confidence: 'high', daynight: 'D', acq_date: '2024-01-01', acq_time: '1200' },
{ lat: 11, lng: 21, frp: 50, brightness: 350, confidence: 'medium', daynight: 'N', acq_date: '2024-01-01', acq_time: '0100' },
{ lat: 12, lng: 22, frp: 10, brightness: 300, confidence: 'low', daynight: 'D', acq_date: '2024-01-01', acq_time: '1400' },
{ lat: 13, lng: 23, frp: 2, brightness: 250, confidence: 'low', daynight: 'D', acq_date: '2024-01-01', acq_time: '1500' },
];
const result = buildFirmsGeoJSON(fires);
expect(result!.features).toHaveLength(4);
expect(result!.features[0].properties?.iconId).toBe('fire-darkred');
expect(result!.features[1].properties?.iconId).toBe('fire-red');
expect(result!.features[2].properties?.iconId).toBe('fire-orange');
expect(result!.features[3].properties?.iconId).toBe('fire-yellow');
});
it('classifies fires by FRP thresholds', () => {
const fires: FireHotspot[] = [
{
lat: 10,
lng: 20,
frp: 150,
brightness: 400,
confidence: 'high',
daynight: 'D',
acq_date: '2024-01-01',
acq_time: '1200',
},
{
lat: 11,
lng: 21,
frp: 50,
brightness: 350,
confidence: 'medium',
daynight: 'N',
acq_date: '2024-01-01',
acq_time: '0100',
},
{
lat: 12,
lng: 22,
frp: 10,
brightness: 300,
confidence: 'low',
daynight: 'D',
acq_date: '2024-01-01',
acq_time: '1400',
},
{
lat: 13,
lng: 23,
frp: 2,
brightness: 250,
confidence: 'low',
daynight: 'D',
acq_date: '2024-01-01',
acq_time: '1500',
},
];
const result = buildFirmsGeoJSON(fires);
expect(result!.features).toHaveLength(4);
expect(result!.features[0].properties?.iconId).toBe('fire-darkred');
expect(result!.features[1].properties?.iconId).toBe('fire-red');
expect(result!.features[2].properties?.iconId).toBe('fire-orange');
expect(result!.features[3].properties?.iconId).toBe('fire-yellow');
});
it('formats daynight correctly', () => {
const fires: FireHotspot[] = [
{ lat: 10, lng: 20, frp: 5, brightness: 300, confidence: 'low', daynight: 'D', acq_date: '2024-01-01', acq_time: '1200' },
{ lat: 11, lng: 21, frp: 5, brightness: 300, confidence: 'low', daynight: 'N', acq_date: '2024-01-01', acq_time: '0100' },
];
const result = buildFirmsGeoJSON(fires);
expect(result!.features[0].properties?.daynight).toBe('Day');
expect(result!.features[1].properties?.daynight).toBe('Night');
});
it('formats daynight correctly', () => {
const fires: FireHotspot[] = [
{
lat: 10,
lng: 20,
frp: 5,
brightness: 300,
confidence: 'low',
daynight: 'D',
acq_date: '2024-01-01',
acq_time: '1200',
},
{
lat: 11,
lng: 21,
frp: 5,
brightness: 300,
confidence: 'low',
daynight: 'N',
acq_date: '2024-01-01',
acq_time: '0100',
},
];
const result = buildFirmsGeoJSON(fires);
expect(result!.features[0].properties?.daynight).toBe('Day');
expect(result!.features[1].properties?.daynight).toBe('Night');
});
});
// ─── Internet Outages ───────────────────────────────────────────────────────
describe('buildInternetOutagesGeoJSON', () => {
it('returns null for empty input', () => {
expect(buildInternetOutagesGeoJSON(undefined)).toBeNull();
});
it('returns null for empty input', () => {
expect(buildInternetOutagesGeoJSON(undefined)).toBeNull();
});
it('builds features with detail string', () => {
const outages: InternetOutage[] = [
{ region_code: 'TX', region_name: 'Texas', country_code: 'US', country_name: 'United States', lat: 31.0, lng: -100.0, severity: 45, level: 'region', datasource: 'bgp' },
];
const result = buildInternetOutagesGeoJSON(outages);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.detail).toContain('Texas');
expect(result!.features[0].properties?.detail).toContain('45% drop');
});
it('builds features with detail string', () => {
const outages: InternetOutage[] = [
{
region_code: 'TX',
region_name: 'Texas',
country_code: 'US',
country_name: 'United States',
lat: 31.0,
lng: -100.0,
severity: 45,
level: 'region',
datasource: 'bgp',
},
];
const result = buildInternetOutagesGeoJSON(outages);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.detail).toContain('Texas');
expect(result!.features[0].properties?.detail).toContain('45% drop');
});
it('filters out entries with null coordinates', () => {
const outages: InternetOutage[] = [
{ region_code: 'TX', region_name: 'Texas', country_code: 'US', country_name: 'United States', lat: null as any, lng: null as any, severity: 20, level: 'region', datasource: 'bgp' },
{ region_code: 'CA', region_name: 'California', country_code: 'US', country_name: 'United States', lat: 37.0, lng: -122.0, severity: 30, level: 'region', datasource: 'bgp' },
];
const result = buildInternetOutagesGeoJSON(outages);
expect(result!.features).toHaveLength(1);
});
it('filters out entries with null coordinates', () => {
const outages: InternetOutage[] = [
{
region_code: 'TX',
region_name: 'Texas',
country_code: 'US',
country_name: 'United States',
lat: null as any,
lng: null as any,
severity: 20,
level: 'region',
datasource: 'bgp',
},
{
region_code: 'CA',
region_name: 'California',
country_code: 'US',
country_name: 'United States',
lat: 37.0,
lng: -122.0,
severity: 30,
level: 'region',
datasource: 'bgp',
},
];
const result = buildInternetOutagesGeoJSON(outages);
expect(result!.features).toHaveLength(1);
});
});
// ─── Data Centers ───────────────────────────────────────────────────────────
describe('buildDataCentersGeoJSON', () => {
it('returns null for empty input', () => {
expect(buildDataCentersGeoJSON(undefined)).toBeNull();
});
it('returns null for empty input', () => {
expect(buildDataCentersGeoJSON(undefined)).toBeNull();
});
it('builds features with datacenter properties', () => {
const dcs: DataCenter[] = [
{ lat: 40.0, lng: -74.0, name: 'NYC-DC1', company: 'Equinix', street: '123 Main', city: 'New York', country: 'US', zip: '10001' },
];
const result = buildDataCentersGeoJSON(dcs);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.id).toBe('dc-0');
expect(result!.features[0].properties?.company).toBe('Equinix');
});
it('builds features with datacenter properties', () => {
const dcs: DataCenter[] = [
{
lat: 40.0,
lng: -74.0,
name: 'NYC-DC1',
company: 'Equinix',
street: '123 Main',
city: 'New York',
country: 'US',
zip: '10001',
},
];
const result = buildDataCentersGeoJSON(dcs);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.id).toBe('dc-0');
expect(result!.features[0].properties?.company).toBe('Equinix');
});
});
// ─── GDELT ──────────────────────────────────────────────────────────────────
describe('buildGdeltGeoJSON', () => {
it('returns null for empty input', () => {
expect(buildGdeltGeoJSON(undefined)).toBeNull();
});
it('returns null for empty input', () => {
expect(buildGdeltGeoJSON(undefined)).toBeNull();
});
it('builds features from GDELT incidents', () => {
const gdelt: GDELTIncident[] = [
{ type: 'Feature', geometry: { type: 'Point', coordinates: [30, 50] }, properties: { name: 'Protest', count: 5, _urls_list: [], _headlines_list: [] } },
];
const result = buildGdeltGeoJSON(gdelt);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.type).toBe('gdelt');
expect(result!.features[0].properties?.title).toBe('Protest');
});
it('builds features from GDELT incidents', () => {
const gdelt: GDELTIncident[] = [
{
type: 'Feature',
geometry: { type: 'Point', coordinates: [30, 50] },
properties: { name: 'Protest', count: 5, _urls_list: [], _headlines_list: [] },
},
];
const result = buildGdeltGeoJSON(gdelt);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.type).toBe('gdelt');
expect(result!.features[0].properties?.title).toBe('Protest');
});
it('filters by inView when provided', () => {
const gdelt: GDELTIncident[] = [
{ type: 'Feature', geometry: { type: 'Point', coordinates: [30, 50] }, properties: { name: 'A', count: 1, _urls_list: [], _headlines_list: [] } },
{ type: 'Feature', geometry: { type: 'Point', coordinates: [100, 10] }, properties: { name: 'B', count: 1, _urls_list: [], _headlines_list: [] } },
];
const inView = (lat: number, _lng: number) => lat > 30;
const result = buildGdeltGeoJSON(gdelt, inView);
expect(result!.features).toHaveLength(1);
});
it('filters by inView when provided', () => {
const gdelt: GDELTIncident[] = [
{
type: 'Feature',
geometry: { type: 'Point', coordinates: [30, 50] },
properties: { name: 'A', count: 1, _urls_list: [], _headlines_list: [] },
},
{
type: 'Feature',
geometry: { type: 'Point', coordinates: [100, 10] },
properties: { name: 'B', count: 1, _urls_list: [], _headlines_list: [] },
},
];
const inView = (lat: number, _lng: number) => lat > 30;
const result = buildGdeltGeoJSON(gdelt, inView);
expect(result!.features).toHaveLength(1);
});
it('filters out entries without geometry', () => {
const gdelt: GDELTIncident[] = [
{ type: 'Feature', geometry: { type: 'Point', coordinates: [30, 50] }, properties: { name: 'Good', count: 1, _urls_list: [], _headlines_list: [] } },
{ type: 'Feature', geometry: null as any, properties: { name: 'Bad', count: 1, _urls_list: [], _headlines_list: [] } },
];
const result = buildGdeltGeoJSON(gdelt);
expect(result!.features).toHaveLength(1);
});
it('filters out entries without geometry', () => {
const gdelt: GDELTIncident[] = [
{
type: 'Feature',
geometry: { type: 'Point', coordinates: [30, 50] },
properties: { name: 'Good', count: 1, _urls_list: [], _headlines_list: [] },
},
{
type: 'Feature',
geometry: null as any,
properties: { name: 'Bad', count: 1, _urls_list: [], _headlines_list: [] },
},
];
const result = buildGdeltGeoJSON(gdelt);
expect(result!.features).toHaveLength(1);
});
});
describe('buildTrainsGeoJSON', () => {
it('builds all trains when no inView filter is provided', () => {
const trains: Train[] = [
{
id: 'amtrak-1',
name: 'Empire Builder',
number: '7',
source: 'amtrak',
source_label: 'Amtraker',
operator: 'Amtrak',
country: 'US',
speed_kmh: 88,
heading: 90,
status: 'active',
route: 'SEA-CHI',
lat: 47.6,
lng: -122.3,
},
{
id: 'fin-1',
name: 'Pendolino',
number: 'S 94',
source: 'digitraffic',
source_label: 'Digitraffic',
operator: 'VR',
country: 'FI',
speed_kmh: 120,
heading: 180,
status: 'active',
route: 'HEL-TKU',
lat: 60.17,
lng: 24.94,
},
];
const result = buildTrainsGeoJSON(trains);
expect(result).not.toBeNull();
expect(result!.features).toHaveLength(2);
});
});
// ─── LiveUAMap ──────────────────────────────────────────────────────────────
describe('buildLiveuaGeoJSON', () => {
it('returns null for empty input', () => {
expect(buildLiveuaGeoJSON(undefined)).toBeNull();
});
it('returns null for empty input', () => {
expect(buildLiveuaGeoJSON(undefined)).toBeNull();
});
it('classifies violent incidents with red icon', () => {
const incidents: LiveUAmapIncident[] = [
{ id: '1', lat: 48.0, lng: 35.0, title: 'Missile strike in Kharkiv', date: '2024-01-01' },
{ id: '2', lat: 49.0, lng: 36.0, title: 'Humanitarian aid delivery', date: '2024-01-01' },
];
const result = buildLiveuaGeoJSON(incidents);
expect(result!.features).toHaveLength(2);
expect(result!.features[0].properties?.iconId).toBe('icon-liveua-red');
expect(result!.features[1].properties?.iconId).toBe('icon-liveua-yellow');
});
it('classifies violent incidents with red icon', () => {
const incidents: LiveUAmapIncident[] = [
{ id: '1', lat: 48.0, lng: 35.0, title: 'Missile strike in Kharkiv', date: '2024-01-01' },
{ id: '2', lat: 49.0, lng: 36.0, title: 'Humanitarian aid delivery', date: '2024-01-01' },
];
const result = buildLiveuaGeoJSON(incidents);
expect(result!.features).toHaveLength(2);
expect(result!.features[0].properties?.iconId).toBe('icon-liveua-red');
expect(result!.features[1].properties?.iconId).toBe('icon-liveua-yellow');
});
it('filters by inView when provided', () => {
const incidents: LiveUAmapIncident[] = [
{ id: '1', lat: 48.0, lng: 35.0, title: 'Test', date: '2024-01-01' },
{ id: '2', lat: 10.0, lng: 20.0, title: 'Far away', date: '2024-01-01' },
];
const inView = (lat: number, _lng: number) => lat > 30;
const result = buildLiveuaGeoJSON(incidents, inView);
expect(result!.features).toHaveLength(1);
});
it('filters by inView when provided', () => {
const incidents: LiveUAmapIncident[] = [
{ id: '1', lat: 48.0, lng: 35.0, title: 'Test', date: '2024-01-01' },
{ id: '2', lat: 10.0, lng: 20.0, title: 'Far away', date: '2024-01-01' },
];
const inView = (lat: number, _lng: number) => lat > 30;
const result = buildLiveuaGeoJSON(incidents, inView);
expect(result!.features).toHaveLength(1);
});
});
// ─── Frontline ──────────────────────────────────────────────────────────────
describe('buildFrontlineGeoJSON', () => {
it('returns null for null/undefined input', () => {
expect(buildFrontlineGeoJSON(null)).toBeNull();
expect(buildFrontlineGeoJSON(undefined)).toBeNull();
});
it('returns null for null/undefined input', () => {
expect(buildFrontlineGeoJSON(null)).toBeNull();
expect(buildFrontlineGeoJSON(undefined)).toBeNull();
});
it('returns the input unchanged when valid', () => {
const fc = { type: 'FeatureCollection' as const, features: [{ type: 'Feature' as const, properties: { name: 'zone', zone_id: 1 }, geometry: { type: 'Polygon' as const, coordinates: [[[30, 48], [31, 49], [30, 49], [30, 48]]] as [number, number][][] } }] };
const result = buildFrontlineGeoJSON(fc);
expect(result).toBe(fc); // Same reference — passthrough
});
it('returns the input unchanged when valid', () => {
const fc = {
type: 'FeatureCollection' as const,
features: [
{
type: 'Feature' as const,
properties: { name: 'zone', zone_id: 1 },
geometry: {
type: 'Polygon' as const,
coordinates: [
[
[30, 48],
[31, 49],
[30, 49],
[30, 48],
],
] as [number, number][][],
},
},
],
};
const result = buildFrontlineGeoJSON(fc);
expect(result).toBe(fc); // Same reference — passthrough
});
it('returns null for empty features array', () => {
const fc = { type: 'FeatureCollection' as const, features: [] };
expect(buildFrontlineGeoJSON(fc)).toBeNull();
});
it('returns null for empty features array', () => {
const fc = { type: 'FeatureCollection' as const, features: [] };
expect(buildFrontlineGeoJSON(fc)).toBeNull();
});
});
// ─── Scanners ───────────────────────────────────────────────────────────────
describe('buildScannerGeoJSON', () => {
it('returns null for empty input', () => {
expect(buildScannerGeoJSON(undefined)).toBeNull();
expect(buildScannerGeoJSON([])).toBeNull();
});
it('builds features with scanner properties', () => {
const scanners: Scanner[] = [
{
shortName: 'TEST',
name: 'Test System',
lat: 39.0,
lng: -104.0,
city: 'Denver',
state: 'CO',
clientCount: 5,
description: 'Demo',
},
];
const result = buildScannerGeoJSON(scanners);
expect(result!.features).toHaveLength(1);
expect(result!.features[0].properties?.type).toBe('scanner');
expect(result!.features[0].properties?.name).toBe('Test System');
});
});
@@ -0,0 +1,91 @@
import { describe, expect, it } from 'vitest';
import {
gateEnvelopeDisplayText,
gateEnvelopeState,
isEncryptedGateEnvelope,
} from '@/mesh/gateEnvelope';
import { normalizePayload } from '@/mesh/meshProtocol';
import { validateEventPayload } from '@/mesh/meshSchema';
describe('gate envelope protocol', () => {
it('normalizes encrypted gate-message payloads', () => {
expect(
normalizePayload('gate_message', {
gate: 'Finance',
epoch: '2',
ciphertext: 'opaque',
nonce: 'nonce-2',
sender_ref: 'persona-fin-1',
}),
).toEqual({
gate: 'finance',
epoch: 2,
ciphertext: 'opaque',
nonce: 'nonce-2',
sender_ref: 'persona-fin-1',
format: 'g1',
});
});
it('accepts encrypted gate-message envelopes and rejects plaintext ones', () => {
expect(
validateEventPayload('gate_message', {
gate: 'finance',
epoch: 2,
ciphertext: 'opaque',
nonce: 'nonce-2',
sender_ref: 'persona-fin-1',
format: 'g1',
}),
).toEqual({ ok: true });
expect(
validateEventPayload('gate_message', {
gate: 'finance',
message: 'plaintext',
}),
).toEqual({ ok: false, reason: 'Payload is not normalized' });
});
});
describe('gate envelope display', () => {
it('detects encrypted gate messages and shows placeholders honestly', () => {
const encrypted = {
event_type: 'gate_message',
gate: 'finance',
epoch: 2,
ciphertext: 'opaque',
nonce: 'nonce-2',
sender_ref: 'persona-fin-1',
};
expect(isEncryptedGateEnvelope(encrypted)).toBe(true);
expect(gateEnvelopeState(encrypted)).toBe('locked');
expect(gateEnvelopeDisplayText(encrypted)).toBe('ENCRYPTED GATE MESSAGE - KEY UNAVAILABLE');
expect(
gateEnvelopeState({
...encrypted,
decrypted_message: 'decoded text',
}),
).toBe('decrypted');
expect(
gateEnvelopeDisplayText({
...encrypted,
decrypted_message: 'decoded text',
}),
).toBe('decoded text');
expect(
gateEnvelopeState({
event_type: 'gate_notice',
message: 'legacy plaintext',
}),
).toBe('plaintext');
expect(
gateEnvelopeDisplayText({
event_type: 'gate_notice',
message: 'legacy plaintext',
}),
).toBe('legacy plaintext');
});
});
@@ -0,0 +1,158 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const deadDropTokensForContacts = vi.fn();
const mailboxClaimToken = vi.fn();
const mailboxDecoySharedToken = vi.fn();
vi.mock('@/mesh/meshDeadDrop', () => ({
deadDropToken: vi.fn(),
deadDropTokensForContacts,
}));
vi.mock('@/mesh/meshMailbox', () => ({
mailboxClaimToken,
mailboxDecoySharedToken,
}));
vi.mock('@/mesh/meshIdentity', () => ({
deriveSenderSealKey: vi.fn(),
ensureDhKeysFresh: vi.fn(),
deriveSharedKey: vi.fn(),
encryptDM: vi.fn(),
getDHAlgo: vi.fn(() => 'X25519'),
getNodeIdentity: vi.fn(() => ({ nodeId: '!self', publicKey: 'pub' })),
getPublicKeyAlgo: vi.fn(() => 'Ed25519'),
nextSequence: vi.fn(() => 1),
verifyNodeIdBindingFromPublicKey: vi.fn(async () => true),
}));
vi.mock('@/mesh/wormholeIdentityClient', () => ({
buildWormholeSenderSeal: vi.fn(),
getActiveSigningContext: vi.fn(async () => null),
isWormholeSecureRequired: vi.fn(async () => false),
issueWormholeDmSenderToken: vi.fn(),
issueWormholeDmSenderTokens: vi.fn(),
registerWormholeDmKey: vi.fn(),
signRawMeshMessage: vi.fn(),
signMeshEvent: vi.fn(),
}));
vi.mock('@/mesh/meshSchema', () => ({
validateEventPayload: vi.fn(() => ({ ok: true, reason: 'ok' })),
}));
describe('mailbox claim privacy padding', () => {
beforeEach(() => {
vi.resetModules();
vi.unstubAllEnvs();
vi.stubEnv('NEXT_PUBLIC_ENABLE_RFC2A_CLAIM_SHAPE', '1');
deadDropTokensForContacts.mockReset();
mailboxClaimToken.mockReset();
mailboxDecoySharedToken.mockReset();
mailboxClaimToken.mockImplementation(async (type: string) => `${type}-token`);
mailboxDecoySharedToken.mockImplementation(async (index: number) => `decoy-${index}`);
});
function buildSharedTokens(count: number): string[] {
return Array.from({ length: count }, (_, index) => `shared-${index + 1}`);
}
it('uses bucketed shared-claim envelopes across multiple contact counts', async () => {
const mod = await import('@/mesh/meshDmClient');
for (const testCase of [
{ realSharedClaims: 0, expectedSharedClaims: 3, expectedTotalClaims: 5 },
{ realSharedClaims: 1, expectedSharedClaims: 3, expectedTotalClaims: 5 },
{ realSharedClaims: 3, expectedSharedClaims: 3, expectedTotalClaims: 5 },
{ realSharedClaims: 4, expectedSharedClaims: 6, expectedTotalClaims: 8 },
{ realSharedClaims: 7, expectedSharedClaims: 12, expectedTotalClaims: 14 },
{ realSharedClaims: 25, expectedSharedClaims: 30, expectedTotalClaims: 32 },
{ realSharedClaims: 30, expectedSharedClaims: 30, expectedTotalClaims: 32 },
]) {
deadDropTokensForContacts.mockResolvedValue(buildSharedTokens(testCase.realSharedClaims));
const claims = await mod.buildMailboxClaims({});
expect(claims.slice(0, 2)).toEqual([
{ type: 'self', token: 'self-token' },
{ type: 'requests', token: 'requests-token' },
]);
expect(claims.filter((claim) => claim.type === 'shared')).toHaveLength(
testCase.expectedSharedClaims,
);
expect(claims).toHaveLength(testCase.expectedTotalClaims);
}
});
it('falls back to the legacy shared-claim floor when the experiment is disabled', async () => {
vi.resetModules();
vi.stubEnv('NEXT_PUBLIC_ENABLE_RFC2A_CLAIM_SHAPE', '0');
deadDropTokensForContacts.mockResolvedValue(['shared-1', 'shared-2', 'shared-3', 'shared-4']);
const mod = await import('@/mesh/meshDmClient');
const claims = await mod.buildMailboxClaims({});
const sharedClaims = claims.filter((claim) => claim.type === 'shared');
expect(mod.MAILBOX_SHARED_CLAIM_SHAPE_VERSION).toBe('legacy-floor-v1');
expect(sharedClaims).toEqual([
{ type: 'shared', token: 'shared-1' },
{ type: 'shared', token: 'shared-2' },
{ type: 'shared', token: 'shared-3' },
{ type: 'shared', token: 'shared-4' },
]);
expect(claims).toHaveLength(6);
});
it('deduplicates real shared tokens before filling the bucketed envelope', async () => {
deadDropTokensForContacts.mockResolvedValue(['shared-real', 'shared-real']);
const mod = await import('@/mesh/meshDmClient');
const claims = await mod.buildMailboxClaims({
alice: { blocked: false, dhPubKey: 'dh-a' },
});
const sharedClaims = claims.filter((claim) => claim.type === 'shared');
expect(sharedClaims).toEqual([
{ type: 'shared', token: 'decoy-0' },
{ type: 'shared', token: 'shared-real' },
{ type: 'shared', token: 'decoy-1' },
]);
});
it('preserves every real shared token within the supported 30-claim shared range', async () => {
const realSharedTokens = buildSharedTokens(30);
deadDropTokensForContacts.mockResolvedValue(realSharedTokens);
const mod = await import('@/mesh/meshDmClient');
const claims = await mod.buildMailboxClaims({});
const sharedTokens = claims
.filter((claim) => claim.type === 'shared')
.map((claim) => claim.token);
expect(sharedTokens).toHaveLength(30);
expect(new Set(sharedTokens)).toEqual(new Set(realSharedTokens));
});
it('keeps decoy shared tokens distinct from real shared tokens', async () => {
const realSharedTokens = ['shared-1', 'shared-2', 'shared-3', 'shared-4'];
deadDropTokensForContacts.mockResolvedValue(realSharedTokens);
const mod = await import('@/mesh/meshDmClient');
const claims = await mod.buildMailboxClaims({});
const sharedTokens = claims
.filter((claim) => claim.type === 'shared')
.map((claim) => String(claim.token || ''));
const decoyTokens = sharedTokens.filter((token) => !realSharedTokens.includes(token));
expect(sharedTokens).toEqual([
'shared-1',
'decoy-0',
'shared-2',
'shared-3',
'decoy-1',
'shared-4',
]);
expect(decoyTokens).toEqual(['decoy-0', 'decoy-1']);
expect(decoyTokens.every((token) => !realSharedTokens.includes(token))).toBe(true);
});
});
@@ -0,0 +1,32 @@
import { readFileSync } from 'fs';
import path from 'path';
import { buildSignaturePayload, type JsonValue } from '@/mesh/meshProtocol';
type Fixture = {
name: string;
event_type: string;
node_id: string;
sequence: number;
payload: Record<string, JsonValue>;
expected: string;
};
describe('mesh canonical signature payloads', () => {
const cwd = process.cwd();
const fixturePath = cwd.endsWith('frontend')
? path.resolve(cwd, '..', 'docs', 'mesh', 'mesh-canonical-fixtures.json')
: path.resolve(cwd, 'docs', 'mesh', 'mesh-canonical-fixtures.json');
const fixtures = JSON.parse(readFileSync(fixturePath, 'utf-8')) as Fixture[];
for (const fixture of fixtures) {
it(`matches fixture: ${fixture.name}`, () => {
const result = buildSignaturePayload({
eventType: fixture.event_type,
nodeId: fixture.node_id,
sequence: fixture.sequence,
payload: fixture.payload,
});
expect(result).toBe(fixture.expected);
});
}
});
@@ -0,0 +1,244 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const controlPlaneJson = vi.fn();
const idbStore = new Map<string, unknown>();
vi.mock('@/lib/controlPlane', () => ({
controlPlaneJson,
}));
vi.mock('@/mesh/meshKeyStore', () => ({
getKey: vi.fn(async (id: string) => idbStore.get(id) ?? null),
setKey: vi.fn(async (id: string, key: unknown) => {
idbStore.set(id, key);
}),
deleteKey: vi.fn(async (id: string) => {
idbStore.delete(id);
}),
}));
async function flushStoragePersistence(): Promise<void> {
await Promise.resolve();
await Promise.resolve();
await new Promise((resolve) => setTimeout(resolve, 0));
}
async function waitForEncryptedContacts(): Promise<string | null> {
for (let i = 0; i < 20; i += 1) {
await flushStoragePersistence();
const stored =
sessionStorage.getItem('sb_mesh_contacts') || localStorage.getItem('sb_mesh_contacts');
if (typeof stored === 'string' && stored.startsWith('enc:')) {
return stored;
}
}
return sessionStorage.getItem('sb_mesh_contacts') || localStorage.getItem('sb_mesh_contacts');
}
function bufToBase64(buf: ArrayBuffer): string {
return btoa(String.fromCharCode(...new Uint8Array(buf)));
}
describe('meshIdentity contact storage hardening', () => {
beforeEach(() => {
vi.resetModules();
controlPlaneJson.mockReset();
idbStore.clear();
const makeStorage = () => {
const values = new Map<string, string>();
return {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => void values.set(key, value),
removeItem: (key: string) => void values.delete(key),
clear: () => void values.clear(),
};
};
Object.defineProperty(globalThis, 'localStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'sessionStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
});
async function provisionLocalIdentity(mod: typeof import('@/mesh/meshIdentity')) {
localStorage.setItem('sb_mesh_pubkey', 'test-pub');
localStorage.setItem('sb_mesh_node_id', '!sb_contacts123456');
localStorage.setItem('sb_mesh_sovereignty_accepted', 'true');
const keyPair = (await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveKey', 'deriveBits'],
)) as CryptoKeyPair;
const publicRaw = await crypto.subtle.exportKey('raw', keyPair.publicKey);
localStorage.setItem('sb_mesh_dh_pubkey', bufToBase64(publicRaw));
localStorage.setItem('sb_mesh_dh_algo', 'ECDH');
idbStore.set('sb_mesh_dh_priv', keyPair.privateKey);
}
it('hydrates secure-mode contacts from Wormhole and avoids localStorage persistence', async () => {
controlPlaneJson
.mockResolvedValueOnce({
ok: true,
contacts: {
alice: { blocked: false, dhPubKey: 'dh_a', sharedAlias: 'alias_a' },
},
})
.mockResolvedValueOnce({
ok: true,
peer_id: 'alice',
contact: { blocked: false, dhPubKey: 'dh_a2', sharedAlias: 'alias_a' },
});
const mod = await import('@/mesh/meshIdentity');
mod.setSecureModeCached(true);
const contacts = await mod.hydrateWormholeContacts(true);
expect(contacts.alice.dhPubKey).toBe('dh_a');
mod.addContact('alice', 'dh_a2');
await Promise.resolve();
expect(localStorage.getItem('sb_mesh_contacts')).toBeNull();
expect(mod.getContacts().alice.dhPubKey).toBe('dh_a2');
expect(controlPlaneJson).toHaveBeenLastCalledWith('/api/wormhole/dm/contact', expect.any(Object));
});
it('stores local contacts as encrypted ciphertext and hydrates them back', async () => {
const mod = await import('@/mesh/meshIdentity');
await provisionLocalIdentity(mod);
mod.addContact('alice', 'dh_a', 'Alice', 'X25519');
mod.updateContact('alice', {
remotePrekeyFingerprint: 'fp-1',
remotePrekeyObservedFingerprint: 'fp-1',
remotePrekeyPinnedAt: 111,
remotePrekeyLastSeenAt: 222,
remotePrekeySequence: 3,
remotePrekeySignedAt: 444,
remotePrekeyMismatch: false,
});
const stored = await waitForEncryptedContacts();
expect(String(stored ?? '')).toMatch(/^enc:/);
expect(String(stored ?? '')).not.toContain('"alice"');
expect(String(stored ?? '')).not.toContain('"dh_a"');
const hydrated = await mod.hydrateWormholeContacts(true);
expect(hydrated.alice.dhPubKey).toBe('dh_a');
expect(hydrated.alice.alias).toBe('Alice');
expect(hydrated.alice.remotePrekeyFingerprint).toBe('fp-1');
expect(hydrated.alice.remotePrekeyObservedFingerprint).toBe('fp-1');
expect(hydrated.alice.remotePrekeyPinnedAt).toBe(111);
expect(hydrated.alice.remotePrekeyLastSeenAt).toBe(222);
expect(hydrated.alice.remotePrekeySequence).toBe(3);
expect(hydrated.alice.remotePrekeySignedAt).toBe(444);
expect(hydrated.alice.remotePrekeyMismatch).toBe(false);
});
it('migrates legacy plaintext contacts to encrypted storage on first hydrate', async () => {
const mod = await import('@/mesh/meshIdentity');
await provisionLocalIdentity(mod);
localStorage.setItem(
'sb_mesh_contacts',
JSON.stringify({ alice: { blocked: false, dhPubKey: 'legacy_dh', alias: 'Legacy Alice' } }),
);
const hydrated = await mod.hydrateWormholeContacts(true);
expect(hydrated.alice.dhPubKey).toBe('legacy_dh');
expect(hydrated.alice.alias).toBe('Legacy Alice');
const stored = await waitForEncryptedContacts();
expect(String(stored ?? '')).toMatch(/^enc:/);
expect(String(stored ?? '')).not.toContain('legacy_dh');
});
it('encrypts identity-bound browser payloads under distinct info domains', async () => {
const mod = await import('@/mesh/meshIdentity');
await provisionLocalIdentity(mod);
const accessCipher = await mod.encryptIdentityBoundStoragePayload(
[{ sender_id: 'alice', timestamp: 1 }],
'SB-ACCESS-REQUESTS-STORAGE-V1',
);
expect(accessCipher).toMatch(/^enc:/);
expect(accessCipher).not.toContain('alice');
const decrypted = await mod.decryptIdentityBoundStoragePayload(
accessCipher,
'SB-ACCESS-REQUESTS-STORAGE-V1',
[],
);
expect(decrypted).toEqual([{ sender_id: 'alice', timestamp: 1 }]);
await expect(
mod.decryptIdentityBoundStoragePayload(
accessCipher,
'SB-PENDING-CONTACTS-STORAGE-V1',
[],
),
).rejects.toThrow();
});
it('treats unreadable encrypted contacts as empty and warns instead of crashing', async () => {
const mod = await import('@/mesh/meshIdentity');
await provisionLocalIdentity(mod);
localStorage.setItem('sb_mesh_contacts', 'enc:not-valid-ciphertext');
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const hydrated = await mod.hydrateWormholeContacts(true);
expect(hydrated).toEqual({});
expect(warn).toHaveBeenCalledWith(
'[mesh] contact storage unreadable — treating as empty contacts',
expect.anything(),
);
warn.mockRestore();
});
it('purges browser-persisted contact graph when secure mode boundary is applied', async () => {
const mod = await import('@/mesh/meshIdentity');
localStorage.setItem(
'sb_mesh_contacts',
JSON.stringify({ bob: { blocked: false, sharedAlias: 'peer-b' } }),
);
mod.purgeBrowserContactGraph();
expect(localStorage.getItem('sb_mesh_contacts')).toBeNull();
expect(mod.getContacts()).toEqual({});
});
it('rotates the mailbox-claim secret when identity state is cleared', async () => {
const { mailboxClaimToken } = await import('@/mesh/meshMailbox');
const mod = await import('@/mesh/meshIdentity');
await provisionLocalIdentity(mod);
const first = await mailboxClaimToken('requests', '!sb_contacts123456');
const second = await mailboxClaimToken('requests', '!sb_contacts123456');
expect(second).toBe(first);
await mod.clearBrowserIdentityState();
localStorage.setItem('sb_mesh_pubkey', 'test-pub');
localStorage.setItem('sb_mesh_node_id', '!sb_contacts123456');
localStorage.setItem('sb_mesh_sovereignty_accepted', 'true');
const keyPair = (await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveKey', 'deriveBits'],
)) as CryptoKeyPair;
const publicRaw = await crypto.subtle.exportKey('raw', keyPair.publicKey);
localStorage.setItem('sb_mesh_dh_pubkey', bufToBase64(publicRaw));
localStorage.setItem('sb_mesh_dh_algo', 'ECDH');
idbStore.set('sb_mesh_dh_priv', keyPair.privateKey);
const rotated = await mailboxClaimToken('requests', '!sb_contacts123456');
expect(rotated).not.toBe(first);
});
});
@@ -0,0 +1,85 @@
import { describe, expect, it } from 'vitest';
import {
allDmPeerIds,
buildAliasRotateMessage,
buildAccessGrantedMessage,
buildContactAcceptMessage,
buildContactDenyMessage,
buildContactOfferMessage,
mergeAliasHistory,
parseAliasRotateMessage,
parseAccessGrantedMessage,
parseDmConsentMessage,
preferredDmPeerId,
} from '@/mesh/meshDmConsent';
describe('mesh DM consent helpers', () => {
it('builds and parses access-granted payloads', () => {
const message = buildAccessGrantedMessage('dmx_alpha');
expect(parseAccessGrantedMessage(message)).toEqual({ shared_alias: 'dmx_alpha' });
});
it('builds and parses off-ledger contact offer payloads', () => {
const message = buildContactOfferMessage('dh_pub', 'X25519', '40.12,-105.27');
expect(parseDmConsentMessage(message)).toEqual({
kind: 'contact_offer',
dh_pub_key: 'dh_pub',
dh_algo: 'X25519',
geo_hint: '40.12,-105.27',
});
});
it('builds and parses off-ledger contact accept payloads', () => {
const message = buildContactAcceptMessage('dmx_pairwise');
expect(parseDmConsentMessage(message)).toEqual({
kind: 'contact_accept',
shared_alias: 'dmx_pairwise',
});
});
it('builds and parses off-ledger contact deny payloads', () => {
const message = buildContactDenyMessage('declined');
expect(parseDmConsentMessage(message)).toEqual({
kind: 'contact_deny',
reason: 'declined',
});
});
it('prefers the pairwise alias for shared DM routing', () => {
expect(preferredDmPeerId('node_public', { sharedAlias: 'dmx_pairwise' })).toBe('dmx_pairwise');
expect(preferredDmPeerId('node_public', { sharedAlias: '' })).toBe('node_public');
});
it('keeps both alias and public ids during the transition window', () => {
expect(allDmPeerIds('node_public', { sharedAlias: 'dmx_pairwise' })).toEqual([
'dmx_pairwise',
'node_public',
]);
expect(allDmPeerIds('node_public', { sharedAlias: 'node_public' })).toEqual(['node_public']);
});
it('builds and parses alias rotation control payloads', () => {
const message = buildAliasRotateMessage('dmx_next');
expect(parseAliasRotateMessage(message)).toEqual({ shared_alias: 'dmx_next' });
});
it('promotes pending alias after the grace window elapses', () => {
const now = Date.now();
expect(
preferredDmPeerId('node_public', {
sharedAlias: 'dmx_current',
pendingSharedAlias: 'dmx_next',
sharedAliasGraceUntil: now - 1,
}),
).toBe('dmx_next');
});
it('keeps alias history compact and unique', () => {
expect(mergeAliasHistory(['dmx_a', 'dmx_b', 'dmx_a', 'dmx_c', 'dmx_d'], 3)).toEqual([
'dmx_a',
'dmx_b',
'dmx_c',
]);
});
});
@@ -0,0 +1,286 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
type StoreRecord = Map<string, unknown>;
type DbRecord = {
version: number;
stores: Map<string, StoreRecord>;
};
const databases = new Map<string, DbRecord>();
const deletedDatabases: string[] = [];
const workerInstances: FakeWorker[] = [];
vi.mock('@/lib/controlPlane', () => ({
controlPlaneJson: vi.fn(),
}));
vi.mock('@/mesh/wormholeIdentityClient', () => ({
ensureWormholeReadyForSecureAction: vi.fn(async () => undefined),
isWormholeReady: vi.fn(async () => false),
}));
vi.mock('@/mesh/meshIdentity', () => ({
getDHAlgo: vi.fn(() => 'X25519'),
}));
function makeStorage() {
const values = new Map<string, string>();
return {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => void values.set(key, value),
removeItem: (key: string) => void values.delete(key),
clear: () => void values.clear(),
get length() {
return values.size;
},
key: (_i: number) => null as string | null,
};
}
class FakeWorker {
onmessage: ((event: MessageEvent<{ id: string; ok: boolean; result?: string }>) => void) | null =
null;
terminated = false;
constructor() {
workerInstances.push(this);
}
postMessage(message: { id: string }) {
queueMicrotask(() => {
this.onmessage?.({
data: { id: message.id, ok: true, result: '' },
} as MessageEvent<{ id: string; ok: boolean; result?: string }>);
});
}
terminate() {
this.terminated = true;
}
}
function domStringList(record: DbRecord): DOMStringList {
return {
contains: (name: string) => record.stores.has(name),
item: (index: number) => Array.from(record.stores.keys())[index] ?? null,
get length() {
return record.stores.size;
},
} as DOMStringList;
}
function makeRequest<T>(
executor: (request: IDBRequest<T>) => void,
tx?: IDBTransaction,
): IDBRequest<T> {
const request = {} as IDBRequest<T>;
queueMicrotask(() => {
executor(request);
tx?.oncomplete?.(new Event('complete') as Event);
});
return request;
}
function makeObjectStore(record: DbRecord, name: string, tx: IDBTransaction): IDBObjectStore {
const store = record.stores.get(name);
if (!store) throw new Error(`missing object store ${name}`);
return {
get(key: IDBValidKey) {
return makeRequest((request) => {
(request as { result?: unknown }).result = store.get(String(key));
request.onsuccess?.(new Event('success') as Event);
}, tx);
},
put(value: unknown, key?: IDBValidKey) {
return makeRequest((request) => {
store.set(String(key ?? ''), value);
(request as { result?: unknown }).result = key;
request.onsuccess?.(new Event('success') as Event);
}, tx);
},
delete(key: IDBValidKey) {
return makeRequest((request) => {
store.delete(String(key));
request.onsuccess?.(new Event('success') as Event);
}, tx);
},
} as unknown as IDBObjectStore;
}
function makeTransaction(record: DbRecord): IDBTransaction {
const tx = {
oncomplete: null,
onerror: null,
onabort: null,
objectStore: (name: string) => makeObjectStore(record, name, tx as unknown as IDBTransaction),
} as unknown as IDBTransaction;
return tx;
}
function makeDb(name: string, record: DbRecord): IDBDatabase {
return {
name,
version: record.version,
objectStoreNames: domStringList(record),
createObjectStore(storeName: string) {
if (!record.stores.has(storeName)) {
record.stores.set(storeName, new Map());
}
return {} as IDBObjectStore;
},
transaction(storeName: string | string[]) {
return makeTransaction(record);
},
close() {
/* noop */
},
} as unknown as IDBDatabase;
}
function createFakeIndexedDb() {
return {
open(name: string, version?: number) {
const request = {} as IDBOpenDBRequest;
queueMicrotask(() => {
const resolvedVersion = Number(version || 1);
let record = databases.get(name);
const upgrading = !record || resolvedVersion > record.version;
if (!record) {
record = { version: resolvedVersion, stores: new Map() };
databases.set(name, record);
}
if (upgrading) {
record.version = resolvedVersion;
(request as { result?: IDBDatabase }).result = makeDb(name, record);
request.onupgradeneeded?.(new Event('upgradeneeded') as IDBVersionChangeEvent);
}
(request as { result?: IDBDatabase }).result = makeDb(name, record);
request.onsuccess?.(new Event('success') as Event);
});
return request;
},
deleteDatabase(name: string) {
const request = {} as IDBOpenDBRequest;
queueMicrotask(() => {
deletedDatabases.push(name);
databases.delete(name);
request.onsuccess?.(new Event('success') as Event);
});
return request;
},
};
}
function ensureStore(name: string, version: number, storeName: string): StoreRecord {
let record = databases.get(name);
if (!record) {
record = { version, stores: new Map() };
databases.set(name, record);
}
record.version = Math.max(record.version, version);
if (!record.stores.has(storeName)) {
record.stores.set(storeName, new Map());
}
return record.stores.get(storeName)!;
}
function getStoredValue(name: string, storeName: string, key: string): unknown {
return databases.get(name)?.stores.get(storeName)?.get(key);
}
describe('worker ratchet vault hardening', () => {
beforeEach(() => {
vi.resetModules();
databases.clear();
deletedDatabases.length = 0;
workerInstances.length = 0;
Object.defineProperty(globalThis, 'localStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'sessionStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'Worker', {
value: FakeWorker,
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'indexedDB', {
value: createFakeIndexedDb(),
configurable: true,
writable: true,
});
});
it('persists worker ratchet state as an encrypted blob instead of raw state', async () => {
const mod = await import('@/mesh/meshDmWorkerVault');
const sample = {
alice: {
algo: 'X25519',
rk: 'root-key',
cks: 'send-chain',
ckr: 'recv-chain',
dhSelfPub: 'pub',
dhSelfPriv: 'private-material',
dhRemote: 'remote',
ns: 1,
nr: 2,
pn: 3,
skipped: { 'remote:1': 'mk' },
updated: 123,
},
};
await mod.writeWorkerRatchetStates(sample);
const raw = getStoredValue(mod.WORKER_RATCHET_DB, 'ratchet', 'state');
expect(typeof raw).toBe('string');
expect(String(raw)).not.toContain('dhSelfPriv');
expect(String(raw)).not.toContain('private-material');
const loaded = await mod.readWorkerRatchetStates();
expect(loaded).toEqual(sample);
});
it('migrates legacy plaintext worker state into encrypted storage on read', async () => {
const legacyStore = ensureStore('sb_mesh_dm_worker', 1, 'ratchet');
legacyStore.set('state', {
bob: {
algo: 'X25519',
rk: 'legacy-rk',
dhSelfPub: 'legacy-pub',
dhSelfPriv: 'legacy-private',
dhRemote: 'legacy-remote',
ns: 0,
nr: 0,
pn: 0,
updated: 999,
},
});
const mod = await import('@/mesh/meshDmWorkerVault');
const loaded = await mod.readWorkerRatchetStates();
const raw = getStoredValue(mod.WORKER_RATCHET_DB, 'ratchet', 'state');
expect(loaded.bob?.dhSelfPriv).toBe('legacy-private');
expect(typeof raw).toBe('string');
expect(String(raw)).not.toContain('legacy-private');
});
it('purgeBrowserDmState clears worker persistence and legacy browser copies', async () => {
localStorage.setItem('sb_mesh_dm_ratchet', 'legacy');
sessionStorage.setItem('sb_mesh_ratchet_telemetry', '{"seen":1}');
const mod = await import('@/mesh/meshDmWorkerClient');
await mod.purgeBrowserDmState();
expect(localStorage.getItem('sb_mesh_dm_ratchet')).toBeNull();
expect(sessionStorage.getItem('sb_mesh_ratchet_telemetry')).toBeNull();
expect(deletedDatabases).toContain('sb_mesh_dm_worker');
expect(workerInstances[0]?.terminated).toBe(true);
});
});
@@ -0,0 +1,112 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('@/lib/controlPlane', () => ({
controlPlaneJson: vi.fn(),
}));
vi.mock('@/mesh/meshKeyStore', () => ({
getKey: vi.fn().mockResolvedValue(null),
setKey: vi.fn().mockResolvedValue(undefined),
deleteKey: vi.fn().mockResolvedValue(undefined),
}));
describe('mesh identity storage separation', () => {
beforeEach(() => {
vi.resetModules();
const makeStorage = () => {
const values = new Map<string, string>();
return {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => void values.set(key, value),
removeItem: (key: string) => void values.delete(key),
clear: () => void values.clear(),
};
};
Object.defineProperty(globalThis, 'localStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'sessionStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
});
it('keeps public browser identity separate from Wormhole descriptor cache', async () => {
const mod = await import('@/mesh/meshIdentity');
mod.cachePublicIdentity({
nodeId: '!sb_public',
publicKey: 'public-key',
publicKeyAlgo: 'Ed25519',
});
mod.cacheWormholeIdentityDescriptor({
nodeId: '!sb_wormhole',
publicKey: 'wormhole-key',
publicKeyAlgo: 'Ed25519',
});
expect(mod.getStoredNodeDescriptor()).toEqual({
nodeId: '!sb_public',
publicKey: 'public-key',
publicKeyAlgo: 'Ed25519',
});
expect(mod.getWormholeIdentityDescriptor()).toEqual({
nodeId: '!sb_wormhole',
publicKey: 'wormhole-key',
publicKeyAlgo: 'Ed25519',
});
});
it('clears browser public identity and Wormhole descriptor cache together on full reset', async () => {
const mod = await import('@/mesh/meshIdentity');
mod.cachePublicIdentity({
nodeId: '!sb_public',
publicKey: 'public-key',
publicKeyAlgo: 'Ed25519',
});
mod.cacheWormholeIdentityDescriptor({
nodeId: '!sb_wormhole',
publicKey: 'wormhole-key',
publicKeyAlgo: 'Ed25519',
});
await mod.clearBrowserIdentityState();
expect(mod.getStoredNodeDescriptor()).toBeNull();
expect(mod.getWormholeIdentityDescriptor()).toBeNull();
});
it('migrates legacy browser and Wormhole node ids to the current format', async () => {
const mod = await import('@/mesh/meshIdentity');
const publicKey = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
const currentNodeId = await mod.deriveNodeIdFromPublicKey(publicKey);
mod.cachePublicIdentity({
nodeId: '!sb_deadbeef',
publicKey,
publicKeyAlgo: 'Ed25519',
});
mod.cacheWormholeIdentityDescriptor({
nodeId: '!sb_deadbeef',
publicKey,
publicKeyAlgo: 'Ed25519',
});
await mod.migrateLegacyNodeIds();
expect(mod.getStoredNodeDescriptor()).toEqual({
nodeId: currentNodeId,
publicKey,
publicKeyAlgo: 'Ed25519',
});
expect(mod.getWormholeIdentityDescriptor()).toEqual({
nodeId: currentNodeId,
publicKey,
publicKeyAlgo: 'Ed25519',
});
});
});
@@ -0,0 +1,32 @@
import { readFileSync } from 'fs';
import path from 'path';
import { buildMerkleRoot, verifyMerkleProof } from '@/mesh/meshMerkle';
type Fixture = {
leaves: string[];
root: string;
proofs: Record<string, { hash: string; side: string }[]>;
};
describe('mesh merkle fixtures', () => {
const cwd = process.cwd();
const fixturePath = cwd.endsWith('frontend')
? path.resolve(cwd, '..', 'docs', 'mesh', 'mesh-merkle-fixtures.json')
: path.resolve(cwd, 'docs', 'mesh', 'mesh-merkle-fixtures.json');
const fixtures = JSON.parse(readFileSync(fixturePath, 'utf-8')) as Fixture;
it('builds the expected root', async () => {
const root = await buildMerkleRoot(fixtures.leaves);
expect(root).toBe(fixtures.root);
});
it('verifies provided proofs', async () => {
const root = fixtures.root;
for (const [idxStr, proof] of Object.entries(fixtures.proofs)) {
const idx = Number(idxStr);
const leaf = fixtures.leaves[idx];
const ok = await verifyMerkleProof(leaf, idx, proof, root);
expect(ok).toBe(true);
}
});
});
@@ -0,0 +1,96 @@
import { describe, expect, it } from 'vitest';
import {
buildDmTrustHint,
buildPrivateLaneHint,
dmTrustPrimaryActionLabel,
isFirstContactTrustOnly,
shortTrustFingerprint,
shouldAutoRevealSasForTrust,
} from '@/mesh/meshPrivacyHints';
describe('meshPrivacyHints', () => {
it('flags recent private-lane fallback as a danger hint', () => {
const hint = buildPrivateLaneHint({
activeTab: 'dms',
recentPrivateFallback: true,
recentPrivateFallbackReason: 'Tor transport failed and clearnet relay was used.',
dmTransportMode: 'relay',
});
expect(hint).toEqual(
expect.objectContaining({
severity: 'danger',
title: 'RECENT PRIVACY DOWNGRADE',
}),
);
expect(hint?.detail).toContain('clearnet relay');
});
it('flags remote prekey mismatch as a danger trust hint', () => {
const hint = buildDmTrustHint({
remotePrekeyMismatch: true,
});
expect(hint).toEqual(
expect.objectContaining({
severity: 'danger',
title: 'REMOTE PREKEY CHANGED',
}),
);
});
it('flags first-seen pinned contacts as TOFU until verified', () => {
const contact = {
remotePrekeyFingerprint: 'abc123',
remotePrekeyPinnedAt: 123,
verify_registry: false,
verify_inband: false,
verified: false,
};
expect(isFirstContactTrustOnly(contact)).toBe(true);
expect(buildDmTrustHint(contact)).toEqual(
expect.objectContaining({
severity: 'warn',
title: 'FIRST CONTACT (TOFU ONLY)',
}),
);
expect(buildDmTrustHint(contact)?.detail).toContain('not proof of sender identity');
expect(dmTrustPrimaryActionLabel(contact)).toBe('VERIFY SAS NOW');
expect(shouldAutoRevealSasForTrust(contact)).toBe(true);
});
it('auto-reveals SAS for trust hazards but keeps ordinary verified contacts quiet', () => {
expect(
shouldAutoRevealSasForTrust({
remotePrekeyMismatch: true,
}),
).toBe(true);
expect(
shouldAutoRevealSasForTrust({
verify_mismatch: true,
}),
).toBe(true);
expect(
shouldAutoRevealSasForTrust({
verified: true,
verify_inband: true,
verify_registry: true,
}),
).toBe(false);
expect(
dmTrustPrimaryActionLabel({
verified: true,
verify_inband: true,
verify_registry: true,
}),
).toBe('SHOW SAS');
});
it('shortens long trust fingerprints for display', () => {
expect(shortTrustFingerprint('abcdef0123456789fedcba9876543210')).toBe('abcdef01..543210');
expect(shortTrustFingerprint('abcd1234')).toBe('abcd1234');
expect(shortTrustFingerprint('')).toBe('unknown');
});
});
@@ -0,0 +1,175 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
// Track what gets stored in IndexedDB
const idbStore = new Map<string, unknown>();
const deletedDatabases: string[] = [];
vi.mock('@/lib/controlPlane', () => ({
controlPlaneJson: vi.fn(),
}));
vi.mock('@/mesh/meshKeyStore', () => ({
getKey: vi.fn(async (id: string) => idbStore.get(id) ?? null),
setKey: vi.fn(async (id: string, key: unknown) => {
idbStore.set(id, key);
}),
deleteKey: vi.fn(async (id: string) => {
idbStore.delete(id);
}),
}));
function makeStorage() {
const values = new Map<string, string>();
return {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => void values.set(key, value),
removeItem: (key: string) => void values.delete(key),
clear: () => void values.clear(),
get length() {
return values.size;
},
key: (_i: number) => null as string | null,
};
}
describe('signing key storage hardening', () => {
beforeEach(() => {
vi.resetModules();
idbStore.clear();
deletedDatabases.length = 0;
Object.defineProperty(globalThis, 'localStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'sessionStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'indexedDB', {
value: {
deleteDatabase: vi.fn((name: string) => {
deletedDatabases.push(name);
const request = {} as IDBOpenDBRequest;
queueMicrotask(() => {
request.onsuccess?.(new Event('success') as Event);
});
return request;
}),
},
configurable: true,
writable: true,
});
});
it('getNodeIdentity returns identity even when privateKey is empty (post-migration)', async () => {
const mod = await import('@/mesh/meshIdentity');
// Simulate a state where the signing key has already been migrated:
// publicKey and nodeId exist, but privateKey does not.
localStorage.setItem('sb_mesh_pubkey', 'test-pub');
localStorage.setItem('sb_mesh_node_id', '!sb_abcd1234abcd1234');
localStorage.setItem('sb_mesh_sovereignty_accepted', 'true');
// No sb_mesh_privkey — simulates post-migration state
const identity = mod.getNodeIdentity();
expect(identity).not.toBeNull();
expect(identity!.publicKey).toBe('test-pub');
expect(identity!.nodeId).toBe('!sb_abcd1234abcd1234');
expect(identity!.privateKey).toBe('');
});
it('getNodeIdentity triggers eager migration and does not expose legacy privateKey', async () => {
const mod = await import('@/mesh/meshIdentity');
localStorage.setItem('sb_mesh_pubkey', 'test-pub');
localStorage.setItem('sb_mesh_node_id', '!sb_abcd1234abcd1234');
localStorage.setItem('sb_mesh_privkey', '{"fake":"jwk"}');
localStorage.setItem('sb_mesh_sovereignty_accepted', 'true');
const identity = mod.getNodeIdentity();
expect(identity).not.toBeNull();
expect(identity!.privateKey).toBe('');
// The eager migration fires asynchronously (void ensureSigningPrivateKey()).
// In this test environment crypto.subtle.importKey will fail on the fake JWK,
// but the extractable browser copy should still be scrubbed.
await new Promise((r) => setTimeout(r, 10));
expect(localStorage.getItem('sb_mesh_privkey')).toBeNull();
expect(identity!.publicKey).toBe('test-pub');
});
it('purgeBrowserSigningMaterial clears IndexedDB signing key', async () => {
const { deleteKey } = await import('@/mesh/meshKeyStore');
const mod = await import('@/mesh/meshIdentity');
idbStore.set('sb_mesh_sign_priv', 'mock-crypto-key');
localStorage.setItem('sb_mesh_privkey', '{"fake":"jwk"}');
localStorage.setItem('sb_mesh_sequence', '42');
await mod.purgeBrowserSigningMaterial();
expect(deleteKey).toHaveBeenCalledWith('sb_mesh_sign_priv');
expect(localStorage.getItem('sb_mesh_privkey')).toBeNull();
expect(localStorage.getItem('sb_mesh_sequence')).toBeNull();
});
it('clearBrowserIdentityState clears both DH and signing keys from IndexedDB', async () => {
const { deleteKey } = await import('@/mesh/meshKeyStore');
const mod = await import('@/mesh/meshIdentity');
localStorage.setItem('sb_mesh_pubkey', 'test-pub');
localStorage.setItem('sb_mesh_node_id', '!sb_test');
localStorage.setItem('sb_mesh_privkey', '{"fake":"jwk"}');
localStorage.setItem('sb_mesh_session_mode', 'true');
localStorage.setItem('sb_mesh_sovereignty_accepted', 'true');
localStorage.setItem('sb_dm_bundle_fingerprint', 'bundle-fp');
sessionStorage.setItem('sb_wormhole_desc_node_id', '!sb_gate');
sessionStorage.setItem('sb_mesh_dm_ratchet', 'encrypted');
sessionStorage.setItem('sb_mesh_ratchet_telemetry', '{"seen":1}');
await mod.clearBrowserIdentityState();
expect(deleteKey).toHaveBeenCalledWith('sb_mesh_dh_priv');
expect(deleteKey).toHaveBeenCalledWith('sb_mesh_sign_priv');
expect(localStorage.getItem('sb_mesh_pubkey')).toBeNull();
expect(localStorage.getItem('sb_mesh_privkey')).toBeNull();
expect(localStorage.getItem('sb_dm_bundle_fingerprint')).toBeNull();
expect(sessionStorage.getItem('sb_wormhole_desc_node_id')).toBeNull();
expect(sessionStorage.getItem('sb_mesh_dm_ratchet')).toBeNull();
expect(sessionStorage.getItem('sb_mesh_ratchet_telemetry')).toBeNull();
expect(deletedDatabases).toContain('sb_mesh_ratchet_crypto');
});
it('generateDHKeys fails closed when non-extractable DH key storage is unavailable', async () => {
const { setKey } = await import('@/mesh/meshKeyStore');
vi.mocked(setKey).mockRejectedValueOnce(new Error('idb unavailable'));
const mod = await import('@/mesh/meshIdentity');
await expect(mod.generateDHKeys()).rejects.toThrow('IndexedDB required for DH key storage');
expect(localStorage.getItem('sb_mesh_dh_privkey')).toBeNull();
expect(sessionStorage.getItem('sb_mesh_dh_privkey')).toBeNull();
});
it('signWithStoredKey is exported and throws when no key available', async () => {
const mod = await import('@/mesh/meshIdentity');
// No key in IndexedDB or localStorage
await expect(mod.signWithStoredKey('test message')).rejects.toThrow(
'No signing key available',
);
});
it('signEvent fails closed when only public identity metadata exists', async () => {
const mod = await import('@/mesh/meshIdentity');
sessionStorage.setItem('sb_mesh_pubkey', 'test-pub');
sessionStorage.setItem('sb_mesh_node_id', '!sb_abcd1234abcd1234');
sessionStorage.setItem('sb_mesh_sovereignty_accepted', 'true');
sessionStorage.setItem('sb_mesh_algo', 'Ed25519');
await expect(
mod.signEvent('message', '!sb_abcd1234abcd1234', 1, { message: 'hello' }),
).rejects.toThrow('No signing key available');
});
});
@@ -0,0 +1,41 @@
import { describe, expect, it } from 'vitest';
import {
getMeshTerminalWriteLockReason,
isMeshTerminalWriteCommand,
} from '@/lib/meshTerminalPolicy';
describe('mesh terminal policy', () => {
it('blocks sensitive terminal writes while anonymous mode is active', () => {
const reason = getMeshTerminalWriteLockReason({
wormholeRequired: true,
wormholeReady: true,
anonymousMode: true,
anonymousModeReady: true,
});
expect(reason).toContain('Anonymous Infonet mode');
expect(isMeshTerminalWriteCommand('dm', ['add', '!sb_test'])).toBe(true);
expect(isMeshTerminalWriteCommand('mesh', ['send', 'hello'])).toBe(true);
});
it('blocks sensitive terminal writes until Wormhole secure mode is ready', () => {
const reason = getMeshTerminalWriteLockReason({
wormholeRequired: true,
wormholeReady: false,
anonymousMode: false,
anonymousModeReady: false,
});
expect(reason).toContain('until Wormhole secure mode is ready');
expect(isMeshTerminalWriteCommand('gate', ['create', 'newsroom'])).toBe(true);
expect(isMeshTerminalWriteCommand('send', ['broadcast', 'hello'])).toBe(true);
});
it('keeps read-only terminal commands available', () => {
expect(isMeshTerminalWriteCommand('status', [])).toBe(false);
expect(isMeshTerminalWriteCommand('signals', ['10'])).toBe(false);
expect(isMeshTerminalWriteCommand('mesh', ['listen', '20'])).toBe(false);
expect(isMeshTerminalWriteCommand('messages', [])).toBe(false);
});
});
@@ -0,0 +1,224 @@
import {
getSenderRecoveryState,
REQUEST_V2_REDUCED_VERSION,
recoverSenderSealWithFallback,
requiresSenderRecovery,
shouldAllowRequestActions,
shouldKeepUnresolvedRequestVisible,
shouldPromoteRecoveredSenderForBootstrap,
shouldPromoteRecoveredSenderForKnownContact,
} from '@/mesh/requestSenderRecovery';
describe('requestSenderRecovery', () => {
it('only promotes a known-contact sender when the seal verified and the sender matches', () => {
expect(
shouldPromoteRecoveredSenderForKnownContact(
{ sender_id: 'alice', seal_verified: true },
'alice',
),
).toBe(true);
expect(
shouldPromoteRecoveredSenderForKnownContact(
{ sender_id: 'alice', seal_verified: false },
'alice',
),
).toBe(false);
expect(
shouldPromoteRecoveredSenderForKnownContact(
{ sender_id: 'mallory', seal_verified: true },
'alice',
),
).toBe(false);
});
it('only promotes a bootstrap-recovered sender when the seal verified', () => {
expect(
shouldPromoteRecoveredSenderForBootstrap({
sender_id: 'alice',
seal_verified: true,
}),
).toBe(true);
expect(
shouldPromoteRecoveredSenderForBootstrap({
sender_id: 'alice',
seal_verified: false,
}),
).toBe(false);
expect(shouldPromoteRecoveredSenderForBootstrap(null)).toBe(false);
});
it('prefers explicit request-v2 recovery markers over sealed-string inference', () => {
expect(
requiresSenderRecovery({
sender_id: 'opaque',
sender_seal: 'v3:test',
request_contract_version: REQUEST_V2_REDUCED_VERSION,
sender_recovery_required: true,
}),
).toBe(true);
expect(
getSenderRecoveryState({
sender_id: 'opaque',
sender_seal: 'v3:test',
request_contract_version: REQUEST_V2_REDUCED_VERSION,
sender_recovery_required: true,
}),
).toBe('pending');
expect(
requiresSenderRecovery({
sender_id: 'sealed:abcd',
sender_seal: 'v2:test',
}),
).toBe(true);
});
it('only allows request actions once canonical recovery reaches verified', () => {
expect(
shouldAllowRequestActions({
request_contract_version: REQUEST_V2_REDUCED_VERSION,
sender_recovery_required: true,
sender_recovery_state: 'verified',
}),
).toBe(true);
expect(
shouldAllowRequestActions({
request_contract_version: REQUEST_V2_REDUCED_VERSION,
sender_recovery_required: true,
sender_recovery_state: 'pending',
}),
).toBe(false);
expect(
shouldAllowRequestActions({
request_contract_version: REQUEST_V2_REDUCED_VERSION,
sender_recovery_required: true,
sender_recovery_state: 'failed',
}),
).toBe(false);
expect(
shouldAllowRequestActions({
request_contract_version: undefined,
sender_recovery_required: undefined,
sender_recovery_state: undefined,
}),
).toBe(true);
});
it('keeps only pending or failed canonical request-v2 mail visible in the unresolved inbox flow', () => {
expect(
shouldKeepUnresolvedRequestVisible({
delivery_class: 'request',
request_contract_version: REQUEST_V2_REDUCED_VERSION,
sender_recovery_required: true,
sender_recovery_state: 'pending',
}),
).toBe(true);
expect(
shouldKeepUnresolvedRequestVisible({
delivery_class: 'request',
request_contract_version: REQUEST_V2_REDUCED_VERSION,
sender_recovery_required: true,
sender_recovery_state: 'failed',
}),
).toBe(true);
expect(
shouldKeepUnresolvedRequestVisible({
delivery_class: 'request',
request_contract_version: REQUEST_V2_REDUCED_VERSION,
sender_recovery_required: true,
sender_recovery_state: 'verified',
}),
).toBe(false);
expect(
shouldKeepUnresolvedRequestVisible({
delivery_class: 'request',
request_contract_version: undefined,
sender_recovery_required: undefined,
sender_recovery_state: 'pending',
}),
).toBe(false);
expect(
shouldKeepUnresolvedRequestVisible({
delivery_class: 'shared',
request_contract_version: REQUEST_V2_REDUCED_VERSION,
sender_recovery_required: true,
sender_recovery_state: 'pending',
}),
).toBe(false);
});
it('prefers local recovery and only falls back to the helper on local failure', async () => {
const openLocal = vi.fn().mockResolvedValue({
sender_id: 'alice',
seal_verified: true,
});
const openHelper = vi.fn().mockResolvedValue({
sender_id: 'helper-alice',
seal_verified: true,
});
await expect(
recoverSenderSealWithFallback({
wormholeReady: true,
openLocal,
openHelper,
}),
).resolves.toEqual({ sender_id: 'alice', seal_verified: true });
expect(openLocal).toHaveBeenCalledTimes(1);
expect(openHelper).not.toHaveBeenCalled();
});
it('uses the helper only as fallback when local recovery cannot open the seal', async () => {
const openLocal = vi.fn().mockResolvedValue(null);
const openHelper = vi.fn().mockResolvedValue({
sender_id: 'alice',
seal_verified: true,
});
await expect(
recoverSenderSealWithFallback({
wormholeReady: true,
openLocal,
openHelper,
}),
).resolves.toEqual({ sender_id: 'alice', seal_verified: true });
expect(openLocal).toHaveBeenCalledTimes(1);
expect(openHelper).toHaveBeenCalledTimes(1);
});
it('does not invoke the helper when Wormhole fallback is unavailable', async () => {
const openLocal = vi.fn().mockResolvedValue(null);
const openHelper = vi.fn().mockResolvedValue({
sender_id: 'alice',
seal_verified: true,
});
await expect(
recoverSenderSealWithFallback({
wormholeReady: false,
openLocal,
openHelper,
}),
).resolves.toBeNull();
expect(openLocal).toHaveBeenCalledTimes(1);
expect(openHelper).not.toHaveBeenCalled();
});
it('treats helper failure as unresolved instead of promoting helper authority', async () => {
const openLocal = vi.fn().mockResolvedValue(null);
const openHelper = vi.fn().mockRejectedValue(new Error('helper_failed'));
await expect(
recoverSenderSealWithFallback({
wormholeReady: true,
openLocal,
openHelper,
}),
).resolves.toBeNull();
expect(openLocal).toHaveBeenCalledTimes(1);
expect(openHelper).toHaveBeenCalledTimes(1);
});
});
@@ -0,0 +1,40 @@
import {
ensureCanonicalRequestV2SenderSeal,
REQUEST_V2_SENDER_SEAL_VERSION_ERROR,
requiresCanonicalRequestV2SenderSeal,
} from '@/mesh/requestSenderSealPolicy';
describe('requestSenderSealPolicy', () => {
it('requires canonical v3 seals only for request-class sealed sender', () => {
expect(
requiresCanonicalRequestV2SenderSeal({
deliveryClass: 'request',
useSealedSender: true,
}),
).toBe(true);
expect(
requiresCanonicalRequestV2SenderSeal({
deliveryClass: 'request',
useSealedSender: false,
}),
).toBe(false);
expect(
requiresCanonicalRequestV2SenderSeal({
deliveryClass: 'shared',
useSealedSender: true,
}),
).toBe(false);
});
it('accepts v3 seals and rejects non-v3 seals for canonical request-v2 sender sealing', () => {
expect(ensureCanonicalRequestV2SenderSeal('v3:ephemeral:payload')).toBe(
'v3:ephemeral:payload',
);
expect(() => ensureCanonicalRequestV2SenderSeal('v2:legacy-payload')).toThrow(
REQUEST_V2_SENDER_SEAL_VERSION_ERROR,
);
expect(() => ensureCanonicalRequestV2SenderSeal('')).toThrow(
REQUEST_V2_SENDER_SEAL_VERSION_ERROR,
);
});
});
@@ -0,0 +1,178 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const controlPlaneJson = vi.fn();
const idbStore = new Map<string, unknown>();
vi.mock('@/lib/controlPlane', () => ({
controlPlaneJson,
}));
vi.mock('@/mesh/meshKeyStore', () => ({
getKey: vi.fn(async (id: string) => idbStore.get(id) ?? null),
setKey: vi.fn(async (id: string, key: unknown) => {
idbStore.set(id, key);
}),
deleteKey: vi.fn(async (id: string) => {
idbStore.delete(id);
}),
}));
function bufToBase64(buf: ArrayBuffer): string {
return btoa(String.fromCharCode(...new Uint8Array(buf)));
}
async function buildV3SealForRecipient(params: {
recipientPublicKey: CryptoKey;
recipientId: string;
msgId: string;
plaintext: string;
}) {
const { encryptDM } = await import('@/mesh/meshIdentity');
const { PROTOCOL_VERSION } = await import('@/mesh/meshProtocol');
const ephemeral = (await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
true,
['deriveBits', 'deriveKey'],
)) as CryptoKeyPair;
const ephemeralPubRaw = await crypto.subtle.exportKey('raw', ephemeral.publicKey);
const ephemeralPub = bufToBase64(ephemeralPubRaw);
const secret = await crypto.subtle.deriveBits(
{ name: 'ECDH', public: params.recipientPublicKey },
ephemeral.privateKey,
256,
);
const salt = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(
`SB-SEAL-SALT|${params.recipientId}|${params.msgId}|${PROTOCOL_VERSION}|${ephemeralPub}`,
),
);
const hkdfKey = await crypto.subtle.importKey('raw', secret, 'HKDF', false, ['deriveKey']);
const sealKey = await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-256',
salt,
info: new TextEncoder().encode('SB-SENDER-SEAL-V3'),
},
hkdfKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt'],
);
const ciphertext = await encryptDM(params.plaintext, sealKey);
return `v3:${ephemeralPub}:${ciphertext}`;
}
describe('request sender seal recovery window', () => {
beforeEach(() => {
vi.resetModules();
controlPlaneJson.mockReset();
idbStore.clear();
const makeStorage = () => {
const values = new Map<string, string>();
return {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => void values.set(key, value),
removeItem: (key: string) => void values.delete(key),
clear: () => void values.clear(),
};
};
Object.defineProperty(globalThis, 'localStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'sessionStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
});
it('opens a v3 sender seal with the immediately previous retained recipient key', async () => {
const mod = await import('@/mesh/meshIdentity');
const previousRecipient = (await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveBits', 'deriveKey'],
)) as CryptoKeyPair;
const currentRecipient = (await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveBits', 'deriveKey'],
)) as CryptoKeyPair;
idbStore.set('sb_mesh_dh_priv', currentRecipient.privateKey);
idbStore.set('sb_mesh_dh_prev_priv', previousRecipient.privateKey);
localStorage.setItem('sb_mesh_dh_algo', 'ECDH');
const plaintext = JSON.stringify({ sender_id: 'alice', msg_id: 'msg-rotation' });
const senderSeal = await buildV3SealForRecipient({
recipientPublicKey: previousRecipient.publicKey,
recipientId: '!sb_recipient',
msgId: 'msg-rotation',
plaintext,
});
await expect(
mod.decryptSenderSealPayloadLocally(senderSeal, '', '!sb_recipient', 'msg-rotation'),
).resolves.toBe(plaintext);
});
it('returns null when the prior retained recipient key is unavailable', async () => {
const mod = await import('@/mesh/meshIdentity');
const previousRecipient = (await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveBits', 'deriveKey'],
)) as CryptoKeyPair;
const currentRecipient = (await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveBits', 'deriveKey'],
)) as CryptoKeyPair;
idbStore.set('sb_mesh_dh_priv', currentRecipient.privateKey);
localStorage.setItem('sb_mesh_dh_algo', 'ECDH');
const senderSeal = await buildV3SealForRecipient({
recipientPublicKey: previousRecipient.publicKey,
recipientId: '!sb_recipient',
msgId: 'msg-rotation-miss',
plaintext: JSON.stringify({ sender_id: 'alice', msg_id: 'msg-rotation-miss' }),
});
await expect(
mod.decryptSenderSealPayloadLocally(senderSeal, '', '!sb_recipient', 'msg-rotation-miss'),
).resolves.toBeNull();
});
it('retains the current DH private key in the previous-key slot when rotating', async () => {
const mod = await import('@/mesh/meshIdentity');
const existing = (await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveBits', 'deriveKey'],
)) as CryptoKeyPair;
const originalGenerateKey = crypto.subtle.generateKey.bind(crypto.subtle);
const generateKeySpy = vi
.spyOn(crypto.subtle, 'generateKey')
.mockImplementation(((algorithm: AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[]) => {
if (algorithm === 'X25519') {
return Promise.reject(new Error('x25519_unavailable_for_test'));
}
return originalGenerateKey(algorithm, extractable, keyUsages);
}) as typeof crypto.subtle.generateKey);
idbStore.set('sb_mesh_dh_priv', existing.privateKey);
try {
await mod.generateDHKeys();
expect(idbStore.get('sb_mesh_dh_prev_priv')).toBe(existing.privateKey);
expect(idbStore.get('sb_mesh_dh_priv')).not.toBe(existing.privateKey);
} finally {
generateKeySpy.mockRestore();
}
});
});
@@ -0,0 +1,125 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const controlPlaneJson = vi.fn();
const getNodeIdentity = vi.fn<
() => { nodeId: string; publicKey: string; privateKey: string } | null
>(() => null);
const signEvent = vi.fn();
const signMessage = vi.fn();
const signWithStoredKey = vi.fn();
const isSecureModeCached = vi.fn(() => true);
const fetchWormholeSettings = vi.fn(async () => ({ enabled: true }));
const fetchWormholeState = vi.fn(async () => ({ ready: true }));
vi.mock('@/lib/controlPlane', () => ({
controlPlaneJson,
}));
vi.mock('@/mesh/meshIdentity', () => ({
cacheWormholeIdentityDescriptor: vi.fn(),
getNodeIdentity,
getPublicKeyAlgo: vi.fn(() => 'ed25519'),
isSecureModeCached,
purgeBrowserSigningMaterial: vi.fn(async () => {}),
setSecureModeCached: vi.fn(),
signEvent,
signMessage,
signWithStoredKey,
}));
vi.mock('@/mesh/meshProtocol', () => ({
PROTOCOL_VERSION: 'sb-test',
}));
vi.mock('@/mesh/wormholeClient', () => ({
fetchWormholeSettings,
fetchWormholeState,
}));
describe('wormholeIdentityClient strict profile hints', () => {
beforeEach(() => {
vi.resetModules();
controlPlaneJson.mockReset();
controlPlaneJson.mockResolvedValue({ ok: true });
getNodeIdentity.mockReset();
getNodeIdentity.mockReturnValue(null);
signEvent.mockReset();
signMessage.mockReset();
signWithStoredKey.mockReset();
isSecureModeCached.mockReset();
isSecureModeCached.mockReturnValue(true);
fetchWormholeSettings.mockReset();
fetchWormholeSettings.mockResolvedValue({ enabled: true });
fetchWormholeState.mockReset();
fetchWormholeState.mockResolvedValue({ ready: true });
});
it('applies strict gate_operator enforcement to gate persona and compose operations', async () => {
const mod = await import('@/mesh/wormholeIdentityClient');
await mod.listWormholeGatePersonas('infonet');
await mod.createWormholeGatePersona('infonet', 'persona-1');
await mod.activateWormholeGatePersona('infonet', 'persona-1');
await mod.clearWormholeGatePersona('infonet');
await mod.retireWormholeGatePersona('infonet', 'persona-1');
await mod.composeWormholeGateMessage('infonet', 'hello');
expect(controlPlaneJson).toHaveBeenNthCalledWith(
1,
'/api/wormhole/gate/infonet/personas',
expect.objectContaining({
capabilityIntent: 'wormhole_gate_persona',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
}),
);
for (let i = 2; i <= 5; i += 1) {
expect(controlPlaneJson).toHaveBeenNthCalledWith(
i,
expect.any(String),
expect.objectContaining({
capabilityIntent: 'wormhole_gate_persona',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
}),
);
}
expect(controlPlaneJson).toHaveBeenNthCalledWith(
6,
'/api/wormhole/gate/message/compose',
expect.objectContaining({
capabilityIntent: 'wormhole_gate_content',
sessionProfileHint: 'gate_operator',
enforceProfileHint: true,
}),
);
});
it('browser raw signing fails closed instead of falling back to legacy jwk signing', async () => {
fetchWormholeSettings.mockResolvedValue({ enabled: false });
fetchWormholeState.mockResolvedValue({ ready: false });
getNodeIdentity.mockReturnValue({
nodeId: '!sb_browser',
publicKey: 'browser-pub',
privateKey: '',
});
signWithStoredKey.mockRejectedValue(new Error('no key'));
const mod = await import('@/mesh/wormholeIdentityClient');
await expect(mod.signRawMeshMessage('payload')).rejects.toThrow(
'browser_signing_key_unavailable',
);
expect(signWithStoredKey).toHaveBeenCalledWith('payload');
expect(signMessage).not.toHaveBeenCalled();
});
it('keeps the cached secure boundary when wormhole settings fetch fails', async () => {
fetchWormholeSettings.mockRejectedValue(new Error('network down'));
isSecureModeCached.mockReturnValue(true);
const mod = await import('@/mesh/wormholeIdentityClient');
await expect(mod.isWormholeSecureRequired()).resolves.toBe(true);
});
});
@@ -1,5 +1,10 @@
import { describe, it, expect } from 'vitest';
import { classifyAircraft, HELI_TYPES, TURBOPROP_TYPES, BIZJET_TYPES } from '@/utils/aircraftClassification';
import {
classifyAircraft,
HELI_TYPES,
TURBOPROP_TYPES,
BIZJET_TYPES,
} from '@/utils/aircraftClassification';
describe('classifyAircraft', () => {
// ─── Helicopter classification ────────────────────────────────────────────
@@ -0,0 +1,101 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
const idbStore = new Map<string, unknown>();
vi.mock('@/mesh/meshKeyStore', () => ({
getKey: vi.fn(async (id: string) => idbStore.get(id) ?? null),
setKey: vi.fn(async (id: string, key: unknown) => {
idbStore.set(id, key);
}),
deleteKey: vi.fn(async (id: string) => {
idbStore.delete(id);
}),
}));
function makeStorage() {
const values = new Map<string, string>();
return {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => void values.set(key, value),
removeItem: (key: string) => void values.delete(key),
clear: () => void values.clear(),
};
}
function bufToBase64(buf: ArrayBuffer): string {
return btoa(String.fromCharCode(...new Uint8Array(buf)));
}
async function provisionLocalIdentity(): Promise<void> {
const meshIdentity = await import('@/mesh/meshIdentity');
localStorage.setItem('sb_mesh_pubkey', 'test-pub');
localStorage.setItem('sb_mesh_node_id', '!sb_sensitive123456');
localStorage.setItem('sb_mesh_sovereignty_accepted', 'true');
const keyPair = (await crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256' },
false,
['deriveKey', 'deriveBits'],
)) as CryptoKeyPair;
const publicRaw = await crypto.subtle.exportKey('raw', keyPair.publicKey);
localStorage.setItem('sb_mesh_dh_pubkey', bufToBase64(publicRaw));
localStorage.setItem('sb_mesh_dh_algo', 'ECDH');
idbStore.set('sb_mesh_dh_priv', keyPair.privateKey);
meshIdentity.getNodeIdentity();
}
describe('identityBoundSensitiveStorage', () => {
beforeEach(() => {
vi.resetModules();
idbStore.clear();
Object.defineProperty(globalThis, 'localStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'sessionStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
});
it('stores encrypted values in sensitive storage and keeps them out of localStorage', async () => {
await provisionLocalIdentity();
const storage = await import('@/lib/identityBoundSensitiveStorage');
await storage.persistIdentityBoundSensitiveValue(
'sb_access_requests:test',
'SB-ACCESS-REQUESTS-STORAGE-V1',
[{ sender_id: 'alice', ts: 1 }],
);
expect(String(sessionStorage.getItem('sb_access_requests:test') ?? '')).toMatch(/^enc:/);
expect(localStorage.getItem('sb_access_requests:test')).toBeNull();
const hydrated = await storage.loadIdentityBoundSensitiveValue(
'sb_access_requests:test',
'SB-ACCESS-REQUESTS-STORAGE-V1',
[],
);
expect(hydrated).toEqual([{ sender_id: 'alice', ts: 1 }]);
});
it('migrates legacy plaintext sensitive values into encrypted session-backed storage', async () => {
await provisionLocalIdentity();
const storage = await import('@/lib/identityBoundSensitiveStorage');
localStorage.setItem('sb_mesh_muted', JSON.stringify(['alice', 'bob']));
const hydrated = await storage.loadIdentityBoundSensitiveValue(
'sb_mesh_muted:!sb_sensitive123456',
'SB-MUTED-LIST-V1',
[],
{ legacyKey: 'sb_mesh_muted' },
);
expect(hydrated).toEqual(['alice', 'bob']);
expect(String(sessionStorage.getItem('sb_mesh_muted:!sb_sensitive123456') ?? '')).toMatch(/^enc:/);
expect(localStorage.getItem('sb_mesh_muted')).toBeNull();
expect(sessionStorage.getItem('sb_mesh_muted')).toBeNull();
});
});
@@ -0,0 +1,87 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
function makeStorage() {
const values = new Map<string, string>();
return {
getItem: (key: string) => values.get(key) ?? null,
setItem: (key: string, value: string) => void values.set(key, value),
removeItem: (key: string) => void values.delete(key),
clear: () => void values.clear(),
};
}
describe('privacyBrowserStorage', () => {
beforeEach(() => {
vi.resetModules();
Object.defineProperty(globalThis, 'localStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
Object.defineProperty(globalThis, 'sessionStorage', {
value: makeStorage(),
configurable: true,
writable: true,
});
});
it('stores sensitive items in sessionStorage by default', async () => {
const mod = await import('@/lib/privacyBrowserStorage');
mod.setSensitiveBrowserItem('secret-key', 'alpha');
expect(mod.getSensitiveBrowserStorageMode()).toBe('session');
expect(sessionStorage.getItem('secret-key')).toBe('alpha');
expect(localStorage.getItem('secret-key')).toBeNull();
expect(mod.getSensitiveBrowserItem('secret-key')).toBe('alpha');
});
it('stores privacy preferences in session storage when session mode is enabled', async () => {
const mod = await import('@/lib/privacyBrowserStorage');
mod.setSessionModePreference(true);
mod.setPrivacyStrictPreference(true, { sessionMode: true });
mod.setPrivacyProfilePreference('high', { sessionMode: true });
expect(mod.getSessionModePreference()).toBe(true);
expect(mod.getPrivacyStrictPreference()).toBe(true);
expect(mod.getPrivacyProfilePreference()).toBe('high');
expect(sessionStorage.getItem('sb_mesh_session_mode')).toBe('true');
expect(sessionStorage.getItem('sb_privacy_strict')).toBe('true');
expect(sessionStorage.getItem('sb_privacy_profile')).toBe('high');
expect(localStorage.getItem('sb_mesh_session_mode')).toBeNull();
expect(localStorage.getItem('sb_privacy_strict')).toBeNull();
expect(localStorage.getItem('sb_privacy_profile')).toBeNull();
});
it('persists session mode locally only when the user explicitly disables it', async () => {
const mod = await import('@/lib/privacyBrowserStorage');
mod.setSessionModePreference(false);
expect(mod.getSessionModePreference()).toBe(false);
expect(localStorage.getItem('sb_mesh_session_mode')).toBe('false');
expect(sessionStorage.getItem('sb_mesh_session_mode')).toBeNull();
});
it('stores sensitive items in sessionStorage when privacy strict is enabled', async () => {
localStorage.setItem('sb_privacy_strict', 'true');
const mod = await import('@/lib/privacyBrowserStorage');
mod.setSensitiveBrowserItem('secret-key', 'bravo');
expect(mod.getSensitiveBrowserStorageMode()).toBe('session');
expect(sessionStorage.getItem('secret-key')).toBe('bravo');
expect(localStorage.getItem('secret-key')).toBeNull();
});
it('migrates legacy localStorage values into sessionStorage in strict mode', async () => {
localStorage.setItem('sb_privacy_strict', 'true');
localStorage.setItem('secret-key', 'charlie');
const mod = await import('@/lib/privacyBrowserStorage');
expect(mod.getSensitiveBrowserItem('secret-key')).toBe('charlie');
expect(sessionStorage.getItem('secret-key')).toBe('charlie');
expect(localStorage.getItem('secret-key')).toBeNull();
});
});
@@ -70,7 +70,8 @@ describe('computeNightPolygon', () => {
.filter(([lng]: number[]) => lng >= -180 && lng <= 180)
.slice(0, 361)
.map(([, lat]: number[]) => lat);
const avgLat = terminatorLats.reduce((a: number, b: number) => a + b, 0) / terminatorLats.length;
const avgLat =
terminatorLats.reduce((a: number, b: number) => a + b, 0) / terminatorLats.length;
expect(avgLat).toBeLessThan(15);
});
@@ -0,0 +1,65 @@
import { describe, expect, it } from 'vitest';
import {
buildBoundsQuery,
coarsenViewBounds,
expandBoundsToRadius,
} from '@/lib/viewportPrivacy';
describe('viewport privacy helper', () => {
it('coarsens narrow bounds outward without clipping the original view', () => {
const original = {
south: 33.612,
west: -84.452,
north: 33.781,
east: -84.211,
};
const coarse = coarsenViewBounds(original);
expect(coarse.south).toBeLessThanOrEqual(original.south);
expect(coarse.west).toBeLessThanOrEqual(original.west);
expect(coarse.north).toBeGreaterThanOrEqual(original.north);
expect(coarse.east).toBeGreaterThanOrEqual(original.east);
expect(coarse.south).toBe(33.6);
expect(coarse.west).toBe(-84.5);
expect(coarse.north).toBe(33.8);
expect(coarse.east).toBe(-84.2);
});
it('canonicalizes the bounds query so nearby pans in the same coarse cell dedupe', () => {
const a = buildBoundsQuery({
south: 47.6011,
west: -122.3484,
north: 47.6902,
east: -122.2012,
});
const b = buildBoundsQuery({
south: 47.6039,
west: -122.3441,
north: 47.6883,
east: -122.2051,
});
expect(a).toBe('?s=47.60&w=-122.35&n=47.70&e=-122.20');
expect(b).toBe(a);
});
it('expands bounds to a fixed preload radius around the current view center', () => {
const original = {
south: 39.55,
west: -105.25,
north: 39.95,
east: -104.75,
};
const expanded = expandBoundsToRadius(original, 3000);
expect(expanded.south).toBeLessThanOrEqual(original.south);
expect(expanded.west).toBeLessThanOrEqual(original.west);
expect(expanded.north).toBeGreaterThanOrEqual(original.north);
expect(expanded.east).toBeGreaterThanOrEqual(original.east);
expect(expanded.north - expanded.south).toBeGreaterThan(80);
expect(expanded.east - expanded.west).toBeGreaterThan(90);
});
});