fix: use sha256 descriptor digest and keep operator errors fully sanitized

This commit is contained in:
2026-08-11 19:24:15 +02:00
parent 929f7dcc5e
commit 82b5453c88
4 changed files with 8 additions and 10 deletions
+3 -4
View File
@@ -41,10 +41,9 @@ function boundedJson(result: WorkspaceOperationResult): string {
return encoded; return encoded;
} }
function sanitizeStderr(error: unknown): string { function sanitizeStderr(_error: unknown): string {
const detail = error instanceof Error ? error.message : String(error); // Never return raw exception text: it may embed endpoints, tokens, or SQL.
const safe = detail.replace(/[\r\n]+/g, " ").slice(0, 512); return "workspace maintenance failed\n";
return safe.length > 0 ? `workspace maintenance failed: ${safe}\n` : "workspace maintenance failed\n";
} }
function parseRequest(command: string, stdin: string): Record<string, unknown> { function parseRequest(command: string, stdin: string): Record<string, unknown> {
@@ -129,8 +129,7 @@ export class WorkspacePreprocessingService {
{ kind: "runtime_config", digest: runtime.configLease.configDigest }, { kind: "runtime_config", digest: runtime.configLease.configDigest },
], ],
}); });
} catch (error) { } catch {
const detail = error instanceof Error ? error.message : String(error);
return { return {
schemaVersion: 1, schemaVersion: 1,
status: "failed", status: "failed",
@@ -140,7 +139,6 @@ export class WorkspacePreprocessingService {
descriptorBlob: "", descriptorBlob: "",
operation: "inspect", operation: "inspect",
completedStages: [], completedStages: [],
warnings: detail.length > 0 ? [detail.slice(0, 512)] : undefined,
}; };
} }
} }
@@ -352,7 +352,8 @@ export async function renderActiveWorkspaceRuntime(options: {
const repository = new GitWorkspaceRepository(options.registryConfig); const repository = new GitWorkspaceRepository(options.registryConfig);
await repository.ensureLayout(); await repository.ensureLayout();
const snapshotPath = options.registry.snapshotPath(revision.commit, revision.id); const snapshotPath = options.registry.snapshotPath(revision.commit, revision.id);
const workspace = parseWorkspaceYaml(await readFileAsync(snapshotPath, "utf8")); const descriptorSource = await readFileAsync(snapshotPath, "utf8");
const workspace = parseWorkspaceYaml(descriptorSource);
const rendered = renderWorkspaceRuntimeFromWorkspace({ const rendered = renderWorkspaceRuntimeFromWorkspace({
workspace, workspace,
workspaceId: revision.id, workspaceId: revision.id,
@@ -367,7 +368,7 @@ export async function renderActiveWorkspaceRuntime(options: {
return { return {
...rendered, ...rendered,
snapshotPath, snapshotPath,
descriptorBlob: revision.blob, descriptorBlob: `sha256:${createHash("sha256").update(descriptorSource).digest("hex")}`,
catalogBlob: (await repository.catalogBlob(revision.commit)).trim(), catalogBlob: (await repository.catalogBlob(revision.commit)).trim(),
}; };
} }
@@ -159,7 +159,7 @@ test("active workspace rendering is byte-identical to direct snapshot rendering"
expect(active.renderedConfig).toBe(direct.renderedConfig); expect(active.renderedConfig).toBe(direct.renderedConfig);
expect(active.workspaceRevision).toBe(f.revision.commit); expect(active.workspaceRevision).toBe(f.revision.commit);
expect(active.descriptorBlob).toBe(f.revision.blob); expect(active.descriptorBlob).toMatch(/^sha256:[0-9a-f]{64}$/);
expect(active.catalogBlob).toMatch(/^[0-9a-f]{40}$/); expect(active.catalogBlob).toMatch(/^[0-9a-f]{40}$/);
}); });