') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Harden SealProof and engineer non-transitive authority · Issue #46 · auths-dev/capsec · GitHub
Skip to content

Harden SealProof and engineer non-transitive authority #46

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:

implPermissionforMyType{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)]pubmod __private {pubstructSealProof(());// private fieldpubtraitSealToken{}implSealTokenforSealProof{}#[doc(hidden)]pubconstfn__capsec_seal() -> SealProof{SealProof(())}}
// Requires a value proof, not just a type#[doc(hidden)]pubfn__capsec_new_derived(_seal:crate::__private::SealProof) -> SelfwhereP: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:

pubtraitCapProvider<P:Permission>{fnprovide_cap(&self,target:&str) -> Result<Cap<P>,CapSecError>;}// Blanket: Has<P> always provides (no scope check)impl<P:Permission,T:Has<P>>CapProvider<P>forT{fnprovide_cap(&self,_target:&str) -> Result<Cap<P>,CapSecError>{Ok(self.cap_ref())}}// Attenuated: scope check on every I/Oimpl<P:Permission,S:Scope>CapProvider<P>forAttenuated<P,S>{fnprovide_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>:

// Beforepubfnread(path:implAsRef<Path>,cap:&implHas<FsRead>) -> io::Result<Vec<u8>>// After — accepts both raw caps and scoped capspubfnread(path:implAsRef<Path>,cap:&implCapProvider<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