Centralize encrypted bot profiles (#14)
Centralize ordered bot profiles and encrypted credentials in `bot-agents`. - Exposes one action input: `master-key`. - Uses Gitea’s automatic job token internally; dependent repositories provide no author/reviewer/profile inputs. - Keeps encrypted Claude and reviewer credentials in `profiles.yml`; tokenless Codex uses its persisted subscription login. - Falls back only on recognized quota exhaustion, with a distinct UID/home per profile and descendant cleanup before fallback. - Separates decryption, agent execution, and reviewer posting so privileged credentials never enter an agent process. - Installs `obra/superpowers` with the standard Skills CLI; removes custom discovery code. - Adds root-level isolation, rollback, symlink, fallback, posting, YAML, and self-workflow CI coverage. Checks: formatting, lint, all entrypoint type checks, 35 tests as root, diff check, secret scan, and repository diagnostics. Reviewed-on: #14 Co-authored-by: Danny Kim <temeddix@gmail.com> Co-committed-by: Danny Kim <temeddix@gmail.com>
This commit was merged in pull request #14.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { assertEquals, assertRejects } from "jsr:@std/assert@1";
|
||||
import {
|
||||
decryptCredential,
|
||||
encryptCredential,
|
||||
parseCredentials,
|
||||
} from "./credentials.ts";
|
||||
|
||||
const MASTER_KEY = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8";
|
||||
const OTHER_KEY = "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE";
|
||||
const NONCE = Uint8Array.from({ length: 12 }, (_, index) => index + 32);
|
||||
const ENCRYPTED = "v1:ICEiIyQlJicoKSor:oV_FAgnsN3p1FyegJxwGVdyjfyPio6qDeSUwbA";
|
||||
|
||||
Deno.test("decrypts a known AES-256-GCM credential", async () => {
|
||||
assertEquals(
|
||||
await decryptCredential(ENCRYPTED, MASTER_KEY),
|
||||
"secret-token",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("encrypts a credential with a fresh supplied nonce", async () => {
|
||||
assertEquals(
|
||||
await encryptCredential("secret-token", MASTER_KEY, NONCE),
|
||||
ENCRYPTED,
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("rejects a credential encrypted by another master key", async () => {
|
||||
await assertRejects(() => decryptCredential(ENCRYPTED, OTHER_KEY));
|
||||
});
|
||||
|
||||
Deno.test("rejects a credential with a nonstandard nonce", async () => {
|
||||
await assertRejects(
|
||||
() =>
|
||||
decryptCredential(
|
||||
"v1:AA:oV_FAgnsN3p1FyegJxwGVdyjfyPio6qDeSUwbA",
|
||||
MASTER_KEY,
|
||||
),
|
||||
Error,
|
||||
"nonce must be 12 bytes",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("decrypts the central reviewer token and ordered profiles", async () => {
|
||||
assertEquals(
|
||||
await parseCredentials(
|
||||
`
|
||||
reviewer-token: ${ENCRYPTED}
|
||||
profiles:
|
||||
- type: claude
|
||||
token: ${ENCRYPTED}
|
||||
- type: codex
|
||||
model: gpt-custom
|
||||
`,
|
||||
MASTER_KEY,
|
||||
),
|
||||
{
|
||||
reviewerToken: "secret-token",
|
||||
profiles: [
|
||||
{
|
||||
type: "claude",
|
||||
model: "claude-sonnet-5",
|
||||
token: "secret-token",
|
||||
},
|
||||
{ type: "codex", model: "gpt-custom" },
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("rejects malformed central credentials", async () => {
|
||||
await assertRejects(
|
||||
() => parseCredentials("profiles: []", MASTER_KEY),
|
||||
Error,
|
||||
"reviewer-token",
|
||||
);
|
||||
await assertRejects(
|
||||
() =>
|
||||
parseCredentials(
|
||||
`reviewer-token: ${ENCRYPTED}\nprofiles:\n - type: claude`,
|
||||
MASTER_KEY,
|
||||
),
|
||||
Error,
|
||||
"profiles[0].token",
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user