mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(coding-agent): preserve managed state file permissions
Closes #7779
This commit is contained in:
parent
f4585b8bec
commit
c49906ec77
4 changed files with 32 additions and 6 deletions
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fixed writes to `auth.json` and `models-store.json` overriding administrator-managed file permissions and ACLs ([#7779](https://github.com/earendil-works/pi/issues/7779)).
|
||||
- Fixed UTF-8 BOM markers preventing frontmatter and user configuration files from loading ([#8337](https://github.com/earendil-works/pi/issues/8337)).
|
||||
- Fixed invalid settings files being easy to miss during interactive startup by rendering warnings with the file path inside the TUI ([#7829](https://github.com/earendil-works/pi/issues/7829)).
|
||||
- Fixed the subagent example repeatedly prompting before running project-local agents in trusted repositories ([#8261](https://github.com/earendil-works/pi/issues/8261)).
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import type { AuthOperationOptions, Credential, CredentialInfo, CredentialStore } from "@earendil-works/pi-ai";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import lockfile from "proper-lockfile";
|
||||
import { setTimeout as sleep } from "timers/promises";
|
||||
|
|
@ -21,6 +21,7 @@ type LockResult<T> = {
|
|||
next?: string;
|
||||
};
|
||||
|
||||
// The mode applies only on creation so administrator-managed modes and ACLs remain intact.
|
||||
const AUTH_FILE_WRITE_OPTIONS = { encoding: "utf-8", mode: 0o600 } as const;
|
||||
|
||||
type AuthFileReload = {
|
||||
|
|
@ -62,7 +63,6 @@ export class FileAuthStorageBackend implements AuthStorageBackend {
|
|||
private ensureFileExists(): void {
|
||||
if (!existsSync(this.authPath)) {
|
||||
writeFileSync(this.authPath, "{}", AUTH_FILE_WRITE_OPTIONS);
|
||||
chmodSync(this.authPath, 0o600);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +104,6 @@ export class FileAuthStorageBackend implements AuthStorageBackend {
|
|||
const { result, next } = fn(current);
|
||||
if (next !== undefined) {
|
||||
writeFileSync(this.authPath, next, AUTH_FILE_WRITE_OPTIONS);
|
||||
chmodSync(this.authPath, 0o600);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
|
|
@ -186,7 +185,6 @@ export class FileAuthStorageBackend implements AuthStorageBackend {
|
|||
options?.signal?.throwIfAborted();
|
||||
if (next !== undefined) {
|
||||
writeFileSync(this.authPath, next, AUTH_FILE_WRITE_OPTIONS);
|
||||
chmodSync(this.authPath, 0o600);
|
||||
}
|
||||
throwIfCompromised();
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { type CredentialStore, createModels, type Provider } from "@earendil-works/pi-ai";
|
||||
|
|
@ -139,6 +139,22 @@ describe("AuthStorage", () => {
|
|||
expect(release).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test.skipIf(process.platform === "win32")("creates new auth files with owner-only permissions", () => {
|
||||
AuthStorage.create(authJsonPath);
|
||||
|
||||
expect(statSync(authJsonPath).mode & 0o777).toBe(0o600);
|
||||
});
|
||||
|
||||
test.skipIf(process.platform === "win32")("preserves the mode of an existing auth file", async () => {
|
||||
writeAuthJson({ anthropic: { type: "api_key", key: "old" } });
|
||||
chmodSync(authJsonPath, 0o660);
|
||||
const storage = AuthStorage.create(authJsonPath);
|
||||
|
||||
await storage.modify("anthropic", async () => ({ type: "api_key", key: "new" }));
|
||||
|
||||
expect(statSync(authJsonPath).mode & 0o777).toBe(0o660);
|
||||
});
|
||||
|
||||
test("modify persists a credential while preserving unrelated external edits", async () => {
|
||||
writeAuthJson({ anthropic: { type: "api_key", key: "old" } });
|
||||
const storage = AuthStorage.create(authJsonPath);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { Model } from "@earendil-works/pi-ai";
|
||||
|
|
@ -53,6 +53,17 @@ describe("FileModelsStore", () => {
|
|||
expect((await reloaded.read("two"))?.models.map((entry) => entry.id)).toEqual(["m2"]);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")("preserves the mode of an existing models file", async () => {
|
||||
const managedModelsPath = join(sharedTempDir, "managed-mode.json");
|
||||
writeFileSync(managedModelsPath, "{}");
|
||||
chmodSync(managedModelsPath, 0o660);
|
||||
const store = new FileModelsStore(managedModelsPath);
|
||||
|
||||
await store.write("one", { models: [model("one", "m1")], checkedAt: 100 });
|
||||
|
||||
expect(statSync(managedModelsPath).mode & 0o777).toBe(0o660);
|
||||
});
|
||||
|
||||
it("coalesces file reloads across concurrent readers and interleaved storage instances", async () => {
|
||||
writeFileSync(
|
||||
sharedModelsPath,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue