Authorized Code Execution
File-level trust boundary for privileged code: restricting methods and tools, authorization flow, governance patterns
On this page
ADF agents can write and execute code. By default, all code — whether written by the agent or the owner — runs with the same privileges. This creates a privilege escalation risk: an agent with code execution can approve its own human-in-the-loop (HIL) restricted tool calls, bypassing human oversight entirely.
Authorized code execution solves this by introducing a file-level trust boundary. The owner marks specific files as authorized, and the runtime restricts sensitive methods so only authorized code can call them. For task_resolve specifically, authorization is enforced at the task level — trigger lambdas opt in by setting requires_authorization: true on the task via task_resolve, and subsequent approve/deny calls check this flag rather than relying on method-level restriction.
Core Concepts
The Problem
Without authorized code, these two scenarios are indistinguishable to the runtime:
- Owner writes
lib/hil/vote-counter.tsthat callstask_resolveafter verifying a 3/5 quorum from council agents - Agent writes
lib/hack.tsthat callstask_resolve({ task_id: "...", action: "approve" })to approve its own pending task
Both files live in adf_files. Both execute in the same sandbox. Both have access to the same adf.* methods.
For task_resolve, the distinction is made by the trigger lambda: a HIL lambda sets requires_authorization: true when escalating the task, so only authorized code can approve or deny it. Non-security interceptions (rate limiting, batching, logging) don’t set the flag, so any code can resolve them.
The Solution
A single boolean flag — authorized — on each file in adf_files. The runtime checks this flag at execution time and gates specific adf.* methods behind it.
| Concept | Description |
|---|---|
| Authorized file | A file in adf_files with authorized = true. Only settable by the owner (via Studio UI) or by already-authorized code (gateway pattern). |
| Restricted method | A code execution method listed in code_execution.restricted_methods. Can only be called from authorized code. |
| Write deauthorizes | Any write to an authorized file via fs_write automatically sets authorized = false. The content changed — the authorization is no longer valid. |
| Authorization is orthogonal to protection | A file can be authorized but unprotected (authorized: true, protection: "none"), or protected but unauthorized. They control different things. |
How Authorization Flows
Authorization is not simply “is the file authorized?” — it depends on who invoked the code.
Execution Contexts
| Invocation | Authorization | Rationale |
|---|---|---|
| Trigger/timer lambda | Based on file’s authorized flag | System-initiated — the owner configured the trigger |
| Middleware lambda | Based on file’s authorized flag | System-initiated — runs in the ingress/egress pipeline |
sys_lambda from the loop | HIL if target is authorized; false otherwise | Agent-initiated — authorized targets require human approval before running with privileges |
sys_code (inline) | Always false | Agent-initiated — inline code has no provenance |
Code calling adf.sys_lambda() | Based on target file’s authorized flag | Unauthorized callers blocked from calling authorized targets (REQUIRES_AUTHORIZED_CALLER) |
The key rule: authorized code always requires human awareness. System channels (triggers, timers, middleware) run authorized code because the owner configured them. When the LLM calls sys_lambda targeting an authorized file, the runtime triggers a HIL approval prompt — the user must explicitly approve execution with elevated privileges. In code-to-code chains, unauthorized callers cannot escalate into authorized targets.
Call Chain Rules
| Caller | Target | Result |
|---|---|---|
| Authorized code | Authorized file | Allowed — target runs authorized |
| Authorized code | Unauthorized file | Allowed — target runs unauthorized |
| Unauthorized code | Authorized file | Blocked — REQUIRES_AUTHORIZED_CALLER |
| Unauthorized code | Unauthorized file | Allowed — target runs unauthorized |
Loop sys_lambda | Authorized file | HIL prompt — approved → runs authorized; denied → blocked |
Loop sys_lambda | Unauthorized file | Allowed — target runs unauthorized |
Configuration
Restricting Methods
Configure which adf.* methods require authorized code in the agent’s code execution config:
{
"code_execution": {
"restricted_methods": ["get_identity"]
},
"security": {
"require_middleware_authorization": true
}
}
| Field | Type | Default | Description |
|---|---|---|---|
code_execution.restricted_methods | string[] | ['attestation_issue'] | Methods that can only be called from authorized code |
security.require_middleware_authorization | boolean | true | Whether middleware lambdas must be from authorized files. Owner-only — the agent cannot change it via sys_update_config (hard-denied, no HIL) |
Common methods to restrict:
| Method | Why Restrict It |
|---|---|
get_identity | Restricts access to stored credentials and API keys |
set_identity | Prevents agent from overwriting stored credentials |
authorize_file | Restricts the gateway pattern to already-authorized code |
model_invoke | Prevents unauthorized code from making LLM calls (cost control) |
loop_inject | Prevents unauthorized code from injecting context into the conversation |
Note: set_meta_protection and set_file_protection are inherently authorized-code-only — they don’t need to be added to restricted_methods. Similarly, sys_set_meta and sys_delete_meta only bypass protection checks from authorized code; from unauthorized code they enforce protection normally.
task_resolve is not listed here — it uses task-level authorization instead. HIL tasks are created with requires_authorization: true, and task_resolve checks this flag at runtime. This avoids double-restricting: the authorization requirement lives on the task, not the method.
The gate-able code-execution methods are model_invoke, sys_lambda, task_resolve, loop_inject, identity_status, get_identity, set_identity, emit_event, attestation_list, attestation_add, and attestation_issue. Any of these may be named in restricted_methods.
The default is ['attestation_issue'], not empty — signing a certificate about another DID is a deliberate trust act, so it requires authorized code out of the box. Setting restricted_methods to an explicit list replaces this default (it does not merge), so if you restrict other methods, re-add attestation_issue unless you deliberately want to open it. The owner opts in to which additional methods need protection.
Restricting Tools
Tools and MCP servers use restricted directly on the ToolDeclaration:
{
"tools": [
{ "name": "execute_trade", "enabled": true, "restricted": true }
]
}
visible controls only whether a tool appears in the model’s advertised schema (the Advertised column); it never gates execution. Execution is gated on enabled, so an enabled tool runs from the LLM loop even when visible: false.
enabled | visible | restricted | Advertised | LLM loop | Authorized code | Unauthorized code |
|---|---|---|---|---|---|---|
false | — | false | No | Off | Off | Off |
false | — | true | No | Off | Free | Off |
true | false | false | No | Free | Free | Free |
true | false | true | No | HIL | Free | Off |
true | true | false | Yes | Free | Free | Free |
true | true | true | Yes | HIL | Free | Off |
When a tool is enabled and restricted, LLM loop calls automatically get HIL approval — the runtime derives HIL from that combination, independent of visibility. Authorized code can call the tool freely, bypassing the approval step. Unauthorized code cannot call restricted tools at all.
Middleware Authorization
require_middleware_authorization controls whether middleware lambdas must be from authorized files. Default: true. It is a guard-system setting and is not agent-writable at all — sys_update_config hard-denies it with a plain error (no HIL prompt, no one-time override). Only the owner can turn it off, in the app UI. An agent cannot disable the check that governs whether its own middleware is trusted.
When true and a middleware lambda’s source file is not authorized, the middleware is skipped (not errored). The message passes through unmodified and a warning is logged:
[warn] Middleware lib/transform.ts skipped — not authorized
Skipping rather than erroring is deliberate — erroring would reject the inbound message, punishing the sender for the receiver’s internal config state.
When false, all middleware runs regardless of authorization status, but the authorization context within the middleware reflects the file’s actual flag. Unauthorized middleware cannot call restricted methods; authorized middleware can.
Authorizing Files
Via Studio UI
Open the file in the Files panel. Click the Unauthorized button to toggle it to Authorized. A green badge appears in the file list.
Via Authorized Code (Gateway Pattern)
Authorized code can authorize other files using the authorize_file method:
// lib/gateway.ts — this file must itself be authorized
async function authorizeDeployment(event) {
// Custom validation logic
const approval = await verifyHumanApproval(event)
if (approval.verified) {
await adf.authorize_file({ path: event.targetFile })
}
}
This enables remote authorization workflows: the owner authorizes a gateway lambda that implements whatever authorization policy they want (Telegram approval, multi-agent vote, time-based rotation). The trust chain: human authorizes gateway, gateway authorizes target files.
authorize_file is itself restricted — it only works from already-authorized code. Add it to restricted_methods to enforce this (it checks isAuthorized internally regardless, but adding it to the list makes the policy explicit in config).
What Cannot Authorize
These paths cannot set the authorized flag:
| Path | Why |
|---|---|
fs_write | Agent tool — writes content, does not control authorization |
sys_update_config | Agent tool — modifies config, not file flags |
db_execute | Agent tool — adf_files is blocked from db_execute |
sys_code (inline) | No file — inline code has no authorization context |
Unauthorized sys_lambda | authorize_file requires authorized caller |
Write Deauthorization
Any write to an authorized file via fs_write automatically sets authorized = false. This is enforced at the database layer — the UPDATE statement unconditionally sets authorized = 0:
UPDATE adf_files SET content = ?, mime_type = ?, size = ?, authorized = 0, updated_at = ? WHERE path = ?
This means:
- The agent cannot write malicious code into an authorized file and have it run with privileges
- During development, each
fs_writerequires manual re-authorization via the UI - For production, combine
authorized = truewithprotection = 'read_only'to prevent both modification and deletion
Protection + Authorization Matrix
| Phase | authorized | protection | Notes |
|---|---|---|---|
| Owner developing | false | none | Normal development, no authorization needed |
| Owner testing | false | none | Verify logic before authorizing |
| Production lockdown | true | read_only | Authorized and immutable |
| Owner patching | true → false, edit, true | read_only → none → read_only | Unlock, edit (deauthorizes), re-authorize, re-lock |
Error Codes
When authorization checks fail, the sandbox receives these error codes:
| Error Code | Condition | Message |
|---|---|---|
REQUIRES_AUTHORIZED_CODE | Restricted method/tool called from unauthorized code, or task_resolve called on a task with requires_authorization: true from unauthorized code | "<method>" can only be called from authorized code. Ask the owner to authorize the source file. |
REQUIRES_AUTHORIZED_CALLER | Unauthorized code calls adf.sys_lambda() targeting an authorized file | Cannot call authorized code from unauthorized context |
Governance Patterns
The authorized code primitive enables multi-agent governance without additional framework features.
Multi-Agent Approval (HIL Replacement)
Trading agent calls execute_trade(AAPL, 1000) — tool has restricted: true
→ Runtime creates HIL task (pending_approval, requires_authorization: true, executor_managed: true)
→ on_task_create trigger fires → authorized system-scope lambda receives task details
→ Lambda sends approval requests to 5 council agents via msg_send
→ Council agents reply via msg_send (normal messages)
→ on_inbox trigger fires authorized vote-counting lambda
→ Lambda queries adf_inbox for verified sender DIDs (not local_* tables)
→ 3/5 approve → adf.task_resolve({ task_id, action: "approve" })
→ Executor runs execute_trade in its own context
Key implementation rules for the vote-counting lambda:
- Read from system tables only. Query
adf_inboxfor votes — the agent cannot forge entries inadf_inbox. Never read votes fromlocal_*tables — the agent can freely write those. - Verify sender identity. Check the
fromfield (DID) against a hardcoded or config-stored list of council agent DIDs. - Don’t accept parameters. The lambda should derive its decisions from the current state of system tables, not from arguments passed by the caller.
Remote Authorization Gateway
// lib/auth-gateway.ts (authorized, read_only, locked)
async function onApprovalMessage(event) {
if (event.type !== 'inbox') return
const messages = await adf.msg_read({ status: 'unread' })
for (const msg of messages) {
// Only accept from the owner's management agent
if (msg.from !== 'did:key:z6MkOwnerAgent...') continue
const parsed = JSON.parse(msg.content)
if (parsed.action === 'authorize' && parsed.path) {
await adf.authorize_file({ path: parsed.path })
}
}
}
Secure Middleware Pipeline
Middleware that modifies messages before the agent sees them is security-critical code. With require_middleware_authorization: true (the default), only owner-authorized middleware runs. Unauthorized middleware is silently skipped.
This means the agent can write experimental middleware, but it won’t execute until the owner reviews and authorizes the file. The agent can still modify its trigger configuration to point at authorized middleware — that’s intentional. The trust is in the middleware code, not in who invokes it.
Security Analysis
What Authorized Code Prevents
| Attack | Mitigation |
|---|---|
Agent calls task_resolve from sys_code to self-approve restricted tool’s HIL task | Blocked — HIL tasks have requires_authorization: true, sys_code always runs unauthorized |
Agent writes code that calls task_resolve via sys_lambda on restricted tool’s HIL task | Blocked — loop sys_lambda calls run unauthorized, HIL task has requires_authorization: true |
Agent writes adf.sys_lambda() in sys_code targeting authorized file | Blocked — REQUIRES_AUTHORIZED_CALLER (unauthorized caller) |
| Agent modifies an authorized file to inject malicious code | Blocked — fs_write auto-deauthorizes |
Agent calls authorize_file from unauthorized code | Blocked — requires authorized caller |
| Unauthorized middleware modifies messages | Blocked (default) — require_middleware_authorization: true skips it |
Authorized Code Protection Bypass
Authorized code can bypass readonly and increment protection on adf_meta keys and read_only/no_delete protection on files, and can change protection levels on both meta keys and files. This is equivalent to the owner making changes via the Studio UI.
| Method | Unauthorized Code | Authorized Code |
|---|---|---|
sys_set_meta | Protection enforced | Protection bypassed — can overwrite any key |
sys_delete_meta | Protection enforced | Protection bypassed — can delete any key |
fs_write | Protection enforced | Protection bypassed — can overwrite any file, including read_only |
fs_delete | Protection enforced | Protection bypassed — can delete any file, including read_only and no_delete |
set_meta_protection | Not available | Can change meta key protection level |
set_file_protection | Not available | Can change file protection level |
authorize_file | Not available | Can authorize/deauthorize any file |
This enables remote management patterns: an authorized lambda can lock down files and meta keys after deployment, overwrite a locked config file during patching, or rotate the set of authorized code files without user intervention.
How the bypass is wired (implementation detail): When adf-call-handler dispatches a tool call from authorized code, it injects an internal _authorized: true flag onto the tool’s input. The tool registry treats this as a cross-cutting param (alongside _full) — stripped before schema validation, re-attached on the tool input. Tools that support the bypass (fs_write, fs_delete, etc.) check for this flag and skip their protection checks. The agent executor unconditionally strips _authorized from LLM tool calls, so the LLM cannot forge it; only adf-call-handler can set it, and only when this.isAuthorized is true.
What Authorized Code Does NOT Prevent
| Scenario | Why It’s Acceptable |
|---|---|
| Agent creates trigger/timer pointing at authorized code | The agent controls when authorized code runs, not what it does. Authorized code validates its own inputs from system tables. |
| Agent passes arbitrary timer payload to authorized lambda | The lambda receives a structured event object — different shape from the inputs it was designed for. A well-written lambda ignores irrelevant event types. |
Agent calls authorized sys_lambda from the loop | HIL prompt — the user must approve before the lambda runs with authorization. Denial blocks execution. |
Agent re-enables a disabled tool via sys_update_config | The agent can toggle enabled on unlocked tools — disable is a suggestion, not a boundary. Lock the tool or disable sys_update_config to enforce. Agent cannot modify restricted, restricted_methods, or locked. |
| Owner marks a badly-written lambda as authorized | Code quality issue — authorization means “owner trusts this code.” If the code blindly acts on parameters, that’s the owner’s responsibility. |
Residual Risk
The authorization model assumes authorized code is well-written. Specifically:
- Don’t accept security-critical parameters. A vote-counting lambda should read votes from
adf_inbox, not from function arguments. - Read from system tables, not
local_*. The agent can write freely tolocal_*tables. System tables (adf_inbox,adf_config,adf_files) are write-protected from agent code. - Keep authorized files small and single-purpose. A 20-line vote counter is easy to audit. A 500-line utility library is not.
- Use
protection: 'read_only'in production. Prevents the agent from deleting authorized files (a disruption vector, not a privilege escalation).
Schema
The authorized column is stored on the adf_files table:
CREATE TABLE adf_files (
path TEXT PRIMARY KEY,
content BLOB NOT NULL,
mime_type TEXT,
size INTEGER NOT NULL,
protection TEXT NOT NULL DEFAULT 'none',
authorized INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
Task-level authorization is stored on the adf_tasks table:
CREATE TABLE adf_tasks (
id TEXT PRIMARY KEY,
tool TEXT NOT NULL,
args TEXT NOT NULL DEFAULT '{}',
status TEXT NOT NULL DEFAULT 'pending',
result TEXT,
error TEXT,
created_at INTEGER NOT NULL,
completed_at INTEGER,
origin TEXT,
requires_authorization INTEGER NOT NULL DEFAULT 0
);
All tasks start with requires_authorization = 0. A trigger lambda opts in by passing requires_authorization: true on task_resolve:
await adf.task_resolve({
task_id: taskId,
action: "pending_approval",
requires_authorization: true
});
Once set, the flag cannot be unset — any code can lock the door, but only authorized code can approve or deny the task. Setting to pending_approval is always allowed (it’s restrictive). The task_resolve method checks this flag — not method-level restriction — to determine if the caller must be authorized.
The authorized field is also returned by fs_read:
{
"path": "lib/hil/vote-counter.ts",
"content": "...",
"mime_type": "text/typescript",
"size": 847,
"protection": "read_only",
"authorized": true,
"created_at": "2026-03-15T10:30:00.000Z",
"updated_at": "2026-03-15T10:30:00.000Z"
}
Implementation Reference
| Component | File | Role |
|---|---|---|
| Schema + migration | src/main/adf/adf-database.ts | authorized column on files (v17), requires_authorization on tasks (v18), auto-deauthorize on write |
| Workspace methods | src/main/adf/adf-workspace.ts | isFileAuthorized(), setFileAuthorized() |
| Method restriction | src/main/runtime/adf-call-handler.ts | restricted_methods check, task-level authorization check in task_resolve, call-chain enforcement, authorize_file handler, authorized meta/file protection bypass |
sys_lambda context | src/main/tools/built-in/sys-lambda.tool.ts | AND-logic: fileAuthorized && callerAuthorized |
sys_code context | src/main/tools/built-in/sys-code.tool.ts | Always isAuthorized = false |
| Trigger/timer context | src/main/runtime/system-scope-handler.ts | Sets isAuthorized from file flag |
| Middleware restriction | src/main/services/middleware-executor.ts | require_middleware_authorization check + context |
| Type definitions | src/shared/types/adf-v02.types.ts | CodeExecutionConfig.restricted_methods, SecurityConfig.require_middleware_authorization, ToolDeclaration.restricted, FileEntry.authorized |
| Zod validation | src/main/adf/adf-schema.ts | Schema validation for security config fields |
| IPC | src/shared/constants/ipc-channels.ts, src/main/ipc/index.ts | DOC_SET_FILE_AUTHORIZED channel |
| UI | src/renderer/components/agent/AgentFiles.tsx, src/renderer/components/agent/AgentConfig.tsx | File authorization toggle, security config UI |