Execution trust model

Official signature trust, permission enforcement, executor capabilities, and immutable remote execution.

Adversary execution trust model

Purpose

An adversary is executable code. The CLI must preserve a fast local workflow without treating every remote package as equally trustworthy. Official signature verification, manifest permissions, executor selection, executor capabilities, and the final policy decision are separate inputs. None of them substitutes for another.

This document specifies:

  • which installed packages may use host execution;
  • how official catalog signatures are verified;
  • how manifests request portable isolation boundaries;
  • how the CLI verifies that the selected executor can enforce mandatory boundaries;
  • how mutable remote references are pinned to a digest; and
  • how a user's teams can become trusted without trusting spoofable names (proposed).

For envelope format, key ids, and signing operations, see Official signatures.

Security boundary at a glance

Rendering diagram…

The policy decision is made before the child process starts. A digest proves which bytes are selected; a valid official signature over that digest proves Adversary Labs endorsed those bytes for host execution. Neither is code review or malware analysis.

Terms

TermMeaning
Local sourceA project selected by an explicit filesystem path, such as . or ../my-adversary, and not materialized from the artifact store.
Official signatureEd25519 signature over the artifact content digest, attached as an OCI referrer and verified with a public key baked into the CLI.
Official-signedAn installed package whose stored signature envelope verifies for its digest with this CLI binary’s keyring.
UntrustedAny installed remote package without a valid official signature. Untrusted does not mean malicious; it means host execution is not implicitly authorized.
Requested boundaryIsolation described by the manifest or CLI. Organization policy may reject the request.
Mandatory boundaryA requested boundary that must be enforced by the selected executor.
ExecutorThe backend that launches adversary code and truthfully reports its enforceable capabilities.

Current trust policy

The production policy (OfficialSignatureTrustPolicy) has exactly two automatic trust cases:

  1. Explicit local source is trusted as local-source (the developer chose the path).
  2. Installed packages with a verified official signature are trusted as trusted-publisher for host execution.

Everything else is untrusted (unknown-publisher in the policy enum). In particular:

  • path allowlists and registry hostnames alone do not grant trust;
  • adversarylabs/* or a domain/name catalog id is not trusted unless the signature verifies;
  • a remote artifact does not become local source merely because it has been pulled and materialized on disk; and
  • a user's team namespace is not yet trusted automatically (see proposed team trust below).
Rendering diagram…

Released CLI binaries embed only the production public key (official-prod). Dev builds embed only the dev key (official-dev). See Official signatures.

Trust and backend decision table

Source/trustHostExecutorSandbox executorUser-facing behavior
Explicit local sourceAllowedAllowed when availableNo trust warning. Host is the default.
Official-signed installed packageAllowedAllowed when availableReport publisher identity, resolved digest, and backend. No alarm-style warning.
Untrusted installed packageBlocked by defaultAllowed when the executor satisfies policyNon-interactive: fail with guidance to use --allow-unsafe-host-execution or a sandbox. Interactive TTY: prompt Run anyway? [y/N].
Untrusted with --allow-unsafe-host-execution or TTY yesAllowedNot requiredPrint an explicit untrusted-host warning naming the adversary and digest.

The unsafe override changes only the signature-trust decision. It does not add isolation, grant permissions forbidden by organization policy, or let the CLI ignore mandatory manifest or CLI requirements.

Untrusted host UX

When host execution would run an untrusted package:

  1. If stdin and stderr are TTYs, the CLI prompts before launch and accepts only y / yes (default is no). Ctrl+C cancels the prompt.
  2. Otherwise the run fails unless --allow-unsafe-host-execution is set.
  3. If the override (flag or confirm) is accepted, stderr prints a clear untrusted warning; the process still runs with full user privileges.

Executor model

go
type Executor interface {
    Run(context.Context, RuntimeSpec) (RuntimeResult, error)
    Backend() ExecutorBackend
    Capabilities() ExecutorCapabilities
}

type ExecutorCapabilities struct {
    FilesystemReadIsolation  bool
    FilesystemWriteIsolation bool
    EnvironmentIsolation     bool
    NetworkIsolation         bool
    CPULimits                bool
    MemoryLimits             bool
    ProcessLimits            bool
}

HostExecutor

HostExecutor is a first-class backend and is the correct default for trusted local development and official-signed catalog packages. It launches a normal process with the user's operating-system authority. It currently reports every isolation capability as false.

Consequently, host-executed code may access resources available to the user, including files, environment variables, credentials, processes, and the network. Signature trust authorizes this risk; it does not reduce it.

NativeSandboxExecutor

This backend identity is reserved for native OS isolation. Linux Landlock is the first implementation target. The backend must report only capabilities it actually enforces for the current platform and invocation.

macOS sandboxing is a separate future backend effort. It is not a prerequisite for local HostExecutor use.

Manifest permissions

The canonical manifest syntax is:

yaml
permissions:
  enforcement: advisory # advisory is the default; required is fail-closed
  filesystem:
    read:
      - src
    write:
      - .adversary-output
  network: false
  environment:
    allow:
      - PATH
      - HOME

The old permissions.env spelling is not supported.

Manifest fieldRequested capability
Non-empty filesystem.readFilesystemReadIsolation
Non-empty filesystem.writeFilesystemWriteIsolation
network: falseNetworkIsolation
Non-empty environment.allowEnvironmentIsolation

Empty lists request no boundary. enforcement: advisory records the portable isolation request but does not require the current executor to enforce it. enforcement: required makes every requested manifest boundary mandatory.

--no-network always requests mandatory network isolation, regardless of the manifest's enforcement mode. Because HostExecutor reports NetworkIsolation: false, --no-network fails before host launch.

Permission evaluation

Rendering diagram…

The checks answer different questions:

  1. Requested: What isolation does the manifest or caller describe?
  2. Allowed: Does organization policy permit that request for this trust identity?
  3. Required: Which requested boundaries are mandatory for this run?
  4. Supported: Can the selected executor enforce every mandatory boundary?
  5. Trusted: May this package use the selected backend, especially host execution? (Local path selection or verified official signature.)

No unsupported mandatory boundary is silently ignored.

Immutable remote execution

Mutable references are resolved exactly once per run:

Rendering diagram…

The CLI never resolves the tag again during that execution. For remote runs it reports publisher identity, immutable digest, and backend on stderr so structured review output on stdout remains valid.

For an untrusted package using the unsafe override or TTY confirm, the CLI additionally emits a prominent untrusted warning.

Pull and offline verification

On adversary pull, the CLI best-effort fetches the official signature referrer, verifies it with the binary’s keyring, and stores it under the local repository so later adversary run can trust host execution offline.

If no valid signature is available at pull or run time, the package remains untrusted for host execution.

Authenticated team trust: proposed extension

Automatically trusting “my team” is desirable only when the relationship is authenticated. A namespace string, email domain, team display name, local configuration value, or successful registry login is insufficient evidence.

Today, team-published packages without an official signature remain untrusted for host execution (same as any third-party artifact).

Required server assertion

The authenticated identity response should provide stable IDs and explicit registry bindings, for example:

json
{
  "subject": "user_01...",
  "teams": [
    {
      "id": "team_01...",
      "slug": "acme-security",
      "role": "member",
      "publishers": [
        {
          "registry": "registry.adversarylabs.ai",
          "namespace": "acme-security"
        }
      ]
    }
  ]
}

The API must derive this mapping from authenticated team membership and registry ownership records. The client must not construct it from a display name or assume that a team slug owns an identically named namespace.

Proposed trust rule

A remote publisher becomes trusted for the current profile when all of the following are true (in addition to any official-signature path above):

  1. The CLI has a currently valid authenticated identity from the configured Adversary Labs API.
  2. The identity asserts active membership in a team.
  3. That team assertion explicitly binds the artifact's canonical registry and namespace.
  4. The artifact resolved from that same registry to the digest being executed.

Any mismatch or unavailable assertion produces untrusted, not a best-effort trust result.

Failure and lifecycle rules

  • Logged-out and offline runs do not automatically trust team publishers.
  • Expired, malformed, or unverifiable identity assertions fail to untrusted.
  • Switching API URL, registry host, or CLI profile invalidates the applicable trust assertion.
  • Logout removes cached authenticated team trust.
  • Team removal or namespace transfer must take effect no later than the identity assertion's bounded expiry; high-assurance deployments may require online validation for every run.
  • Team trust authorizes host execution but never overrides mandatory permission enforcement.
  • Enterprise trust stores may add or remove trusted identities independently; their precedence and audit logging must be explicit.

Examples

Local source

sh
adversary run . --path ../project
  • Trust: local-source
  • Default backend: HostExecutor
  • Warning: none
  • Digest display: not applicable to source selected directly from disk

Official-signed catalog package

sh
adversary pull go/security
adversary run go/security --path ../project
  • Trust: verified official signature for the resolved digest
  • Default backend: HostExecutor
  • Output: identity, immutable digest, backend

Official free-catalog packages (domain/name ids such as go/security, ci/github-actions) are signed at release with official-prod. A current release CLI verifies those signatures automatically.

Untrusted remote package

sh
adversary run ghcr.io/acme/private-review:1.0.0 --path ../project

Without a valid official signature, host execution is blocked unless a capable sandbox is selected, the caller confirms on a TTY, or they pass:

sh
adversary run ghcr.io/acme/private-review:1.0.0 \
  --path ../project \
  --allow-unsafe-host-execution

Mandatory network isolation on host

sh
adversary run . --path ../project --no-network

This fails before launch because HostExecutor cannot enforce network isolation. The fact that local source is trusted does not change executor capabilities.

Non-goals

This model does not claim that:

  • signature verification is code review or malware analysis;
  • a digest alone proves publisher authenticity (the signature over the digest does);
  • HostExecutor provides isolation;
  • a future sandbox provides capabilities it does not explicitly report;
  • complete macOS App Sandbox, Windows AppContainer, or container execution is currently implemented; or
  • manifest permissions and signature trust are interchangeable.

Implementation invariants

Changes to the CLI, API, registry, or trust store must preserve these rules:

  1. Explicit local source continues to work with HostExecutor without friction.
  2. Untrusted remote packages never receive silent host execution.
  3. The unsafe override and TTY confirm always produce an explicit warning.
  4. Mandatory permissions fail closed when unsupported.
  5. Executors report guarantees truthfully and per platform.
  6. Remote mutable references resolve once and execution uses that digest.
  7. Remote execution reports identity, digest, and backend.
  8. Official trust comes only from verifying an official signature (or future authenticated team assertions), never from a path or hostname match alone.