/** * #502: operator-signed org definitions. * * `org serve` treats every `.monomind/orgs/.json` as an org, and a role * whose Bash can write there (unsandboxed, full access, or a runtime whose * native file tools are not policy-gated) could add `evil.json` with a * `policy.git: push` role plus an `evil/run` file, or widen its own org's * policy for the next reload. #365's `access_ack` only covered full-access * grants. This module extends the same idea to the whole definition: * * - WHAT is signed: every field of the org JSON (as authored, before zod * defaults) EXCEPT the prompt-only and cosmetic ones listed in * `UNSIGNED_ORG_FIELDS` / `UNSIGNED_ROLE_FIELDS`. Deny-by-default: each * role's whole `policy`, its id/runtime/adapter_config/provider/tool * providers/budgets/endpoint, its `instructions_file` (a path the daemon * reads into the prompt) and `skills`/`skill_pool` (they decide the MCP * tools the daemon grants) — plus a digest of each catalog blueprint a * role names, which can fill those two (#571) — the role list itself, `run_config`, * `schedule`, `runtime`, `fence`, `federation` and `loadouts` are all * covered, and so is any field added later. A definition holding a * `__proto__`, `constructor` or `prototype` key anywhere is refused. * - THE KEY: the same machine-local HMAC key #365 uses for `access_ack.sig` * (access-grant-key.ts: owner-only, not a symlink, and a key swapped * after this process loaded it is refused), in the operator-credential * directory, which every role sandbox denies reading and writing. * - WHERE the signature lives: a sidecar in that same directory, * `org-signatures//.json`, never in the org JSON — so a * role can't write it, a tracked org file is not dirtied on every * machine, and a signature can't be committed and replayed elsewhere. The * HMAC input binds the project root and org name too. The signed * projection is kept beside it, so `org sign` can show what changed. * - WHERE it is enforced: `assertOrgDefSigned`, called with the exact bytes * about to be used by `prepareOrgStart` (org run, serve's runfile poll and * schedule, resume) and `reloadOrgDef` (org reload / hot reload), plus * early checks in the CLI paths for a clearer message. * * Residual (same as #365's key): a role that can already read the operator * directory — full access, or a role that runs with neither the SDK sandbox * nor the bubblewrap mask — can read the key and sign. `org sign` names * those roles (org-sign-review.ts). */ export type OrgSignatureReason = 'unsigned' | 'changed' | 'invalid-signature' | 'forbidden-key'; export type OrgSignatureCheck = { ok: true; } | { ok: false; reason: OrgSignatureReason; message: string; }; export declare class OrgSignatureError extends Error { readonly reason: OrgSignatureReason; constructor(message: string, reason: OrgSignatureReason); } export { orgSignatureEnforced, setOrgSignatureEnforcement } from './org-signature-enforcement.js'; /** The first `__proto__` / `constructor` / `prototype` key in `value`, as a * dotted path, or undefined. JSON.parse makes such a key an own property; * copied into a plain object it would set that object's prototype. */ export declare function forbiddenKeyPath(value: unknown, path?: string): string | undefined; /** The canonical, signed projection of a raw (as-authored) org JSON. */ export declare function orgSignatureInput(raw: unknown): unknown; /** Digests of every `instructions_file` in the definition (roles and * loadouts), keyed `role:` / `loadout:` (instructions-file.ts), * and of every catalog blueprint a role names, keyed `blueprint:` * (#571: its skills/skill_pool become the role's at start). */ export declare function instructionsDigests(raw: unknown, root: string): Record; /** Pin each role's and loadout's verified instructions digest on the * parsed definition (`instructions_sha256`), so every later session reads * the file only while its content still matches (session-prompt.ts). */ export declare function pinInstructionDigests(def: { roles: Array<{ id: string; }>; loadouts?: Record; }, digests: Record): void; /** What is signed: the projection, plus — with the project root — the * digests of the instructions files and blueprints it names, each group * only when there is one (so an org with neither hashes bare). */ export declare function signedProjection(raw: unknown, root?: string, digests?: Record): unknown; export declare function computeOrgDefHash(raw: unknown, root?: string, digests?: Record): string; /** A project's id under the operator dir: a hash of its real path. */ export declare function orgProjectId(root: string): string; /** Where the signature for `org` in project `root` is kept. */ export declare function orgSignaturePath(root: string, org: string, dir?: string): string; /** The signed projection kept beside the signature, for `org sign`'s diff. */ export declare function orgProjectionPath(root: string, org: string, dir?: string): string; export declare function orgSignatureMessage(org: string, reason: OrgSignatureReason, problem?: string): string; /** `org sign --expect-hash`'s refusal, naming both hashes. */ export declare function orgHashMismatchMessage(org: string, expected: string, actual: string): string; /** Verify `raw` (the parsed JSON of `.monomind/orgs/.json`) against the * operator's signature. Always checks, whatever the enforcement switch; * never throws for a missing key or sidecar. */ export declare function verifyOrgDef(root: string, org: string, raw: unknown, opts?: { dir?: string; digests?: Record; }): OrgSignatureCheck; /** Throw `OrgSignatureError` unless `raw` verifies (or enforcement is off). */ export declare function assertOrgDefSigned(root: string, org: string, raw: unknown, opts?: { dir?: string; digests?: Record; }): void; /** Sign `raw` as the operator: creates the key on first use (same key and * directory as #365's full-access grants) and writes the sidecar and the * signed projection atomically. Callers are the human-only paths (`org * sign`, `org create`, `org role set-access`) — never the runtime. * `digests` are the instructions digests the caller already read, so the * content it compared is the content signed; `expectHash` refuses, before * anything is written, unless the hash about to be signed is that one. */ export declare function signOrgDef(root: string, org: string, raw: unknown, opts?: { dir?: string; now?: Date; digests?: Record; expectHash?: string; }): { hash: string; at: string; path: string; }; /** When `org` was last signed (the sidecar's `at`), if a trusted sidecar is * there. Meaningful only once `verifyOrgDef` has checked its HMAC. */ export declare function orgSignedAt(root: string, org: string, dir?: string): string | undefined; /** The projection that was last signed for `org`, if its copy is there. */ export declare function lastSignedProjection(root: string, org: string, dir?: string): unknown | undefined; export declare function roleContextMarker(env?: NodeJS.ProcessEnv): string | undefined; //# sourceMappingURL=org-signature.d.ts.map