Skip to content

Latest commit

 

History

History
119 lines (106 loc) · 8.22 KB

immutable-path-scopes.md

File metadata and controls

119 lines (106 loc) · 8.22 KB

Immutable native filesystem scopes

Status: implemented. Owner: TinySecurity. Contract vocabulary: 1.1.

OpenHuman needs independent filesystem authorization for every acting agent and turn. Changing SDK module configuration for each request would make concurrent agents share whichever configuration was installed last. A path scope therefore has its own immutable identity, independent of the bootstrap policy generation.

RegisterPathPolicy accepts the trusted host's native workspace, action and home roots, enabled flag, workspace-only setting, forbidden paths, operation-specific trusted roots, host-state reservations and read-only subtrees. Product-specific state directory names remain in host configuration. The engine never reads the process's home environment or imports a host crate.

The registry assigns 256-bit OS-entropy references and deduplicates equal configurations and accepts at most 1024 distinct scopes per module process. Registration never replaces an existing scope. A full registry fails closed. Filesystem admission runs on a blocking worker before acquiring the shared registry lock, so a stalled mount cannot block other scopes or the module runtime. SDK reinitialization preserves registered scopes. A new module process requires fresh registration. Unknown identities fail as invalid arguments. Entropy failure or repeated identifier collisions also fail closed. References prevent insertion-index enumeration as defense in depth; they are not caller ownership or an authentication mechanism. The host is responsible for restricting this interface to its private, trusted in-process module bus; the service does not authenticate bus callers. Do not expose it through a network listener or an agent-controlled generic bus forwarder. First-party native modules share the host's address space and OS privileges; they are not a sandbox boundary. A hostile native module can already perform filesystem I/O without this policy engine. Untrusted code belongs in a separate execution sandbox, without a connection to the policy bus.

A caller-authored scope cannot change another scope or the host's selected scope: registrations are immutable, and the host adapter derives the policy and keeps its returned reference private. Model arguments contain operation paths, never registration payloads or policy references. Registries constructed through the Rust library likewise operate inside the caller's process and grant no new OS authority. Identity authentication and bus admission belong to the host, not to this portable policy evaluator. Scope handles are cloned under the registry lock; filesystem work runs on blocking workers after releasing it, keeping the module runtime and unrelated agent requests responsive during a stalled filesystem operation.

ValidatePath resolves an existing read target. ValidateParent resolves a prospective write through its deepest existing ancestor, including missing parent directories. Existing target symlinks are resolved too: a writable alias cannot turn a read-only grant into write access. Broken symlinks and permission errors are failures, not missing descendants. Both return an Allowed native path or Denied with a stable category. CheckPath exposes boolean string, resolved, reservation, trusted-root and unconditional-floor queries for host adapters.

The unconditional credential, system-root, traversal and null-byte floor applies even with policy disabled. System paths include every Windows drive and native extended aliases, administrative UNC shares and GLOBALROOT device namespaces. Unlike the conservative bootstrap Check path contract, portable acting scopes leave /usr, /bin, /dev and library roots to configurable forbidden roots, preserving explicit grants for legitimate tool workloads. Enabled scopes also protect host state regardless of trusted grants. To preserve the host contract, an absolute forbidden root containing the entire workspace does not revoke workspace access, whether configured directly or through a symlink alias resolving to that ancestor. A forbidden subtree inside the workspace still denies access. Trusted grants override configurable forbidden roots only for operations their access permits; read-only grants cannot authorize writes outside the workspace. Relative requests resolve against the action root, while relative forbidden roots resolve against the workspace root. Reserved single-component filenames retain the host's lexical reservation contract, including equivalent leading current-directory components such as ./.env. Public resolved and trusted-root queries resolve the candidate, including existing symlinks and missing descendants, before checking containment and the protected floor. String checks apply resolved forbidden roots within the workspace as well as outside it. Reservations may allow child exceptions and may reserve only children so an artifact container remains listable. Read-only subtrees remain unwritable through a broad workspace grant. Denial roots, reserved names and prefixes compare case-insensitively on Windows and conservatively on macOS, including missing components. The macOS comparison can deny an additional case alias on a case-sensitive volume. Trusted grants and reservation exceptions use exact canonical containment, so denial folding never broadens their authority.

Authorization is distinct from approval. These methods neither prompt nor park a tool invocation. Their canonical-path results do not make later filesystem I/O atomic: a host still needs an execution sandbox to bound filesystem races.

Acceptance coverage includes missing targets, aliased reserved state, disabled policy floors, home expansion, relative forbidden roots, independent registry identities, registry exhaustion, read-only grants and existing/broken symlinks. The compiled scope constructor is private; production handles are issued only after registry validation. A compile-fail API test pins that boundary. The Windows alias normalization is a floor classifier, never a filesystem path rewrite: on POSIX, //?/etc/passwd names the ? directory beneath /, not /etc/passwd, and is never rebased beneath the action directory. Validation checks the original native path and repeats the floor on its resolved target. Native critical-system fixtures supplement cross-platform Windows-spelling tests. The real native loader verifier calls every new member and verifies that a scope survives an accepted SDK reinitialization. This completes filesystem capability migration support; command policy, redaction, approvals, egress, audit and crypto remain separate capabilities.

Boolean string queries fail closed if existing-ancestor resolution fails, even when configurable policy is disabled. Prospective missing paths remain valid when their existing ancestor resolves. Grant/exception comparisons never fall back to an unresolvable lexical root. On Windows, denial comparisons also trim ordinary Win32 trailing-dot/space component aliases; grants remain exact. ValidateParent requires an input filename: current-directory-only inputs such as . and ./ are invalid, while ValidatePath can read that directory.

Authorization returns a resolved path, not an opened file or directory handle. Filesystem changes after the response can race with the host's subsequent I/O. Hosts requiring atomic containment must use directory handles/no-follow I/O or an execution sandbox; this policy module does not perform the file operation.

Windows action-relative requests reject drive-relative or root-relative forms before joining the action directory. Denial filename comparisons include NTFS stream aliases (split the stream suffix, then trim dots/spaces), while drive and UNC prefix components stay intact. Exact grants remain unchanged, and Unix colon filenames do not acquire Windows stream semantics.

Reservation exceptions must resolve to strict descendants of their reservation at registration and at evaluation. A parent traversal or a symlink to the reservation's ancestor cannot exempt the whole reservation. Prospective child exceptions remain supported through their deepest existing ancestor. Home-directory requests using ~/ or equivalent forms such as ~/./ can be read, but cannot supply a filename for ValidateParent. Home expansion applies only to the leading ~/ form; a bare ~ is an ordinary relative filename.