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
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
| Term | Meaning |
|---|---|
| Local source | A project selected by an explicit filesystem path, such as . or ../my-adversary, and not materialized from the artifact store. |
| Official signature | Ed25519 signature over the artifact content digest, attached as an OCI referrer and verified with a public key baked into the CLI. |
| Official-signed | An installed package whose stored signature envelope verifies for its digest with this CLI binary’s keyring. |
| Untrusted | Any installed remote package without a valid official signature. Untrusted does not mean malicious; it means host execution is not implicitly authorized. |
| Requested boundary | Isolation described by the manifest or CLI. Organization policy may reject the request. |
| Mandatory boundary | A requested boundary that must be enforced by the selected executor. |
| Executor | The backend that launches adversary code and truthfully reports its enforceable capabilities. |
Current trust policy
The production policy (OfficialSignatureTrustPolicy) has exactly two automatic
trust cases:
- Explicit local source is trusted as
local-source(the developer chose the path). - Installed packages with a verified official signature are trusted as
trusted-publisherfor 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).
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/trust | HostExecutor | Sandbox executor | User-facing behavior |
|---|---|---|---|
| Explicit local source | Allowed | Allowed when available | No trust warning. Host is the default. |
| Official-signed installed package | Allowed | Allowed when available | Report publisher identity, resolved digest, and backend. No alarm-style warning. |
| Untrusted installed package | Blocked by default | Allowed when the executor satisfies policy | Non-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 yes | Allowed | Not required | Print 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:
- If stdin and stderr are TTYs, the CLI prompts before launch and accepts only
y/yes(default is no). Ctrl+C cancels the prompt. - Otherwise the run fails unless
--allow-unsafe-host-executionis set. - If the override (flag or confirm) is accepted, stderr prints a clear untrusted warning; the process still runs with full user privileges.
Executor model
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:
permissions:
enforcement: advisory # advisory is the default; required is fail-closed
filesystem:
read:
- src
write:
- .adversary-output
network: false
environment:
allow:
- PATH
- HOMEThe old permissions.env spelling is not supported.
| Manifest field | Requested capability |
|---|---|
Non-empty filesystem.read | FilesystemReadIsolation |
Non-empty filesystem.write | FilesystemWriteIsolation |
network: false | NetworkIsolation |
Non-empty environment.allow | EnvironmentIsolation |
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
The checks answer different questions:
- Requested: What isolation does the manifest or caller describe?
- Allowed: Does organization policy permit that request for this trust identity?
- Required: Which requested boundaries are mandatory for this run?
- Supported: Can the selected executor enforce every mandatory boundary?
- 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:
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:
{
"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):
- The CLI has a currently valid authenticated identity from the configured Adversary Labs API.
- The identity asserts active membership in a team.
- That team assertion explicitly binds the artifact's canonical registry and namespace.
- 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
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
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
adversary run ghcr.io/acme/private-review:1.0.0 --path ../projectWithout a valid official signature, host execution is blocked unless a capable sandbox is selected, the caller confirms on a TTY, or they pass:
adversary run ghcr.io/acme/private-review:1.0.0 \
--path ../project \
--allow-unsafe-host-executionMandatory network isolation on host
adversary run . --path ../project --no-networkThis 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:
- Explicit local source continues to work with HostExecutor without friction.
- Untrusted remote packages never receive silent host execution.
- The unsafe override and TTY confirm always produce an explicit warning.
- Mandatory permissions fail closed when unsupported.
- Executors report guarantees truthfully and per platform.
- Remote mutable references resolve once and execution uses that digest.
- Remote execution reports identity, digest, and backend.
- Official trust comes only from verifying an official signature (or future authenticated team assertions), never from a path or hostname match alone.