@code-partner/codepipe-hub 0.14.1-dev.422.gf90f06c4 → 0.14.1-dev.423.ga49d1f61
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/hub/dist/embedded.js +66 -9
- package/hub/dist/index.js +183 -76
- package/package.json +1 -1
package/hub/dist/embedded.js
CHANGED
|
@@ -457,7 +457,7 @@ var PLATFORM_VERSION;
|
|
|
457
457
|
var init_version_generated = __esm({
|
|
458
458
|
"../packages/shared/dist/version.generated.js"() {
|
|
459
459
|
"use strict";
|
|
460
|
-
PLATFORM_VERSION = "0.14.1-dev.
|
|
460
|
+
PLATFORM_VERSION = "0.14.1-dev.423.ga49d1f61";
|
|
461
461
|
}
|
|
462
462
|
});
|
|
463
463
|
|
|
@@ -467,7 +467,7 @@ var init_version = __esm({
|
|
|
467
467
|
"../packages/shared/dist/version.js"() {
|
|
468
468
|
"use strict";
|
|
469
469
|
init_version_generated();
|
|
470
|
-
PROTOCOL_VERSION =
|
|
470
|
+
PROTOCOL_VERSION = 5;
|
|
471
471
|
}
|
|
472
472
|
});
|
|
473
473
|
|
|
@@ -4983,6 +4983,8 @@ function createCommandOperations(deps) {
|
|
|
4983
4983
|
throw errors.unprocessable({ details: { reason: outcome.reason } });
|
|
4984
4984
|
case "not_found":
|
|
4985
4985
|
throw errors.notFound();
|
|
4986
|
+
case "rate_limited":
|
|
4987
|
+
throw errors.rateLimited();
|
|
4986
4988
|
}
|
|
4987
4989
|
}
|
|
4988
4990
|
function accountActorOf(actor) {
|
|
@@ -5572,6 +5574,14 @@ function createCommandOperations(deps) {
|
|
|
5572
5574
|
return { value: run.result, replayed: run.replayed };
|
|
5573
5575
|
});
|
|
5574
5576
|
},
|
|
5577
|
+
async issueWorkerRegistrationToken(actor) {
|
|
5578
|
+
const subjectId = actor.principal.subjectId;
|
|
5579
|
+
return await audited(actor, { operationId: "worker.issueRegistrationToken", aggregateType: "account", aggregateId: subjectId, projectId: null }, async (policy) => {
|
|
5580
|
+
await require2(actor, "account.manage", { type: "account" }, policy);
|
|
5581
|
+
const issued = unwrap(await write2.issueWorkerRegistrationToken({ owner: accountActorOf(actor) }));
|
|
5582
|
+
return { value: { secret: issued.secret, expiresAt: toTimestamp(issued.expiresAt) }, replayed: false };
|
|
5583
|
+
});
|
|
5584
|
+
},
|
|
5575
5585
|
async reportInitStatus(actor, command) {
|
|
5576
5586
|
const { projectId, phase } = command;
|
|
5577
5587
|
return await audited(actor, { operationId: "project.reportInitStatus", aggregateType: "project", aggregateId: projectId, projectId, data: { phase, failed: command.error !== void 0 } }, async (policy) => {
|
|
@@ -10972,7 +10982,20 @@ var init_auth = __esm({
|
|
|
10972
10982
|
});
|
|
10973
10983
|
|
|
10974
10984
|
// src/sandbox-reg-tokens.ts
|
|
10985
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10975
10986
|
import { nanoid as nanoid7 } from "nanoid";
|
|
10987
|
+
function hashSandboxRegToken(secret) {
|
|
10988
|
+
return createHash2("sha256").update(secret, "utf8").digest("hex");
|
|
10989
|
+
}
|
|
10990
|
+
async function issueEphemeralSandboxRegToken(db, email, ttlMs = EPHEMERAL_REG_TOKEN_TTL_MS) {
|
|
10991
|
+
const normalized = email.trim().toLowerCase();
|
|
10992
|
+
const secret = `sbxreg_${nanoid7(48)}`;
|
|
10993
|
+
const now = Date.now();
|
|
10994
|
+
const expiresAt = now + ttlMs;
|
|
10995
|
+
await db.run(`INSERT INTO sandbox_reg_tokens (token_hash, email, user_id, created_at, expires_at)
|
|
10996
|
+
VALUES (?, ?, (SELECT id FROM users WHERE email = ?), ?, ?)`, hashSandboxRegToken(secret), normalized, normalized, now, expiresAt);
|
|
10997
|
+
return { secret, expiresAt };
|
|
10998
|
+
}
|
|
10976
10999
|
var EPHEMERAL_REG_TOKEN_TTL_MS;
|
|
10977
11000
|
var init_sandbox_reg_tokens = __esm({
|
|
10978
11001
|
"src/sandbox-reg-tokens.ts"() {
|
|
@@ -11268,6 +11291,25 @@ var init_state_machine2 = __esm({
|
|
|
11268
11291
|
});
|
|
11269
11292
|
|
|
11270
11293
|
// src/abuse-throttle.ts
|
|
11294
|
+
function createAbuseThrottle(opts) {
|
|
11295
|
+
const hits = /* @__PURE__ */ new Map();
|
|
11296
|
+
return {
|
|
11297
|
+
take(key) {
|
|
11298
|
+
const now = Date.now();
|
|
11299
|
+
const entry = hits.get(key);
|
|
11300
|
+
if (!entry || entry.resetAt <= now) {
|
|
11301
|
+
hits.set(key, { count: 1, resetAt: now + opts.windowMs });
|
|
11302
|
+
return true;
|
|
11303
|
+
}
|
|
11304
|
+
if (entry.count >= opts.max) return false;
|
|
11305
|
+
entry.count += 1;
|
|
11306
|
+
return true;
|
|
11307
|
+
},
|
|
11308
|
+
reset() {
|
|
11309
|
+
hits.clear();
|
|
11310
|
+
}
|
|
11311
|
+
};
|
|
11312
|
+
}
|
|
11271
11313
|
var init_abuse_throttle = __esm({
|
|
11272
11314
|
"src/abuse-throttle.ts"() {
|
|
11273
11315
|
"use strict";
|
|
@@ -11298,11 +11340,11 @@ var init_sandbox2 = __esm({
|
|
|
11298
11340
|
init_sealedbox();
|
|
11299
11341
|
init_abuse_throttle();
|
|
11300
11342
|
init_auth();
|
|
11301
|
-
init_cli_auth();
|
|
11302
11343
|
init_sandbox_reg_tokens();
|
|
11303
11344
|
init_users();
|
|
11304
11345
|
init_agent_auth_probe();
|
|
11305
11346
|
init_ws();
|
|
11347
|
+
init_route_registry();
|
|
11306
11348
|
}
|
|
11307
11349
|
});
|
|
11308
11350
|
|
|
@@ -11357,6 +11399,7 @@ var init_farm2 = __esm({
|
|
|
11357
11399
|
init_sealedbox();
|
|
11358
11400
|
init_abuse_throttle();
|
|
11359
11401
|
init_ws();
|
|
11402
|
+
init_route_registry();
|
|
11360
11403
|
}
|
|
11361
11404
|
});
|
|
11362
11405
|
|
|
@@ -11389,6 +11432,7 @@ var init_runner2 = __esm({
|
|
|
11389
11432
|
init_users();
|
|
11390
11433
|
init_runner_reg_tokens();
|
|
11391
11434
|
init_ws();
|
|
11435
|
+
init_route_registry();
|
|
11392
11436
|
}
|
|
11393
11437
|
});
|
|
11394
11438
|
|
|
@@ -12367,10 +12411,10 @@ __export(regimen_config_exports, {
|
|
|
12367
12411
|
loadWorkflowSettings: () => loadWorkflowSettings,
|
|
12368
12412
|
loadYouTrackWrites: () => loadYouTrackWrites
|
|
12369
12413
|
});
|
|
12370
|
-
import { createHash as
|
|
12414
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
12371
12415
|
import { parse as parseYaml2 } from "yaml";
|
|
12372
12416
|
function contentSha2(content) {
|
|
12373
|
-
return
|
|
12417
|
+
return createHash3("sha1").update(content, "utf-8").digest("hex");
|
|
12374
12418
|
}
|
|
12375
12419
|
async function applyRegimenContent(db, projectId, content, log, preParsed) {
|
|
12376
12420
|
const previous = cache3.get(projectId) ?? null;
|
|
@@ -19186,9 +19230,9 @@ var init_cluster_order = __esm({
|
|
|
19186
19230
|
});
|
|
19187
19231
|
|
|
19188
19232
|
// src/prompts/prompt-cache.ts
|
|
19189
|
-
import { createHash as
|
|
19233
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
19190
19234
|
function contentSha3(content) {
|
|
19191
|
-
return
|
|
19235
|
+
return createHash4("sha1").update(content, "utf-8").digest("hex");
|
|
19192
19236
|
}
|
|
19193
19237
|
function getProjectPromptTemplate(projectId, role) {
|
|
19194
19238
|
return cache4.get(projectId)?.get(role)?.content ?? null;
|
|
@@ -22786,7 +22830,7 @@ __export(lifecycle_exports, {
|
|
|
22786
22830
|
taskRunsDirectImplement: () => taskRunsDirectImplement,
|
|
22787
22831
|
triggerRework: () => triggerRework
|
|
22788
22832
|
});
|
|
22789
|
-
import { createHash as
|
|
22833
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
22790
22834
|
async function reconcileStuckTasks(db, log) {
|
|
22791
22835
|
let recovered = 0;
|
|
22792
22836
|
for (const task of await listTasks(db)) {
|
|
@@ -23381,7 +23425,7 @@ async function maybeAwaitingAnswer(db, task, log, blockingQuestions = []) {
|
|
|
23381
23425
|
if (blocking.length === 0) return false;
|
|
23382
23426
|
const now = Date.now();
|
|
23383
23427
|
for (let i = 0; i < blocking.length; i++) {
|
|
23384
|
-
const digest2 =
|
|
23428
|
+
const digest2 = createHash5("sha256").update(blocking[i]).digest("hex").slice(0, 12);
|
|
23385
23429
|
await ingestComment(db, {
|
|
23386
23430
|
taskId: task.id,
|
|
23387
23431
|
trackerCommentId: `agent:${task.phase.toLowerCase()}:${i}:${digest2}`,
|
|
@@ -28487,6 +28531,8 @@ function requestGhRunnersDelete(runnerId, projectId, sealedBundle, timeoutMs) {
|
|
|
28487
28531
|
init_farm2();
|
|
28488
28532
|
init_runner2();
|
|
28489
28533
|
init_init_status();
|
|
28534
|
+
init_sandbox_reg_tokens();
|
|
28535
|
+
init_abuse_throttle();
|
|
28490
28536
|
|
|
28491
28537
|
// src/projects/forget.ts
|
|
28492
28538
|
init_legacy_cred_bundles();
|
|
@@ -28675,6 +28721,7 @@ function nodeUnavailable(capability, err) {
|
|
|
28675
28721
|
function taskState(row) {
|
|
28676
28722
|
return { ...row, revision: String(row.updatedAt) };
|
|
28677
28723
|
}
|
|
28724
|
+
var WORKER_REG_TOKEN_MINTS = createAbuseThrottle({ windowMs: 60 * 6e4, max: 30 });
|
|
28678
28725
|
function createWriteModel(db, log, hooks = {}) {
|
|
28679
28726
|
async function readTaskRow(projectId, key) {
|
|
28680
28727
|
const row = await db.get(
|
|
@@ -29357,6 +29404,16 @@ function createWriteModel(db, log, hooks = {}) {
|
|
|
29357
29404
|
log.info({ projectId, jobId, kind }, kind === "backfill" ? "backfill job enqueued" : "index job enqueued");
|
|
29358
29405
|
return ok({ jobId });
|
|
29359
29406
|
},
|
|
29407
|
+
async issueWorkerRegistrationToken({ owner }) {
|
|
29408
|
+
if (owner.email === null) return { ok: false, refusal: "unprocessable", reason: "account_has_no_email" };
|
|
29409
|
+
if (!WORKER_REG_TOKEN_MINTS.take(`email:${owner.email}`)) {
|
|
29410
|
+
log.warn({ email: owner.email }, "ephemeral sandbox reg token mint throttled");
|
|
29411
|
+
return { ok: false, refusal: "rate_limited" };
|
|
29412
|
+
}
|
|
29413
|
+
const issued = await issueEphemeralSandboxRegToken(db, owner.email);
|
|
29414
|
+
log.info({ email: owner.email }, "ephemeral sandbox reg token issued for a client");
|
|
29415
|
+
return ok(issued);
|
|
29416
|
+
},
|
|
29360
29417
|
async reportInitStatus({ projectId, phase, error }) {
|
|
29361
29418
|
const recorded = await advanceInitPhase(db, projectId, phase);
|
|
29362
29419
|
if (error !== null) {
|
package/hub/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ var PLATFORM_VERSION;
|
|
|
13
13
|
var init_version_generated = __esm({
|
|
14
14
|
"../packages/shared/dist/version.generated.js"() {
|
|
15
15
|
"use strict";
|
|
16
|
-
PLATFORM_VERSION = "0.14.1-dev.
|
|
16
|
+
PLATFORM_VERSION = "0.14.1-dev.423.ga49d1f61";
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -31,8 +31,8 @@ var init_version = __esm({
|
|
|
31
31
|
"../packages/shared/dist/version.js"() {
|
|
32
32
|
"use strict";
|
|
33
33
|
init_version_generated();
|
|
34
|
-
PROTOCOL_VERSION =
|
|
35
|
-
MIN_SUPPORTED_PROTOCOL =
|
|
34
|
+
PROTOCOL_VERSION = 5;
|
|
35
|
+
MIN_SUPPORTED_PROTOCOL = 5;
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -2960,21 +2960,14 @@ var init_duration = __esm({
|
|
|
2960
2960
|
});
|
|
2961
2961
|
|
|
2962
2962
|
// ../packages/shared/dist/node-handshake.js
|
|
2963
|
-
function
|
|
2964
|
-
const found = [];
|
|
2963
|
+
function readNodeSocketSecret(headers) {
|
|
2965
2964
|
const raw = headers["authorization"];
|
|
2966
2965
|
const header = Array.isArray(raw) ? raw[0] : raw;
|
|
2967
|
-
if (typeof header
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
}
|
|
2973
|
-
const fromQuery = query?.token;
|
|
2974
|
-
if (typeof fromQuery === "string" && fromQuery !== "") {
|
|
2975
|
-
found.push({ token: fromQuery, source: "query" });
|
|
2976
|
-
}
|
|
2977
|
-
return found;
|
|
2966
|
+
if (typeof header !== "string")
|
|
2967
|
+
return null;
|
|
2968
|
+
const match = /^Bearer\s+(.+)$/i.exec(header.trim());
|
|
2969
|
+
const token = match?.[1]?.trim();
|
|
2970
|
+
return token !== void 0 && token !== "" ? token : null;
|
|
2978
2971
|
}
|
|
2979
2972
|
function decideNodeHandshake(frame, hub, recognizedCapabilities, authenticatedAs) {
|
|
2980
2973
|
const handshake = parseNodeHandshake(frame);
|
|
@@ -8716,7 +8709,12 @@ var init_route_definition = __esm({
|
|
|
8716
8709
|
"bootstrap.request",
|
|
8717
8710
|
"bootstrap.confirm",
|
|
8718
8711
|
"bootstrap.authorizeEmail",
|
|
8719
|
-
"bootstrap.authorizeConfirm"
|
|
8712
|
+
"bootstrap.authorizeConfirm",
|
|
8713
|
+
// DEV-927: a node registers with a registration token in the body — the
|
|
8714
|
+
// token IS the credential, and there is no node identity yet to bear it.
|
|
8715
|
+
"node.sandbox.register",
|
|
8716
|
+
"node.farm.register",
|
|
8717
|
+
"node.runner.register"
|
|
8720
8718
|
];
|
|
8721
8719
|
}
|
|
8722
8720
|
});
|
|
@@ -12239,6 +12237,8 @@ function createCommandOperations(deps) {
|
|
|
12239
12237
|
throw errors.unprocessable({ details: { reason: outcome.reason } });
|
|
12240
12238
|
case "not_found":
|
|
12241
12239
|
throw errors.notFound();
|
|
12240
|
+
case "rate_limited":
|
|
12241
|
+
throw errors.rateLimited();
|
|
12242
12242
|
}
|
|
12243
12243
|
}
|
|
12244
12244
|
function accountActorOf(actor) {
|
|
@@ -12828,6 +12828,14 @@ function createCommandOperations(deps) {
|
|
|
12828
12828
|
return { value: run.result, replayed: run.replayed };
|
|
12829
12829
|
});
|
|
12830
12830
|
},
|
|
12831
|
+
async issueWorkerRegistrationToken(actor) {
|
|
12832
|
+
const subjectId = actor.principal.subjectId;
|
|
12833
|
+
return await audited(actor, { operationId: "worker.issueRegistrationToken", aggregateType: "account", aggregateId: subjectId, projectId: null }, async (policy) => {
|
|
12834
|
+
await require2(actor, "account.manage", { type: "account" }, policy);
|
|
12835
|
+
const issued = unwrap(await write2.issueWorkerRegistrationToken({ owner: accountActorOf(actor) }));
|
|
12836
|
+
return { value: { secret: issued.secret, expiresAt: toTimestamp(issued.expiresAt) }, replayed: false };
|
|
12837
|
+
});
|
|
12838
|
+
},
|
|
12831
12839
|
async reportInitStatus(actor, command) {
|
|
12832
12840
|
const { projectId, phase } = command;
|
|
12833
12841
|
return await audited(actor, { operationId: "project.reportInitStatus", aggregateType: "project", aggregateId: projectId, projectId, data: { phase, failed: command.error !== void 0 } }, async (policy) => {
|
|
@@ -13125,8 +13133,8 @@ var init_cutover_preflight = __esm({
|
|
|
13125
13133
|
SECRET_CONSUMERS = [
|
|
13126
13134
|
{ consumer: "agent credential in the job bundle (Claude / Codex)", purpose: "agent_execution", implemented: true, note: "issued per job from the project's auth mode" },
|
|
13127
13135
|
{ consumer: "repository clone tokens in the job bundle (GitHub / GitLab / Bitbucket)", purpose: "repository_read", implemented: true, note: "one grant for each host the job clones" },
|
|
13128
|
-
{ consumer: "Context Repo push of the delivery from the SANDBOX (contextWrite)", purpose: "context_write", implemented:
|
|
13129
|
-
{ consumer: "mirror clone token in the job bundle (mirrorWrite, read use)", purpose: "repository_read", implemented:
|
|
13136
|
+
{ consumer: "Context Repo push of the delivery from the SANDBOX (contextWrite)", purpose: "context_write", implemented: true, note: "the SANDBOX publishes the delivery over the host-local relay and the holder of the materializer role pushes it; no write credential on the SANDBOX" },
|
|
13137
|
+
{ consumer: "mirror clone token in the job bundle (mirrorWrite, read use)", purpose: "repository_read", implemented: true, note: "a read grant under the mirror's host, issued by the holder of the role with the other repository grants" },
|
|
13130
13138
|
{ consumer: "tracker writes (YouTrack) \u2014 CLI-side, never on the SANDBOX", purpose: "tracker_write", implemented: true, note: "nothing to move: the holder of the role already writes" },
|
|
13131
13139
|
{ consumer: "named project secrets in custom-action runs \u2014 CLI-side", purpose: "custom_action", implemented: true, note: "nothing to move until a tool-run executes on the SANDBOX" }
|
|
13132
13140
|
];
|
|
@@ -25805,10 +25813,16 @@ async function deleteSandboxRegistration(db, sandboxId) {
|
|
|
25805
25813
|
async function sandboxRoutes(app, deps) {
|
|
25806
25814
|
const { db, config } = deps;
|
|
25807
25815
|
const registerByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 30 });
|
|
25808
|
-
const regTokenMintByEmail = createAbuseThrottle({ windowMs: 60 * 6e4, max: 30 });
|
|
25809
25816
|
app.post(
|
|
25810
|
-
"/sandbox/register",
|
|
25817
|
+
"/node/v1/sandbox/register",
|
|
25811
25818
|
{
|
|
25819
|
+
...routeMeta({
|
|
25820
|
+
surface: "node",
|
|
25821
|
+
operationId: "node.sandbox.register",
|
|
25822
|
+
authPolicy: "public",
|
|
25823
|
+
stability: "preview",
|
|
25824
|
+
summary: "Register a SANDBOX with a registration token"
|
|
25825
|
+
}),
|
|
25812
25826
|
schema: {
|
|
25813
25827
|
body: {
|
|
25814
25828
|
type: "object",
|
|
@@ -25849,14 +25863,29 @@ async function sandboxRoutes(app, deps) {
|
|
|
25849
25863
|
return { sandboxId: id, token };
|
|
25850
25864
|
}
|
|
25851
25865
|
);
|
|
25852
|
-
app.get("/sandbox/me",
|
|
25866
|
+
app.get("/node/v1/sandbox/me", routeMeta({
|
|
25867
|
+
surface: "node",
|
|
25868
|
+
operationId: "node.sandbox.me",
|
|
25869
|
+
authPolicy: "node_actor",
|
|
25870
|
+
resourcePolicy: "node.connect",
|
|
25871
|
+
stability: "preview",
|
|
25872
|
+
summary: "Is this SANDBOX registration still known"
|
|
25873
|
+
}), async (req, reply) => {
|
|
25853
25874
|
const sandbox = await authenticateSandboxByBearer(db, req.headers.authorization);
|
|
25854
25875
|
if (!sandbox) return reply.status(401).send({ error: "unauthorized" });
|
|
25855
25876
|
return { sandboxId: sandbox.id, name: sandbox.name };
|
|
25856
25877
|
});
|
|
25857
25878
|
app.post(
|
|
25858
|
-
"/sandbox/agent-auth",
|
|
25879
|
+
"/node/v1/sandbox/agent-auth",
|
|
25859
25880
|
{
|
|
25881
|
+
...routeMeta({
|
|
25882
|
+
surface: "node",
|
|
25883
|
+
operationId: "node.sandbox.agentAuth",
|
|
25884
|
+
authPolicy: "node_actor",
|
|
25885
|
+
resourcePolicy: "node.connect",
|
|
25886
|
+
stability: "preview",
|
|
25887
|
+
summary: "Report the host's agent login and ask whether to probe it"
|
|
25888
|
+
}),
|
|
25860
25889
|
schema: {
|
|
25861
25890
|
body: {
|
|
25862
25891
|
type: "object",
|
|
@@ -25892,8 +25921,16 @@ async function sandboxRoutes(app, deps) {
|
|
|
25892
25921
|
}
|
|
25893
25922
|
);
|
|
25894
25923
|
app.post(
|
|
25895
|
-
"/sandbox/agent-auth/result",
|
|
25924
|
+
"/node/v1/sandbox/agent-auth/result",
|
|
25896
25925
|
{
|
|
25926
|
+
...routeMeta({
|
|
25927
|
+
surface: "node",
|
|
25928
|
+
operationId: "node.sandbox.agentAuthResult",
|
|
25929
|
+
authPolicy: "node_actor",
|
|
25930
|
+
resourcePolicy: "node.connect",
|
|
25931
|
+
stability: "preview",
|
|
25932
|
+
summary: "Report the verdict of an agent login probe"
|
|
25933
|
+
}),
|
|
25897
25934
|
schema: {
|
|
25898
25935
|
body: {
|
|
25899
25936
|
type: "object",
|
|
@@ -25943,19 +25980,6 @@ async function sandboxRoutes(app, deps) {
|
|
|
25943
25980
|
app.log.info({ email: session.email, revoked }, "sandbox reg token revoked");
|
|
25944
25981
|
return { revoked };
|
|
25945
25982
|
});
|
|
25946
|
-
app.post("/sandbox/reg-token/cli", async (req, reply) => {
|
|
25947
|
-
const auth = await resolveCliAuth(db, config, req.headers.authorization);
|
|
25948
|
-
if (!auth) {
|
|
25949
|
-
return reply.status(401).send({ error: "bad_cli_token" });
|
|
25950
|
-
}
|
|
25951
|
-
if (!regTokenMintByEmail.take(`email:${auth.email}`)) {
|
|
25952
|
-
app.log.warn({ email: auth.email }, "ephemeral sandbox reg token mint throttled");
|
|
25953
|
-
return reply.status(429).send({ error: "too_many_requests" });
|
|
25954
|
-
}
|
|
25955
|
-
const { secret, expiresAt } = await issueEphemeralSandboxRegToken(db, auth.email);
|
|
25956
|
-
app.log.info({ email: auth.email }, "ephemeral sandbox reg token issued for CLI");
|
|
25957
|
-
return { secret, expiresAt };
|
|
25958
|
-
});
|
|
25959
25983
|
app.delete(
|
|
25960
25984
|
"/sandbox/:sandboxId",
|
|
25961
25985
|
async (req, reply) => {
|
|
@@ -25992,11 +26016,11 @@ var init_sandbox2 = __esm({
|
|
|
25992
26016
|
init_sealedbox();
|
|
25993
26017
|
init_abuse_throttle();
|
|
25994
26018
|
init_auth();
|
|
25995
|
-
init_cli_auth();
|
|
25996
26019
|
init_sandbox_reg_tokens();
|
|
25997
26020
|
init_users();
|
|
25998
26021
|
init_agent_auth_probe();
|
|
25999
26022
|
init_ws();
|
|
26023
|
+
init_route_registry();
|
|
26000
26024
|
}
|
|
26001
26025
|
});
|
|
26002
26026
|
|
|
@@ -26060,8 +26084,15 @@ async function farmRoutes(app, deps) {
|
|
|
26060
26084
|
const { db, config } = deps;
|
|
26061
26085
|
const registerByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 10 });
|
|
26062
26086
|
app.post(
|
|
26063
|
-
"/farm/register",
|
|
26087
|
+
"/node/v1/farm/register",
|
|
26064
26088
|
{
|
|
26089
|
+
...routeMeta({
|
|
26090
|
+
surface: "node",
|
|
26091
|
+
operationId: "node.farm.register",
|
|
26092
|
+
authPolicy: "public",
|
|
26093
|
+
stability: "preview",
|
|
26094
|
+
summary: "Register a FARM with the operator's registration token"
|
|
26095
|
+
}),
|
|
26065
26096
|
schema: {
|
|
26066
26097
|
body: {
|
|
26067
26098
|
type: "object",
|
|
@@ -26140,6 +26171,7 @@ var init_farm2 = __esm({
|
|
|
26140
26171
|
init_sealedbox();
|
|
26141
26172
|
init_abuse_throttle();
|
|
26142
26173
|
init_ws();
|
|
26174
|
+
init_route_registry();
|
|
26143
26175
|
}
|
|
26144
26176
|
});
|
|
26145
26177
|
|
|
@@ -26241,8 +26273,15 @@ async function runnerRoutes(app, deps) {
|
|
|
26241
26273
|
const { db, config } = deps;
|
|
26242
26274
|
const registerByIp = createAbuseThrottle({ windowMs: 15 * 6e4, max: 10 });
|
|
26243
26275
|
app.post(
|
|
26244
|
-
"/runner/register",
|
|
26276
|
+
"/node/v1/runner/register",
|
|
26245
26277
|
{
|
|
26278
|
+
...routeMeta({
|
|
26279
|
+
surface: "node",
|
|
26280
|
+
operationId: "node.runner.register",
|
|
26281
|
+
authPolicy: "public",
|
|
26282
|
+
stability: "preview",
|
|
26283
|
+
summary: "Register a RUNNER host with a personal registration token"
|
|
26284
|
+
}),
|
|
26246
26285
|
schema: {
|
|
26247
26286
|
body: {
|
|
26248
26287
|
type: "object",
|
|
@@ -26345,6 +26384,7 @@ var init_runner2 = __esm({
|
|
|
26345
26384
|
init_users();
|
|
26346
26385
|
init_runner_reg_tokens();
|
|
26347
26386
|
init_ws();
|
|
26387
|
+
init_route_registry();
|
|
26348
26388
|
}
|
|
26349
26389
|
});
|
|
26350
26390
|
|
|
@@ -27524,23 +27564,11 @@ function installSocketKeepalive(socket, periodMs) {
|
|
|
27524
27564
|
}
|
|
27525
27565
|
async function wsRoutes(app, deps) {
|
|
27526
27566
|
const { db, config } = deps;
|
|
27527
|
-
async function authenticateSocket(req,
|
|
27528
|
-
|
|
27529
|
-
|
|
27530
|
-
|
|
27531
|
-
|
|
27532
|
-
return resolved;
|
|
27533
|
-
}
|
|
27534
|
-
return null;
|
|
27535
|
-
}
|
|
27536
|
-
const warnedComponents = /* @__PURE__ */ new Set();
|
|
27537
|
-
function warnAboutQueryCredential(component, remote) {
|
|
27538
|
-
if (warnedComponents.has(component)) return;
|
|
27539
|
-
warnedComponents.add(component);
|
|
27540
|
-
app.log.warn(
|
|
27541
|
-
{ component, remote },
|
|
27542
|
-
"node socket authenticated from the query string \u2014 the credential lands in proxy logs; update the component to send it in the Authorization header (this is logged once)"
|
|
27543
|
-
);
|
|
27567
|
+
async function authenticateSocket(req, resolve4) {
|
|
27568
|
+
const token = readNodeSocketSecret(req.headers);
|
|
27569
|
+
if (token === null) return null;
|
|
27570
|
+
const resolved = await resolve4(token);
|
|
27571
|
+
return resolved === null || resolved === void 0 ? null : resolved;
|
|
27544
27572
|
}
|
|
27545
27573
|
const cliSocket = async (socket, req) => {
|
|
27546
27574
|
let boundProjectId = null;
|
|
@@ -27877,7 +27905,7 @@ async function wsRoutes(app, deps) {
|
|
|
27877
27905
|
app.log.info({ remote: req.ip }, "CLI socket closed (never registered)");
|
|
27878
27906
|
}
|
|
27879
27907
|
});
|
|
27880
|
-
auth = await authenticateSocket(req,
|
|
27908
|
+
auth = await authenticateSocket(req, (token) => authFromSecret(db, config, token));
|
|
27881
27909
|
if (!auth) {
|
|
27882
27910
|
socket.close(1008, "unauthorized");
|
|
27883
27911
|
return;
|
|
@@ -27895,7 +27923,7 @@ async function wsRoutes(app, deps) {
|
|
|
27895
27923
|
void pump();
|
|
27896
27924
|
};
|
|
27897
27925
|
const sandboxSocket = async (socket, req) => {
|
|
27898
|
-
const sandbox = await authenticateSocket(req,
|
|
27926
|
+
const sandbox = await authenticateSocket(req, (token) => findSandboxByToken(db, token));
|
|
27899
27927
|
if (!sandbox) {
|
|
27900
27928
|
socket.close(1008, "unauthorized");
|
|
27901
27929
|
return;
|
|
@@ -28013,7 +28041,7 @@ async function wsRoutes(app, deps) {
|
|
|
28013
28041
|
});
|
|
28014
28042
|
};
|
|
28015
28043
|
const farmSocket = async (socket, req) => {
|
|
28016
|
-
const farm = await authenticateSocket(req,
|
|
28044
|
+
const farm = await authenticateSocket(req, (token) => findFarmByToken(db, token));
|
|
28017
28045
|
if (!farm) {
|
|
28018
28046
|
socket.close(1008, "unauthorized");
|
|
28019
28047
|
return;
|
|
@@ -28105,7 +28133,7 @@ async function wsRoutes(app, deps) {
|
|
|
28105
28133
|
});
|
|
28106
28134
|
};
|
|
28107
28135
|
const runnerSocket = async (socket, req) => {
|
|
28108
|
-
const runner = await authenticateSocket(req,
|
|
28136
|
+
const runner = await authenticateSocket(req, (token) => findRunnerByToken(db, token));
|
|
28109
28137
|
if (!runner) {
|
|
28110
28138
|
socket.close(1008, "unauthorized");
|
|
28111
28139
|
return;
|
|
@@ -28237,10 +28265,12 @@ async function wsRoutes(app, deps) {
|
|
|
28237
28265
|
}
|
|
28238
28266
|
const auth = parseNodeAuth(parsed);
|
|
28239
28267
|
if (auth !== null) {
|
|
28240
|
-
|
|
28268
|
+
const withFrameCredential = { ...req, headers: { ...req.headers, authorization: `Bearer ${auth.token}` } };
|
|
28269
|
+
if (auth.component !== component || !await authenticate(withFrameCredential)) {
|
|
28241
28270
|
refuse("unauthenticated");
|
|
28242
28271
|
return;
|
|
28243
28272
|
}
|
|
28273
|
+
effectiveReq = withFrameCredential;
|
|
28244
28274
|
authenticated = true;
|
|
28245
28275
|
return;
|
|
28246
28276
|
}
|
|
@@ -28277,7 +28307,7 @@ async function wsRoutes(app, deps) {
|
|
|
28277
28307
|
socket.off("message", listener);
|
|
28278
28308
|
socket.pause();
|
|
28279
28309
|
try {
|
|
28280
|
-
await inner(socket,
|
|
28310
|
+
await inner(socket, effectiveReq);
|
|
28281
28311
|
} catch (err) {
|
|
28282
28312
|
app.log.error({ err, component }, "node socket handler failed after handshake");
|
|
28283
28313
|
socket.close(1011, "handler_failed");
|
|
@@ -28288,6 +28318,7 @@ async function wsRoutes(app, deps) {
|
|
|
28288
28318
|
socket.resume();
|
|
28289
28319
|
};
|
|
28290
28320
|
let authenticated = false;
|
|
28321
|
+
let effectiveReq = req;
|
|
28291
28322
|
const ready = authenticate(req).then((ok2) => {
|
|
28292
28323
|
authenticated = ok2;
|
|
28293
28324
|
});
|
|
@@ -28321,7 +28352,6 @@ async function wsRoutes(app, deps) {
|
|
|
28321
28352
|
});
|
|
28322
28353
|
}
|
|
28323
28354
|
}
|
|
28324
|
-
app.get("/cli/connect", { websocket: true }, cliSocket);
|
|
28325
28355
|
app.get(
|
|
28326
28356
|
"/node/v1/cli/connect",
|
|
28327
28357
|
{
|
|
@@ -28335,9 +28365,8 @@ async function wsRoutes(app, deps) {
|
|
|
28335
28365
|
summary: "Control socket of a CLI instance"
|
|
28336
28366
|
})
|
|
28337
28367
|
},
|
|
28338
|
-
withNodeHandshake("cli", async (r) => await authenticateSocket(r,
|
|
28368
|
+
withNodeHandshake("cli", async (r) => await authenticateSocket(r, (token) => authFromSecret(db, config, token)) !== null, cliSocket)
|
|
28339
28369
|
);
|
|
28340
|
-
app.get("/sandbox/connect", { websocket: true }, sandboxSocket);
|
|
28341
28370
|
app.get(
|
|
28342
28371
|
"/node/v1/sandbox/connect",
|
|
28343
28372
|
{
|
|
@@ -28351,9 +28380,8 @@ async function wsRoutes(app, deps) {
|
|
|
28351
28380
|
summary: "Control socket of a SANDBOX instance"
|
|
28352
28381
|
})
|
|
28353
28382
|
},
|
|
28354
|
-
withNodeHandshake("sandbox", async (r) => await authenticateSocket(r,
|
|
28383
|
+
withNodeHandshake("sandbox", async (r) => await authenticateSocket(r, (token) => findSandboxByToken(db, token)) !== null, sandboxSocket)
|
|
28355
28384
|
);
|
|
28356
|
-
app.get("/farm/connect", { websocket: true }, farmSocket);
|
|
28357
28385
|
app.get(
|
|
28358
28386
|
"/node/v1/farm/connect",
|
|
28359
28387
|
{
|
|
@@ -28367,9 +28395,8 @@ async function wsRoutes(app, deps) {
|
|
|
28367
28395
|
summary: "Control socket of a FARM instance"
|
|
28368
28396
|
})
|
|
28369
28397
|
},
|
|
28370
|
-
withNodeHandshake("farm", async (r) => await authenticateSocket(r,
|
|
28398
|
+
withNodeHandshake("farm", async (r) => await authenticateSocket(r, (token) => findFarmByToken(db, token)) !== null, farmSocket)
|
|
28371
28399
|
);
|
|
28372
|
-
app.get("/runner/connect", { websocket: true }, runnerSocket);
|
|
28373
28400
|
app.get(
|
|
28374
28401
|
"/node/v1/runner/connect",
|
|
28375
28402
|
{
|
|
@@ -28383,7 +28410,7 @@ async function wsRoutes(app, deps) {
|
|
|
28383
28410
|
summary: "Control socket of a RUNNER instance"
|
|
28384
28411
|
})
|
|
28385
28412
|
},
|
|
28386
|
-
withNodeHandshake("runner", async (r) => await authenticateSocket(r,
|
|
28413
|
+
withNodeHandshake("runner", async (r) => await authenticateSocket(r, (token) => findRunnerByToken(db, token)) !== null, runnerSocket)
|
|
28387
28414
|
);
|
|
28388
28415
|
}
|
|
28389
28416
|
var agentLoginProviders, deliveryFollowers, projectCliConnections, projectLastSeenAt, projectLastDuplicateRegisterAt, sandboxConnections, farmConnections, runnerConnections, cliMessageHandlers, cliRegisterHandlers, cliDisconnectHandlers, farmMessageHandlers, runnerMessageHandlers, providerMessageHandlers, RECOGNIZED_NODE_CAPABILITIES, NODE_HANDSHAKE_BACKLOG, NODE_HANDSHAKE_TIMEOUT_MS;
|
|
@@ -28978,9 +29005,22 @@ var RETIRED_SOCKET_PATHS = [
|
|
|
28978
29005
|
"/farm/connect",
|
|
28979
29006
|
"/runner/connect"
|
|
28980
29007
|
];
|
|
29008
|
+
var RETIRED_NODE_PATHS = [
|
|
29009
|
+
"/jobs/claim",
|
|
29010
|
+
"/jobs/heartbeat",
|
|
29011
|
+
"/jobs/release",
|
|
29012
|
+
"/jobs/result",
|
|
29013
|
+
"/sandbox/register",
|
|
29014
|
+
"/sandbox/me",
|
|
29015
|
+
"/sandbox/agent-auth",
|
|
29016
|
+
"/sandbox/agent-auth/result",
|
|
29017
|
+
"/sandbox/reg-token/cli",
|
|
29018
|
+
"/farm/register",
|
|
29019
|
+
"/runner/register"
|
|
29020
|
+
];
|
|
28981
29021
|
function isRetiredClientPath(url, registeredPaths = []) {
|
|
28982
29022
|
const path = url.split("?")[0].split("#")[0];
|
|
28983
|
-
if (RETIRED_SOCKET_PATHS.includes(path)) return !registeredPaths.includes(path);
|
|
29023
|
+
if (RETIRED_SOCKET_PATHS.includes(path) || RETIRED_NODE_PATHS.includes(path)) return !registeredPaths.includes(path);
|
|
28984
29024
|
const retired = RETIRED_ROOTS.find((root) => path === root || path.startsWith(`${root}/`));
|
|
28985
29025
|
if (retired === void 0) return false;
|
|
28986
29026
|
return !registeredPaths.some((p) => p === retired || p.startsWith(`${retired}/`));
|
|
@@ -31024,6 +31064,28 @@ async function apiV1MutationRoutes(app, deps) {
|
|
|
31024
31064
|
}
|
|
31025
31065
|
}
|
|
31026
31066
|
);
|
|
31067
|
+
app.post(
|
|
31068
|
+
"/workers/registration-token",
|
|
31069
|
+
routeMeta({
|
|
31070
|
+
surface: "application",
|
|
31071
|
+
operationId: "worker.issueRegistrationToken",
|
|
31072
|
+
authPolicy: "application_actor",
|
|
31073
|
+
resourcePolicy: "account.manage",
|
|
31074
|
+
stability: "preview",
|
|
31075
|
+
summary: "Mint a single-use registration token for a worker of this account"
|
|
31076
|
+
}),
|
|
31077
|
+
async (req, reply) => {
|
|
31078
|
+
const requestId = requestIdOf(req);
|
|
31079
|
+
try {
|
|
31080
|
+
const actor = await actorOr401({ db, config }, req, reply, requestId);
|
|
31081
|
+
if (!actor) return reply;
|
|
31082
|
+
const issued = await commands.issueWorkerRegistrationToken(actor);
|
|
31083
|
+
return reply.status(201).send(issued);
|
|
31084
|
+
} catch (err) {
|
|
31085
|
+
return sendError(reply, err, requestId);
|
|
31086
|
+
}
|
|
31087
|
+
}
|
|
31088
|
+
);
|
|
31027
31089
|
app.post(
|
|
31028
31090
|
"/projects/:projectId/init-status",
|
|
31029
31091
|
routeMeta({
|
|
@@ -34145,6 +34207,8 @@ function requestGhRunnersDelete(runnerId, projectId, sealedBundle, timeoutMs) {
|
|
|
34145
34207
|
init_farm2();
|
|
34146
34208
|
init_runner2();
|
|
34147
34209
|
init_init_status();
|
|
34210
|
+
init_sandbox_reg_tokens();
|
|
34211
|
+
init_abuse_throttle();
|
|
34148
34212
|
|
|
34149
34213
|
// src/projects/forget.ts
|
|
34150
34214
|
init_legacy_cred_bundles();
|
|
@@ -34426,6 +34490,7 @@ function nodeUnavailable(capability, err) {
|
|
|
34426
34490
|
function taskState(row) {
|
|
34427
34491
|
return { ...row, revision: String(row.updatedAt) };
|
|
34428
34492
|
}
|
|
34493
|
+
var WORKER_REG_TOKEN_MINTS = createAbuseThrottle({ windowMs: 60 * 6e4, max: 30 });
|
|
34429
34494
|
function createWriteModel(db, log, hooks = {}) {
|
|
34430
34495
|
async function readTaskRow(projectId, key) {
|
|
34431
34496
|
const row = await db.get(
|
|
@@ -35108,6 +35173,16 @@ function createWriteModel(db, log, hooks = {}) {
|
|
|
35108
35173
|
log.info({ projectId, jobId, kind }, kind === "backfill" ? "backfill job enqueued" : "index job enqueued");
|
|
35109
35174
|
return ok({ jobId });
|
|
35110
35175
|
},
|
|
35176
|
+
async issueWorkerRegistrationToken({ owner }) {
|
|
35177
|
+
if (owner.email === null) return { ok: false, refusal: "unprocessable", reason: "account_has_no_email" };
|
|
35178
|
+
if (!WORKER_REG_TOKEN_MINTS.take(`email:${owner.email}`)) {
|
|
35179
|
+
log.warn({ email: owner.email }, "ephemeral sandbox reg token mint throttled");
|
|
35180
|
+
return { ok: false, refusal: "rate_limited" };
|
|
35181
|
+
}
|
|
35182
|
+
const issued = await issueEphemeralSandboxRegToken(db, owner.email);
|
|
35183
|
+
log.info({ email: owner.email }, "ephemeral sandbox reg token issued for a client");
|
|
35184
|
+
return ok(issued);
|
|
35185
|
+
},
|
|
35111
35186
|
async reportInitStatus({ projectId, phase, error }) {
|
|
35112
35187
|
const recorded = await advanceInitPhase(db, projectId, phase);
|
|
35113
35188
|
if (error !== null) {
|
|
@@ -39522,10 +39597,18 @@ async function negotiateJobCredentials(db, spec, workerId, log, issuer) {
|
|
|
39522
39597
|
// src/routes/jobs.ts
|
|
39523
39598
|
init_sandbox2();
|
|
39524
39599
|
init_ws();
|
|
39600
|
+
init_route_registry();
|
|
39525
39601
|
async function jobsRoutes(app, deps) {
|
|
39526
39602
|
const { db, config } = deps;
|
|
39527
39603
|
const notifyDeps = deps.notify;
|
|
39528
|
-
app.post("/jobs/claim",
|
|
39604
|
+
app.post("/node/v1/jobs/claim", routeMeta({
|
|
39605
|
+
surface: "node",
|
|
39606
|
+
operationId: "node.jobs.claim",
|
|
39607
|
+
authPolicy: "node_actor",
|
|
39608
|
+
resourcePolicy: "node.connect",
|
|
39609
|
+
stability: "preview",
|
|
39610
|
+
summary: "Claim the next job for this SANDBOX (long poll)"
|
|
39611
|
+
}), async (req, reply) => {
|
|
39529
39612
|
const sandbox = await authenticateSandboxByBearer(db, req.headers.authorization);
|
|
39530
39613
|
if (!sandbox) {
|
|
39531
39614
|
return reply.status(401).send({ error: "unauthorized" });
|
|
@@ -39579,8 +39662,16 @@ async function jobsRoutes(app, deps) {
|
|
|
39579
39662
|
}
|
|
39580
39663
|
});
|
|
39581
39664
|
app.post(
|
|
39582
|
-
"/jobs/heartbeat",
|
|
39665
|
+
"/node/v1/jobs/heartbeat",
|
|
39583
39666
|
{
|
|
39667
|
+
...routeMeta({
|
|
39668
|
+
surface: "node",
|
|
39669
|
+
operationId: "node.jobs.heartbeat",
|
|
39670
|
+
authPolicy: "node_actor",
|
|
39671
|
+
resourcePolicy: "node.connect",
|
|
39672
|
+
stability: "preview",
|
|
39673
|
+
summary: "Keep a claimed job's lease alive"
|
|
39674
|
+
}),
|
|
39584
39675
|
schema: {
|
|
39585
39676
|
body: {
|
|
39586
39677
|
type: "object",
|
|
@@ -39642,8 +39733,16 @@ async function jobsRoutes(app, deps) {
|
|
|
39642
39733
|
}
|
|
39643
39734
|
);
|
|
39644
39735
|
app.post(
|
|
39645
|
-
"/jobs/release",
|
|
39736
|
+
"/node/v1/jobs/release",
|
|
39646
39737
|
{
|
|
39738
|
+
...routeMeta({
|
|
39739
|
+
surface: "node",
|
|
39740
|
+
operationId: "node.jobs.release",
|
|
39741
|
+
authPolicy: "node_actor",
|
|
39742
|
+
resourcePolicy: "node.connect",
|
|
39743
|
+
stability: "preview",
|
|
39744
|
+
summary: "Hand a claimed job back to the queue"
|
|
39745
|
+
}),
|
|
39647
39746
|
schema: {
|
|
39648
39747
|
body: {
|
|
39649
39748
|
type: "object",
|
|
@@ -39679,8 +39778,16 @@ async function jobsRoutes(app, deps) {
|
|
|
39679
39778
|
}
|
|
39680
39779
|
);
|
|
39681
39780
|
app.post(
|
|
39682
|
-
"/jobs/result",
|
|
39781
|
+
"/node/v1/jobs/result",
|
|
39683
39782
|
{
|
|
39783
|
+
...routeMeta({
|
|
39784
|
+
surface: "node",
|
|
39785
|
+
operationId: "node.jobs.result",
|
|
39786
|
+
authPolicy: "node_actor",
|
|
39787
|
+
resourcePolicy: "node.connect",
|
|
39788
|
+
stability: "preview",
|
|
39789
|
+
summary: "Report a finished job"
|
|
39790
|
+
}),
|
|
39684
39791
|
schema: {
|
|
39685
39792
|
body: {
|
|
39686
39793
|
type: "object",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-partner/codepipe-hub",
|
|
3
|
-
"version": "0.14.1-dev.
|
|
3
|
+
"version": "0.14.1-dev.423.ga49d1f61",
|
|
4
4
|
"description": "The CodePipe hub, packaged for one machine: the CLI installs it on demand for the local profile.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./hub/dist/index.js",
|