Verify runtime ownership before stop

This commit is contained in:
thePR0M3TH3AN committed 2026-09-27 19:23:09 -04:00
1 parent 6e79960721
commit 325ed613be
5 files changed
+66 -5

No files matched your search

+24 -3
View File
@@ -1,6 +1,6 @@
import { randomUUID } from 'node:crypto';
import { accessSync, constants, existsSync } from 'node:fs';
import { delimiter, join } from 'node:path';
import { delimiter, join, resolve } from 'node:path';
import { TorchError } from '../kernel/errors.mjs';
import { unsupportedCapability, validateRuntimeAdapter } from './runtime.mjs';
@@ -161,10 +161,31 @@ export class ClaudeRuntimeAdapter {
return session ?? { sessionId: id, status: 'offline' };
}
stopSession({ runtimeSessionId } = {}) {
stopSession({ runtimeSessionId, worktree } = {}) {
const id = text(runtimeSessionId, 'runtimeSessionId');
const expectedWorktree = resolve(text(worktree, 'worktree'));
const session = this.getStatus({ runtimeSessionId: id });
if (session.status === 'offline') {
return { runtimeSessionId: id, stopped: true, alreadyOffline: true };
}
const reportedWorktree = session.cwd ?? session.worktree
?? session.workingDirectory ?? session.working_directory;
if (typeof reportedWorktree !== 'string' || !reportedWorktree.trim()) {
throw new TorchError('Claude session stop refused because its working directory is unavailable', {
code: 'RUNTIME_OWNERSHIP_UNVERIFIED', details: { runtimeSessionId: id, expectedWorktree },
});
}
if (resolve(reportedWorktree) !== expectedWorktree) {
throw new TorchError('Claude session stop refused because its working directory does not match the managed identity', {
code: 'RUNTIME_OWNERSHIP_MISMATCH',
details: { runtimeSessionId: id, expectedWorktree, reportedWorktree: resolve(reportedWorktree) },
});
}
const result = this.run(['stop', id]);
return { runtimeSessionId: id, stopped: result.status === undefined || result.status === 0 };
return {
runtimeSessionId: id, stopped: result.status === undefined || result.status === 0,
verifiedWorktree: expectedWorktree, alreadyOffline: false,
};
}
captureRuntimeId({ areaId, runtimeSessionId } = {}) {
+3 -1
View File
@@ -221,7 +221,9 @@ function stopThrough(adapters, action) {
if (!adapter) throw new TorchError(`No runtime stopper for ${action.runtime}`, {
code: 'RUNTIME_ADAPTER_MISSING', details: { runtime: action.runtime },
});
return adapter.stopSession({ runtimeSessionId: action.runtimeSessionId });
return adapter.stopSession({
runtimeSessionId: action.runtimeSessionId, worktree: action.worktree?.path,
});
}
function runCandidateAcceptance(candidateRoot, spawn) {