@forgezero/agent 0.1.27 → 0.1.28
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/README.md +3 -2
- package/dist/agent-heartbeat.js +15 -2
- package/dist/fz-agent.js +39 -7
- package/dist/fz.js +1 -1
- package/dist/guest-enrolment.js +14 -1
- package/dist/metal-helper-socket.js +23 -4
- package/dist/metal-provision.js +23 -4
- package/dist/migration-pull.js +14 -1
- package/dist/node-vault.js +15 -2
- package/dist/provision.js +1 -1
- package/dist/provisioning-pull.js +14 -1
- package/dist/signed-node-http.d.ts +10 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,8 +180,9 @@ compute's systemd-sealed SSH deploy key, or a fine-grained HTTPS token selected
|
|
|
180
180
|
by a project-vault secret name. A signed claim contains the mode and secret name
|
|
181
181
|
only. The agent resolves the value from its scoped memory cache, limits the Git
|
|
182
182
|
header to the repository origin, never writes the token into a command, and
|
|
183
|
-
never exposes it to tenant pipeline steps.
|
|
184
|
-
|
|
183
|
+
never exposes it to tenant pipeline steps. These provider-neutral modes are the
|
|
184
|
+
complete contract; a GitHub App is not a missing dependency and would be added
|
|
185
|
+
later only as an explicit provider-specific mode with expiry and revocation tests.
|
|
185
186
|
|
|
186
187
|
An operator may also force a deployment and await the complete result over the
|
|
187
188
|
private control socket:
|
package/dist/agent-heartbeat.js
CHANGED
|
@@ -376,8 +376,21 @@ class SignedNodeHttpError extends Error {
|
|
|
376
376
|
this.name = "SignedNodeHttpError";
|
|
377
377
|
}
|
|
378
378
|
}
|
|
379
|
+
function signedNodeApiUrl(value) {
|
|
380
|
+
let url;
|
|
381
|
+
try {
|
|
382
|
+
url = new URL(value);
|
|
383
|
+
} catch {
|
|
384
|
+
throw new Error("Agent API URL must be an absolute HTTPS URL.");
|
|
385
|
+
}
|
|
386
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
387
|
+
if (url.protocol !== "https:" && !(loopback && url.protocol === "http:") || url.username || url.password || url.search || url.hash) {
|
|
388
|
+
throw new Error("Agent API URL must be HTTPS without credentials, query or fragment.");
|
|
389
|
+
}
|
|
390
|
+
return url;
|
|
391
|
+
}
|
|
379
392
|
async function postSignedNode(options, path, body) {
|
|
380
|
-
const url =
|
|
393
|
+
const url = signedNodeApiUrl(options.apiUrl);
|
|
381
394
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
382
395
|
url.search = "";
|
|
383
396
|
url.hash = "";
|
|
@@ -416,7 +429,7 @@ async function postSignedNode(options, path, body) {
|
|
|
416
429
|
}
|
|
417
430
|
|
|
418
431
|
// src/version.ts
|
|
419
|
-
var VERSION2 = "0.1.
|
|
432
|
+
var VERSION2 = "0.1.28";
|
|
420
433
|
|
|
421
434
|
// src/agent-heartbeat.ts
|
|
422
435
|
var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
|
package/dist/fz-agent.js
CHANGED
|
@@ -4267,8 +4267,21 @@ class SignedNodeHttpError extends Error {
|
|
|
4267
4267
|
this.name = "SignedNodeHttpError";
|
|
4268
4268
|
}
|
|
4269
4269
|
}
|
|
4270
|
+
function signedNodeApiUrl(value) {
|
|
4271
|
+
let url;
|
|
4272
|
+
try {
|
|
4273
|
+
url = new URL(value);
|
|
4274
|
+
} catch {
|
|
4275
|
+
throw new Error("Agent API URL must be an absolute HTTPS URL.");
|
|
4276
|
+
}
|
|
4277
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
4278
|
+
if (url.protocol !== "https:" && !(loopback && url.protocol === "http:") || url.username || url.password || url.search || url.hash) {
|
|
4279
|
+
throw new Error("Agent API URL must be HTTPS without credentials, query or fragment.");
|
|
4280
|
+
}
|
|
4281
|
+
return url;
|
|
4282
|
+
}
|
|
4270
4283
|
async function postSignedNode(options, path, body) {
|
|
4271
|
-
const url =
|
|
4284
|
+
const url = signedNodeApiUrl(options.apiUrl);
|
|
4272
4285
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
4273
4286
|
url.search = "";
|
|
4274
4287
|
url.hash = "";
|
|
@@ -4328,7 +4341,7 @@ function projectVaultCoordinate(key) {
|
|
|
4328
4341
|
function tenantNodeApiUrl(apiUrl, tenantSlug) {
|
|
4329
4342
|
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(tenantSlug))
|
|
4330
4343
|
throw new Error("tenant slug is malformed");
|
|
4331
|
-
const url =
|
|
4344
|
+
const url = signedNodeApiUrl(apiUrl);
|
|
4332
4345
|
url.pathname = `/api/t/${encodeURIComponent(tenantSlug)}`;
|
|
4333
4346
|
url.search = "";
|
|
4334
4347
|
url.hash = "";
|
|
@@ -6564,16 +6577,36 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
6564
6577
|
throw new MetalProvisionError("create claim cannot remove a guest");
|
|
6565
6578
|
const name = claim.spec.guestName ?? guestNameFor(claim.computeKey);
|
|
6566
6579
|
const manifestPath = join2(profile.stateDir, `${name}.json`);
|
|
6580
|
+
const service = `forgezero-guest@${name}.service`;
|
|
6581
|
+
const unitPath = join2(profile.unitDir, service);
|
|
6582
|
+
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
6567
6583
|
const manifest = existsSync5(manifestPath) ? JSON.parse(readFileSync4(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
|
|
6568
|
-
if (!manifest)
|
|
6584
|
+
if (!manifest) {
|
|
6585
|
+
const seedBase = join2(profile.seedDir, name);
|
|
6586
|
+
const localArtifacts = [
|
|
6587
|
+
unitPath,
|
|
6588
|
+
`${seedBase}-seed.iso`,
|
|
6589
|
+
`${seedBase}-user-data`,
|
|
6590
|
+
`${seedBase}-meta-data`,
|
|
6591
|
+
`${seedBase}-network-config`
|
|
6592
|
+
];
|
|
6593
|
+
if (localArtifacts.some(existsSync5)) {
|
|
6594
|
+
throw new MetalProvisionError("guest artifacts exist without owned inventory");
|
|
6595
|
+
}
|
|
6596
|
+
const [volume, active] = await Promise.all([
|
|
6597
|
+
exec(["lvs", "--noheadings", lv]),
|
|
6598
|
+
exec(["systemctl", "is-active", service])
|
|
6599
|
+
]);
|
|
6600
|
+
if (volume.exitCode === 0 || active.exitCode === 0) {
|
|
6601
|
+
throw new MetalProvisionError("guest runtime exists without owned inventory");
|
|
6602
|
+
}
|
|
6569
6603
|
return {};
|
|
6604
|
+
}
|
|
6570
6605
|
const currentIdentity = manifest.computeKey === claim.computeKey && manifest.reference === claim.spec.reference && manifest.name === name;
|
|
6571
6606
|
const legacyPlatformIdentity = claim.bootstrap?.kind === "platform-genesis" && claim.computeKey === `platform:${name}` && claim.spec.reference === `platform:${name}` && manifest.name === name && manifest.disk === `/dev/${profile.volumeGroup}/${name}` && manifest.address === claim.spec.guestAddress && (manifest.unit === `fz-guest@${name}.service` || manifest.unit === `forgezero-guest@${name}.service`);
|
|
6572
6607
|
if (!currentIdentity && !legacyPlatformIdentity) {
|
|
6573
6608
|
throw new MetalProvisionError("compute identity conflicts with host inventory");
|
|
6574
6609
|
}
|
|
6575
|
-
const service = `forgezero-guest@${name}.service`;
|
|
6576
|
-
const unitPath = join2(profile.unitDir, service);
|
|
6577
6610
|
if (existsSync5(unitPath))
|
|
6578
6611
|
await checked(exec, ["systemctl", "disable", "--now", service]);
|
|
6579
6612
|
else if (legacyPlatformIdentity)
|
|
@@ -6581,7 +6614,6 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
6581
6614
|
else if ((await exec(["systemctl", "is-active", service])).exitCode === 0) {
|
|
6582
6615
|
throw new MetalProvisionError("guest unit is active but its owned unit file is missing");
|
|
6583
6616
|
}
|
|
6584
|
-
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
6585
6617
|
if ((await exec(["lvs", "--noheadings", lv])).exitCode === 0)
|
|
6586
6618
|
await checked(exec, ["lvremove", "-fy", lv]);
|
|
6587
6619
|
for (const path of [
|
|
@@ -7844,7 +7876,7 @@ function requestAgentUpdate(request, socketPath = DEFAULT_AGENT_UPDATE_SOCKET, t
|
|
|
7844
7876
|
import { readFileSync as readFileSync7 } from "fs";
|
|
7845
7877
|
|
|
7846
7878
|
// src/version.ts
|
|
7847
|
-
var VERSION2 = "0.1.
|
|
7879
|
+
var VERSION2 = "0.1.28";
|
|
7848
7880
|
|
|
7849
7881
|
// src/agent-heartbeat.ts
|
|
7850
7882
|
var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
|
package/dist/fz.js
CHANGED
|
@@ -4886,7 +4886,7 @@ var AGENT_UPDATE_HELPER_UNIT_PATH = "/etc/systemd/system/forgezero-agent-update-
|
|
|
4886
4886
|
var MAX_REQUEST_BYTES = 8 * 1024;
|
|
4887
4887
|
|
|
4888
4888
|
// src/version.ts
|
|
4889
|
-
var VERSION2 = "0.1.
|
|
4889
|
+
var VERSION2 = "0.1.28";
|
|
4890
4890
|
|
|
4891
4891
|
// src/software.ts
|
|
4892
4892
|
var BUN_INSTALLER_SHA256 = "bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd";
|
package/dist/guest-enrolment.js
CHANGED
|
@@ -28,8 +28,21 @@ class SignedNodeHttpError extends Error {
|
|
|
28
28
|
this.name = "SignedNodeHttpError";
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
function signedNodeApiUrl(value) {
|
|
32
|
+
let url;
|
|
33
|
+
try {
|
|
34
|
+
url = new URL(value);
|
|
35
|
+
} catch {
|
|
36
|
+
throw new Error("Agent API URL must be an absolute HTTPS URL.");
|
|
37
|
+
}
|
|
38
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
39
|
+
if (url.protocol !== "https:" && !(loopback && url.protocol === "http:") || url.username || url.password || url.search || url.hash) {
|
|
40
|
+
throw new Error("Agent API URL must be HTTPS without credentials, query or fragment.");
|
|
41
|
+
}
|
|
42
|
+
return url;
|
|
43
|
+
}
|
|
31
44
|
async function postSignedNode(options, path, body) {
|
|
32
|
-
const url =
|
|
45
|
+
const url = signedNodeApiUrl(options.apiUrl);
|
|
33
46
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
34
47
|
url.search = "";
|
|
35
48
|
url.hash = "";
|
|
@@ -552,16 +552,36 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
552
552
|
throw new MetalProvisionError("create claim cannot remove a guest");
|
|
553
553
|
const name = claim.spec.guestName ?? guestNameFor(claim.computeKey);
|
|
554
554
|
const manifestPath = join(profile.stateDir, `${name}.json`);
|
|
555
|
+
const service = `forgezero-guest@${name}.service`;
|
|
556
|
+
const unitPath = join(profile.unitDir, service);
|
|
557
|
+
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
555
558
|
const manifest = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
|
|
556
|
-
if (!manifest)
|
|
559
|
+
if (!manifest) {
|
|
560
|
+
const seedBase = join(profile.seedDir, name);
|
|
561
|
+
const localArtifacts = [
|
|
562
|
+
unitPath,
|
|
563
|
+
`${seedBase}-seed.iso`,
|
|
564
|
+
`${seedBase}-user-data`,
|
|
565
|
+
`${seedBase}-meta-data`,
|
|
566
|
+
`${seedBase}-network-config`
|
|
567
|
+
];
|
|
568
|
+
if (localArtifacts.some(existsSync)) {
|
|
569
|
+
throw new MetalProvisionError("guest artifacts exist without owned inventory");
|
|
570
|
+
}
|
|
571
|
+
const [volume, active] = await Promise.all([
|
|
572
|
+
exec(["lvs", "--noheadings", lv]),
|
|
573
|
+
exec(["systemctl", "is-active", service])
|
|
574
|
+
]);
|
|
575
|
+
if (volume.exitCode === 0 || active.exitCode === 0) {
|
|
576
|
+
throw new MetalProvisionError("guest runtime exists without owned inventory");
|
|
577
|
+
}
|
|
557
578
|
return {};
|
|
579
|
+
}
|
|
558
580
|
const currentIdentity = manifest.computeKey === claim.computeKey && manifest.reference === claim.spec.reference && manifest.name === name;
|
|
559
581
|
const legacyPlatformIdentity = claim.bootstrap?.kind === "platform-genesis" && claim.computeKey === `platform:${name}` && claim.spec.reference === `platform:${name}` && manifest.name === name && manifest.disk === `/dev/${profile.volumeGroup}/${name}` && manifest.address === claim.spec.guestAddress && (manifest.unit === `fz-guest@${name}.service` || manifest.unit === `forgezero-guest@${name}.service`);
|
|
560
582
|
if (!currentIdentity && !legacyPlatformIdentity) {
|
|
561
583
|
throw new MetalProvisionError("compute identity conflicts with host inventory");
|
|
562
584
|
}
|
|
563
|
-
const service = `forgezero-guest@${name}.service`;
|
|
564
|
-
const unitPath = join(profile.unitDir, service);
|
|
565
585
|
if (existsSync(unitPath))
|
|
566
586
|
await checked(exec, ["systemctl", "disable", "--now", service]);
|
|
567
587
|
else if (legacyPlatformIdentity)
|
|
@@ -569,7 +589,6 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
569
589
|
else if ((await exec(["systemctl", "is-active", service])).exitCode === 0) {
|
|
570
590
|
throw new MetalProvisionError("guest unit is active but its owned unit file is missing");
|
|
571
591
|
}
|
|
572
|
-
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
573
592
|
if ((await exec(["lvs", "--noheadings", lv])).exitCode === 0)
|
|
574
593
|
await checked(exec, ["lvremove", "-fy", lv]);
|
|
575
594
|
for (const path of [
|
package/dist/metal-provision.js
CHANGED
|
@@ -552,16 +552,36 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
552
552
|
throw new MetalProvisionError("create claim cannot remove a guest");
|
|
553
553
|
const name = claim.spec.guestName ?? guestNameFor(claim.computeKey);
|
|
554
554
|
const manifestPath = join(profile.stateDir, `${name}.json`);
|
|
555
|
+
const service = `forgezero-guest@${name}.service`;
|
|
556
|
+
const unitPath = join(profile.unitDir, service);
|
|
557
|
+
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
555
558
|
const manifest = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf8")) : legacyPlatformManifest(profile, claim, name);
|
|
556
|
-
if (!manifest)
|
|
559
|
+
if (!manifest) {
|
|
560
|
+
const seedBase = join(profile.seedDir, name);
|
|
561
|
+
const localArtifacts = [
|
|
562
|
+
unitPath,
|
|
563
|
+
`${seedBase}-seed.iso`,
|
|
564
|
+
`${seedBase}-user-data`,
|
|
565
|
+
`${seedBase}-meta-data`,
|
|
566
|
+
`${seedBase}-network-config`
|
|
567
|
+
];
|
|
568
|
+
if (localArtifacts.some(existsSync)) {
|
|
569
|
+
throw new MetalProvisionError("guest artifacts exist without owned inventory");
|
|
570
|
+
}
|
|
571
|
+
const [volume, active] = await Promise.all([
|
|
572
|
+
exec(["lvs", "--noheadings", lv]),
|
|
573
|
+
exec(["systemctl", "is-active", service])
|
|
574
|
+
]);
|
|
575
|
+
if (volume.exitCode === 0 || active.exitCode === 0) {
|
|
576
|
+
throw new MetalProvisionError("guest runtime exists without owned inventory");
|
|
577
|
+
}
|
|
557
578
|
return {};
|
|
579
|
+
}
|
|
558
580
|
const currentIdentity = manifest.computeKey === claim.computeKey && manifest.reference === claim.spec.reference && manifest.name === name;
|
|
559
581
|
const legacyPlatformIdentity = claim.bootstrap?.kind === "platform-genesis" && claim.computeKey === `platform:${name}` && claim.spec.reference === `platform:${name}` && manifest.name === name && manifest.disk === `/dev/${profile.volumeGroup}/${name}` && manifest.address === claim.spec.guestAddress && (manifest.unit === `fz-guest@${name}.service` || manifest.unit === `forgezero-guest@${name}.service`);
|
|
560
582
|
if (!currentIdentity && !legacyPlatformIdentity) {
|
|
561
583
|
throw new MetalProvisionError("compute identity conflicts with host inventory");
|
|
562
584
|
}
|
|
563
|
-
const service = `forgezero-guest@${name}.service`;
|
|
564
|
-
const unitPath = join(profile.unitDir, service);
|
|
565
585
|
if (existsSync(unitPath))
|
|
566
586
|
await checked(exec, ["systemctl", "disable", "--now", service]);
|
|
567
587
|
else if (legacyPlatformIdentity)
|
|
@@ -569,7 +589,6 @@ async function removeMetalGuest(profile, claim, exec) {
|
|
|
569
589
|
else if ((await exec(["systemctl", "is-active", service])).exitCode === 0) {
|
|
570
590
|
throw new MetalProvisionError("guest unit is active but its owned unit file is missing");
|
|
571
591
|
}
|
|
572
|
-
const lv = `/dev/${profile.volumeGroup}/${name}`;
|
|
573
592
|
if ((await exec(["lvs", "--noheadings", lv])).exitCode === 0)
|
|
574
593
|
await checked(exec, ["lvremove", "-fy", lv]);
|
|
575
594
|
for (const path of [
|
package/dist/migration-pull.js
CHANGED
|
@@ -15,8 +15,21 @@ class SignedNodeHttpError extends Error {
|
|
|
15
15
|
this.name = "SignedNodeHttpError";
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
function signedNodeApiUrl(value) {
|
|
19
|
+
let url;
|
|
20
|
+
try {
|
|
21
|
+
url = new URL(value);
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error("Agent API URL must be an absolute HTTPS URL.");
|
|
24
|
+
}
|
|
25
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
26
|
+
if (url.protocol !== "https:" && !(loopback && url.protocol === "http:") || url.username || url.password || url.search || url.hash) {
|
|
27
|
+
throw new Error("Agent API URL must be HTTPS without credentials, query or fragment.");
|
|
28
|
+
}
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
18
31
|
async function postSignedNode(options, path, body) {
|
|
19
|
-
const url =
|
|
32
|
+
const url = signedNodeApiUrl(options.apiUrl);
|
|
20
33
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
21
34
|
url.search = "";
|
|
22
35
|
url.hash = "";
|
package/dist/node-vault.js
CHANGED
|
@@ -135,8 +135,21 @@ class SignedNodeHttpError extends Error {
|
|
|
135
135
|
this.name = "SignedNodeHttpError";
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
+
function signedNodeApiUrl(value) {
|
|
139
|
+
let url;
|
|
140
|
+
try {
|
|
141
|
+
url = new URL(value);
|
|
142
|
+
} catch {
|
|
143
|
+
throw new Error("Agent API URL must be an absolute HTTPS URL.");
|
|
144
|
+
}
|
|
145
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
146
|
+
if (url.protocol !== "https:" && !(loopback && url.protocol === "http:") || url.username || url.password || url.search || url.hash) {
|
|
147
|
+
throw new Error("Agent API URL must be HTTPS without credentials, query or fragment.");
|
|
148
|
+
}
|
|
149
|
+
return url;
|
|
150
|
+
}
|
|
138
151
|
async function postSignedNode(options, path, body) {
|
|
139
|
-
const url =
|
|
152
|
+
const url = signedNodeApiUrl(options.apiUrl);
|
|
140
153
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
141
154
|
url.search = "";
|
|
142
155
|
url.hash = "";
|
|
@@ -196,7 +209,7 @@ function projectVaultCoordinate(key) {
|
|
|
196
209
|
function tenantNodeApiUrl(apiUrl, tenantSlug) {
|
|
197
210
|
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(tenantSlug))
|
|
198
211
|
throw new Error("tenant slug is malformed");
|
|
199
|
-
const url =
|
|
212
|
+
const url = signedNodeApiUrl(apiUrl);
|
|
200
213
|
url.pathname = `/api/t/${encodeURIComponent(tenantSlug)}`;
|
|
201
214
|
url.search = "";
|
|
202
215
|
url.hash = "";
|
package/dist/provision.js
CHANGED
|
@@ -15,8 +15,21 @@ class SignedNodeHttpError extends Error {
|
|
|
15
15
|
this.name = "SignedNodeHttpError";
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
function signedNodeApiUrl(value) {
|
|
19
|
+
let url;
|
|
20
|
+
try {
|
|
21
|
+
url = new URL(value);
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error("Agent API URL must be an absolute HTTPS URL.");
|
|
24
|
+
}
|
|
25
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
26
|
+
if (url.protocol !== "https:" && !(loopback && url.protocol === "http:") || url.username || url.password || url.search || url.hash) {
|
|
27
|
+
throw new Error("Agent API URL must be HTTPS without credentials, query or fragment.");
|
|
28
|
+
}
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
18
31
|
async function postSignedNode(options, path, body) {
|
|
19
|
-
const url =
|
|
32
|
+
const url = signedNodeApiUrl(options.apiUrl);
|
|
20
33
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/+/g, "/");
|
|
21
34
|
url.search = "";
|
|
22
35
|
url.hash = "";
|
|
@@ -10,5 +10,15 @@ export declare class SignedNodeHttpError extends Error {
|
|
|
10
10
|
readonly status: number;
|
|
11
11
|
constructor(status: number, message: string);
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Validate the one origin every outbound Agent client shares.
|
|
15
|
+
*
|
|
16
|
+
* Hybrid request authentication and a sealed response protect the payload, but
|
|
17
|
+
* they do not hide routing metadata. Accepting plain HTTP for a remote API
|
|
18
|
+
* would still expose node identity, operation timing and ciphertext to an
|
|
19
|
+
* on-path observer. Loopback HTTP remains available for local development and
|
|
20
|
+
* for a supervised same-host API; production traffic must use HTTPS.
|
|
21
|
+
*/
|
|
22
|
+
export declare function signedNodeApiUrl(value: string): URL;
|
|
13
23
|
/** One implementation of the hybrid-signed machine HTTP contract. */
|
|
14
24
|
export declare function postSignedNode<T>(options: SignedNodeHttpOptions, path: string, body: object): Promise<T>;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** One package version shared by both public binaries. Pinned to package.json by tests. */
|
|
2
|
-
export declare const VERSION = "0.1.
|
|
2
|
+
export declare const VERSION = "0.1.28";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
|
|
3
3
|
"name": "@forgezero/agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.28",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"check": "tsc --noEmit",
|