use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Verification {
Verified { evidence: String },
Absent {
deviation: &'static str,
reason: String,
},
}
impl Verification {
#[must_use]
pub fn is_verified(&self) -> bool {
matches!(self, Self::Verified { .. })
}
#[must_use]
pub fn deviation(&self) -> Option<&'static str> {
match self {
Self::Absent { deviation, .. } => Some(deviation),
Self::Verified { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FailClosed {
pub deviation: &'static str,
pub reason: String,
}
impl fmt::Display for FailClosed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"refused (fail-closed): OCAP invariant '{}' is not enforced — {}",
self.deviation, self.reason
)
}
}
impl std::error::Error for FailClosed {}
fn require(v: Verification) -> Result<(), FailClosed> {
match v {
Verification::Verified { .. } => Ok(()),
Verification::Absent { deviation, reason } => Err(FailClosed { deviation, reason }),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum B1Property {
FilesystemConfinement,
DirectNetworkEgressDenied,
MediatedEgressOnly,
EnvironmentIsolation,
InheritedHandleIsolation,
ProcessTreeContainment,
CredentialIsolation,
DisclosureFiltering,
FailClosedEnforcement,
}
impl B1Property {
pub const REQUIRED: [Self; 9] = [
Self::FilesystemConfinement,
Self::DirectNetworkEgressDenied,
Self::MediatedEgressOnly,
Self::CredentialIsolation,
Self::EnvironmentIsolation,
Self::InheritedHandleIsolation,
Self::ProcessTreeContainment,
Self::DisclosureFiltering,
Self::FailClosedEnforcement,
];
#[must_use]
pub fn name(self) -> &'static str {
match self {
Self::FilesystemConfinement => "filesystem-confinement",
Self::DirectNetworkEgressDenied => "direct-network-egress-denied",
Self::MediatedEgressOnly => "mediated-egress-only",
Self::EnvironmentIsolation => "environment-isolation",
Self::InheritedHandleIsolation => "inherited-handle-isolation",
Self::ProcessTreeContainment => "process-tree-containment",
Self::CredentialIsolation => "credential-isolation",
Self::DisclosureFiltering => "disclosure-filtering",
Self::FailClosedEnforcement => "fail-closed-enforcement",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct B1Floor {
unmet: Vec<B1Property>,
}
impl B1Floor {
#[must_use]
pub fn live_attacker_path() -> Self {
Self {
unmet: vec![
B1Property::MediatedEgressOnly,
B1Property::InheritedHandleIsolation,
B1Property::ProcessTreeContainment,
],
}
}
#[must_use]
pub fn first_unmet(&self) -> Option<B1Property> {
B1Property::REQUIRED
.into_iter()
.find(|p| self.unmet.contains(p))
}
#[must_use]
pub fn is_complete(&self) -> bool {
self.unmet.is_empty()
}
}
#[must_use]
pub fn verify_b1() -> Verification {
let floor = B1Floor::live_attacker_path();
let reason = match floor.first_unmet() {
Some(p) => format!(
"b1 credential-floor property not proven on the live attacker-exec path \
(run_command → agent-bridle shell): {} — DIRECT off-box socket creation \
IS now denied there (seccomp, agent-bridle 0.7.15 DenyDirect), but there \
is no mediated-egress broker (deferred, #1599) and a confined child can \
still reach a host AF_UNIX deputy (local-deputy-egress), so egress is not \
broker-only; fd hygiene on this path is CLOEXEC-based and cgroup \
subtree-kill is not wired here. b1 is a set of PROVEN properties, each met \
by ANY sufficient mechanism, not a fixed uid-ns/netns stack",
p.name()
),
None => "b1 structural floor complete but live-session enforcement is \
unproven (per-session evidence seam + canary grounding test pending)"
.to_string(),
};
Verification::Absent {
deviation: "b1-os-isolation",
reason,
}
}
#[must_use]
pub fn verify_disclosure_gate() -> Verification {
const DEVIATION: &str = "disclosure-gate-live-path";
SESSION_DISCLOSURE.with(|slot| match &*slot.borrow() {
None => Verification::Absent {
deviation: DEVIATION,
reason: "no session disclosure filter is installed on this thread, so \
`redact_session_ingress` is the identity function here — the \
memory / observation / compaction / spill funnels would not \
value-filter. Expected outside a live turn; inside one it means \
the turn is running on a thread that never installed the guard."
.into(),
},
Some(filter) if !filter.redacts_what_it_registered() => Verification::Absent {
deviation: DEVIATION,
reason: if filter.is_empty() {
"a session disclosure filter is installed on this thread but has no \
registered secrets, so it would catch nothing"
.into()
} else {
"the session disclosure filter installed on this thread failed its own \
redaction probe — a registered value survived `redact`"
.into()
},
},
Some(_) => Verification::Verified {
evidence: "probed live on this thread: the installed session filter redacted \
every value it has registered, so the tool-result, summary, memory \
and repeat-steer model-ingress funnels all value-filter (the last \
via the TLS backstop); guarded by \
no_model_ingress_funnel_leaks_a_registered_session_secret + \
redact_secrets_value_filters_a_registered_session_secret + \
repeat_steer_value_filters_a_registered_session_secret"
.into(),
},
})
}
#[derive(Debug, Clone)]
pub struct ScopedCredential {
pub label: String,
}
pub fn seed_live_credential(cred: &ScopedCredential) -> Result<(), FailClosed> {
require(verify_b1())?;
require(verify_disclosure_gate())?;
let _ = cred;
Ok(())
}
pub fn admit_untrusted_remote(voice_fingerprint: &str) -> Result<(), FailClosed> {
require(verify_b1())?;
let _ = voice_fingerprint;
Ok(())
}
use base64::Engine as _;
#[derive(Debug, Default, Clone)]
pub struct DisclosureFilter {
secrets: Vec<String>,
}
impl DisclosureFilter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, secret: impl Into<String>) {
let s = secret.into();
if !s.is_empty() {
self.secrets.push(s);
}
}
fn encodings(secret: &str) -> Vec<String> {
use base64::engine::general_purpose::{
STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD,
};
let bytes = secret.as_bytes();
let hex_lower: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
let hex_upper: String = bytes.iter().map(|b| format!("{b:02X}")).collect();
let pct_upper: String = bytes.iter().map(|b| format!("%{b:02X}")).collect();
let pct_lower: String = bytes.iter().map(|b| format!("%{b:02x}")).collect();
let esc_x: String = bytes.iter().map(|b| format!("\\x{b:02x}")).collect();
let esc_u: String = bytes.iter().map(|b| format!("\\u{b:04x}")).collect();
let mut forms = vec![
secret.to_string(),
STANDARD.encode(bytes),
STANDARD_NO_PAD.encode(bytes),
URL_SAFE.encode(bytes),
URL_SAFE_NO_PAD.encode(bytes),
hex_lower,
hex_upper,
pct_upper,
pct_lower,
esc_x,
esc_u,
];
forms.sort();
forms.dedup();
forms
}
fn strip_ws(text: &str) -> String {
text.chars().filter(|c| !c.is_whitespace()).collect()
}
#[must_use]
pub fn leaks(&self, text: &str) -> bool {
let normalized = Self::strip_ws(text);
self.secrets.iter().any(|s| {
Self::encodings(s)
.iter()
.any(|e| text.contains(e.as_str()) || normalized.contains(e.as_str()))
})
}
#[must_use]
pub fn redact(&self, text: &str) -> String {
let mut out = text.to_string();
for s in &self.secrets {
for enc in Self::encodings(s) {
out = out.replace(enc.as_str(), "[REDACTED]");
}
}
if self.leaks(&out) {
return "[REDACTED: withheld — disclosed a registered secret]".to_string();
}
out
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.secrets.is_empty()
}
#[must_use]
pub fn redacts_what_it_registered(&self) -> bool {
!self.secrets.is_empty()
&& self.secrets.iter().all(|s| {
Self::encodings(s).iter().all(|enc| {
let probe = format!("probe prefix {enc} probe suffix");
!self.leaks(&self.redact(&probe))
})
})
}
}
#[must_use]
pub fn session_disclosure_filter(api_key: Option<&str>) -> DisclosureFilter {
let mut filter = DisclosureFilter::new();
if let Some(key) = api_key {
if key.len() >= 8 {
filter.register(key);
}
}
filter
}
std::thread_local! {
static SESSION_DISCLOSURE: std::cell::RefCell<Option<DisclosureFilter>> =
const { std::cell::RefCell::new(None) };
}
#[must_use]
pub struct ScopedSessionDisclosure {
previous: Option<DisclosureFilter>,
_thread_bound: std::marker::PhantomData<std::rc::Rc<()>>,
}
impl Drop for ScopedSessionDisclosure {
fn drop(&mut self) {
let prev = self.previous.take();
let _ = SESSION_DISCLOSURE.try_with(|slot| *slot.borrow_mut() = prev);
}
}
pub fn scoped_session_disclosure(filter: DisclosureFilter) -> ScopedSessionDisclosure {
let previous = SESSION_DISCLOSURE.with(|slot| slot.borrow_mut().replace(filter));
ScopedSessionDisclosure {
previous,
_thread_bound: std::marker::PhantomData,
}
}
#[must_use]
pub fn redact_session_ingress(text: &str) -> String {
SESSION_DISCLOSURE.with(|slot| match &*slot.borrow() {
Some(filter) => filter.redact(text),
None => text.to_string(),
})
}
#[must_use]
pub fn proposer_distinct(proposer_fp: &str, worker_fp: &str) -> bool {
!proposer_fp.is_empty() && proposer_fp != worker_fp
}
#[must_use]
pub fn verify_sod(proposer_fp: &str, worker_fp: &str) -> Verification {
if !proposer_distinct(proposer_fp, worker_fp) {
return Verification::Absent {
deviation: "sod-proposer-not-worker",
reason: "proposer key is not distinct from the worker (self-proposal)".into(),
};
}
Verification::Absent {
deviation: "sod-proposer-not-worker",
reason: "distinct proposer key confirmed, but taint-aware observe-then-propose is unbuilt"
.into(),
}
}
pub fn auto_apply_policy(proposer_fp: &str, worker_fp: &str) -> Result<(), FailClosed> {
require(verify_sod(proposer_fp, worker_fp))?;
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Guarantee {
FsConfinement,
ProcessConfinement,
NetworkConfinement,
EnvIsolation,
CredentialIsolation,
DisclosureFiltering,
FailClosedExecution,
}
impl Guarantee {
pub const ALL: [Self; 7] = [
Self::FsConfinement,
Self::ProcessConfinement,
Self::NetworkConfinement,
Self::EnvIsolation,
Self::CredentialIsolation,
Self::DisclosureFiltering,
Self::FailClosedExecution,
];
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::FsConfinement => "fs-confinement",
Self::ProcessConfinement => "process-confinement",
Self::NetworkConfinement => "network-confinement",
Self::EnvIsolation => "env-isolation",
Self::CredentialIsolation => "credential-isolation",
Self::DisclosureFiltering => "disclosure-filtering",
Self::FailClosedExecution => "fail-closed-execution",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Achieved {
Enforced { evidence: String },
Unverified {
deviation: &'static str,
reason: String,
},
Unsupported { reason: String },
}
impl Achieved {
#[must_use]
pub fn is_enforced(&self) -> bool {
matches!(self, Self::Enforced { .. })
}
}
#[derive(Debug)]
pub struct PlatformCeiling {
pub platform: &'static str,
table: [(Guarantee, Option<&'static str>); 7],
}
impl PlatformCeiling {
#[must_use]
pub fn cannot_provide(&self, g: Guarantee) -> Option<&'static str> {
for (row, reason) in &self.table {
if *row == g {
return *reason;
}
}
Some("guarantee not present in this platform's ceiling table")
}
}
pub const LINUX_CEILING: PlatformCeiling = PlatformCeiling {
platform: "linux",
table: [
(Guarantee::FsConfinement, None),
(Guarantee::ProcessConfinement, None),
(Guarantee::NetworkConfinement, None),
(Guarantee::EnvIsolation, None),
(Guarantee::CredentialIsolation, None),
(Guarantee::DisclosureFiltering, None),
(Guarantee::FailClosedExecution, None),
],
};
pub const MACOS_CEILING: PlatformCeiling = PlatformCeiling {
platform: "macos",
table: [
(
Guarantee::FsConfinement,
Some("no openat2(RESOLVE_BENEATH); lexical fallback is not object-bound"),
),
(
Guarantee::ProcessConfinement,
Some("Seatbelt floor unbuilt/unverified (no macOS runner); B1 unsupported here"),
),
(
Guarantee::NetworkConfinement,
Some("no kernel-backed default-deny egress on this platform"),
),
(Guarantee::EnvIsolation, None),
(
Guarantee::CredentialIsolation,
Some("without fs/process confinement, ambient credentials are reachable"),
),
(Guarantee::DisclosureFiltering, None),
(Guarantee::FailClosedExecution, None),
],
};
pub const WINDOWS_CEILING: PlatformCeiling = PlatformCeiling {
platform: "windows",
table: [
(
Guarantee::FsConfinement,
Some("no openat2(RESOLVE_BENEATH); lexical fallback is not object-bound"),
),
(
Guarantee::ProcessConfinement,
Some("AppContainer floor unbuilt; B1 unsupported here"),
),
(
Guarantee::NetworkConfinement,
Some("no kernel-backed default-deny egress on this platform"),
),
(Guarantee::EnvIsolation, None),
(
Guarantee::CredentialIsolation,
Some("without fs/process confinement, ambient credentials are reachable"),
),
(Guarantee::DisclosureFiltering, None),
(Guarantee::FailClosedExecution, None),
],
};
pub const UNKNOWN_CEILING: PlatformCeiling = PlatformCeiling {
platform: "unknown",
table: [
(Guarantee::FsConfinement, Some("unrecognized platform")),
(Guarantee::ProcessConfinement, Some("unrecognized platform")),
(Guarantee::NetworkConfinement, Some("unrecognized platform")),
(Guarantee::EnvIsolation, Some("unrecognized platform")),
(
Guarantee::CredentialIsolation,
Some("unrecognized platform"),
),
(
Guarantee::DisclosureFiltering,
Some("unrecognized platform"),
),
(
Guarantee::FailClosedExecution,
Some("unrecognized platform"),
),
],
};
#[must_use]
pub fn current_platform_ceiling() -> &'static PlatformCeiling {
#[cfg(target_os = "linux")]
{
&LINUX_CEILING
}
#[cfg(target_os = "macos")]
{
&MACOS_CEILING
}
#[cfg(windows)]
{
&WINDOWS_CEILING
}
#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
{
&UNKNOWN_CEILING
}
}
#[must_use]
pub fn verify_fs_object_bound() -> Verification {
#[cfg(target_os = "linux")]
{
Verification::Verified {
evidence: "fs_cap::WorkspaceDir: openat2(RESOLVE_BENEATH|NO_MAGICLINKS) object-bound \
resolve on every access; ENOSYS kernels refuse rather than degrade \
(register: fs-canonical-containment closed on Linux)"
.into(),
}
}
#[cfg(not(target_os = "linux"))]
{
Verification::Absent {
deviation: "fs-canonical-containment",
reason: "no openat2 on this platform; only the lexical-prefix fallback exists".into(),
}
}
}
#[must_use]
pub fn verify_constrained_executor() -> Verification {
if crate::confined_exec::kernel_fs_fence_available() {
Verification::Verified {
evidence: "ConstrainedExecutor routes every attacker-influenced spawn through \
agent_bridle::ConfinedCommand under a Kernel strength floor: the child is \
env-cleared (only explicit grants) and the fs fence is kernel-enforced, \
fail-closed when it cannot be; the CI spawn-inventory gate holds \
agent-exec-todo-p4 at 0 so no attacker spawn bypasses it \
(constrained_executor_truth.rs ties the gate to this verifier)"
.into(),
}
} else {
Verification::Absent {
deviation: "p4-constrained-executor",
reason: "the kernel fs fence is unavailable on this host — an AgentInfluenced spawn \
refuses (fail-closed) rather than confine \
(see docs/security/spawn-inventory.toml)"
.into(),
}
}
}
#[must_use]
pub fn verify_fail_closed_execution() -> Verification {
#[cfg(target_os = "linux")]
{
Verification::Verified {
evidence: "require()/FailClosed capability gates; OCAP-DANGER/OCAP-GATE pairing \
statically enforced in CI (scripts/ocap_check.py); confined-by-default \
launch lane (resolve_lane: Yolo only via explicit --unsafe-host-exec)"
.into(),
}
}
#[cfg(not(target_os = "linux"))]
{
Verification::Absent {
deviation: "fs-canonical-containment",
reason: "untrusted fs falls back to lexical containment off Linux — a silent \
downgrade, not a refusal"
.into(),
}
}
}
#[must_use]
pub fn verify_network_confinement() -> Verification {
#[cfg(target_os = "linux")]
{
if crate::netguard::egress_deny_supported()
&& crate::confined_exec::kernel_fs_fence_available()
{
Verification::Verified {
evidence: "every live attacker-exec path denies direct egress by default, \
fail-closed: the ConstrainedExecutor callers (build_check / crew) run \
under NetGrant::DenyAll -> newt-net-guard seccomp socket() deny for \
AF_INET/AF_INET6/AF_PACKET (net_guard_executor.rs), and run_command's \
agent-bridle shell installs the same seccomp deny at the spawn owner via \
ChildNetworkPolicy::DenyDirect (agent-bridle 0.7.15), proven by \
run_command_child_under_net_none_cannot_open_a_socket_b1 — all beneath \
the Landlock fs fence. NARROW claim: no DIRECT off-box socket (AF_INET/\
AF_INET6/AF_PACKET) can be created on any path. NOT a no-egress claim — \
AF_UNIX local-deputy egress is a separate ACTIVE residual \
(local-deputy-egress)"
.into(),
}
} else {
Verification::Absent {
deviation: "b1-os-isolation",
reason: "the seccomp egress floor or the Landlock fs fence is unavailable on this \
host — an AgentInfluenced spawn refuses rather than run with weaker \
network confinement"
.into(),
}
}
}
#[cfg(not(target_os = "linux"))]
{
Verification::Absent {
deviation: "b1-os-isolation",
reason: "no seccomp egress floor off Linux; the platform ceiling reports this \
unsupported"
.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct RuntimeEvidence {
pub b1: Verification,
pub disclosure: Verification,
pub fs_object_bound: Verification,
pub constrained_executor: Verification,
pub fail_closed: Verification,
}
impl RuntimeEvidence {
#[must_use]
pub fn current() -> Self {
Self {
b1: verify_b1(),
disclosure: verify_disclosure_gate(),
fs_object_bound: verify_fs_object_bound(),
constrained_executor: verify_constrained_executor(),
fail_closed: verify_fail_closed_execution(),
}
}
fn meet(a: &Verification, b: &Verification) -> Verification {
match (a, b) {
(Verification::Verified { evidence: ea }, Verification::Verified { evidence: eb }) => {
Verification::Verified {
evidence: format!("{ea}; {eb}"),
}
}
(Verification::Absent { .. }, _) => a.clone(),
(_, Verification::Absent { .. }) => b.clone(),
}
}
#[must_use]
pub fn for_guarantee(&self, g: Guarantee) -> Verification {
match g {
Guarantee::FsConfinement => self.fs_object_bound.clone(),
Guarantee::ProcessConfinement => Self::meet(&self.constrained_executor, &self.b1),
Guarantee::NetworkConfinement => self.b1.clone(),
Guarantee::EnvIsolation => self.constrained_executor.clone(),
Guarantee::CredentialIsolation => Self::meet(&self.constrained_executor, &self.b1),
Guarantee::DisclosureFiltering => self.disclosure.clone(),
Guarantee::FailClosedExecution => self.fail_closed.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct SecurityReport {
pub platform: &'static str,
entries: Vec<(Guarantee, Achieved)>,
}
impl SecurityReport {
#[must_use]
pub fn current() -> Self {
Self::from_parts(current_platform_ceiling(), &RuntimeEvidence::current())
}
#[must_use]
pub fn from_parts(ceiling: &PlatformCeiling, ev: &RuntimeEvidence) -> Self {
let entries = Guarantee::ALL
.iter()
.map(|&g| {
let achieved = match ceiling.cannot_provide(g) {
Some(reason) => Achieved::Unsupported {
reason: reason.into(),
},
None => match ev.for_guarantee(g) {
Verification::Verified { evidence } => Achieved::Enforced { evidence },
Verification::Absent { deviation, reason } => {
Achieved::Unverified { deviation, reason }
}
},
};
(g, achieved)
})
.collect();
Self {
platform: ceiling.platform,
entries,
}
}
#[must_use]
pub fn achieved(&self, g: Guarantee) -> &Achieved {
&self
.entries
.iter()
.find(|(row, _)| *row == g)
.expect("SecurityReport holds every Guarantee")
.1
}
#[must_use]
pub fn is_enforced(&self, g: Guarantee) -> bool {
self.achieved(g).is_enforced()
}
#[must_use]
pub fn summary_lines(&self) -> Vec<String> {
self.entries
.iter()
.map(|(g, a)| match a {
Achieved::Enforced { .. } => format!("{}: enforced", g.label()),
Achieved::Unverified { deviation, .. } => {
format!("{}: OPEN ({deviation})", g.label())
}
Achieved::Unsupported { .. } => {
format!("{}: unsupported on {}", g.label(), self.platform)
}
})
.collect()
}
}
pub fn require_achieved(report: &SecurityReport, g: Guarantee) -> Result<(), FailClosed> {
match report.achieved(g) {
Achieved::Enforced { .. } => Ok(()),
Achieved::Unverified { deviation, reason } => Err(FailClosed {
deviation,
reason: reason.clone(),
}),
Achieved::Unsupported { reason } => Err(FailClosed {
deviation: "platform-unsupported",
reason: format!(
"{} is unsupported on {}: {reason}",
g.label(),
report.platform
),
}),
}
}
#[cfg(test)]
#[path = "ocap_tests/mod.rs"]
mod ocap_tests;