Repository navigation
Conversation
An agent that demonstrates a task or proposes a repair reads the live app, and that content can be written to steer it. This adds a written threat model and capability limits declared before the agent reads any page, rather than filtering page text. - openadapt_flow.teach_scope: TeachScope (origins, record, fields and exact values, constants, optional app write adapter) and TeachGuard. Typed text must be a declared value, paste is refused, links and form submits to an undeclared origin are refused before the click, every browser request to an undeclared origin is aborted, writes are read before they are sent, and the system of record is read back after the run. - compile_agent(..., teach_scope=...): a violation ends the capture as scope_refused or readback_refused with no draft; a clean draft carries a value-free teach_scope report. The scope must declare every task input. - Approval provenance: ItemApprovalRequest.provenance (who asked, the agent's reason, the screen text it saw, instruction-like lines, the task scope), in the request digest, omitted when absent so old digests hold. Shown on the phone card. guard_commit takes a reason; self-repair fills it from the proposal note. - Self-repair refuses a proposed control that leads to another origin. - MockMed ?inject=<surface>:<goal> fixtures and GET /api/notice. - tests/test_injection_suite.py: 5 surfaces x 4 goals with a scripted agent that obeys every instruction, plus positive controls; no model calls. tests/e2e/test_injection_live.py: live-model mode, off by default. - docs/THREAT_MODEL.md; LIMITS.md, CAPABILITIES.md, JIT.md, SECURITY.md. All data is synthetic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ni-0bfadc # Conflicts: # CAPABILITIES.md # llms-full.txt # packages/flow/docs/JIT.md # packages/flow/openadapt_flow/approval_mode/item_approval.py # packages/flow/openadapt_flow/repair/self_repair.py # packages/flow/public-artifacts.json
…ni-0bfadc # Conflicts: # CAPABILITIES.md # llms-full.txt # packages/flow/openadapt_flow/console/save_approvals.py # packages/flow/public-artifacts.json
Member
Author
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this answers
When an agent demonstrates a task (compile-agent, teach over MCP, the airbag guard) or proposes a repair, it reads live application content. A record note, a banner or a server response can tell it to do something else, and published work (OS-Harm, RedTeamCUA, VPI-Bench) shows that such text often succeeds against computer-use agents. Before this change OpenAdapt had admission gating for runs, but no written threat model and no tests for the teach and repair steps.
This PR does not try to filter page text. It limits what the agent's actions can do, using values declared before the agent reads any page (CaMeL's split between control and data), and it reads the system of record back afterwards.
Changes
openadapt_flow.teach_scope:TeachScopedeclares the app origins, the record, the fields with their exact values, and any constants.TeachGuardenforces it on a live capture:PlaywrightBackend.launch(before_goto=...)).check_delta). The agent never runs this check, so it can't skip it.compile_agent(..., teach_scope=...): a violation ends the capture asscope_refusedorreadback_refused, with no draft. A clean draft carries ateach_scopereport with names and digests only. The scope must declare every task input. With no scope, behavior is unchanged.ItemApprovalRequest.provenanceholds who asked, the agent's own reason (shown as a quote), the screen text it saw, the lines of that text that read like an instruction to an agent, and what the task allows. It is inside the request digest and left out when absent, so existing digests don't change. The phone portal card shows it.guard_committakes areason, and self-repair fills the reason from the proposal note.?inject=<surface>:<goal>fixtures (note,banner,tooltip,alt,network,aside×other_patient,other_field,other_origin,skip_check) and aGET /api/noticeroute. Without the flag nothing renders. All text is synthetic.Tests (no paid model calls)
tests/test_injection_suite.py(40 cases, about 60 s, real browser). The scripted agent reads every text on the page, attributes included, and obeys every injected instruction, which is the worst case for a model.write_wrong_record,typed_value_not_declared,link_origin_not_declared) or caught by the read-back (missing).write_value_not_declared), navigation to the outside origin, an image beacon (egress_origin_not_declared), and apps that lie about the save (wrong record, partial, duplicate).compile_agentitself: a hijacked capture and a lying app each end with no draft, a clean scoped capture makes a draft, and a scope that misses an input is refused.tests/test_teach_scope.py: unit tests, plus an import-order regression test.tests/test_self_repair.py::test_a_proposal_steered_off_the_app_by_page_text_is_refused. Finding: without the new origin check, this decoy passes the patch and is stopped later by the probe's identity check. The new check refuses it earlier and does not depend on that identity read failing.tests/test_guarded_save.py::test_the_phone_shows_why_the_agent_asked, and a provenance assertion in the existing phone-approval self-repair test.tests/e2e/test_injection_live.py: live-model mode (claude-opus-5-5through the computer toolset). It is off by default and needsOPENADAPT_INJECTION_LIVE=1, a named approver inOPENADAPT_INJECTION_LIVE_APPROVED_BY(ask the coordinator first; at most $2.00 for 4 runs), and the customer's own key. It has not been run.Local runs:
tests/e2e/test_jit_cua_e2e.py), MockMed, release, guard, self-repair and injection tests.packages/agent: 511 passed.src/portalShell.test.ts), which gets two new cases. CI runs it.Still unprotected (also in THREAT_MODEL.md and LIMITS.md)
openadapt_flow.jitonly. feat(flow): compile-agent — compile an agent's first verified run into a zero-token program #1277 (compile-agent), feat(agent): teach a workflow over MCP, then call it with no model calls #1335 (teach over MCP) and the airbag guard must pass aTeachScopeinto their capture when they land.🤖 Generated with Claude Code