fix(grok): discard credential-bearing diagnostic logs

Exclude Grok diagnostics from durable recovery checkpoints and remove them after provider containment while preserving session history.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-23 16:00:40 -07:00
1 parent 697b4d0526
commit a8f109080e
5 files changed
+61 -6

No files matched your search

+4 -3
View File
@@ -36,9 +36,10 @@ existing Grok login when no company login has been selected.
Only the selected credential is staged in the private runtime home. An ownership
lease fences concurrent processes. After the provider exits, refreshed credentials
are copied back through Grok's existing identity and refresh checks. Runtime
credentials and refresh handoffs are excluded from workspace backups and removed
on close. Host configuration, other provider credentials and unselected keys are
not forwarded to Grok.
credentials, refresh handoffs, and diagnostic logs are excluded from workspace
backups and removed on close. Session history remains available for resume. Host
configuration, other provider credentials and unselected keys are not forwarded
to Grok.
## Evaluation
@@ -1,4 +1,4 @@
import { mkdtemp, readFile, rm, stat, symlink, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readFile, rm, stat, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
@@ -29,6 +29,20 @@ describe("Grok isolated credentials", () => {
expect(await readFile(join(directory, "auth-refresh.json"), "utf8")).toBe(refreshed);
await lease.close();
});
it.each([true, false])("removes diagnostic logs after shutdown without removing session history (admitted=%s)", async retainRefresh => {
const directory = await home();
const lease = await stageManagedGrokCredential({ agentHomeDirectory: directory,
environment: { PAPERCLIP_ACPX_GROK_AUTH_JSON_SECRET: credential }, retainRefresh: () => retainRefresh });
await mkdir(join(directory, "logs"));
await mkdir(join(directory, "sessions"));
await writeFile(join(directory, "logs", "unified.jsonl"), credential, { mode: 0o600 });
await writeFile(join(directory, "sessions", "session.json"), "resume-state", { mode: 0o600 });
await lease.close();
await expect(stat(join(directory, "logs"))).rejects.toMatchObject({ code: "ENOENT" });
expect(await readFile(join(directory, "sessions", "session.json"), "utf8")).toBe("resume-state");
if (retainRefresh) expect(await readFile(join(directory, "auth-refresh.json"), "utf8")).toBe(credential);
else await expect(stat(join(directory, "auth-refresh.json"))).rejects.toMatchObject({ code: "ENOENT" });
});
it("does not stage subscription credentials for an explicit API key", async () => {
const directory = await home();
const lease = await stageManagedGrokCredential({ agentHomeDirectory: directory, environment: { XAI_API_KEY: "test-key" } });
@@ -79,6 +79,10 @@ export async function stageManagedGrokCredential(input: {
} finally { await auth.close(); }
}
}
// Grok diagnostics can contain account/authentication fields. They are
// disposable, unlike session history, and must not outlive this owner.
// close is invoked only after the provider has been contained.
await rm(join(input.agentHomeDirectory, "logs"), { recursive: true, force: true });
await lease.close();
closed = true;
})().finally(() => { closing = null; });
@@ -1,6 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
access,
cp,
lstat,
mkdir,
mkdtemp,
@@ -1809,6 +1810,40 @@ describe("split durable provider checkpoint identity", () => {
});
describe("remote provider checkpoint snapshots", () => {
it("excludes Grok credentials and diagnostic logs from real checkpoint copies while retaining sessions", async () => {
const root = await mkdtemp(join(tmpdir(), "grok-checkpoint-"));
try {
const source = join(root, "source");
const target = join(root, "backup");
const sessionDirectory = acpxRuntimeSessionDirectoryName("session");
const relativeHome = join("acpx", sessionDirectory, "grok-home");
const home = join(source, relativeHome);
await mkdir(join(home, "logs"), { recursive: true });
await mkdir(join(home, "sessions"));
await writeFile(join(home, "logs", "unified.jsonl"), "AUTH_SENTINEL");
await writeFile(join(home, "auth.json"), "AUTH_SENTINEL");
await writeFile(join(home, "auth-refresh.json"), "AUTH_SENTINEL");
await writeFile(join(home, "sessions", "session.json"), "resume-state");
const profile = resolveNativeHarnessPersistenceProfile({
provider: { kind: "acpx", agent: "grok" },
session: { normalizedSessionId: "session", driverKind: "acpx_runtime" },
} as unknown as NativeExecutionInputV1);
const execute = async ({ command, args }: { command: string; args: string[] }) => ({
exitCode: 0, timedOut: false, stdout: execFileSync(command, args, { encoding: "utf8" }), stderr: "",
});
const syncOut = async (operations: Array<{ files: Array<{ sourcePath: string; targetPath: string }> }>) => {
for (const operation of operations) for (const file of operation.files)
await cp(file.sourcePath, file.targetPath, { recursive: true });
};
await syncRemoteRunnerDirectoryOut({ runner: { execute, syncOut } as never,
sourcePath: source, targetPath: target, mode: 0o700,
excludeEntries: profile.directories.find(directory => directory.name === "acpx")!.excludeEntries });
for (const entry of ["auth.json", "auth-refresh.json", "logs"])
await expect(access(join(target, relativeHome, entry))).rejects.toMatchObject({ code: "ENOENT" });
expect(await readFile(join(target, relativeHome, "sessions", "session.json"), "utf8")).toBe("resume-state");
expect(await readFile(join(home, "logs", "unified.jsonl"), "utf8")).toBe("AUTH_SENTINEL");
} finally { await rm(root, { recursive: true, force: true }); }
});
it("excludes Codex scratch and credential state without mutating the live provider home", async () => {
const execute = vi
.fn()
@@ -5301,10 +5301,11 @@ export function resolveNativeHarnessPersistenceProfile(
// Codex creates process-local executable aliases in tmp/arg0;
// they may point outside the runtime tree and are neither safe
// nor necessary to restore. Credentials and launch-time config
// are also re-materialized in the replacement sandbox.
// are also re-materialized in the replacement sandbox. Grok
// diagnostics can contain auth fields and are not session state.
excludeEntries:
execution.provider.agent === "grok"
? ["auth.json", "auth-refresh.json", "auth-refresh.json.tmp", "config.toml"].map((entry) =>
? ["auth.json", "auth-refresh.json", "auth-refresh.json.tmp", "config.toml", "logs"].map((entry) =>
`acpx/${acpxRuntimeSessionDirectoryName(nativeSessionKey(execution))}/grok-home/${entry}`)
: execution.provider.agent === "codex"
? CODEX_HOME_NON_PERSISTENT_ENTRIES.map(