ftown 0.19.52 → 0.19.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-mod/hooks/register.js +6 -5
- package/dist/bridge-auth.d.ts +1 -0
- package/dist/bridge-auth.js +2 -2
- package/dist/bridge-auth.js.map +1 -1
- package/dist/centrifugo-client.d.ts +11 -1
- package/dist/centrifugo-client.js +71 -69
- package/dist/centrifugo-client.js.map +1 -1
- package/dist/cli-client.d.ts +6 -0
- package/dist/cli-client.js +43 -18
- package/dist/cli-client.js.map +1 -1
- package/dist/command-rpc.d.ts +5 -2
- package/dist/command-rpc.js +41 -4
- package/dist/command-rpc.js.map +1 -1
- package/dist/direct-transport/loopback-server.d.ts +5 -1
- package/dist/direct-transport/loopback-server.js +19 -6
- package/dist/direct-transport/loopback-server.js.map +1 -1
- package/dist/direct-transport/peer-manager.d.ts +4 -0
- package/dist/direct-transport/peer-manager.js +16 -2
- package/dist/direct-transport/peer-manager.js.map +1 -1
- package/dist/ftown-cli.js +4 -1
- package/dist/ftown-cli.js.map +1 -1
- package/dist/index.js +54 -37
- package/dist/index.js.map +1 -1
- package/dist/local-api-server.d.ts +12 -0
- package/dist/local-api-server.js +182 -41
- package/dist/local-api-server.js.map +1 -1
- package/dist/local-pair-cli.d.ts +2 -3
- package/dist/local-pair-cli.js +12 -53
- package/dist/local-pair-cli.js.map +1 -1
- package/dist/mcp/fleet.d.ts +11 -9
- package/dist/mcp/fleet.js +61 -44
- package/dist/mcp/fleet.js.map +1 -1
- package/dist/mcp/http.d.ts +10 -9
- package/dist/mcp/http.js +133 -8
- package/dist/mcp/http.js.map +1 -1
- package/dist/mcp/legacy-relay.d.ts +2 -2
- package/dist/mcp/legacy-relay.js +4 -43
- package/dist/mcp/legacy-relay.js.map +1 -1
- package/dist/mcp/mcp-key-store.d.ts +52 -0
- package/dist/mcp/mcp-key-store.js +206 -0
- package/dist/mcp/mcp-key-store.js.map +1 -0
- package/dist/mcp/oauth.d.ts +5 -1
- package/dist/mcp/oauth.js +113 -39
- package/dist/mcp/oauth.js.map +1 -1
- package/dist/mcp/relay-handler.d.ts +2 -1
- package/dist/mcp/relay-handler.js +5 -40
- package/dist/mcp/relay-handler.js.map +1 -1
- package/dist/mcp/relay.d.ts +4 -4
- package/dist/mcp/relay.js +35 -85
- package/dist/mcp/relay.js.map +1 -1
- package/dist/mcp/secure-client.d.ts +23 -0
- package/dist/mcp/secure-client.js +124 -0
- package/dist/mcp/secure-client.js.map +1 -0
- package/dist/mcp/security-events.d.ts +65 -0
- package/dist/mcp/security-events.js +210 -0
- package/dist/mcp/security-events.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +2 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/test-support.d.ts +32 -0
- package/dist/mcp/test-support.js +120 -0
- package/dist/mcp/test-support.js.map +1 -0
- package/dist/mcp-cli.js +56 -36
- package/dist/mcp-cli.js.map +1 -1
- package/dist/pairing-client.d.ts +1 -0
- package/dist/pairing-client.js +2 -2
- package/dist/pairing-client.js.map +1 -1
- package/dist/push-notifier.js +3 -1
- package/dist/push-notifier.js.map +1 -1
- package/dist/security/bridge/audit.d.ts +37 -0
- package/dist/security/bridge/audit.js +185 -0
- package/dist/security/bridge/audit.js.map +1 -0
- package/dist/security/bridge/direct.d.ts +22 -0
- package/dist/security/bridge/direct.js +72 -0
- package/dist/security/bridge/direct.js.map +1 -0
- package/dist/security/bridge/errors.d.ts +7 -0
- package/dist/security/bridge/errors.js +12 -0
- package/dist/security/bridge/errors.js.map +1 -0
- package/dist/security/bridge/execute.d.ts +3 -0
- package/dist/security/bridge/execute.js +37 -0
- package/dist/security/bridge/execute.js.map +1 -0
- package/dist/security/bridge/identity.d.ts +5 -0
- package/dist/security/bridge/identity.js +42 -0
- package/dist/security/bridge/identity.js.map +1 -0
- package/dist/security/bridge/local-client.d.ts +4 -0
- package/dist/security/bridge/local-client.js +43 -0
- package/dist/security/bridge/local-client.js.map +1 -0
- package/dist/security/bridge/metadata.d.ts +99 -0
- package/dist/security/bridge/metadata.js +23 -0
- package/dist/security/bridge/metadata.js.map +1 -0
- package/dist/security/bridge/native-wake.d.ts +16 -0
- package/dist/security/bridge/native-wake.js +15 -0
- package/dist/security/bridge/native-wake.js.map +1 -0
- package/dist/security/bridge/permissions.d.ts +11 -0
- package/dist/security/bridge/permissions.js +97 -0
- package/dist/security/bridge/permissions.js.map +1 -0
- package/dist/security/bridge/policy.d.ts +35 -0
- package/dist/security/bridge/policy.js +263 -0
- package/dist/security/bridge/policy.js.map +1 -0
- package/dist/security/bridge/security.d.ts +50 -0
- package/dist/security/bridge/security.js +298 -0
- package/dist/security/bridge/security.js.map +1 -0
- package/dist/security/bridge/snapshot.d.ts +14 -0
- package/dist/security/bridge/snapshot.js +77 -0
- package/dist/security/bridge/snapshot.js.map +1 -0
- package/dist/security/crypto.d.ts +17 -0
- package/dist/security/crypto.js +280 -0
- package/dist/security/crypto.js.map +1 -0
- package/dist/security/protocol.d.ts +389 -0
- package/dist/security/protocol.js +9 -0
- package/dist/security/protocol.js.map +1 -0
- package/dist/security-cli.d.ts +3 -0
- package/dist/security-cli.js +62 -0
- package/dist/security-cli.js.map +1 -0
- package/dist/solo/solo-server.js +24 -2
- package/dist/solo/solo-server.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/wire-types.d.ts +3 -0
- package/dist/wire-types.js.map +1 -1
- package/hooks/notify.sh +1 -1
- package/muse-plugin/lib/ftown-hook.sh +6 -22
- package/opencode-plugin/ftown.js +33 -5
- package/package.json +4 -2
- package/pi-extension/ftown.js +33 -5
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DirectMessage } from '../../direct-transport/contract.js';
|
|
2
|
+
import type { InboundTransportContext } from '../protocol.js';
|
|
3
|
+
import { BridgeSecurityService } from './security.js';
|
|
4
|
+
/** Serial crypto at the peer boundary; application frames never escape as plaintext. */
|
|
5
|
+
export declare class EncryptedPeerChannel {
|
|
6
|
+
private readonly security;
|
|
7
|
+
private readonly route;
|
|
8
|
+
private readonly wireSend;
|
|
9
|
+
private readonly deliver;
|
|
10
|
+
private readonly authorized;
|
|
11
|
+
private readonly teardown;
|
|
12
|
+
private inbound;
|
|
13
|
+
private outbound;
|
|
14
|
+
private endpointId;
|
|
15
|
+
private closed;
|
|
16
|
+
private readonly stopRevocation;
|
|
17
|
+
constructor(security: BridgeSecurityService, route: InboundTransportContext, wireSend: (json: string) => void, deliver: (frame: DirectMessage) => void, authorized: () => void, teardown: () => void);
|
|
18
|
+
receive(json: string): Promise<void>;
|
|
19
|
+
send(frame: DirectMessage): void;
|
|
20
|
+
drain(): Promise<void>;
|
|
21
|
+
close(): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { parseSecureEnvelope } from '../crypto.js';
|
|
2
|
+
import { applicationJson } from './security.js';
|
|
3
|
+
/** Serial crypto at the peer boundary; application frames never escape as plaintext. */
|
|
4
|
+
export class EncryptedPeerChannel {
|
|
5
|
+
security;
|
|
6
|
+
route;
|
|
7
|
+
wireSend;
|
|
8
|
+
deliver;
|
|
9
|
+
authorized;
|
|
10
|
+
teardown;
|
|
11
|
+
inbound = Promise.resolve();
|
|
12
|
+
outbound = Promise.resolve();
|
|
13
|
+
endpointId = null;
|
|
14
|
+
closed = false;
|
|
15
|
+
stopRevocation;
|
|
16
|
+
constructor(security, route, wireSend, deliver, authorized, teardown) {
|
|
17
|
+
this.security = security;
|
|
18
|
+
this.route = route;
|
|
19
|
+
this.wireSend = wireSend;
|
|
20
|
+
this.deliver = deliver;
|
|
21
|
+
this.authorized = authorized;
|
|
22
|
+
this.teardown = teardown;
|
|
23
|
+
this.stopRevocation = security.onRevoked(id => { if (id === this.endpointId)
|
|
24
|
+
this.teardown(); });
|
|
25
|
+
}
|
|
26
|
+
receive(json) {
|
|
27
|
+
this.inbound = this.inbound.then(async () => {
|
|
28
|
+
if (this.closed)
|
|
29
|
+
return;
|
|
30
|
+
const packet = parseSecureEnvelope(JSON.parse(json));
|
|
31
|
+
if (packet.purpose === 'hello') {
|
|
32
|
+
if (this.endpointId && this.endpointId !== packet.senderId)
|
|
33
|
+
throw new Error('Wrong peer');
|
|
34
|
+
const response = await this.security.handleHandshake(packet, this.route);
|
|
35
|
+
// A pending MCP hello is harmless but cannot open an application data channel.
|
|
36
|
+
await this.security.policy.principal(packet.senderId);
|
|
37
|
+
this.endpointId = packet.senderId;
|
|
38
|
+
this.wireSend(JSON.stringify(response));
|
|
39
|
+
this.authorized();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!this.endpointId || packet.senderId !== this.endpointId)
|
|
43
|
+
throw new Error('Unpaired peer');
|
|
44
|
+
const verified = await this.security.acceptApplication(packet, this.route);
|
|
45
|
+
if (verified.application.route.kind !== 'terminal')
|
|
46
|
+
throw new Error('Invalid direct application route');
|
|
47
|
+
await this.security.revalidate(verified.principal);
|
|
48
|
+
this.deliver(verified.application.data);
|
|
49
|
+
await this.security.complete(verified, 'succeeded');
|
|
50
|
+
await this.outbound;
|
|
51
|
+
}).catch(() => { this.teardown(); });
|
|
52
|
+
return this.inbound;
|
|
53
|
+
}
|
|
54
|
+
send(frame) {
|
|
55
|
+
this.outbound = this.outbound.then(async () => {
|
|
56
|
+
if (this.closed || !this.endpointId)
|
|
57
|
+
return;
|
|
58
|
+
const envelope = await this.security.sealTo(this.endpointId, { type: 'application', route: { kind: 'terminal',
|
|
59
|
+
sessionId: 'sessionId' in frame ? frame.sessionId : '', frameType: frame.kind, pairId: this.route.pairId },
|
|
60
|
+
data: applicationJson(frame), mcp: null }, this.route);
|
|
61
|
+
if (!this.closed) {
|
|
62
|
+
try {
|
|
63
|
+
this.wireSend(JSON.stringify(envelope));
|
|
64
|
+
}
|
|
65
|
+
catch { /* Native send failure drops only this frame; keepalive detects dead peers. */ }
|
|
66
|
+
}
|
|
67
|
+
}).catch(() => { this.teardown(); });
|
|
68
|
+
}
|
|
69
|
+
async drain() { await this.inbound; await this.outbound; }
|
|
70
|
+
close() { this.closed = true; this.stopRevocation(); }
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=direct.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"direct.js","sourceRoot":"","sources":["../../../src/security/bridge/direct.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SecurityErrorCode, SecurityFailure } from '../protocol.js';
|
|
2
|
+
/** Messages are deliberately fixed: underlying storage/crypto exceptions never cross this boundary. */
|
|
3
|
+
export declare class BridgeSecurityError extends Error implements SecurityFailure {
|
|
4
|
+
readonly code: SecurityErrorCode;
|
|
5
|
+
readonly retryable: boolean;
|
|
6
|
+
constructor(code: SecurityErrorCode);
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Messages are deliberately fixed: underlying storage/crypto exceptions never cross this boundary. */
|
|
2
|
+
export class BridgeSecurityError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
retryable;
|
|
5
|
+
constructor(code) {
|
|
6
|
+
super(`Bridge security: ${code}`);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.name = 'BridgeSecurityError';
|
|
9
|
+
this.retryable = code === 'storage_unavailable' || code === 'audit_unavailable';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/security/bridge/errors.ts"],"names":[],"mappings":"AAEA,uGAAuG;AACvG,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAEvB;IADZ,SAAS,CAAU;IAC5B,YAAqB,IAAuB;QAC1C,KAAK,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;QADf,SAAI,GAAJ,IAAI,CAAmB;QAE1C,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,IAAI,KAAK,qBAAqB,IAAI,IAAI,KAAK,mBAAmB,CAAC;IAClF,CAAC;CACF"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ApplicationJson, EnrollmentPacket, InboundTransportContext, SecureEnvelope, VerifiedApplication } from '../protocol.js';
|
|
2
|
+
import { BridgeSecurityService } from './security.js';
|
|
3
|
+
export declare function executeSecurePacket(security: BridgeSecurityService, packet: SecureEnvelope | EnrollmentPacket, route: InboundTransportContext, dispatch: (message: VerifiedApplication) => Promise<ApplicationJson>, admission?: (accept: () => Promise<VerifiedApplication>) => Promise<VerifiedApplication>): Promise<SecureEnvelope>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BridgeSecurityError } from './errors.js';
|
|
2
|
+
export async function executeSecurePacket(security, packet, route, dispatch, admission) {
|
|
3
|
+
if (packet.type === 'ftown_enrollment' || packet.purpose !== 'application')
|
|
4
|
+
return security.handleHandshake(packet, route);
|
|
5
|
+
let verified;
|
|
6
|
+
let dispatchStarted = false;
|
|
7
|
+
try {
|
|
8
|
+
verified = await (admission ? admission(() => security.acceptApplication(packet, route)) : security.acceptApplication(packet, route));
|
|
9
|
+
const activation = verified.application.route.kind === 'command' && verified.application.route.command === 'security_activate_mcp';
|
|
10
|
+
if (!activation)
|
|
11
|
+
await security.revalidate(verified.principal);
|
|
12
|
+
const selfRevocation = await security.revokeSelfWithReceipt(verified, route);
|
|
13
|
+
if (selfRevocation)
|
|
14
|
+
return selfRevocation;
|
|
15
|
+
dispatchStarted = true;
|
|
16
|
+
const data = await dispatch(verified);
|
|
17
|
+
const result = data && typeof data === 'object' && !Array.isArray(data) ? data : null;
|
|
18
|
+
const response = result?.response && typeof result.response === 'object' && !Array.isArray(result.response) ? result.response : null;
|
|
19
|
+
const body = result?.body && typeof result.body === 'object' && !Array.isArray(result.body) ? result.body : null;
|
|
20
|
+
const failedHttp = typeof result?.status === 'number' && result.status >= 400;
|
|
21
|
+
const unknown = body?.outcomeUnknown === true;
|
|
22
|
+
await security.complete(verified, unknown ? 'unknown' : failedHttp || response?.success === false ? 'rejected' : 'succeeded', failedHttp ? typeof body?.code === 'string' ? body.code : 'bridge_error' : response?.success === false ? 'bridge_error' : null);
|
|
23
|
+
return security.sealTo(packet.senderId, { type: 'application', route: { kind: 'response', requestId: packet.messageId }, data, mcp: null }, route);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
// Never authenticate an error on behalf of an unauthenticated/tampered packet.
|
|
27
|
+
if (!verified && (!(error instanceof BridgeSecurityError)
|
|
28
|
+
|| !['permission_denied', 'epoch_changed', 'expired', 'replayed', 'capacity_exceeded', 'audit_unavailable'].includes(error.code)))
|
|
29
|
+
throw error;
|
|
30
|
+
const code = error instanceof BridgeSecurityError ? error.code : dispatchStarted ? 'outcome_unknown' : 'permission_denied';
|
|
31
|
+
if (verified)
|
|
32
|
+
await security.complete(verified, dispatchStarted ? 'unknown' : 'rejected', code).catch(() => undefined);
|
|
33
|
+
return security.sealTo(packet.senderId, { type: 'application', route: { kind: 'response', requestId: packet.messageId },
|
|
34
|
+
data: { status: dispatchStarted ? 500 : 403, body: { error: code, code, outcomeUnknown: dispatchStarted } }, mcp: null }, route);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=execute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/security/bridge/execute.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { EndpointIdentity, EndpointKind } from '../protocol.js';
|
|
2
|
+
/** OS-local persisted keys; corruption/wrong owner never silently generates a replacement identity. */
|
|
3
|
+
export declare function loadOrCreateIdentity(dataDir: string, ownerId: string, kind: 'bridge' | 'local-cli'): Promise<EndpointIdentity>;
|
|
4
|
+
export declare function isLocalIdentityKind(kind: EndpointKind): kind is 'bridge' | 'local-cli';
|
|
5
|
+
export declare function loadLocalIdentity(dataDir: string, ownerId: string): Promise<EndpointIdentity>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { generateEndpoint, importEndpoint } from '../crypto.js';
|
|
5
|
+
import { endpointSchema } from './metadata.js';
|
|
6
|
+
import { AtomicSnapshot } from './snapshot.js';
|
|
7
|
+
import { BridgeSecurityError } from './errors.js';
|
|
8
|
+
const recordSchema = z.object({ version: z.literal(1), endpoint: endpointSchema, privateKey: z.object({
|
|
9
|
+
kty: z.literal('EC'), crv: z.literal('P-256'), x: z.string(), y: z.string(), d: z.string(),
|
|
10
|
+
key_ops: z.array(z.string()).optional(), ext: z.boolean().optional(),
|
|
11
|
+
}).strict() }).strict();
|
|
12
|
+
/** OS-local persisted keys; corruption/wrong owner never silently generates a replacement identity. */
|
|
13
|
+
export async function loadOrCreateIdentity(dataDir, ownerId, kind) {
|
|
14
|
+
const generated = await generateEndpoint(ownerId, kind, true);
|
|
15
|
+
const jwk = await crypto.subtle.exportKey('jwk', generated.keyPair.privateKey);
|
|
16
|
+
const initial = recordSchema.parse({ version: 1, endpoint: generated.endpoint, privateKey: jwk });
|
|
17
|
+
const store = new AtomicSnapshot(join(dataDir, 'security', `${kind}-identity.json`), recordSchema, () => initial);
|
|
18
|
+
const saved = await store.transact(state => {
|
|
19
|
+
if (state.endpoint.ownerId !== ownerId || state.endpoint.kind !== kind)
|
|
20
|
+
throw new BridgeSecurityError('wrong_context');
|
|
21
|
+
return state;
|
|
22
|
+
});
|
|
23
|
+
try {
|
|
24
|
+
return await importEndpoint(saved.endpoint, saved.privateKey, false);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new BridgeSecurityError('key_unavailable');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function isLocalIdentityKind(kind) { return kind === 'bridge' || kind === 'local-cli'; }
|
|
31
|
+
export async function loadLocalIdentity(dataDir, ownerId) {
|
|
32
|
+
try {
|
|
33
|
+
const saved = recordSchema.parse(JSON.parse(await readFile(join(dataDir, 'security', 'local-cli-identity.json'), 'utf8')));
|
|
34
|
+
if (saved.endpoint.ownerId !== ownerId || saved.endpoint.kind !== 'local-cli')
|
|
35
|
+
throw new Error('Wrong local key');
|
|
36
|
+
return await importEndpoint(saved.endpoint, saved.privateKey, false);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new BridgeSecurityError('key_unavailable');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/security/bridge/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACpG,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE;QAC1F,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KACrE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAExB,uGAAuG;AACvG,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAAe,EAAE,OAAe,EAAE,IAA4B;IACvG,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/E,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;IAClG,MAAM,KAAK,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,gBAAgB,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IAClH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;QACzC,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,IAAI;YAAE,MAAM,IAAI,mBAAmB,CAAC,eAAe,CAAC,CAAC;QACvH,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QAAC,OAAO,MAAM,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAAC,CAAC;IAC7E,MAAM,CAAC;QAAC,MAAM,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAkB,IAAoC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC;AAE7I,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAe,EAAE,OAAe;IACtE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,yBAAyB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3H,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAClH,OAAO,MAAM,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QAAC,MAAM,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BridgePointer } from '../../wire-types.js';
|
|
2
|
+
import type { ApplicationJson, SecureApplication } from '../protocol.js';
|
|
3
|
+
/** OS-local clients pin bridge metadata from the owner-only pointer, never remote discovery. */
|
|
4
|
+
export declare function localApplication(pointer: BridgePointer, application: SecureApplication, signal?: AbortSignal, fetchFn?: typeof fetch): Promise<ApplicationJson>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { open, parseSecureEnvelope, seal } from '../crypto.js';
|
|
4
|
+
import { loadLocalIdentity } from './identity.js';
|
|
5
|
+
import { BridgeSecurityError } from './errors.js';
|
|
6
|
+
/** OS-local clients pin bridge metadata from the owner-only pointer, never remote discovery. */
|
|
7
|
+
export async function localApplication(pointer, application, signal, fetchFn = fetch) {
|
|
8
|
+
if (!pointer.dataDir || !pointer.ownerId || !pointer.endpoint || !pointer.bridgeId)
|
|
9
|
+
throw new BridgeSecurityError('upgrade_required');
|
|
10
|
+
const bridge = pointer.endpoint;
|
|
11
|
+
if (bridge.kind !== 'bridge' || bridge.ownerId !== pointer.ownerId)
|
|
12
|
+
throw new BridgeSecurityError('wrong_context');
|
|
13
|
+
const identity = await loadLocalIdentity(pointer.dataDir, pointer.ownerId);
|
|
14
|
+
const channel = `local-http:${pointer.bridgeId}`;
|
|
15
|
+
const requestId = application.route.kind === 'command' || application.route.kind === 'http' || application.route.kind === 'response'
|
|
16
|
+
? application.route.requestId : randomUUID();
|
|
17
|
+
const post = async (purpose, body, epoch, messageId) => {
|
|
18
|
+
const createdAt = Date.now();
|
|
19
|
+
const packet = await seal(identity, bridge, { type: 'ftown_encrypted', version: 1, senderId: identity.endpoint.id,
|
|
20
|
+
recipientId: bridge.id, ownerId: pointer.ownerId, bridgeId: pointer.bridgeId, channel, purpose, messageId,
|
|
21
|
+
createdAt, expiresAt: createdAt + 120000, receiverEpoch: epoch }, new TextEncoder().encode(JSON.stringify(body)));
|
|
22
|
+
const response = await fetchFn(`http://127.0.0.1:${pointer.port}/api/security/envelope`, { method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(packet), redirect: 'error',
|
|
24
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(65000)]) : AbortSignal.timeout(65000) });
|
|
25
|
+
if (!response.ok)
|
|
26
|
+
throw new BridgeSecurityError('bridge_offline');
|
|
27
|
+
const envelope = parseSecureEnvelope(await response.json());
|
|
28
|
+
if (envelope.messageId !== messageId)
|
|
29
|
+
throw new BridgeSecurityError('wrong_context');
|
|
30
|
+
const bytes = await open(identity, bridge, envelope, { ownerId: pointer.ownerId, bridgeId: pointer.bridgeId, channel,
|
|
31
|
+
recipientId: identity.endpoint.id, purpose: purpose === 'hello' ? 'welcome' : 'application' });
|
|
32
|
+
if (envelope.expiresAt <= Date.now() || envelope.createdAt > Date.now() + 30000)
|
|
33
|
+
throw new BridgeSecurityError('expired');
|
|
34
|
+
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
35
|
+
};
|
|
36
|
+
const challenge = randomUUID();
|
|
37
|
+
const hello = z.object({ type: z.literal('welcome'), challenge: z.literal(challenge), receiverEpoch: z.string().min(1).max(128) }).strict()
|
|
38
|
+
.parse(await post('hello', { type: 'hello', challenge }, null, randomUUID()));
|
|
39
|
+
const result = z.object({ type: z.literal('application'), route: z.object({ kind: z.literal('response'), requestId: z.literal(requestId) }).strict(),
|
|
40
|
+
data: z.unknown(), mcp: z.null() }).strict().parse(await post('application', application, hello.receiverEpoch, requestId));
|
|
41
|
+
return result.data;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=local-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-client.js","sourceRoot":"","sources":["../../../src/security/bridge/local-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const boundedId: z.ZodString;
|
|
3
|
+
export declare const endpointSchema: z.ZodEffects<z.ZodObject<{
|
|
4
|
+
version: z.ZodLiteral<1>;
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
ownerId: z.ZodString;
|
|
7
|
+
kind: z.ZodEnum<["bridge", "browser", "mcp", "local-cli"]>;
|
|
8
|
+
publicKey: z.ZodString;
|
|
9
|
+
}, "strict", z.ZodTypeAny, {
|
|
10
|
+
id: string;
|
|
11
|
+
version: 1;
|
|
12
|
+
ownerId: string;
|
|
13
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
14
|
+
publicKey: string;
|
|
15
|
+
}, {
|
|
16
|
+
id: string;
|
|
17
|
+
version: 1;
|
|
18
|
+
ownerId: string;
|
|
19
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
20
|
+
publicKey: string;
|
|
21
|
+
}>, {
|
|
22
|
+
id: string;
|
|
23
|
+
version: 1;
|
|
24
|
+
ownerId: string;
|
|
25
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
26
|
+
publicKey: string;
|
|
27
|
+
}, {
|
|
28
|
+
id: string;
|
|
29
|
+
version: 1;
|
|
30
|
+
ownerId: string;
|
|
31
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
32
|
+
publicKey: string;
|
|
33
|
+
}>;
|
|
34
|
+
export declare const permissionSchema: z.ZodEnum<["read", "control", "admin"]>;
|
|
35
|
+
export declare const approvedSchema: z.ZodObject<{
|
|
36
|
+
endpoint: z.ZodEffects<z.ZodObject<{
|
|
37
|
+
version: z.ZodLiteral<1>;
|
|
38
|
+
id: z.ZodString;
|
|
39
|
+
ownerId: z.ZodString;
|
|
40
|
+
kind: z.ZodEnum<["bridge", "browser", "mcp", "local-cli"]>;
|
|
41
|
+
publicKey: z.ZodString;
|
|
42
|
+
}, "strict", z.ZodTypeAny, {
|
|
43
|
+
id: string;
|
|
44
|
+
version: 1;
|
|
45
|
+
ownerId: string;
|
|
46
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
47
|
+
publicKey: string;
|
|
48
|
+
}, {
|
|
49
|
+
id: string;
|
|
50
|
+
version: 1;
|
|
51
|
+
ownerId: string;
|
|
52
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
53
|
+
publicKey: string;
|
|
54
|
+
}>, {
|
|
55
|
+
id: string;
|
|
56
|
+
version: 1;
|
|
57
|
+
ownerId: string;
|
|
58
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
59
|
+
publicKey: string;
|
|
60
|
+
}, {
|
|
61
|
+
id: string;
|
|
62
|
+
version: 1;
|
|
63
|
+
ownerId: string;
|
|
64
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
65
|
+
publicKey: string;
|
|
66
|
+
}>;
|
|
67
|
+
label: z.ZodString;
|
|
68
|
+
permissions: z.ZodArray<z.ZodEnum<["read", "control", "admin"]>, "many">;
|
|
69
|
+
approvedAt: z.ZodString;
|
|
70
|
+
grantId: z.ZodNullable<z.ZodString>;
|
|
71
|
+
expiresAt: z.ZodNullable<z.ZodString>;
|
|
72
|
+
}, "strict", z.ZodTypeAny, {
|
|
73
|
+
expiresAt: string | null;
|
|
74
|
+
endpoint: {
|
|
75
|
+
id: string;
|
|
76
|
+
version: 1;
|
|
77
|
+
ownerId: string;
|
|
78
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
79
|
+
publicKey: string;
|
|
80
|
+
};
|
|
81
|
+
label: string;
|
|
82
|
+
permissions: ("read" | "control" | "admin")[];
|
|
83
|
+
approvedAt: string;
|
|
84
|
+
grantId: string | null;
|
|
85
|
+
}, {
|
|
86
|
+
expiresAt: string | null;
|
|
87
|
+
endpoint: {
|
|
88
|
+
id: string;
|
|
89
|
+
version: 1;
|
|
90
|
+
ownerId: string;
|
|
91
|
+
kind: "bridge" | "browser" | "mcp" | "local-cli";
|
|
92
|
+
publicKey: string;
|
|
93
|
+
};
|
|
94
|
+
label: string;
|
|
95
|
+
permissions: ("read" | "control" | "admin")[];
|
|
96
|
+
approvedAt: string;
|
|
97
|
+
grantId: string | null;
|
|
98
|
+
}>;
|
|
99
|
+
export declare function displayLabel(label: string): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export const boundedId = z.string().regex(/^[A-Za-z0-9_-]{1,200}$/);
|
|
4
|
+
export const endpointSchema = z.object({
|
|
5
|
+
version: z.literal(1), id: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
6
|
+
ownerId: z.string().min(1).max(200), kind: z.enum(['bridge', 'browser', 'mcp', 'local-cli']),
|
|
7
|
+
publicKey: z.string().regex(/^[A-Za-z0-9_-]{87}$/),
|
|
8
|
+
}).strict().superRefine((endpoint, context) => {
|
|
9
|
+
const raw = Buffer.from(endpoint.publicKey, 'base64url');
|
|
10
|
+
if (raw.length !== 65 || raw[0] !== 4 || raw.toString('base64url') !== endpoint.publicKey
|
|
11
|
+
|| createHash('sha256').update(raw).digest('base64url') !== endpoint.id) {
|
|
12
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: 'Invalid endpoint fingerprint' });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
export const permissionSchema = z.enum(['read', 'control', 'admin']);
|
|
16
|
+
export const approvedSchema = z.object({
|
|
17
|
+
endpoint: endpointSchema, label: z.string().max(100), permissions: z.array(permissionSchema).max(3),
|
|
18
|
+
approvedAt: z.string().datetime(), grantId: boundedId.nullable(), expiresAt: z.string().datetime().nullable(),
|
|
19
|
+
}).strict();
|
|
20
|
+
export function displayLabel(label) {
|
|
21
|
+
return label.replace(/[\u0000-\u001f\u007f-\u009f]/g, '').trim().slice(0, 100);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../../src/security/bridge/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,qBAAqB,CAAC;IAClE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAC5F,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,qBAAqB,CAAC;CACnD,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;IAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACzD,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,SAAS;WACpF,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,EAAE,EAAE,CAAC;QAC1E,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC,CAAC;IAC7F,CAAC;AACH,CAAC,CAAC,CAAC;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACnG,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC9G,CAAC,CAAC,MAAM,EAAE,CAAC;AACZ,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,+BAA+B,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Session } from '../../types.js';
|
|
2
|
+
/** Deliberate trusted-local-provider IPC: no application route, message body, consumption or prompt execution. */
|
|
3
|
+
export declare function nativeWake(input: unknown, dependencies: {
|
|
4
|
+
getSession(id: string): Promise<Session | null>;
|
|
5
|
+
register(session: Session, registration: {
|
|
6
|
+
adapter: 'claude-mod';
|
|
7
|
+
nativeSessionId: string;
|
|
8
|
+
}): void;
|
|
9
|
+
unread(id: string): Promise<{
|
|
10
|
+
id: string;
|
|
11
|
+
}[]>;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
messages: {
|
|
14
|
+
id: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BridgeSecurityError } from './errors.js';
|
|
3
|
+
const payloadSchema = z.object({ hook_event_name: z.literal('FtownNativeWake'), ftown_session_id: z.string().regex(/^[A-Za-z0-9_-]{1,200}$/),
|
|
4
|
+
session_id: z.string().regex(/^[A-Za-z0-9_-]{1,200}$/), adapter: z.literal('claude-mod'), registerWake: z.boolean().optional() }).strict();
|
|
5
|
+
/** Deliberate trusted-local-provider IPC: no application route, message body, consumption or prompt execution. */
|
|
6
|
+
export async function nativeWake(input, dependencies) {
|
|
7
|
+
const payload = payloadSchema.parse(input);
|
|
8
|
+
const session = await dependencies.getSession(payload.ftown_session_id);
|
|
9
|
+
if (!session || session.claudeSessionId !== payload.session_id)
|
|
10
|
+
throw new BridgeSecurityError('wrong_context');
|
|
11
|
+
if (payload.registerWake !== false)
|
|
12
|
+
dependencies.register(session, { adapter: 'claude-mod', nativeSessionId: payload.session_id });
|
|
13
|
+
return { messages: (await dependencies.unread(session.id)).slice(0, 500).map(message => ({ id: message.id })) };
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=native-wake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-wake.js","sourceRoot":"","sources":["../../../src/security/bridge/native-wake.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,wBAAwB,CAAC;IAC1I,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,wBAAwB,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAE7I,kHAAkH;AAClH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAc,EAAE,YAIhD;IACC,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACxE,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,eAAe,KAAK,OAAO,CAAC,UAAU;QAAE,MAAM,IAAI,mBAAmB,CAAC,eAAe,CAAC,CAAC;IAC/G,IAAI,OAAO,CAAC,YAAY,KAAK,KAAK;QAAE,YAAY,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACnI,OAAO,EAAE,QAAQ,EAAE,CAAC,MAAM,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;AAClH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ApplicationJson, EndpointPermission } from '../protocol.js';
|
|
2
|
+
export interface OperationPermission {
|
|
3
|
+
permission: EndpointPermission;
|
|
4
|
+
mutating: boolean;
|
|
5
|
+
operation: string;
|
|
6
|
+
sessionId: string | null;
|
|
7
|
+
}
|
|
8
|
+
/** Deliberately semantic: GET inbox consumes mail unless explicitly non-consuming. */
|
|
9
|
+
export declare function classifyHttp(method: string, path: string): OperationPermission;
|
|
10
|
+
export declare function classifyCommand(command: string, payload?: ApplicationJson): OperationPermission;
|
|
11
|
+
export declare function classifyTerminal(frameType: string): OperationPermission;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { BridgeSecurityError } from './errors.js';
|
|
2
|
+
const session = /^[A-Za-z0-9_-]+$/;
|
|
3
|
+
const readCommands = new Set(['list_sessions', 'get_history', 'get_session_usage', 'get_sessions_usage', 'get_session_attention']);
|
|
4
|
+
const controlCommands = new Set(['create_session', 'stop_session', 'retry_session', 'send_message', 'rename_session', 'remove_session',
|
|
5
|
+
'bridge_exec', 'clear_terminal', 'update_session_parent', 'resolve_session_attention']);
|
|
6
|
+
const adminCommands = new Set(['security_devices', 'security_invite', 'security_revoke', 'security_approve_mcp', 'security_activity']);
|
|
7
|
+
const result = (permission, operation, sessionId = null) => ({ permission, mutating: permission === 'control' || ['security_invite', 'security_revoke', 'security_approve_mcp'].includes(operation), operation, sessionId });
|
|
8
|
+
const deny = () => { throw new BridgeSecurityError('permission_denied'); };
|
|
9
|
+
const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
10
|
+
/** Deliberately semantic: GET inbox consumes mail unless explicitly non-consuming. */
|
|
11
|
+
export function classifyHttp(method, path) {
|
|
12
|
+
if (path.length > 16_384 || !path.startsWith('/api/') || path.includes('#') || path.includes('\\') || path.split('?')[0].includes('%'))
|
|
13
|
+
return deny();
|
|
14
|
+
const url = new URL(path, 'http://bridge.invalid');
|
|
15
|
+
if (url.origin !== 'http://bridge.invalid' || url.pathname.split('/').some(part => part === '.' || part === '..')
|
|
16
|
+
|| path.split('?')[0] !== url.pathname)
|
|
17
|
+
return deny();
|
|
18
|
+
if (url.pathname === '/api/sessions') {
|
|
19
|
+
if (method === 'GET')
|
|
20
|
+
return result('read', 'sessions_list');
|
|
21
|
+
if (method === 'POST')
|
|
22
|
+
return result('control', 'sessions_create');
|
|
23
|
+
return deny();
|
|
24
|
+
}
|
|
25
|
+
if (method === 'GET' && url.pathname === '/api/archive')
|
|
26
|
+
return result('read', 'archive_list');
|
|
27
|
+
if (method === 'GET' && url.pathname === '/api/inbox/resolve')
|
|
28
|
+
return result('read', 'inbox_resolve');
|
|
29
|
+
if (method === 'GET' && url.pathname === '/api/browser/events')
|
|
30
|
+
return result('read', 'local_events');
|
|
31
|
+
if (url.pathname === '/api/loops' || /^\/api\/loops\/[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)?$/.test(url.pathname)) {
|
|
32
|
+
if (!['GET', 'POST', 'PATCH', 'DELETE'].includes(method))
|
|
33
|
+
return deny();
|
|
34
|
+
return { permission: method === 'GET' ? 'read' : 'control', mutating: false, operation: 'automation_removed', sessionId: null };
|
|
35
|
+
}
|
|
36
|
+
const match = /^\/api\/sessions\/([A-Za-z0-9_-]+)(?:\/(screen|grep|keys|message|inbox|resize|usage|running|revive|stop|retry|attention)(?:\/(ack|resolve))?)?$/.exec(url.pathname);
|
|
37
|
+
if (!match)
|
|
38
|
+
return deny();
|
|
39
|
+
const [, id, suffix, attentionAction] = match;
|
|
40
|
+
if (attentionAction && suffix !== 'attention')
|
|
41
|
+
return deny();
|
|
42
|
+
if (!suffix) {
|
|
43
|
+
if (method === 'GET')
|
|
44
|
+
return result('read', 'sessions_get', id);
|
|
45
|
+
if (method === 'PATCH')
|
|
46
|
+
return result('control', 'sessions_update', id);
|
|
47
|
+
if (method === 'DELETE')
|
|
48
|
+
return result('control', 'sessions_remove', id);
|
|
49
|
+
return deny();
|
|
50
|
+
}
|
|
51
|
+
if (suffix === 'inbox' && method === 'GET') {
|
|
52
|
+
// Native wake registration mutates the delivery target even when peek=1.
|
|
53
|
+
const peek = url.searchParams.getAll('peek');
|
|
54
|
+
const readOnly = peek.length === 1 && peek[0] === '1'
|
|
55
|
+
&& !url.searchParams.has('adapter') && !url.searchParams.has('nativeSessionId');
|
|
56
|
+
return result(readOnly ? 'read' : 'control', readOnly ? 'messages_peek' : 'messages_read', id);
|
|
57
|
+
}
|
|
58
|
+
if (method === 'GET' && ['screen', 'usage', 'running', 'attention'].includes(suffix) && !attentionAction)
|
|
59
|
+
return result('read', `${suffix}_get`, id);
|
|
60
|
+
if (method === 'POST' && suffix === 'grep' && !attentionAction)
|
|
61
|
+
return result('read', 'history_search', id);
|
|
62
|
+
if (method === 'POST' && ['keys', 'message', 'inbox', 'resize', 'revive', 'stop', 'retry', 'attention'].includes(suffix)) {
|
|
63
|
+
return result('control', attentionAction ? `attention_${attentionAction}` : `${suffix}_send`, id);
|
|
64
|
+
}
|
|
65
|
+
if (method === 'DELETE' && suffix === 'screen')
|
|
66
|
+
return result('control', 'screen_clear', id);
|
|
67
|
+
return deny();
|
|
68
|
+
}
|
|
69
|
+
export function classifyCommand(command, payload) {
|
|
70
|
+
const body = object(payload);
|
|
71
|
+
const sessionId = typeof body?.sessionId === 'string' && session.test(body.sessionId) ? body.sessionId : null;
|
|
72
|
+
if (command === 'mcp_request') {
|
|
73
|
+
const request = object(body?.request);
|
|
74
|
+
if (typeof request?.method !== 'string' || typeof request.path !== 'string')
|
|
75
|
+
return deny();
|
|
76
|
+
return classifyHttp(request.method, request.path);
|
|
77
|
+
}
|
|
78
|
+
if (readCommands.has(command))
|
|
79
|
+
return result('read', command, sessionId);
|
|
80
|
+
if (controlCommands.has(command))
|
|
81
|
+
return result('control', command, sessionId);
|
|
82
|
+
if (adminCommands.has(command))
|
|
83
|
+
return result('admin', command, sessionId);
|
|
84
|
+
if (command === 'terminal_watch' || command === 'terminal_unwatch' || command === 'webrtc_offer'
|
|
85
|
+
|| command === 'webrtc_answer' || command === 'webrtc_ice' || command === 'webrtc_close')
|
|
86
|
+
return result('read', command, sessionId);
|
|
87
|
+
// Activation has a separate exact-pending-grant check; it is never granted by a generic permission.
|
|
88
|
+
return deny();
|
|
89
|
+
}
|
|
90
|
+
export function classifyTerminal(frameType) {
|
|
91
|
+
if (['attach', 'detach', 'init', 'ping', 'pong', 'hello'].includes(frameType))
|
|
92
|
+
return result('read', `terminal_${frameType}`);
|
|
93
|
+
if (frameType === 'input' || frameType === 'resize')
|
|
94
|
+
return result('control', `terminal_${frameType}`);
|
|
95
|
+
return deny();
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../../src/security/bridge/permissions.ts"],"names":[],"mappings":"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"}
|