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:

  1. Owner writes lib/hil/vote-counter.ts that calls task_resolve after verifying a 3/5 quorum from council agents
  2. Agent writes lib/hack.ts that calls task_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.

ConceptDescription
Authorized fileA file in adf_files with authorized = true. Only settable by the owner (via Studio UI) or by already-authorized code (gateway pattern).
Restricted methodA code execution method listed in code_execution.restricted_methods. Can only be called from authorized code.
Write deauthorizesAny 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 protectionA 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

InvocationAuthorizationRationale
Trigger/timer lambdaBased on file’s authorized flagSystem-initiated — the owner configured the trigger
Middleware lambdaBased on file’s authorized flagSystem-initiated — runs in the ingress/egress pipeline
sys_lambda from the loopHIL if target is authorized; false otherwiseAgent-initiated — authorized targets require human approval before running with privileges
sys_code (inline)Always falseAgent-initiated — inline code has no provenance
Code calling adf.sys_lambda()Based on target file’s authorized flagUnauthorized 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

CallerTargetResult
Authorized codeAuthorized fileAllowed — target runs authorized
Authorized codeUnauthorized fileAllowed — target runs unauthorized
Unauthorized codeAuthorized fileBlocked — REQUIRES_AUTHORIZED_CALLER
Unauthorized codeUnauthorized fileAllowed — target runs unauthorized
Loop sys_lambdaAuthorized fileHIL prompt — approved → runs authorized; denied → blocked
Loop sys_lambdaUnauthorized fileAllowed — 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
  }
}
FieldTypeDefaultDescription
code_execution.restricted_methodsstring[]['attestation_issue']Methods that can only be called from authorized code
security.require_middleware_authorizationbooleantrueWhether 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:

MethodWhy Restrict It
get_identityRestricts access to stored credentials and API keys
set_identityPrevents agent from overwriting stored credentials
authorize_fileRestricts the gateway pattern to already-authorized code
model_invokePrevents unauthorized code from making LLM calls (cost control)
loop_injectPrevents 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.

enabledvisiblerestrictedAdvertisedLLM loopAuthorized codeUnauthorized code
false—falseNoOffOffOff
false—trueNoOffFreeOff
truefalsefalseNoFreeFreeFree
truefalsetrueNoHILFreeOff
truetruefalseYesFreeFreeFree
truetruetrueYesHILFreeOff

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:

PathWhy
fs_writeAgent tool — writes content, does not control authorization
sys_update_configAgent tool — modifies config, not file flags
db_executeAgent tool — adf_files is blocked from db_execute
sys_code (inline)No file — inline code has no authorization context
Unauthorized sys_lambdaauthorize_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_write requires manual re-authorization via the UI
  • For production, combine authorized = true with protection = 'read_only' to prevent both modification and deletion

Protection + Authorization Matrix

PhaseauthorizedprotectionNotes
Owner developingfalsenoneNormal development, no authorization needed
Owner testingfalsenoneVerify logic before authorizing
Production lockdowntrueread_onlyAuthorized and immutable
Owner patchingtrue → false, edit, trueread_only → none → read_onlyUnlock, edit (deauthorizes), re-authorize, re-lock

Error Codes

When authorization checks fail, the sandbox receives these error codes:

Error CodeConditionMessage
REQUIRES_AUTHORIZED_CODERestricted 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_CALLERUnauthorized code calls adf.sys_lambda() targeting an authorized fileCannot 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:

  1. Read from system tables only. Query adf_inbox for votes — the agent cannot forge entries in adf_inbox. Never read votes from local_* tables — the agent can freely write those.
  2. Verify sender identity. Check the from field (DID) against a hardcoded or config-stored list of council agent DIDs.
  3. 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

AttackMitigation
Agent calls task_resolve from sys_code to self-approve restricted tool’s HIL taskBlocked — 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 taskBlocked — loop sys_lambda calls run unauthorized, HIL task has requires_authorization: true
Agent writes adf.sys_lambda() in sys_code targeting authorized fileBlocked — REQUIRES_AUTHORIZED_CALLER (unauthorized caller)
Agent modifies an authorized file to inject malicious codeBlocked — fs_write auto-deauthorizes
Agent calls authorize_file from unauthorized codeBlocked — requires authorized caller
Unauthorized middleware modifies messagesBlocked (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.

MethodUnauthorized CodeAuthorized Code
sys_set_metaProtection enforcedProtection bypassed — can overwrite any key
sys_delete_metaProtection enforcedProtection bypassed — can delete any key
fs_writeProtection enforcedProtection bypassed — can overwrite any file, including read_only
fs_deleteProtection enforcedProtection bypassed — can delete any file, including read_only and no_delete
set_meta_protectionNot availableCan change meta key protection level
set_file_protectionNot availableCan change file protection level
authorize_fileNot availableCan 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

ScenarioWhy It’s Acceptable
Agent creates trigger/timer pointing at authorized codeThe 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 lambdaThe 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 loopHIL prompt — the user must approve before the lambda runs with authorization. Denial blocks execution.
Agent re-enables a disabled tool via sys_update_configThe 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 authorizedCode 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 to local_* 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

ComponentFileRole
Schema + migrationsrc/main/adf/adf-database.tsauthorized column on files (v17), requires_authorization on tasks (v18), auto-deauthorize on write
Workspace methodssrc/main/adf/adf-workspace.tsisFileAuthorized(), setFileAuthorized()
Method restrictionsrc/main/runtime/adf-call-handler.tsrestricted_methods check, task-level authorization check in task_resolve, call-chain enforcement, authorize_file handler, authorized meta/file protection bypass
sys_lambda contextsrc/main/tools/built-in/sys-lambda.tool.tsAND-logic: fileAuthorized && callerAuthorized
sys_code contextsrc/main/tools/built-in/sys-code.tool.tsAlways isAuthorized = false
Trigger/timer contextsrc/main/runtime/system-scope-handler.tsSets isAuthorized from file flag
Middleware restrictionsrc/main/services/middleware-executor.tsrequire_middleware_authorization check + context
Type definitionssrc/shared/types/adf-v02.types.tsCodeExecutionConfig.restricted_methods, SecurityConfig.require_middleware_authorization, ToolDeclaration.restricted, FileEntry.authorized
Zod validationsrc/main/adf/adf-schema.tsSchema validation for security config fields
IPCsrc/shared/constants/ipc-channels.ts, src/main/ipc/index.tsDOC_SET_FILE_AUTHORIZED channel
UIsrc/renderer/components/agent/AgentFiles.tsx, src/renderer/components/agent/AgentConfig.tsxFile authorization toggle, security config UI