Repository navigation
Teach authorization signed with approver keys, bound to every teach path - #1436
Conversation
Adds openadapt.teach-authorization/v1 (person-signed scope: task, inputs, origins, permitted writes, forbidden actions), binds it on the JIT capture path, records its digest in the signed recorder block and admission manifest, and requires it at admission for agent-taught drafts. Other authoring paths, the live-model harness and full test runs are not done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fuse every teach path The signed teach authorization (openadapt.teach-authorization/v1) now uses the approver keys and the approver verifier instead of GitHub SSH keys. Admission of every JIT draft requires one, keyed on the signed recorder block. compile-agent, MCP teach, the skill cache, browser-teach writes, agent-demonstrated fixes and self-repair of agent-taught programs are bound; the airbag handoff's drafts are refused. Adds openadapt-flow teach-authorization create/sign/verify.
Independent adversarial review (round 1), head 4f7ed30A fresh reviewer got only the diff and the goal. It read the code and didn't run tests. Verdict: request changes. The core verifier holds up. Findings:
The reviewer checked and found sound: hashed vs signed bytes, swapping the statement after verification, revocation and rollback at admission, the agent signing its own statement, and the airbag handoff refusal. |
Refuse browser-teach writes instead of half-binding them. Pin the roster from the deployment config for servers an agent runs; pass recorded heads; add --approver-root to jit admit. Check the task at repair approval and at admission from the signed recorder block. Keep the signed write adapter in narrowed scopes. Refuse a click whose label probe fails when controls are forbidden. Drop the Desktop stdio signing path. Default expiry 24 hours. Changelog with the breaking changes and upgrade notes.
Benchmarks and the JIT example sign a throwaway authorization per run (demo_authority) so their drafts still admit. admission_status uses the same trust as admit. create() defaults to a 24-hour expiry, at most 90 days. Document the remaining trust limits.
Independent adversarial re-review (round 2), commit d56fd00The round-1 reviewer read the new commit and the full diff. It didn't run tests. Verdict: approve with nits. Item 1 below must be fixed before merge. Round-1 findings, as the reviewer checked them:
New findings:
Addressed in 3f0db00:
Documented as limits in |
teach_run's scripts act through the same authorized run 1 as teach_act, so the scope bound from the statement checks every action and the save. Tests with a real browser, roster and admission gate: a teach_run draft without a statement is refused at admission, one under a statement passes the gate, and a script that types a value the statement doesn't allow saves nothing.
Focused review: merging #1542 (
|
…test nits; merge the #1436 head Merge claude/teach-authorization (teach_run binding) into this branch, and: - run 1's adapter check pins the record's key wherever the statement lists it; - the program-signing key directory must come from the config's approvers section, the key is never created on demand, the program carries a signature domain, and running tools needs only the config's roster pin; - the verified copy of the statement is the one kept, and teaching again replaces it; - coverage maps literal matches and refuses effects it can't map, the row count must match, expiry is required (at most 90 days) at teach and run time, and run 1 counts its writes and watches the forbidden actions; - press in run 1 takes no character keys; - forbidden controls are read from Chromium's accessibility tree, and every key is checked against the control that has focus when it arrives; - the teach_run tests assert the refusal code, reach the save under a statement (wrong record, and a person-approved save), and no longer say admissible.
- test_agent_guard_e2e: the handoff's draft is refused at admission by an AdmissionError, not a returned detail; assert the raise. - docs/phi_at_rest.md lists the new teach-authorization/* bundle entry (test_sealed_at_rest checks every inventory entry is documented). - The two browser-teach write e2e files assert the refusal against the real example app instead of skipping: flow-critical fails on any skipped test.
The two browser-write e2e files keep this branch's full versions, which teach and run writes under a signed statement.
A person signs what an agent may do when it teaches or repairs a program: a teach authorization,
openadapt.teach-authorization/v1. The signature uses the person's approver key on the signed approver roster from #1500, not a GitHub SSH key. Every teach path is either bound to it or refused.What it does
packages/flow/openadapt_flow/teach_authorization.py). It names:teach,repair), origins and inputs (spelled out, or as a SHA-256);HumanAttestationover a separate domain plus the canonical statement. It is checked with the approver verifier itself (human_attestation.refusal→verified_approvers); this PR adds no second signature scheme.teach_auth_unknown_signer);teach_auth_bad_signature);teach_auth_identity_unverified);jit/lifecycle.teach_authorization_gate):openadapt-flow teach-authorization create|sign|verify, plus--teach-authorizationoncompile-agentand onopenadapt-agent serve --teach, and--approver-rootonjit admit.Entry points
compile_agent(teach_authorization=...))teach_scopemay only narrow it (the signed write adapter is kept)compile-agent/FirstRunCompiler/compile_first_run/SkillCacheserve --teach,teach_act)--config)admit_repair)repairand verify nowteach_repair)repairof that workflow before the click.RepairStore.approvechecks it again and refuses without oneteach_auth_not_recorded); the guarded run can't carry a signature yetserve --teach-browserwith a contract)teach_run(#1542)Registering a key without a Secure Enclave
A person uses an encrypted software key file:
openadapt-flow jit keygen, thenjit approver add(orjit approver initto start a roster). They sign withteach-authorization sign --approver-key KEY.pemat an interactive terminal. On Windows and Linux,openadapt-desktop approver-key setupalso creates such a key. Desktop's Touch ID key answers item approvals only and doesn't sign teach authorizations. Seepackages/flow/docs/TEACH_AUTHORIZATION.md.Limits and breaking changes
examples/jit/run_mockmed.pynow sign a throwaway authorization per run (demo_authority) in place of the person.--approver-rootor the environment. Self-repair's carry and repair approval use the environment only. A process controlling both the environment and the key directory can pin its own roster, the same limit the approval docs state. Forserve --teach, also pinapprovers.headin the config.teach_scopeblock themselves.Review
An independent adversarial review is recorded on this PR. Round 1 requested changes; they were addressed in d56fd00. Round 2 approved with nits on condition that the benchmarks be fixed; that and two of the nits were addressed in 3f0db00. The rest are documented as limits.