@
fix(security): sovereign-audit hardening pass — RCE, multi-tenant, reliability Reasoning-based audit fixes (all verified by typecheck, attack paths re-traced): - build-time RCE: validate spec.dependencies to npm-registry semver only (no git/url/file specifiers) + --ignore-scripts in runner Dockerfile. - container hardening fail-CLOSED: harden unless RUNNER_DISABLE_HARDENING=1, no longer gated on a fragile NODE_ENV string compare. - secret env keys validated (UPPER_SNAKE, reject NODE_*/PATH/LD_*). - cross-org image-tag collision: qualify tag with serverId. - /iterate now enforces suspension + daily-build limits like /servers. - preview SSE: clear keepalive in finally + on client close (timer/FD leak). - SMS OTP: atomic attempt counter (lt(attempts,MAX) in UPDATE) — brute-force race. - getSession orders membership by createdAt (deterministic primary org). - template scopes aggregated from real tool scopes (was hardcoded mcp:read). - template category filter pushed into WHERE (was applied after LIMIT). - support admin reply/status: 404 on unknown ticket; status change now audited. - build worker: queue defaultJobOptions, docker build/run/stop timeouts, old-container teardown in finally (no orphan on post-deploy DB failure). - nginx: HSTS, X-Frame-Options DENY, nosniff, Referrer-Policy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -184,30 +184,34 @@ export const worker = new Worker<JobData>(
|
||||
`Container ${handle.containerId.slice(0, 12)} running at ${handle.publicUrl}`,
|
||||
);
|
||||
|
||||
await db
|
||||
.update(builds)
|
||||
.set({ status: 'success', finishedAt: new Date() })
|
||||
.where(eq(builds.id, buildId));
|
||||
await db
|
||||
.update(mcpServers)
|
||||
.set({
|
||||
status: 'live',
|
||||
currentVersion: version,
|
||||
publicUrl: handle.publicUrl,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(mcpServers.id, serverId));
|
||||
|
||||
// Rolling deploy: the new container is live — now retire the previous one.
|
||||
// Without this every iterate would leave an orphan holding a host port.
|
||||
if (oldContainerId && oldContainerId !== handle.containerId) {
|
||||
const stopped = await stopContainer(oldContainerId);
|
||||
await log(
|
||||
stopped.ok ? 'info' : 'warn',
|
||||
stopped.ok
|
||||
? `Retired previous container ${oldContainerId.slice(0, 12)}`
|
||||
: `Could not stop previous container ${oldContainerId.slice(0, 12)}: ${stopped.detail}`,
|
||||
);
|
||||
try {
|
||||
await db
|
||||
.update(builds)
|
||||
.set({ status: 'success', finishedAt: new Date() })
|
||||
.where(eq(builds.id, buildId));
|
||||
await db
|
||||
.update(mcpServers)
|
||||
.set({
|
||||
status: 'live',
|
||||
currentVersion: version,
|
||||
publicUrl: handle.publicUrl,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(mcpServers.id, serverId));
|
||||
} finally {
|
||||
// Rolling deploy: retire the previous container even if the success DB
|
||||
// writes above threw — otherwise a DB hiccup after a healthy deploy
|
||||
// leaves the old container orphaned, holding its host port. The new
|
||||
// container is already live and its id is persisted in deployContainer. (GEN-007)
|
||||
if (oldContainerId && oldContainerId !== handle.containerId) {
|
||||
const stopped = await stopContainer(oldContainerId);
|
||||
await log(
|
||||
stopped.ok ? 'info' : 'warn',
|
||||
stopped.ok
|
||||
? `Retired previous container ${oldContainerId.slice(0, 12)}`
|
||||
: `Could not stop previous container ${oldContainerId.slice(0, 12)}: ${stopped.detail}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await emitStatus(buildId, 'success');
|
||||
|
||||
Reference in New Issue
Block a user