mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-13 14:10:28 +02:00
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:
@@ -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',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user