@forgezero/agent 0.1.58 → 0.1.60

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.
@@ -1,11 +1,10 @@
1
- import { finalizeCloudflareBootstrapAcceptance, verifyCloudflareBootstrapAcceptance, type AttendedCloudflareBootstrapRequest, type CloudflareBootstrapEvidence, type CloudflareBootstrapAcceptanceEvidence, type CloudflareBootstrapFinalizeRequest, type CloudflareBootstrapPhaseRunner, type CloudflareBootstrapTokenFiles } from '../cloudflare-bootstrap';
1
+ import { finalizeCloudflareBootstrapAcceptance, verifyCloudflareBootstrapAcceptance, type AttendedCloudflareBootstrapRequest, type CloudflareBootstrapEvidence, type CloudflareBootstrapAcceptanceEvidence, type CloudflareBootstrapFinalizeRequest, type CloudflareBootstrapPhaseRunner, type CloudflareBootstrapTokens } from '../cloudflare-bootstrap';
2
2
  import { discoverCloudflareBootstrapResources, type CloudflareBootstrapDiscovery } from '../cloudflare-edge';
3
3
  export interface CloudflareBootstrapCommandConfig {
4
4
  format: 1;
5
5
  kind: 'forgezero-cloudflare-bootstrap-request';
6
6
  checkpointPath: string;
7
7
  coordinates: AttendedCloudflareBootstrapRequest['coordinates'];
8
- tokenFiles: CloudflareBootstrapTokenFiles;
9
8
  }
10
9
  export interface CloudflareBootstrapCommandDependencies {
11
10
  run?: CloudflareBootstrapPhaseRunner;
@@ -15,7 +14,7 @@ export declare function discoverCloudflareBootstrapCommandResources(input: {
15
14
  zoneName: string;
16
15
  kvNamespaceTitle: string;
17
16
  workerScriptName?: string;
18
- tokenFiles: CloudflareBootstrapTokenFiles;
17
+ tokens: CloudflareBootstrapTokens;
19
18
  }, dependencies?: {
20
19
  fetcher?: typeof fetch;
21
20
  discover?: typeof discoverCloudflareBootstrapResources;
@@ -33,14 +32,13 @@ export interface CloudflareBootstrapFinalizeConfig {
33
32
  export declare function createCloudflareBootstrapCommandConfig(input: {
34
33
  checkpointPath: string;
35
34
  coordinates: AttendedCloudflareBootstrapRequest['coordinates'];
36
- tokenFiles: CloudflareBootstrapTokenFiles;
37
35
  }): CloudflareBootstrapCommandConfig;
38
36
  /**
39
- * Read reviewed non-secret coordinates and token-file paths for the attended
40
- * operator phase. Raw token values and a persisted mode are not schema fields.
37
+ * Read reviewed non-secret coordinates for the attended operator phase. Raw
38
+ * token values and a persisted mode are not schema fields.
41
39
  */
42
40
  export declare function readCloudflareBootstrapCommandConfig(path: string, mode: 'plan' | 'apply'): AttendedCloudflareBootstrapRequest;
43
- export declare function runCloudflareBootstrapCommand(configPath: string, apply: boolean, dependencies?: CloudflareBootstrapCommandDependencies): Promise<CloudflareBootstrapEvidence>;
41
+ export declare function runCloudflareBootstrapCommand(configPath: string, apply: boolean, tokens?: CloudflareBootstrapTokens, dependencies?: CloudflareBootstrapCommandDependencies): Promise<CloudflareBootstrapEvidence>;
44
42
  export declare function runCloudflareBootstrapVerificationCommand(checkpointPath: string, dependencies?: {
45
43
  verify?: typeof verifyCloudflareBootstrapAcceptance;
46
44
  write?: (text: string) => void;
@@ -33,32 +33,18 @@ export interface CloudflareBootstrapCoordinates {
33
33
  realtime?: CloudflareRealtimeCoordinates;
34
34
  nodes: CloudflareBootstrapNodeCoordinates[];
35
35
  }
36
- export interface CloudflareBootstrapTokenFiles {
37
- /** CF_TUNNEL_TOKEN: Tunnel, DNS, Mesh connector/network and device-profile writes. */
38
- tunnelTokenFile: string;
39
- /** Exact-account Workers KV Storage Write plus existing-Worker Scripts Write. */
40
- apiTokenFile: string;
41
- }
42
36
  export interface CloudflareBootstrapTokens {
43
37
  tunnelToken: string;
44
38
  apiToken: string;
45
39
  }
46
40
  interface CloudflareBootstrapNodeResource extends CloudflareBootstrapNodeCoordinates {
47
41
  tunnelId: string;
48
- connectorToken: string;
49
42
  mesh?: CloudflareMeshCoordinates & {
50
43
  connectorId: string;
51
- connectorToken: string;
52
44
  };
53
45
  }
54
46
  interface CloudflareBootstrapResources {
55
47
  kvNamespaceId: string;
56
- /** Owner-supplied CF_API_TOKEN, sealed into each host handoff. */
57
- apiToken: string;
58
- realtime?: {
59
- publishSecret: string;
60
- ticketSecret: string;
61
- };
62
48
  nodes: CloudflareBootstrapNodeResource[];
63
49
  }
64
50
  export interface CloudflareBootstrapOutput {
@@ -79,7 +65,7 @@ export interface CloudflareBootstrapOutput {
79
65
  export interface CloudflareBootstrapPlan {
80
66
  format: 1;
81
67
  kind: 'forgezero-cloudflare-bootstrap-plan';
82
- mode: 'attended-token-file';
68
+ mode: 'attended-hidden-input';
83
69
  outputFile: string;
84
70
  coordinates: CloudflareBootstrapCoordinates;
85
71
  operations: readonly string[];
@@ -88,7 +74,7 @@ export interface CloudflareBootstrapPlan {
88
74
  export interface AttendedCloudflareBootstrapRequest {
89
75
  mode: 'plan' | 'apply';
90
76
  coordinates: CloudflareBootstrapCoordinates;
91
- tokenFiles?: CloudflareBootstrapTokenFiles;
77
+ tokens?: CloudflareBootstrapTokens;
92
78
  checkpointPath: string;
93
79
  }
94
80
  /** Secret-free evidence safe to embed in the platform bootstrap journal. */
@@ -128,9 +114,7 @@ export interface CloudflareBootstrapDependencies {
128
114
  fetcher?: typeof fetch;
129
115
  }
130
116
  export type CloudflareBootstrapPhaseRunner = (request: AttendedCloudflareBootstrapRequest) => Promise<CloudflareBootstrapEvidence>;
131
- /** Read exactly one API token from a non-symlinked, owner-only regular file. */
132
- export declare function readOwnerApiToken(path: string): Promise<string>;
133
- export declare function readCloudflareBootstrapTokens(files: CloudflareBootstrapTokenFiles): Promise<CloudflareBootstrapTokens>;
117
+ export declare function validateCloudflareBootstrapTokens(input: unknown): CloudflareBootstrapTokens;
134
118
  export declare function validateCloudflareBootstrapCoordinates(input: CloudflareBootstrapCoordinates): CloudflareBootstrapCoordinates;
135
119
  export declare function planCloudflareBootstrap(input: CloudflareBootstrapCoordinates, outputPath: string): CloudflareBootstrapPlan;
136
120
  export interface CloudflareConnectorHandoff {
@@ -138,10 +122,8 @@ export interface CloudflareConnectorHandoff {
138
122
  hostname: string;
139
123
  service: string;
140
124
  tunnelId: string;
141
- connectorToken: string;
142
125
  mesh?: {
143
126
  connectorId: string;
144
- connectorToken: string;
145
127
  routes: readonly string[];
146
128
  };
147
129
  }
@@ -149,30 +131,29 @@ export interface CloudflareHostHandoff extends CloudflareConnectorHandoff {
149
131
  accountId: string;
150
132
  zoneId: string;
151
133
  kvNamespaceId: string;
152
- /** CF_API_TOKEN: Workers KV Storage Write plus existing-Worker Scripts Write. */
153
- apiToken: string;
154
- realtime?: CloudflareRealtimeCoordinates & {
155
- publishSecret: string;
156
- ticketSecret: string;
157
- };
134
+ realtime?: CloudflareRealtimeCoordinates;
158
135
  }
159
136
  export declare function cloudflareHostHandoffPath(checkpointPath: string, nodeName: string): string;
160
137
  export declare function readCloudflareConnectorHandoff(checkpointPath: string, nodeName: string): Promise<CloudflareConnectorHandoff>;
161
138
  export declare function readCloudflareHostHandoff(handoffPath: string, nodeName: string): Promise<CloudflareHostHandoff>;
162
139
  export declare function writeOwnerBootstrapOutput(path: string, output: CloudflareBootstrapOutput): Promise<void>;
140
+ export declare const deriveCloudflareRealtimeSecrets: (apiToken: string) => {
141
+ publishSecret: string;
142
+ ticketSecret: string;
143
+ };
163
144
  /** Attended, resumable Tunnel/DNS/KV handoff bootstrap. */
164
145
  export declare function applyCloudflareBootstrap(input: CloudflareBootstrapCoordinates, tokens: CloudflareBootstrapTokens, outputPath: string, fetcher?: typeof fetch): Promise<CloudflareBootstrapOutput>;
165
146
  export declare function runAttendedCloudflareBootstrap(request: AttendedCloudflareBootstrapRequest, dependencies?: CloudflareBootstrapDependencies): Promise<CloudflareBootstrapEvidence>;
166
147
  export declare function verifyCloudflareBootstrapAcceptance(checkpointPath: string, fetcher?: typeof fetch): Promise<CloudflareBootstrapAcceptanceEvidence>;
167
148
  /**
168
- * Read finalized, secret-free Cloudflare acceptance after the capability-bearing
149
+ * Read finalized, secret-free Cloudflare acceptance after the working
169
150
  * checkpoint has been removed. This is the durable input consumed by the fleet
170
151
  * rehearsal; it never attempts to reopen the deleted handoff.
171
152
  */
172
153
  export declare function readCloudflareBootstrapAcceptanceEvidence(acceptancePath: string, expectedNodes?: readonly CloudflareBootstrapAcceptedNode[]): Promise<CloudflareBootstrapAcceptanceEvidence>;
173
154
  /**
174
155
  * Prove every public node, persist secret-free acceptance evidence, then remove
175
- * the resumable checkpoint and node handoffs that contain runtime capabilities.
156
+ * the resumable secret-free checkpoint and node handoffs.
176
157
  */
177
158
  export declare function finalizeCloudflareBootstrapAcceptance(request: CloudflareBootstrapFinalizeRequest, fetcher?: typeof fetch): Promise<CloudflareBootstrapAcceptanceEvidence>;
178
159
  export {};
@@ -344,18 +344,28 @@ async function ensureCloudflareTunnel(config, fetcher = fetch) {
344
344
  }
345
345
  return { tunnel, connectorToken, created };
346
346
  }
347
+ async function retrieveCloudflareConnectorTokens(config, fetcher = fetch) {
348
+ if (!/^[a-f0-9]{32}$/i.test(config.accountId) || !/^[0-9a-f-]{36}$/i.test(config.tunnelId) || config.meshConnectorId && !/^[0-9a-f-]{36}$/i.test(config.meshConnectorId)) {
349
+ throw new Error("Cloudflare connector retrieval coordinates are invalid");
350
+ }
351
+ const connectorToken = await cf(config, `/accounts/${config.accountId}/cfd_tunnel/${encodeURIComponent(config.tunnelId)}/token`, {}, fetcher);
352
+ const meshConnectorToken = config.meshConnectorId ? await cf(config, `/accounts/${config.accountId}/warp_connector/${encodeURIComponent(config.meshConnectorId)}/token`, {}, fetcher) : undefined;
353
+ for (const value of [connectorToken, meshConnectorToken]) {
354
+ if (value !== undefined && (!value || value.length > 16384))
355
+ throw new Error("Cloudflare returned an invalid connector token");
356
+ }
357
+ return { connectorToken, ...meshConnectorToken ? { meshConnectorToken } : {} };
358
+ }
347
359
 
348
360
  // src/cloudflare-bootstrap.ts
349
361
  import { constants } from "fs";
350
- import { randomBytes, randomUUID } from "crypto";
362
+ import { createHmac, randomUUID } from "crypto";
351
363
  import { chmod, lstat, mkdir, open, readdir, rename, rmdir, stat, unlink } from "fs/promises";
352
364
  import { dirname, join, resolve } from "path";
353
365
  import { isIP as isIP2 } from "net";
354
366
  var TOKEN = /^[A-Za-z0-9._-]{40,80}$/;
355
- var CONNECTOR_TOKEN = /^[A-Za-z0-9._-]{40,16384}$/;
356
367
  var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
357
368
  var HOSTNAME = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
358
- var REALTIME_SECRET = /^[A-Za-z0-9_-]{64,128}$/;
359
369
  var ownerUid = () => typeof process.getuid === "function" ? process.getuid() : undefined;
360
370
  async function assertOwnerOnlyHandle(path, handle, maximumBytes) {
361
371
  const metadata = await handle.stat();
@@ -389,26 +399,17 @@ async function readOwnerOnlyFile(path, maximumBytes) {
389
399
  await handle?.close();
390
400
  }
391
401
  }
392
- async function readOwnerApiToken(path) {
393
- const token = (await readOwnerOnlyFile(path, 4096)).trim();
394
- if (!TOKEN.test(token))
395
- throw new Error(`${resolve(path)} must contain exactly one Cloudflare API token`);
396
- return token;
397
- }
398
- async function readCloudflareBootstrapTokens(files) {
399
- const unsupported = Object.keys(files).filter((key) => ![
400
- "tunnelTokenFile",
401
- "apiTokenFile"
402
- ].includes(key));
402
+ function validateCloudflareBootstrapTokens(input) {
403
+ if (!input || typeof input !== "object" || Array.isArray(input))
404
+ throw new Error("Cloudflare bootstrap tokens must be an object");
405
+ const source = input;
406
+ const unsupported = Object.keys(source).filter((key) => !["tunnelToken", "apiToken"].includes(key));
403
407
  if (unsupported.length)
404
- throw new Error(`Cloudflare bootstrap token files contain unsupported field ${unsupported[0]}`);
405
- if (!files.tunnelTokenFile?.trim() || !files.apiTokenFile?.trim()) {
406
- throw new Error("Cloudflare bootstrap requires exactly tunnelTokenFile and apiTokenFile");
407
- }
408
- const [tunnelToken, apiToken] = await Promise.all([
409
- readOwnerApiToken(files.tunnelTokenFile),
410
- readOwnerApiToken(files.apiTokenFile)
411
- ]);
408
+ throw new Error(`Cloudflare bootstrap tokens contain unsupported field ${unsupported[0]}`);
409
+ const tunnelToken = typeof source.tunnelToken === "string" ? source.tunnelToken.trim() : "";
410
+ const apiToken = typeof source.apiToken === "string" ? source.apiToken.trim() : "";
411
+ if (!TOKEN.test(tunnelToken) || !TOKEN.test(apiToken))
412
+ throw new Error("Cloudflare bootstrap requires two valid API tokens");
412
413
  if (tunnelToken === apiToken) {
413
414
  throw new Error("CF_TUNNEL_TOKEN and CF_API_TOKEN must be distinct least-privilege tokens");
414
415
  }
@@ -584,7 +585,7 @@ function planCloudflareBootstrap(input, outputPath) {
584
585
  return {
585
586
  format: 1,
586
587
  kind: "forgezero-cloudflare-bootstrap-plan",
587
- mode: "attended-token-file",
588
+ mode: "attended-hidden-input",
588
589
  outputFile: resolve(outputPath),
589
590
  coordinates,
590
591
  operations: [
@@ -592,19 +593,19 @@ function planCloudflareBootstrap(input, outputPath) {
592
593
  ...coordinates.realtime ? [
593
594
  "prove the existing Worker owns a Durable Object namespace and install its publish/ticket secrets with CF_API_TOKEN"
594
595
  ] : [],
595
- "create or reuse one remotely-managed Tunnel per node and checkpoint every connector token",
596
+ "create or reuse one remotely-managed Tunnel per node and checkpoint only its non-secret id",
596
597
  ...coordinates.nodes.some(({ mesh }) => mesh) ? [
597
- "create or reuse one Mesh/WARP Connector per declared private-network node and checkpoint its registration token",
598
+ "create or reuse one Mesh/WARP Connector per declared private-network node and checkpoint only its non-secret id",
598
599
  "reconcile every unique private CIDR to its Mesh connector and include all declared CIDRs in the dedicated Mesh device profile"
599
600
  ] : [],
600
601
  "preflight each exact DNS hostname, refuse ambiguous or incompatible records, and update its existing CNAME or create it only when absent",
601
602
  "reconcile each Tunnel public-hostname ingress rule to the declared loopback API service",
602
- "write one node-specific handoff containing the connector token and owner-supplied KV/Worker runtime token"
603
+ "write one secret-free node handoff containing only bound Cloudflare ids and public coordinates"
603
604
  ],
604
605
  secrets: [
605
- "API tokens are read only from owner-only files and are never placed in argv or stdout",
606
- "CF_TUNNEL_TOKEN is never persisted; cloudflared, Mesh, CF_API_TOKEN and generated realtime capabilities are atomically checkpointed with mode 0600",
607
- "the normal API process receives only CF_API_TOKEN and never Tunnel, DNS or Mesh management authority"
606
+ "API tokens are accepted only from a hidden prompt and are never placed in JSON, files, argv or stdout",
607
+ "checkpoint and host handoffs contain no connector, management, runtime or realtime secret",
608
+ "the target Agent retrieves its connector tokens and seals both Vault fallback tokens directly"
608
609
  ]
609
610
  };
610
611
  }
@@ -669,25 +670,15 @@ async function readExistingOutput(path) {
669
670
  const resources = output.resources;
670
671
  const unsupportedResource = Object.keys(resources).filter((key) => ![
671
672
  "kvNamespaceId",
672
- "apiToken",
673
- "realtime",
674
673
  "nodes"
675
674
  ].includes(key));
676
675
  if (unsupportedResource.length) {
677
676
  throw new Error(`${resolve(path)} resources contain unsupported field ${unsupportedResource[0]}`);
678
677
  }
679
678
  const coordinates = validateCloudflareBootstrapCoordinates(output.coordinates);
680
- if (resources.kvNamespaceId !== coordinates.kvNamespaceId || !TOKEN.test(String(resources.apiToken ?? "")) || !Array.isArray(resources.nodes) || resources.nodes.length > coordinates.nodes.length) {
679
+ if (resources.kvNamespaceId !== coordinates.kvNamespaceId || !Array.isArray(resources.nodes) || resources.nodes.length > coordinates.nodes.length) {
681
680
  throw new Error(`${resolve(path)} has malformed Cloudflare bootstrap resources`);
682
681
  }
683
- if (coordinates.realtime) {
684
- const realtime = resources.realtime;
685
- if (!realtime || !REALTIME_SECRET.test(String(realtime.publishSecret ?? "")) || !REALTIME_SECRET.test(String(realtime.ticketSecret ?? "")) || realtime.publishSecret === realtime.ticketSecret) {
686
- throw new Error(`${resolve(path)} has malformed Cloudflare realtime resources`);
687
- }
688
- } else if (resources.realtime !== undefined) {
689
- throw new Error(`${resolve(path)} contains undeclared Cloudflare realtime resources`);
690
- }
691
682
  const seen = new Set;
692
683
  for (const item of resources.nodes) {
693
684
  if (!item || typeof item !== "object" || Array.isArray(item)) {
@@ -700,16 +691,15 @@ async function readExistingOutput(path) {
700
691
  "service",
701
692
  "tunnelName",
702
693
  "tunnelId",
703
- "connectorToken",
704
694
  "mesh"
705
695
  ].includes(key));
706
696
  const expected = coordinates.nodes.find((candidate) => candidate.nodeName === node.nodeName);
707
- if (unknownNode.length || !expected || seen.has(expected.nodeName) || node.hostname !== expected.hostname || node.service !== expected.service || node.tunnelName !== expected.tunnelName || !UUID.test(String(node.tunnelId ?? "")) || !CONNECTOR_TOKEN.test(String(node.connectorToken ?? ""))) {
697
+ if (unknownNode.length || !expected || seen.has(expected.nodeName) || node.hostname !== expected.hostname || node.service !== expected.service || node.tunnelName !== expected.tunnelName || !UUID.test(String(node.tunnelId ?? ""))) {
708
698
  throw new Error(`${resolve(path)} has a malformed or unbound Cloudflare node resource`);
709
699
  }
710
700
  if (expected.mesh) {
711
701
  const mesh = node.mesh;
712
- if (!mesh || mesh.connectorName !== expected.mesh.connectorName || JSON.stringify(mesh.routes) !== JSON.stringify(expected.mesh.routes) || mesh.highAvailability !== expected.mesh.highAvailability || !UUID.test(String(mesh.connectorId ?? "")) || !CONNECTOR_TOKEN.test(String(mesh.connectorToken ?? ""))) {
702
+ if (!mesh || mesh.connectorName !== expected.mesh.connectorName || JSON.stringify(mesh.routes) !== JSON.stringify(expected.mesh.routes) || mesh.highAvailability !== expected.mesh.highAvailability || !UUID.test(String(mesh.connectorId ?? ""))) {
713
703
  throw new Error(`${resolve(path)} has a malformed or unbound Cloudflare Mesh resource`);
714
704
  }
715
705
  } else if (node.mesh !== undefined) {
@@ -750,7 +740,7 @@ async function readCloudflareConnectorHandoff(checkpointPath, nodeName) {
750
740
  throw new Error(`Cloudflare connector handoff has no unique completed node ${normalizedNodeName}`);
751
741
  }
752
742
  const resource = matches[0];
753
- if (resource.hostname !== expected.hostname || resource.service !== expected.service || resource.tunnelName !== expected.tunnelName || !UUID.test(resource.tunnelId) || !CONNECTOR_TOKEN.test(resource.connectorToken)) {
743
+ if (resource.hostname !== expected.hostname || resource.service !== expected.service || resource.tunnelName !== expected.tunnelName || !UUID.test(resource.tunnelId)) {
754
744
  throw new Error(`Cloudflare connector handoff for ${normalizedNodeName} is malformed or incomplete`);
755
745
  }
756
746
  return {
@@ -758,10 +748,8 @@ async function readCloudflareConnectorHandoff(checkpointPath, nodeName) {
758
748
  hostname: resource.hostname,
759
749
  service: resource.service,
760
750
  tunnelId: resource.tunnelId,
761
- connectorToken: resource.connectorToken,
762
751
  ...resource.mesh ? { mesh: {
763
752
  connectorId: resource.mesh.connectorId,
764
- connectorToken: resource.mesh.connectorToken,
765
753
  routes: resource.mesh.routes
766
754
  } } : {}
767
755
  };
@@ -785,11 +773,9 @@ async function readCloudflareHostHandoff(handoffPath, nodeName) {
785
773
  "hostname",
786
774
  "service",
787
775
  "tunnelId",
788
- "connectorToken",
789
776
  "accountId",
790
777
  "zoneId",
791
778
  "kvNamespaceId",
792
- "apiToken",
793
779
  "mesh",
794
780
  "realtime"
795
781
  ].includes(key));
@@ -800,11 +786,11 @@ async function readCloudflareHostHandoff(handoffPath, nodeName) {
800
786
  try {
801
787
  service = normalizeService(output.service ?? "");
802
788
  } catch {}
803
- if (output.format !== 1 || output.kind !== "forgezero-cloudflare-host-handoff" || output.nodeName !== normalizedNodeName || !HOSTNAME.test(output.hostname ?? "") || !service || !UUID.test(output.tunnelId ?? "") || !CONNECTOR_TOKEN.test(output.connectorToken ?? "") || !TOKEN.test(output.apiToken ?? "") || !/^[a-f0-9]{32}$/i.test(output.accountId ?? "") || !/^[a-f0-9]{32}$/i.test(output.zoneId ?? "") || !/^[a-f0-9]{32}$/i.test(output.kvNamespaceId ?? "")) {
789
+ if (output.format !== 1 || output.kind !== "forgezero-cloudflare-host-handoff" || output.nodeName !== normalizedNodeName || !HOSTNAME.test(output.hostname ?? "") || !service || !UUID.test(output.tunnelId ?? "") || !/^[a-f0-9]{32}$/i.test(output.accountId ?? "") || !/^[a-f0-9]{32}$/i.test(output.zoneId ?? "") || !/^[a-f0-9]{32}$/i.test(output.kvNamespaceId ?? "")) {
804
790
  throw new Error("Cloudflare host handoff is malformed or belongs to another node");
805
791
  }
806
792
  if (output.mesh !== undefined) {
807
- if (!output.mesh || typeof output.mesh !== "object" || !UUID.test(output.mesh.connectorId) || !CONNECTOR_TOKEN.test(output.mesh.connectorToken) || !Array.isArray(output.mesh.routes) || output.mesh.routes.length < 1 || output.mesh.routes.length > 64 || output.mesh.routes.some((route) => {
793
+ if (!output.mesh || typeof output.mesh !== "object" || !UUID.test(output.mesh.connectorId) || !Array.isArray(output.mesh.routes) || output.mesh.routes.length < 1 || output.mesh.routes.length > 64 || output.mesh.routes.some((route) => {
808
794
  try {
809
795
  return privateMeshCidr(route) !== route;
810
796
  } catch {
@@ -833,7 +819,7 @@ async function readCloudflareHostHandoff(handoffPath, nodeName) {
833
819
  } catch {
834
820
  throw new Error("Cloudflare host handoff contains malformed realtime coordinates");
835
821
  }
836
- if (!REALTIME_SECRET.test(output.realtime.publishSecret) || !REALTIME_SECRET.test(output.realtime.ticketSecret) || output.realtime.publishSecret === output.realtime.ticketSecret || realtime.workerScriptName !== output.realtime.workerScriptName || realtime.endpoint !== output.realtime.endpoint || realtime.producer !== output.realtime.producer) {
822
+ if (realtime.workerScriptName !== output.realtime.workerScriptName || realtime.endpoint !== output.realtime.endpoint || realtime.producer !== output.realtime.producer) {
837
823
  throw new Error("Cloudflare host handoff contains malformed realtime credentials");
838
824
  }
839
825
  }
@@ -898,18 +884,12 @@ async function writeCloudflareHostHandoffs(checkpointPath, output) {
898
884
  hostname: node.hostname,
899
885
  service: node.service,
900
886
  tunnelId: node.tunnelId,
901
- connectorToken: node.connectorToken,
902
887
  accountId: output.coordinates.accountId,
903
888
  zoneId: output.coordinates.zoneId,
904
889
  kvNamespaceId: output.resources.kvNamespaceId,
905
- apiToken: output.resources.apiToken,
906
- ...output.coordinates.realtime && output.resources.realtime ? { realtime: {
907
- ...output.coordinates.realtime,
908
- ...output.resources.realtime
909
- } } : {},
890
+ ...output.coordinates.realtime ? { realtime: output.coordinates.realtime } : {},
910
891
  ...node.mesh ? { mesh: {
911
892
  connectorId: node.mesh.connectorId,
912
- connectorToken: node.mesh.connectorToken,
913
893
  routes: node.mesh.routes
914
894
  } } : {}
915
895
  };
@@ -917,7 +897,10 @@ async function writeCloudflareHostHandoffs(checkpointPath, output) {
917
897
  }
918
898
  }
919
899
  var sameCoordinates = (left, right) => JSON.stringify(left) === JSON.stringify(right);
920
- var newRealtimeSecret = () => randomBytes(48).toString("base64url");
900
+ var deriveCloudflareRealtimeSecrets = (apiToken) => ({
901
+ publishSecret: createHmac("sha512", apiToken).update("forgezero/realtime/publish/v1").digest("base64url"),
902
+ ticketSecret: createHmac("sha512", apiToken).update("forgezero/realtime/ticket/v1").digest("base64url")
903
+ });
921
904
  async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fetch) {
922
905
  const coordinates = validateCloudflareBootstrapCoordinates(input);
923
906
  const absoluteOutput = resolve(outputPath);
@@ -935,10 +918,7 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
935
918
  throw new Error("CF_TUNNEL_TOKEN and CF_API_TOKEN must be distinct least-privilege capabilities");
936
919
  }
937
920
  await preflightCloudflareKvRuntime(coordinates, apiToken, fetcher);
938
- const realtime = coordinates.realtime ? existing?.resources.realtime ?? {
939
- publishSecret: newRealtimeSecret(),
940
- ticketSecret: newRealtimeSecret()
941
- } : undefined;
921
+ const realtime = coordinates.realtime ? deriveCloudflareRealtimeSecrets(apiToken) : undefined;
942
922
  if (coordinates.realtime && realtime) {
943
923
  await verifyCloudflareWorkerDurableObjects({
944
924
  accountId: coordinates.accountId,
@@ -953,8 +933,6 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
953
933
  coordinates,
954
934
  resources: {
955
935
  kvNamespaceId: coordinates.kvNamespaceId,
956
- apiToken,
957
- realtime,
958
936
  nodes: existing?.resources.nodes ?? []
959
937
  }
960
938
  });
@@ -973,7 +951,7 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
973
951
  let resource;
974
952
  let created = false;
975
953
  if (checkpointed) {
976
- if (checkpointed.hostname !== node.hostname || checkpointed.service !== node.service || checkpointed.tunnelName !== node.tunnelName || !UUID.test(checkpointed.tunnelId) || !CONNECTOR_TOKEN.test(checkpointed.connectorToken)) {
954
+ if (checkpointed.hostname !== node.hostname || checkpointed.service !== node.service || checkpointed.tunnelName !== node.tunnelName || !UUID.test(checkpointed.tunnelId)) {
977
955
  throw new Error(`checkpointed Cloudflare node ${node.nodeName} is malformed`);
978
956
  }
979
957
  resource = checkpointed;
@@ -994,15 +972,13 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
994
972
  }, fetcher);
995
973
  mesh = {
996
974
  ...node.mesh,
997
- connectorId: ensured.connector.id,
998
- connectorToken: ensured.connectorToken
975
+ connectorId: ensured.connector.id
999
976
  };
1000
977
  }
1001
978
  const { mesh: _declaredMesh, ...publicNode } = node;
1002
979
  resource = {
1003
980
  ...publicNode,
1004
981
  tunnelId: tunnel.tunnel.id,
1005
- connectorToken: tunnel.connectorToken,
1006
982
  ...mesh ? { mesh } : {}
1007
983
  };
1008
984
  }
@@ -1016,8 +992,6 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
1016
992
  coordinates,
1017
993
  resources: {
1018
994
  kvNamespaceId: coordinates.kvNamespaceId,
1019
- apiToken,
1020
- ...realtime ? { realtime } : {},
1021
995
  nodes: [...nodeResources]
1022
996
  }
1023
997
  });
@@ -1061,8 +1035,6 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
1061
1035
  coordinates,
1062
1036
  resources: {
1063
1037
  kvNamespaceId: coordinates.kvNamespaceId,
1064
- apiToken,
1065
- ...realtime ? { realtime } : {},
1066
1038
  nodes: nodeResources
1067
1039
  },
1068
1040
  created: {
@@ -1084,10 +1056,10 @@ async function runAttendedCloudflareBootstrap(request, dependencies = {}) {
1084
1056
  nodes: plan.coordinates.nodes.map(({ nodeName, hostname }) => ({ nodeName, hostname }))
1085
1057
  };
1086
1058
  }
1087
- if (!request.tokenFiles) {
1088
- throw new Error("Cloudflare apply requires exactly two owner-only token file paths");
1059
+ if (!request.tokens) {
1060
+ throw new Error("Cloudflare apply requires exactly two attended API tokens");
1089
1061
  }
1090
- const tokens = await readCloudflareBootstrapTokens(request.tokenFiles);
1062
+ const tokens = validateCloudflareBootstrapTokens(request.tokens);
1091
1063
  const output = await applyCloudflareBootstrap(plan.coordinates, tokens, plan.outputFile, dependencies.fetcher ?? fetch);
1092
1064
  return {
1093
1065
  format: 1,
@@ -1239,15 +1211,15 @@ async function finalizeCloudflareBootstrapAcceptance(request, fetcher = fetch) {
1239
1211
  export {
1240
1212
  writeOwnerBootstrapOutput,
1241
1213
  verifyCloudflareBootstrapAcceptance,
1214
+ validateCloudflareBootstrapTokens,
1242
1215
  validateCloudflareBootstrapCoordinates,
1243
1216
  runAttendedCloudflareBootstrap,
1244
- readOwnerApiToken,
1245
1217
  readCloudflareHostHandoff,
1246
1218
  readCloudflareConnectorHandoff,
1247
- readCloudflareBootstrapTokens,
1248
1219
  readCloudflareBootstrapAcceptanceEvidence,
1249
1220
  planCloudflareBootstrap,
1250
1221
  finalizeCloudflareBootstrapAcceptance,
1222
+ deriveCloudflareRealtimeSecrets,
1251
1223
  cloudflareHostHandoffPath,
1252
1224
  applyCloudflareBootstrap
1253
1225
  };
@@ -184,4 +184,14 @@ export declare function ensureCloudflareTunnel(config: {
184
184
  connectorToken: string;
185
185
  created: boolean;
186
186
  }>;
187
+ /** Retrieve node-scoped connector capabilities only in the target Agent process. */
188
+ export declare function retrieveCloudflareConnectorTokens(config: {
189
+ accountId: string;
190
+ tunnelId: string;
191
+ meshConnectorId?: string;
192
+ apiToken: string;
193
+ }, fetcher?: typeof fetch): Promise<{
194
+ connectorToken: string;
195
+ meshConnectorToken?: string;
196
+ }>;
187
197
  export {};
@@ -344,8 +344,21 @@ async function ensureCloudflareTunnel(config, fetcher = fetch) {
344
344
  }
345
345
  return { tunnel, connectorToken, created };
346
346
  }
347
+ async function retrieveCloudflareConnectorTokens(config, fetcher = fetch) {
348
+ if (!/^[a-f0-9]{32}$/i.test(config.accountId) || !/^[0-9a-f-]{36}$/i.test(config.tunnelId) || config.meshConnectorId && !/^[0-9a-f-]{36}$/i.test(config.meshConnectorId)) {
349
+ throw new Error("Cloudflare connector retrieval coordinates are invalid");
350
+ }
351
+ const connectorToken = await cf(config, `/accounts/${config.accountId}/cfd_tunnel/${encodeURIComponent(config.tunnelId)}/token`, {}, fetcher);
352
+ const meshConnectorToken = config.meshConnectorId ? await cf(config, `/accounts/${config.accountId}/warp_connector/${encodeURIComponent(config.meshConnectorId)}/token`, {}, fetcher) : undefined;
353
+ for (const value of [connectorToken, meshConnectorToken]) {
354
+ if (value !== undefined && (!value || value.length > 16384))
355
+ throw new Error("Cloudflare returned an invalid connector token");
356
+ }
357
+ return { connectorToken, ...meshConnectorToken ? { meshConnectorToken } : {} };
358
+ }
347
359
  export {
348
360
  verifyCloudflareWorkerDurableObjects,
361
+ retrieveCloudflareConnectorTokens,
349
362
  removeCloudflareWarpDatabaseInclude,
350
363
  removeCloudflarePrivateRoute,
351
364
  removeCloudflarePrivateDatabaseRoute,
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * One credential policy for every place the Agent can run.
3
3
  *
4
- * Operator credentials are attended input files and are never installed as a
5
- * runtime source. Physical-metal credentials are fixed host identities loaded
4
+ * Operator credentials are hidden attended input and are never persisted as a
5
+ * plaintext source. Physical-metal credentials are fixed host identities loaded
6
6
  * only by systemd. Enrolled platform and tenant computes read their assigned
7
7
  * project/environment Vault replica first and may use only an explicitly
8
8
  * installed, same-name systemd credential while that replica cannot answer.
@@ -34,13 +34,13 @@ export declare const CLOUDFLARE_CREDENTIAL_NAMES: {
34
34
  export declare const CLOUDFLARE_CREDENTIAL_SCHEMA: {
35
35
  readonly CF_TUNNEL_TOKEN: {
36
36
  readonly permissions: readonly ["Account:Cloudflare Tunnel Write", "Account:Cloudflare One Connector: WARP Write", "Account:Cloudflare One Networks Write", "Account:Zero Trust Write", "Zone:DNS Write"];
37
- readonly platformBootstrap: "attended-file";
38
- readonly platformRuntime: "vault";
37
+ readonly platformBootstrap: "hidden-prompt-to-systemd";
38
+ readonly platformRuntime: "vault-then-systemd";
39
39
  readonly tenantControl: "vault";
40
40
  };
41
41
  readonly CF_API_TOKEN: {
42
42
  readonly permissions: readonly ["Account:Workers KV Storage Write", "Account:Workers Scripts Write"];
43
- readonly platformBootstrap: "attended-file";
43
+ readonly platformBootstrap: "hidden-prompt-to-systemd";
44
44
  readonly platformRuntime: "vault-then-systemd";
45
45
  readonly tenantControl: "vault";
46
46
  };
@@ -67,22 +67,22 @@ export declare const AGENT_CREDENTIAL_POLICY: {
67
67
  readonly operator: {
68
68
  readonly vault: false;
69
69
  readonly systemdFallback: false;
70
- readonly attendedFile: true;
70
+ readonly hiddenInput: true;
71
71
  };
72
72
  readonly metal: {
73
73
  readonly vault: false;
74
74
  readonly systemdFallback: true;
75
- readonly attendedFile: false;
75
+ readonly hiddenInput: false;
76
76
  };
77
77
  readonly 'platform-compute': {
78
78
  readonly vault: true;
79
79
  readonly systemdFallback: true;
80
- readonly attendedFile: false;
80
+ readonly hiddenInput: false;
81
81
  };
82
82
  readonly 'tenant-compute': {
83
83
  readonly vault: true;
84
84
  readonly systemdFallback: true;
85
- readonly attendedFile: false;
85
+ readonly hiddenInput: false;
86
86
  };
87
87
  };
88
88
  export interface DeploymentCredentialBinding {
@@ -300,13 +300,13 @@ var CLOUDFLARE_CREDENTIAL_SCHEMA = {
300
300
  "Account:Zero Trust Write",
301
301
  "Zone:DNS Write"
302
302
  ],
303
- platformBootstrap: "attended-file",
304
- platformRuntime: "vault",
303
+ platformBootstrap: "hidden-prompt-to-systemd",
304
+ platformRuntime: "vault-then-systemd",
305
305
  tenantControl: "vault"
306
306
  },
307
307
  CF_API_TOKEN: {
308
308
  permissions: ["Account:Workers KV Storage Write", "Account:Workers Scripts Write"],
309
- platformBootstrap: "attended-file",
309
+ platformBootstrap: "hidden-prompt-to-systemd",
310
310
  platformRuntime: "vault-then-systemd",
311
311
  tenantControl: "vault"
312
312
  },
@@ -330,10 +330,10 @@ var CLOUDFLARE_CREDENTIAL_SCHEMA = {
330
330
  }
331
331
  };
332
332
  var AGENT_CREDENTIAL_POLICY = {
333
- operator: { vault: false, systemdFallback: false, attendedFile: true },
334
- metal: { vault: false, systemdFallback: true, attendedFile: false },
335
- "platform-compute": { vault: true, systemdFallback: true, attendedFile: false },
336
- "tenant-compute": { vault: true, systemdFallback: true, attendedFile: false }
333
+ operator: { vault: false, systemdFallback: false, hiddenInput: true },
334
+ metal: { vault: false, systemdFallback: true, hiddenInput: false },
335
+ "platform-compute": { vault: true, systemdFallback: true, hiddenInput: false },
336
+ "tenant-compute": { vault: true, systemdFallback: true, hiddenInput: false }
337
337
  };
338
338
  var CREDENTIAL_NAME = /^[A-Z_][A-Z0-9_]*$/;
339
339
  var SCOPE_PART2 = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/;