pub struct ScopeReport {Show 27 fields
pub declared_containment: ContainmentClass,
pub achieved_containment: ContainmentClass,
pub floor_met: bool,
pub shortfall_reason: Option<String>,
pub enforcement_tier: &'static str,
pub userns_grant: Option<UsernsGrant>,
pub filesystem_scope: Option<String>,
pub workdir_exec: bool,
pub read_only_paths: Vec<String>,
pub read_only_advisory_for: Vec<String>,
pub preopens: Vec<PreopenReport>,
pub network_allow: Vec<String>,
pub unix_sockets: bool,
pub shell_allow: Vec<String>,
pub spawn_allow: Vec<String>,
pub env_allow: Vec<String>,
pub plan_submit: bool,
pub interpreter_runtime_grants: Vec<String>,
pub exports_files: Option<ExportsFilesReport>,
pub peer_files: Option<PeerFilesReport>,
pub peer_fetch_allow: Vec<String>,
pub state_stores: Vec<StateStoreReport>,
pub configured_artifacts: Vec<String>,
pub staged_runtime_grants: Vec<String>,
pub runtime_writes: Vec<RuntimeWriteReport>,
pub io_max: IoMaxReport,
pub filesystem_boundary: FilesystemBoundaryReport,
}Expand description
Read-only answer to “what would this capsule actually be allowed to do on this host?”.
Built from the already-parsed CapabilityPolicy plus one host tier probe. Nothing here
stages artifacts, touches a registry, creates a workdir or resolves DNS — it is a diagnostic,
so it reports even when Self::floor_met is false.
Fields§
§declared_containment: ContainmentClassStrongest class any source asked for (manifest / workspace config / --containment).
achieved_containment: ContainmentClassStrongest class this host can actually back, derived from the tier probe alone.
floor_met: boolachieved_containment >= declared_containment. false means mur run would refuse.
shortfall_reason: Option<String>The missing mechanism when floor_met is false; absent otherwise.
enforcement_tier: &'static strThe probed host tier, as a stable wire name.
userns_grant: Option<UsernsGrant>Where this host’s permission to create an unprivileged user namespace comes from, as
UsernsGrant::wire_name, or null off Linux, where AppArmor does not exist and the
question has no answer.
Always serialized, on the same terms as Self::workdir_exec: the key’s absence
identifies a runtime that predates it, not a host that was not asked. Reported because
three very different hosts — no AppArmor, hardening switched off host-wide, and the shipped
profile confining mur — otherwise produce byte-identical reports while differing enormously
in what else on the machine can create a user namespace.
filesystem_scope: Option<String>capabilities.filesystem.scope, verbatim.
workdir_exec: boolcapabilities.filesystem.workdir_exec. Always present (never skipped when false), so a
consumer can tell “declared false” from “this runtime predates the key” by the field’s
presence rather than by its value.
true is the only manifest declaration that lowers Self::achieved_containment, which is
why it is reported next to the grants rather than buried: reading achieved: advisory on a
Landlock-capable host makes sense only alongside it.
read_only_paths: Vec<String>capabilities.filesystem.read_only, verbatim — the workdir subtrees the capsule may read
but must not write. Empty when the capsule declares none, which is the whole workdir
writable. Always serialized as an array (never skipped), on the same terms as
Self::state_stores: an absent key identifies a runtime that predates the field, not a
capsule that declared nothing.
The one field here that subtracts rather than grants, which is why it is reported beside
Self::read_only_advisory_for rather than alone: a protection stated without its
qualification reads as a boundary the capsule does not have.
read_only_advisory_for: Vec<String>The entries of Self::shell_allow that Self::read_only_paths is only advisory
against — the same set W-SEC-017 fires for, resolved by the same
read_only_advisory_for both this report and mur doctor call.
Empty means the declaration is enforced for every readable call. Non-empty names the
binaries whose own file I/O the dispatch-time write-intent analyser cannot read, so the
report cannot claim more than W-SEC-017 already qualifies. Always serialized as an array
(never skipped), on the same terms as Self::read_only_paths, and always empty when
that list is: nothing is advisory against a protection that was never declared.
preopens: Vec<PreopenReport>The filesystem surface every guest-bearing artifact in this capsule is preopened into, one
entry per runtime: tool, runtime: driver and runtime: hook entry, in manifest order.
Empty when the capsule declares only skills. Always serialized as an array (never skipped),
on the same terms as Self::state_stores: an absent key identifies a runtime that
predates the field, not a capsule with no guest artifacts.
This is the field that answers “what can this tool read and write?”.
Self::filesystem_scope above does not: it is the capsule-wide declaration, and it is
joined into no preopen root.
Copied through untouched, exactly as the reports beside it are — resolving a preopen changes no class, no floor and no tier.
network_allow: Vec<String>capabilities.network.allow, verbatim — declared destinations, not resolved IPs.
unix_sockets: boolcapabilities.network.unix_sockets.
shell_allow: Vec<String>capabilities.shell.allow, verbatim.
spawn_allow: Vec<String>capabilities.spawn.allow, verbatim.
env_allow: Vec<String>capabilities.env.allow, verbatim.
plan_submit: boolcapabilities.plan.submit — whether the model is offered the submit-plan tool. Always
serialized (never skipped when false), on the same terms as Self::workdir_exec: an
absent key identifies a runtime that predates the grant, not a capsule that declined it.
It opens no host resource of its own. It is reported beside the grants because it changes how much of every other grant one model turn can reach: a plan runs many steps against this capsule’s own tools, shell allowlist and spawn grant without a turn in between.
interpreter_runtime_grants: Vec<String>Every host directory a capabilities.shell.interpreter_runtime grant opens, rendered as
<binary>: <dir>[ (list_dir)]. These are the paths outside the workdir that stay
reachable even at scoped.
exports_files: Option<ExportsFilesReport>The read-only file surface exports.files declares, or null when the capsule declares
none. Always serialized (never skipped), so a consumer can tell “this runtime predates
exports” from “this capsule declined to export anything” by the field’s presence rather
than by its value — the same terms Self::workdir_exec is reported on.
An export is a disclosure, not a grant: it never appears in
Self::achieved_containment, and declaring one cannot change any other field of this
report. It is printed beside the grants because it is the other direction of the same
question — what crosses the capsule boundary, and which way.
peer_files: Option<PeerFilesReport>The peer-handoff surface exports.peer_files declares, or null when the capsule
declares none. Always serialized, on the same terms as Self::exports_files.
A separate authoriser from exports.files, over a separate subtree: a capsule may declare
either, both or neither, and this field says nothing about the other. Like every export it
is a disclosure rather than a grant, and declaring it cannot change
Self::achieved_containment, Self::enforcement_tier or Self::floor_met.
peer_fetch_allow: Vec<String>capabilities.peer_fetch.allow, verbatim — the peers this capsule may redeem a handle
against. Empty when the block is absent, which is deny.
A separate list from Self::network_allow and never an alias for it: a destination
reachable by send is not redeemable unless it also appears here, because ingesting a
peer’s bytes into this workdir is a different question from being allowed to talk to it.
state_stores: Vec<StateStoreReport>Every durable state store this session’s artifacts declare, one entry per declaring
artifact, empty when none does. Always serialized as an array (never skipped), on the same
terms as Self::exports_files: an absent key identifies a runtime that predates state
stores, not a capsule that declined one.
Declaring a store cannot change Self::achieved_containment, Self::floor_met or
Self::enforcement_tier — it is copied through untouched, exactly as the export reports
beside it are. It is reported because it is the one grant that opens a path outside every
workdir, which nothing else in this block would reveal.
configured_artifacts: Vec<String>Every artifact whose manifest entry declares a config: block, in manifest order, empty
when none does. Always serialized as an array (never skipped), on the same terms as
Self::state_stores: an absent key identifies a runtime that predates artifact config,
not a capsule that declined it.
Names only. What an artifact is configured with is operator-authored plaintext that
belongs in the manifest and in nothing this report — or trace.jsonl, which carries this
report verbatim — writes out.
Declaring config cannot change Self::achieved_containment, Self::floor_met or
Self::enforcement_tier: it adds one environment variable to one guest and reaches no
grant. Reported because a variable an artifact silently depends on is otherwise invisible
to an operator reading the capsule’s scope.
staged_runtime_grants: Vec<String>Every capabilities.shell.staged_runtime grant, rendered as
<binary>: <source_path> (pin: <pin>). These are host runtime trees a sealed capsule
asks to have bind-mounted read-only into its composed root.
Reported whatever the floor and whatever this host can back — a capsule that declares a
staged runtime and cannot run here is exactly the case an operator is inspecting when they
reach for --explain-scope, so hiding the grant behind a met floor would blank out the
diagnostic in the one case it is for.
runtime_writes: Vec<RuntimeWriteReport>Every path the runtime itself writes inside the capsule’s workdir, so a consumer for whom
that workdir is the deliverable can subtract them and be left with what the capsule
changed. Always serialized as an array (never skipped), on the same terms as
Self::state_stores: an absent key identifies a runtime that predates the declaration,
not a runtime that writes nothing.
The one field here that describes what the runtime does rather than what the capsule may
do. It is reported beside the grants because it answers the other half of “what is this
workdir”, and because a consumer that has to maintain its own list of runtime entries
silently falls behind every time the set changes — enabling sealed being exactly that
case.
Keyed on the tier this session would install: Self::enforcement_tier capped by
Self::declared_containment, which is what decides whether a composed root is built at
all. Reading enforcement_tier: mountns+pivot_root+landlock+seccomp next to a report with
no sealed rows is that cap, not a disagreement — the host could seal and the capsule did
not ask. See crate::workdir_writes.
io_max: IoMaxReportThe declared capabilities.resources.cgroup_io_bytes_per_sec ceiling, and whether it is
actually on this session’s cgroup scope.
Always serialized (never skipped), on the same terms as Self::workdir_exec and
Self::state_stores: an absent key identifies a runtime that predates the field, not a
host that was not asked. IoMaxStatus::NotProbed is what “not asked” looks like, and it
is a value in the report rather than a missing key.
The one field here filled from a performed operation rather than from the policy: at
launch it carries the outcome of the real io.max write, established before session_start
is written so trace.jsonl cannot claim a ceiling the kernel refused; under
--explain-scope it carries the outcome of the same write against a throwaway cgroup that
is created and removed. Declaring a ceiling that does not apply changes no class and refuses
no launch — io.max is the one non-fatal cgroup limit — so this field is the only place
the shortfall appears.
filesystem_boundary: FilesystemBoundaryReportWhat this session’s filesystem mechanism does not restrict — the block --explain-scope
prints as Not protected here, directly under Containment.
Always serialized (never skipped), on the same terms as Self::state_stores: an absent
key identifies a runtime that predates the field, and an empty
FilesystemBoundaryReport::not_protected identifies a session with nothing to disclaim.
Derived in [filesystem_boundary] from [crate::sandbox::applied_tier] — the same value
Self::runtime_writes is keyed on — and from no CapabilityPolicy field. Not from
Self::achieved_containment, which reports the class this host can back: a capsule
declaring less than sealed on a sealed-capable host reads achieved: sealed and composes
no root, so a boundary keyed on the class would report an absence the capsule does not have.
It stands beside the grants rather than among them because it qualifies every one at once.
Implementations§
Trait Implementations§
Source§impl Clone for ScopeReport
impl Clone for ScopeReport
Source§fn clone(&self) -> ScopeReport
fn clone(&self) -> ScopeReport
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for ScopeReport
impl Debug for ScopeReport
impl Eq for ScopeReport
Source§impl PartialEq for ScopeReport
impl PartialEq for ScopeReport
Source§impl Serialize for ScopeReport
impl Serialize for ScopeReport
impl StructuralPartialEq for ScopeReport
Auto Trait Implementations§
impl Freeze for ScopeReport
impl RefUnwindSafe for ScopeReport
impl Send for ScopeReport
impl Sync for ScopeReport
impl Unpin for ScopeReport
impl UnsafeUnpin for ScopeReport
impl UnwindSafe for ScopeReport
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more