ftown 0.19.52 → 0.19.54

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.
Files changed (139) hide show
  1. package/claude-mod/hooks/register.js +6 -5
  2. package/dist/bridge-auth.d.ts +4 -0
  3. package/dist/bridge-auth.js +2 -2
  4. package/dist/bridge-auth.js.map +1 -1
  5. package/dist/centrifugo-client.d.ts +11 -1
  6. package/dist/centrifugo-client.js +71 -69
  7. package/dist/centrifugo-client.js.map +1 -1
  8. package/dist/cli-client.d.ts +6 -0
  9. package/dist/cli-client.js +43 -18
  10. package/dist/cli-client.js.map +1 -1
  11. package/dist/command-rpc.d.ts +5 -2
  12. package/dist/command-rpc.js +62 -16
  13. package/dist/command-rpc.js.map +1 -1
  14. package/dist/direct-transport/loopback-server.d.ts +5 -1
  15. package/dist/direct-transport/loopback-server.js +19 -6
  16. package/dist/direct-transport/loopback-server.js.map +1 -1
  17. package/dist/direct-transport/peer-manager.d.ts +4 -0
  18. package/dist/direct-transport/peer-manager.js +16 -2
  19. package/dist/direct-transport/peer-manager.js.map +1 -1
  20. package/dist/ftown-cli.js +4 -1
  21. package/dist/ftown-cli.js.map +1 -1
  22. package/dist/index.js +100 -40
  23. package/dist/index.js.map +1 -1
  24. package/dist/local-api-server.d.ts +12 -0
  25. package/dist/local-api-server.js +182 -41
  26. package/dist/local-api-server.js.map +1 -1
  27. package/dist/local-pair-cli.d.ts +2 -3
  28. package/dist/local-pair-cli.js +12 -53
  29. package/dist/local-pair-cli.js.map +1 -1
  30. package/dist/mcp/fleet.d.ts +11 -9
  31. package/dist/mcp/fleet.js +61 -44
  32. package/dist/mcp/fleet.js.map +1 -1
  33. package/dist/mcp/http.d.ts +10 -9
  34. package/dist/mcp/http.js +133 -8
  35. package/dist/mcp/http.js.map +1 -1
  36. package/dist/mcp/legacy-relay.d.ts +2 -2
  37. package/dist/mcp/legacy-relay.js +4 -43
  38. package/dist/mcp/legacy-relay.js.map +1 -1
  39. package/dist/mcp/mcp-key-store.d.ts +52 -0
  40. package/dist/mcp/mcp-key-store.js +206 -0
  41. package/dist/mcp/mcp-key-store.js.map +1 -0
  42. package/dist/mcp/oauth.d.ts +5 -1
  43. package/dist/mcp/oauth.js +113 -39
  44. package/dist/mcp/oauth.js.map +1 -1
  45. package/dist/mcp/relay-handler.d.ts +2 -1
  46. package/dist/mcp/relay-handler.js +5 -40
  47. package/dist/mcp/relay-handler.js.map +1 -1
  48. package/dist/mcp/relay.d.ts +4 -4
  49. package/dist/mcp/relay.js +35 -85
  50. package/dist/mcp/relay.js.map +1 -1
  51. package/dist/mcp/secure-client.d.ts +23 -0
  52. package/dist/mcp/secure-client.js +124 -0
  53. package/dist/mcp/secure-client.js.map +1 -0
  54. package/dist/mcp/security-events.d.ts +65 -0
  55. package/dist/mcp/security-events.js +210 -0
  56. package/dist/mcp/security-events.js.map +1 -0
  57. package/dist/mcp/server.d.ts +1 -0
  58. package/dist/mcp/server.js +2 -2
  59. package/dist/mcp/server.js.map +1 -1
  60. package/dist/mcp/test-support.d.ts +32 -0
  61. package/dist/mcp/test-support.js +120 -0
  62. package/dist/mcp/test-support.js.map +1 -0
  63. package/dist/mcp-cli.js +56 -36
  64. package/dist/mcp-cli.js.map +1 -1
  65. package/dist/pairing-client.d.ts +10 -12
  66. package/dist/pairing-client.js +36 -9
  67. package/dist/pairing-client.js.map +1 -1
  68. package/dist/push-notifier.js +3 -1
  69. package/dist/push-notifier.js.map +1 -1
  70. package/dist/security/account-crypto.d.ts +33 -0
  71. package/dist/security/account-crypto.js +649 -0
  72. package/dist/security/account-crypto.js.map +1 -0
  73. package/dist/security/account-protocol.d.ts +228 -0
  74. package/dist/security/account-protocol.js +2 -0
  75. package/dist/security/account-protocol.js.map +1 -0
  76. package/dist/security/bridge/account-client.d.ts +7 -0
  77. package/dist/security/bridge/account-client.js +87 -0
  78. package/dist/security/bridge/account-client.js.map +1 -0
  79. package/dist/security/bridge/account-state.d.ts +471 -0
  80. package/dist/security/bridge/account-state.js +78 -0
  81. package/dist/security/bridge/account-state.js.map +1 -0
  82. package/dist/security/bridge/audit.d.ts +37 -0
  83. package/dist/security/bridge/audit.js +185 -0
  84. package/dist/security/bridge/audit.js.map +1 -0
  85. package/dist/security/bridge/direct.d.ts +22 -0
  86. package/dist/security/bridge/direct.js +72 -0
  87. package/dist/security/bridge/direct.js.map +1 -0
  88. package/dist/security/bridge/errors.d.ts +7 -0
  89. package/dist/security/bridge/errors.js +12 -0
  90. package/dist/security/bridge/errors.js.map +1 -0
  91. package/dist/security/bridge/execute.d.ts +3 -0
  92. package/dist/security/bridge/execute.js +38 -0
  93. package/dist/security/bridge/execute.js.map +1 -0
  94. package/dist/security/bridge/identity.d.ts +441 -0
  95. package/dist/security/bridge/identity.js +64 -0
  96. package/dist/security/bridge/identity.js.map +1 -0
  97. package/dist/security/bridge/local-client.d.ts +4 -0
  98. package/dist/security/bridge/local-client.js +43 -0
  99. package/dist/security/bridge/local-client.js.map +1 -0
  100. package/dist/security/bridge/metadata.d.ts +99 -0
  101. package/dist/security/bridge/metadata.js +23 -0
  102. package/dist/security/bridge/metadata.js.map +1 -0
  103. package/dist/security/bridge/native-wake.d.ts +16 -0
  104. package/dist/security/bridge/native-wake.js +15 -0
  105. package/dist/security/bridge/native-wake.js.map +1 -0
  106. package/dist/security/bridge/pending-enrollment.d.ts +12 -0
  107. package/dist/security/bridge/pending-enrollment.js +113 -0
  108. package/dist/security/bridge/pending-enrollment.js.map +1 -0
  109. package/dist/security/bridge/permissions.d.ts +11 -0
  110. package/dist/security/bridge/permissions.js +97 -0
  111. package/dist/security/bridge/permissions.js.map +1 -0
  112. package/dist/security/bridge/policy.d.ts +55 -0
  113. package/dist/security/bridge/policy.js +415 -0
  114. package/dist/security/bridge/policy.js.map +1 -0
  115. package/dist/security/bridge/security.d.ts +61 -0
  116. package/dist/security/bridge/security.js +388 -0
  117. package/dist/security/bridge/security.js.map +1 -0
  118. package/dist/security/bridge/snapshot.d.ts +14 -0
  119. package/dist/security/bridge/snapshot.js +77 -0
  120. package/dist/security/bridge/snapshot.js.map +1 -0
  121. package/dist/security/crypto.d.ts +17 -0
  122. package/dist/security/crypto.js +280 -0
  123. package/dist/security/crypto.js.map +1 -0
  124. package/dist/security/protocol.d.ts +400 -0
  125. package/dist/security/protocol.js +9 -0
  126. package/dist/security/protocol.js.map +1 -0
  127. package/dist/security-cli.d.ts +3 -0
  128. package/dist/security-cli.js +69 -0
  129. package/dist/security-cli.js.map +1 -0
  130. package/dist/solo/solo-server.js +24 -2
  131. package/dist/solo/solo-server.js.map +1 -1
  132. package/dist/types.d.ts +1 -1
  133. package/dist/wire-types.d.ts +3 -0
  134. package/dist/wire-types.js.map +1 -1
  135. package/hooks/notify.sh +1 -1
  136. package/muse-plugin/lib/ftown-hook.sh +6 -22
  137. package/opencode-plugin/ftown.js +33 -5
  138. package/package.json +4 -2
  139. package/pi-extension/ftown.js +33 -5
@@ -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,12 @@
1
+ import type { AccountEnrollmentLink, VerifiedAccountTrust } from '../account-protocol.js';
2
+ import type { EndpointIdentity } from '../protocol.js';
3
+ export interface PendingBridgeEnrollment {
4
+ identity: EndpointIdentity;
5
+ link: AccountEnrollmentLink;
6
+ }
7
+ export declare function prepareBridgeEnrollment(dataDir: string, bridgeId: string, label: string, now?: number): Promise<PendingBridgeEnrollment>;
8
+ /** URL is constructed from operator configuration, never a service-provided redirect. */
9
+ export declare function savePairingVerificationUrl(dataDir: string, apiUrl: string, userCode: string): Promise<string>;
10
+ export declare function pendingPairingLink(dataDir: string, now?: number): Promise<string>;
11
+ /** Save the normalized SAME private/public key and trusted bootstrap in one durable identity record. */
12
+ export declare function bindPendingBridgeIdentity(dataDir: string, trust: VerifiedAccountTrust): Promise<EndpointIdentity>;
@@ -0,0 +1,113 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { z } from 'zod';
4
+ import { createAccountEnrollment, encodeAccountEnrollmentLink, generateAccountBridgeCandidate, importAccountBridgeCandidate, parseAccountEnrollmentLink, verifyAccountHistory } from '../account-crypto.js';
5
+ import { AtomicSnapshot } from './snapshot.js';
6
+ import { endpointSchema } from './metadata.js';
7
+ import { identityRecordSchema } from './identity.js';
8
+ import { importEndpoint } from '../crypto.js';
9
+ import { BridgeSecurityError } from './errors.js';
10
+ import { accountReadUrl } from './account-client.js';
11
+ const pendingSchema = z.object({ version: z.literal(1), bridgeId: z.string().regex(/^[A-Za-z0-9_-]{1,200}$/),
12
+ endpoint: endpointSchema, privateKey: identityRecordSchema.shape.privateKey,
13
+ link: z.custom(), verificationUrl: z.string().max(4096).nullable() }).strict();
14
+ const pendingPath = (dataDir) => join(dataDir, 'security', 'account-enrollment.json');
15
+ async function readPending(dataDir) {
16
+ let raw;
17
+ try {
18
+ raw = await readFile(pendingPath(dataDir), 'utf8');
19
+ }
20
+ catch (error) {
21
+ if (error.code === 'ENOENT')
22
+ return null;
23
+ throw new BridgeSecurityError('account_storage_unavailable');
24
+ }
25
+ try {
26
+ const saved = pendingSchema.parse(JSON.parse(raw));
27
+ encodeAccountEnrollmentLink(saved.link);
28
+ return saved;
29
+ }
30
+ catch {
31
+ throw new BridgeSecurityError('account_storage_unavailable');
32
+ }
33
+ }
34
+ export async function prepareBridgeEnrollment(dataDir, bridgeId, label, now = Date.now()) {
35
+ const existing = await readPending(dataDir);
36
+ if (existing && existing.bridgeId !== bridgeId)
37
+ throw new BridgeSecurityError('wrong_context');
38
+ const unexpired = existing && typeof existing.link?.request?.expiresAt === 'string' && Date.parse(existing.link.request.expiresAt) > now;
39
+ let initial = existing;
40
+ if (!unexpired) {
41
+ // Expiring a request rotates its challenge/secret, never the pending bridge point.
42
+ // Approval may already exist remotely even when its response was lost.
43
+ const identity = existing ? await importAccountBridgeCandidate(existing.endpoint, existing.privateKey, false)
44
+ : await generateAccountBridgeCandidate(bridgeId, true);
45
+ initial = pendingSchema.parse({ version: 1, bridgeId, endpoint: identity.endpoint,
46
+ privateKey: existing?.privateKey ?? await crypto.subtle.exportKey('jwk', identity.keyPair.privateKey),
47
+ link: await createAccountEnrollment(identity, label.slice(0, 200), null, bridgeId, now), verificationUrl: null });
48
+ }
49
+ const store = new AtomicSnapshot(pendingPath(dataDir), pendingSchema, () => initial);
50
+ const saved = await store.transact(state => {
51
+ if (!unexpired && existing)
52
+ Object.assign(state, initial);
53
+ return state;
54
+ });
55
+ const link = await parseAccountEnrollmentLink(saved.link, now);
56
+ if (link.request.kind !== 'bridge' || link.request.bridgeId !== bridgeId || link.request.candidate.id !== saved.endpoint.id
57
+ || link.request.candidate.publicKey !== saved.endpoint.publicKey || link.request.candidate.ownerId !== saved.endpoint.ownerId)
58
+ throw new BridgeSecurityError('account_enrollment_invalid');
59
+ return { identity: await importAccountBridgeCandidate(saved.endpoint, saved.privateKey, false), link };
60
+ }
61
+ /** URL is constructed from operator configuration, never a service-provided redirect. */
62
+ export async function savePairingVerificationUrl(dataDir, apiUrl, userCode) {
63
+ accountReadUrl(apiUrl);
64
+ if (!/^[A-Za-z0-9-]{1,100}$/.test(userCode))
65
+ throw new BridgeSecurityError('account_enrollment_invalid');
66
+ const url = new URL('/pair', apiUrl);
67
+ url.searchParams.set('code', userCode);
68
+ const existing = await readPending(dataDir);
69
+ if (!existing)
70
+ throw new BridgeSecurityError('account_enrollment_invalid');
71
+ const store = new AtomicSnapshot(pendingPath(dataDir), pendingSchema, () => existing);
72
+ await store.transact(state => { state.verificationUrl = url.href; });
73
+ return url.href;
74
+ }
75
+ export async function pendingPairingLink(dataDir, now = Date.now()) {
76
+ const saved = await readPending(dataDir);
77
+ if (!saved?.verificationUrl)
78
+ throw new BridgeSecurityError('account_enrollment_invalid');
79
+ const link = await parseAccountEnrollmentLink(saved.link, now);
80
+ const url = new URL(saved.verificationUrl);
81
+ accountReadUrl(url.origin);
82
+ if (url.pathname !== '/pair' || url.hash || url.username || url.password || [...url.searchParams.keys()].some(key => key !== 'code')) {
83
+ throw new BridgeSecurityError('account_enrollment_invalid');
84
+ }
85
+ url.hash = `account=${encodeAccountEnrollmentLink(link)}`;
86
+ return url.href;
87
+ }
88
+ /** Save the normalized SAME private/public key and trusted bootstrap in one durable identity record. */
89
+ export async function bindPendingBridgeIdentity(dataDir, trust) {
90
+ const pending = await readPending(dataDir);
91
+ if (!pending)
92
+ throw new BridgeSecurityError('account_enrollment_invalid');
93
+ const checkpoint = await verifyAccountHistory(trust.history, trust.checkpoint);
94
+ const own = checkpoint.bridges.find(item => item.bridgeId === pending.bridgeId);
95
+ if (!own || own.endpoint.id !== pending.endpoint.id || own.endpoint.publicKey !== pending.endpoint.publicKey)
96
+ throw new BridgeSecurityError('wrong_context');
97
+ await importAccountBridgeCandidate(pending.endpoint, pending.privateKey, false);
98
+ const record = identityRecordSchema.parse({ version: 1, endpoint: own.endpoint, privateKey: pending.privateKey,
99
+ accountBootstrap: { checkpoint, history: trust.history } });
100
+ const store = new AtomicSnapshot(join(dataDir, 'security', 'bridge-identity.json'), identityRecordSchema, () => record);
101
+ const saved = await store.transact(state => {
102
+ if (state.endpoint.id !== record.endpoint.id || state.endpoint.ownerId !== record.endpoint.ownerId
103
+ || state.endpoint.publicKey !== record.endpoint.publicKey)
104
+ throw new BridgeSecurityError('wrong_context');
105
+ // A pre-existing bound identity is never repinned by repeating bootstrap.
106
+ if (state.accountBootstrap && state.accountBootstrap.checkpoint.rootId !== checkpoint.rootId)
107
+ throw new BridgeSecurityError('account_trust_rollback');
108
+ state.accountBootstrap ??= record.accountBootstrap;
109
+ return state;
110
+ });
111
+ return importEndpoint(saved.endpoint, saved.privateKey, false);
112
+ }
113
+ //# sourceMappingURL=pending-enrollment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-enrollment.js","sourceRoot":"","sources":["../../../src/security/bridge/pending-enrollment.ts"],"names":[],"mappings":"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"}
@@ -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', 'security_account_adopt']);
7
+ const result = (permission, operation, sessionId = null) => ({ permission, mutating: permission === 'control' || ['security_invite', 'security_revoke', 'security_approve_mcp', 'security_account_adopt'].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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"}
@@ -0,0 +1,55 @@
1
+ import type { ApprovedEndpoint, BridgeInvitation, EndpointPermission, EndpointPublic, EnrollmentMessage, InvitationOptions, LocalRecoveryAuthority, McpApproval, McpApprovalReceipt, VerifiedPrincipal } from '../protocol.js';
2
+ import type { AccountHead, AccountHistory, StoredAccountTrust, VerifiedAccountTrust } from '../account-protocol.js';
3
+ import type { SecurityErrorCode } from '../protocol.js';
4
+ /** Identity comparison matters: request JSON cannot manufacture this recovery capability. */
5
+ export declare const LOCAL_RECOVERY_AUTHORITY: LocalRecoveryAuthority;
6
+ export interface PolicyOptions {
7
+ now?: () => number;
8
+ monotonicNow?: () => number;
9
+ relayUrl?: string;
10
+ localOrigin?: string;
11
+ }
12
+ /** Bridge-owned authorization. Directory entries and caller-claimed permissions never enter this store. */
13
+ export declare class BridgePolicyStore {
14
+ readonly bridgeId: string;
15
+ readonly bridge: EndpointPublic;
16
+ private readonly options;
17
+ private readonly snapshot;
18
+ private readonly now;
19
+ private readonly connectivity;
20
+ constructor(dataDir: string, bridgeId: string, bridge: EndpointPublic, options?: PolicyOptions);
21
+ private check;
22
+ private active;
23
+ private principalFrom;
24
+ private authorize;
25
+ principal(endpointId: string, allowPendingHello?: boolean): Promise<VerifiedPrincipal>;
26
+ revalidate(actor: VerifiedPrincipal, permission?: EndpointPermission): Promise<void>;
27
+ createInvitation(actor: VerifiedPrincipal | LocalRecoveryAuthority, options: InvitationOptions): Promise<BridgeInvitation>;
28
+ consumeInvitation(message: EnrollmentMessage): Promise<ApprovedEndpoint>;
29
+ list(actor: VerifiedPrincipal): Promise<ApprovedEndpoint[]>;
30
+ recipients(): Promise<ApprovedEndpoint[]>;
31
+ approveMcp(actor: VerifiedPrincipal, approval: McpApproval): Promise<McpApprovalReceipt>;
32
+ activateMcp(actor: VerifiedPrincipal, grantId: string): Promise<{
33
+ grantId: string;
34
+ expiresAt: string;
35
+ }>;
36
+ revoke(actor: VerifiedPrincipal, endpointId: string): Promise<void>;
37
+ provisionLocalEndpoint(endpoint: EndpointPublic): Promise<void>;
38
+ accountTrust(): Promise<StoredAccountTrust | null>;
39
+ accountEndpointIds(): Promise<string[]>;
40
+ blockAccount(code: SecurityErrorCode): void;
41
+ accountAvailable(): Promise<boolean>;
42
+ /** Trusted startup bootstrap only; no remote endpoint can call this entry point. */
43
+ pinBootstrap(trust: VerifiedAccountTrust): Promise<void>;
44
+ private requireOwnBridge;
45
+ private projectAccount;
46
+ /** Only an authenticated API read reaches here; browser-supplied timestamps never do. */
47
+ applyAccountRead(history: AccountHistory, adoption?: {
48
+ actor: VerifiedPrincipal;
49
+ head: AccountHead;
50
+ }): Promise<{
51
+ head: AccountHead;
52
+ revoked: string[];
53
+ }>;
54
+ close(): Promise<void>;
55
+ }