Protocol / Authorization / Attenuation
shippedLayer 1 · Protocol

Attenuation

Attenuation is the narrowing-only rule — a child capability must be a strict subset of its parent, enforced by ResourceId::extends (path/space/service/fragment containment) plus exact ability match.

Attenuation

Attenuation is the rule that authority can only shrink as it flows down a delegation chain: a child capability must be a strict subset of its parent — never wider. In TinyCloud this is enforced by exactly two checks at every link: ResourceId::extends (the resource is within the parent's resource) and an exact ability == ability match. A delegator can hand out less than it holds, never more.

Role

Attenuation is the safety property that makes capability delegation sound. Because grants are bearer tokens verified offline by a node, the only thing standing between a session key and the whole space is the guarantee that each re-grant narrowed scope. It is Layer 1 bedrock, invoked identically in delegation and invocation verification.

Mechanics

The narrowing test is a per-capability predicate run in both delegation::validate and invocation::validate (tinycloud-core/src/models/delegation.rs:248, invocation.rs:169):

c.resource.extends(&pc.resource) && c.ability == pc.ability

c is the child/invoked cap, pc a parent cap. Both halves must hold:

  • Resource containmentResourceId::extends (tinycloud-auth/src/resource.rs:193). The child resource extends the base iff:
    • same space (base.space() == self.space()), else IncorrectSpace;
    • same service (kv/sql/…), else IncorrectService;
    • same fragment, else IncorrectFragment;
    • the child path is path-prefixed by the base path (below), else DoesNotExtendPath.
  • Ability equalityc.ability == pc.ability, an exact string compare of the {namespace}.{service}/{action} form (e.g. tinycloud.kv/get). There is no ability hierarchy in this check — tinycloud.kv/get does not satisfy a request for tinycloud.kv/put and vice-versa; a grant must enumerate each ability it wants to support.

The path-prefix rule

The path containment logic (resource.rs:200-212) is boundary-aware so prefixes can't bleed across path segments:

base pathchild pathextends?why
Noneanythingbase with no path covers any path
notes/notes/a.txtbase ends in /, child starts_with it
notesnotesequal length
notesnotes/aboundary char after base is /
notesnotesxyznext byte is not /notes does not cover notesxyz
notnotessame: no / boundary
notes/(none)base has a path, child lacks one

Concretely the rule is: child.starts_with(base) and (base ends with / or the strings are equal length or the byte at base.len() in child is /).

Shape

extends returns Result<(), ResourceCheckError> with variants IncorrectSpace | IncorrectService | IncorrectFragment | DoesNotExtendPath. The chain checks call it as a boolean (extends(...).is_ok() semantics via the &&). The only widening directions are all rejected: different space, different service, different fragment, or a path that escapes the parent's prefix.

Relationships

Enforced during delegation and invocation verification; defined over the resource URI grammar (extends is a method on ResourceId); the narrowing dimension complementing the time narrowing in delegation (child [nbf,exp] ⊆ parent); the guarantee that makes capabilities safe to delegate; the server-side mirror of the SDK's isCapabilitySubset path-containment check (see cap-string-grammar).

Example

A parent delegation grants tinycloud.kv/get over …:applications/kv/com.listen.app/. A child may re-grant tinycloud.kv/get over …/com.listen.app/transcript/ (✓ — transcript/ is prefixed by the trailing-slash base) but not tinycloud.kv/put over the same path (✗ — ability mismatch), nor tinycloud.kv/get over …/com.other.app/ (✗ — escapes the prefix), nor the same ability over a different space (✗ — IncorrectSpace). (See delegation.)

Status & drift

Shipped and exercised by test vectors in resource.rs. Note the ability check is exact-equality, not subset — there is no */wildcard expansion inside the extends+== chain check itself; a grant intending to cover multiple actions must list each ability (the SDK's short-name expansion happens before this, when building the grant — see cap-string-grammar). Resource Name/Service/Path parsing currently does no structural validation (// TODO finish this by doing validation, resource.rs:160), so extends operates on whatever strings parsed — code is canonical.

Sources

  • tinycloud-node: tinycloud-auth/src/resource.rs:193-260 (ResourceId::extends, ResourceCheckError), tinycloud-core/src/models/delegation.rs:248-252 + models/invocation.rs:169-174 (the extends && ability == coverage predicate)