feat(backend): enforce user-owned sessions

This commit is contained in:
User
2026-07-16 18:32:52 +02:00
parent 72db6b823d
commit 458eb13c89
15 changed files with 626 additions and 105 deletions
+10 -7
View File
@@ -5,6 +5,7 @@ import { SessionBridge } from "../bridge/session-bridge.js";
import type { ThtRunner } from "../tht/tht-runner.js";
import { buildPiChildEnv, canonicalPiProvider } from "./provider-credentials.js";
import { secretValue } from "../config/secret-bundle.js";
import { principalEnvironment, type PrincipalContext } from "../auth/principal.js";
export interface SessionRuntime {
rpc: RpcClient;
@@ -19,6 +20,7 @@ export interface RuntimeOptions {
author?: string;
question?: string;
mode?: "new" | "resume";
principal?: PrincipalContext;
}
/** Injectable child-process boundary; callbacks may ignore arguments in simpler tests. */
@@ -31,21 +33,21 @@ type SpawnFn = (
export class PiProcessManager {
private runtimes = new Map<string, SessionRuntime>();
private spawnFn: (
sessionId: string, author: string, provider: string | undefined,
sessionId: string, author: string, provider: string | undefined, principal?: PrincipalContext,
) => ChildProcessWithoutNullStreams;
constructor(private cfg: AppConfig, opts?: { spawnFn?: SpawnFn }) {
if (opts?.spawnFn) {
this.spawnFn = (sessionId, author, provider) =>
this.spawnPi(opts.spawnFn!, sessionId, author, provider);
this.spawnFn = (sessionId, author, provider, principal) =>
this.spawnPi(opts.spawnFn!, sessionId, author, provider, principal);
} else {
this.spawnFn = (sessionId, author, provider) =>
this.spawnPi(nodeSpawn, sessionId, author, provider);
this.spawnFn = (sessionId, author, provider, principal) =>
this.spawnPi(nodeSpawn, sessionId, author, provider, principal);
}
}
private spawnPi(
spawnFn: SpawnFn, sessionId: string, author: string, provider: string | undefined,
spawnFn: SpawnFn, sessionId: string, author: string, provider: string | undefined, principal?: PrincipalContext,
): ChildProcessWithoutNullStreams {
const env = buildPiChildEnv({
provider,
@@ -53,6 +55,7 @@ export class PiProcessManager {
credentialFile: this.cfg.modelApiKeyFile,
additions: { THT_SESSION: sessionId, THT_AUTHOR: author },
});
if (principal) Object.assign(env, principalEnvironment(principal));
// The Thoth gate executes the deterministic `tht` CLI as a Pi tool. Give only
// this managed session process the adapter values already loaded by the core
// entrypoint; the generic provider helper continues to scrub them by default.
@@ -95,7 +98,7 @@ export class PiProcessManager {
}
const author = o.author ?? "dev@local";
const provider = canonicalPiProvider(o.provider ?? this.cfg.defaults.provider);
const child = this.spawnFn(sessionId, author, provider);
const child = this.spawnFn(sessionId, author, provider, o.principal);
let rt: SessionRuntime | undefined;
try {
const rpc = new RpcClient(child);