Skip to content

Harden SealProof and engineer non-transitive authority #46

@bordumb

Description

@bordumb

Two engineering fixes from the deep analysis

1. Harden SealProof (seal the seal)

Current state: SealProof is a public unit struct. External code can write:

impl Permission for MyType {
    type __CapsecSeal = capsec_core::__private::SealProof;
}

This is a soft barrier (#[doc(hidden)]), not a hard one. While exploiting it doesn't enable forging built-in caps, the README claims "no external code can forge a Cap<P> in safe Rust" which is technically inaccurate.

Proposed fix: Change SealProof from a unit struct to a struct with a private field, constructible only via a #[doc(hidden)] function. Then change Cap::__capsec_new_derived() to require a value proof (not just a type-level associated type):

#[doc(hidden)]
pub mod __private {
    pub struct SealProof(());  // private field

    pub trait SealToken {}
    impl SealToken for SealProof {}

    #[doc(hidden)]
    pub const fn __capsec_seal() -> SealProof {
        SealProof(())
    }
}
// Requires a value proof, not just a type
#[doc(hidden)]
pub fn __capsec_new_derived(_seal: crate::__private::SealProof) -> Self
where
    P: Permission<__CapsecSeal = crate::__private::SealProof>,

Attacker now needs to explicitly call __capsec_seal() — not just name a type. Still bypassable (pub), but requires deliberate intent. The naming makes abuse obvious in code review.

Limitation: Cannot be made fully airtight at the library level in stable Rust. Any type that a proc macro references from a user crate must be pub.

2. Non-transitive authority via CapProvider (the bigger win)

Current state: Has<FsRead> passes full authority. Attenuated<P, S> does NOT implement Has<P>, so scoped capabilities can't be used transparently in the Has<P> system. This means capsec does not achieve Wyvern-style non-transitive authority.

Why Attenuated can't implement Has

: If Attenuated<FsRead, DirScope> implements Has<FsRead>, any callee can extract an unscoped Cap<FsRead> via cap_ref() and bypass the scope entirely.

Proposed fix: CapProvider<P> trait that unifies Has<P> (unscoped) and Attenuated<P, S> (scoped), with transparent scope enforcement at the I/O site:

pub trait CapProvider<P: Permission> {
    fn provide_cap(&self, target: &str) -> Result<Cap<P>, CapSecError>;
}

// Blanket: Has<P> always provides (no scope check)
impl<P: Permission, T: Has<P>> CapProvider<P> for T {
    fn provide_cap(&self, _target: &str) -> Result<Cap<P>, CapSecError> {
        Ok(self.cap_ref())
    }
}

// Attenuated: scope check on every I/O
impl<P: Permission, S: Scope> CapProvider<P> for Attenuated<P, S> {
    fn provide_cap(&self, target: &str) -> Result<Cap<P>, CapSecError> {
        self.check(target)?;
        Ok(Cap::new())
    }
}

capsec-std functions change from &impl Has<P> to &impl CapProvider<P>:

// Before
pub fn read(path: impl AsRef<Path>, cap: &impl Has<FsRead>) -> io::Result<Vec<u8>>

// After — accepts both raw caps and scoped caps
pub fn read(path: impl AsRef<Path>, cap: &impl CapProvider<FsRead>) -> Result<Vec<u8>, CapSecError>

What this achieves: A logger module wrapping Attenuated<FsWrite, DirScope> can pass it to capsec-std functions, and the scope is enforced at every I/O site. Callees can't escape the scope even though they receive what looks like FsWrite authority. This IS Wyvern-style non-transitive authority.

Breaking change: capsec-std function signatures change. Existing Has<P> code continues to work via the blanket impl, but signatures are technically different. Major version bump.

Priority

Issue 2 (CapProvider) is the higher-impact fix — a genuine architectural improvement that closes the gap with Wyvern. Issue 1 (seal hardening) is incremental.

References

  • Deep analysis: Section 1.1 (SealProof attack) and Section 2.3 (non-transitivity)
  • Melicher et al. (2017): authority non-transitivity theorem
  • crates/capsec-core/src/permission.rs:163-176 — current seal
  • crates/capsec-core/src/cap.rs:48-64__capsec_new_derived()
  • crates/capsec-core/src/attenuate.rs — Attenuated<P, S>
  • crates/capsec-std/src/fs.rs — current Has

    function signatures

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions