Skip to content

Resource Access Control (ACL) ​

OpenViking ACL shares directories or files from the shared resource scope with users or groups inside one account. ACL never changes the account boundary: every grant is limited to the current account.

ACL uses a collaborative-document inheritance model. A directory grant applies to descendants by default. Child directories and files can add direct grants or establish a restricted boundary that ignores inherited permissions.

Supported URIs ​

ACL applies only to shared resources:

text
viking://resources/...
  • An account ADMIN implicitly has manage on viking://resources/....
  • viking://resources has a fixed, immutable user:* = manage ACL. Descendants inherit it until a restricted boundary.
  • viking://user/{user_id}/resources/... is private and does not accept ACLs. To share it, move the resource into a writable shared directory and inherit that directory's ACL.

Implicit management is not stored as an ACL entry and cannot be removed by ACL changes. It ensures that shared resources always have an identity that can establish or recover permissions.

Principals and Levels ​

ACL entries use typed principals:

  • user:{user_id}: a user in the current account.
  • group:{group_id}: a caller-supplied, account-unique group ID.
  • user:*: any user in the current account.

group:* is not supported. Groups are flat. Membership changes do not rewrite resource ACL or context records; they take effect when the next request builds RequestContext.group_ids. Asynchronous parse and semantic tasks created by a request carry the same group identity. After an add-resource destination write has been authorized, automatic semantic maintenance preserves that identity and uses an explicit internal ACL bypass instead of changing the caller's role.

LevelAllowed operations
readRead, list, and find/search/grep
writeread capabilities plus write, create, file delete/move, and tag updates
managewrite capabilities plus directory delete/move and ACL management

Each higher level includes the lower levels. A manage grant therefore includes read and write.

Inheritance ​

A normal node combines direct and inherited grants. A restricted node uses only direct grants:

text
effective(node) = direct(node) + (acl_mode(node) == "restricted" ? empty : inherited(node))

inherited(node) always stores the parent's current effective permissions. It continues to refresh while the node is restricted, and disabling restricted mode applies the latest inherited value immediately. Descendants inherit the current node's effective permissions, so they cannot bypass an intermediate restricted boundary.

For example, first make A restricted to cut off the root wildcard grant, then configure:

text
read user:bob   on viking://resources/A
write group:engineering on viking://resources/A/B
read user:carol on viking://resources/A/B/C/report.md

The effective permissions on report.md are:

  • Bob: read
  • Members of engineering: write
  • Carol: read

If A/B becomes restricted, Bob's grant on A no longer applies to A/B or its descendants, but the stored inherited value is not deleted. Disabling restricted mode immediately restores Bob's inherited access.

Default Behavior and acl_mode ​

The account-level acl.enabled setting is disabled by default. While disabled, shared resources keep the existing URI namespace visibility and write rules. ACL is not enforced. Indexed content still stores ACL fields using the same inheritance rules; the switch only controls access checks and retrieval filtering.

When enabled, the root grants user:* = manage, so all account members can manage shared content that continues to inherit from the root. Without acl, new nodes have empty direct ACLs and inherit their parent’s effective permissions. Creating content grants no additional permissions. Import roots and descendants follow the same rule. Existing shared content without ACL fields uses default inheritance without a historical data migration. Disabling the setting stops enforcing existing ACLs. Re-embedding or replacing an existing context record does not change its direct ACL unless explicitly supplied. See ACL API for creation parameters.

acl_mode describes a resource's ACL behavior, separately from the account-wide acl.enabled switch:

  • none: ACL fields have not been materialized; shared nodes use default inheritance when ACL is enabled.
  • inherit: use direct grants and permissions inherited from the parent.
  • restricted: use direct grants only, while retaining and refreshing inherited grants.

Users with manage can switch between inherit and restricted, but cannot set none to bypass the parent's ACL. Exiting restricted mode restores parent inheritance; all account members regain manage when no other restricted boundary interrupts the path to the root. A restricted node with no direct grants remains protected, as do descendants without separate grants; account administrators retain implicit management access.

File Operations ​

All filesystem APIs use the same permission mapping:

OperationRequired capability
read, stat, list, tree, find, search, grep, globread
write, create, mkdir, set tagswrite
delete or move a filewrite
delete or move a directorymanage on the directory and complete subtree
manage ACLmanage
move destination parentwrite

The server canonicalizes the URI, then uses one authorization entry point for account/owner/actor-peer boundaries, effective ACLs when enabled or existing namespace rules when disabled, and write/delete namespace guards.

When the account enables acl.enabled, ordinary shared nodes inherit the root’s user:* = manage grant, allowing members to manage their ACLs. Creating content inside a restricted directory only inherits the parent’s permissions.

An ACL grant on a directory is inherited by every descendant. list, tree, and other batch results still check every returned node because an open directory can contain independently restricted descendants.

Within the shared scope, a moved node keeps its direct ACL and restricted state, then recalculates inherited permissions from its new parent. A private resource moved into the shared scope carries no ACL and inherits the destination directory; a shared resource moved back to a private area has its ACL cleared.

Recursive tag updates, directory deletion, and directory moves validate the complete affected subtree first. The operation stops if any node lacks the required capability or the subtree cannot be scanned completely.

For a directory, stat.count uses the same path and ACL scalar filter and reports the number of context records visible to the caller.

Retrieval Filtering ​

ACL data exists only in the context collection. Each context record stores direct and inherited permissions in native scalar fields:

text
acl_mode
acl_direct_grants
acl_inherited_grants

acl_direct_grants is the ACL assigned to the current node. acl_inherited_grants stores the parent's current effective ACL. acl_mode controls whether inherited grants contribute to the current node's effective permissions. Each principal stores only its highest level as {mask}:{principal}: 1 means read, 3 means write, and 7 means manage. There is no separate ACL collection.

ACLs update with the index, so records for the same URI may temporarily hold different versions. Reads use one ACL snapshot returned by the index, without merging grants across versions or blocking on temporary disagreement. Permission changes take effect as index updates complete; strong consistency is not guaranteed.

The request principals are user:{ctx.user_id}, user:*, and one group:{group_id} for each ID in ctx.group_ids. Within the shared scope, retrieval identifies controlled records with acl_mode IN [inherit, restricted], then matches each principal's 1, 3, and 7 grant tokens: direct or inherited for inherit mode, direct only for restricted mode. Shared records with missing, null, or none modes use the root’s default wildcard access in retrieval. Stored inherit nodes must match their direct or inherited grants; inherit mode alone never grants wildcard access. Private resources remain isolated by URI owner.

A retrieval target URI is only a search scope; the caller does not need to read the target node itself. A user can discover a deeply shared file even when intermediate directories are not readable.

When the account enables acl.enabled, shared-scope context writes preserve an existing direct ACL for the same URI. New nodes have empty direct ACLs and derive inherited ACL fields from their parent, including 7:user:* when no restricted boundary intervenes. Creator identity does not affect authorization. Re-embedding and ordinary replacement writes cannot reset controlled records to default visibility or modify ACLs through regular context fields. When the account disables the switch, retrieval uses only the original account and URI scope filters and ignores these ACL fields.

Example ​

Assume project-a is already restricted and the caller has manage on it. Grant Bob read-only access to the directory:

bash
ov acl grant viking://resources/project-a --principal user:bob --level read

Bob can read and retrieve descendants, but cannot write or delete them. Upgrade the grant to write:

bash
ov acl grant viking://resources/project-a --principal user:bob --level write

Remove Bob's direct grant from this node:

bash
ov acl revoke viking://resources/project-a --principal user:bob

If an ancestor still grants Bob access, that inherited permission remains effective.

Use only direct grants on the current node while preserving and refreshing inherited grants:

bash
ov acl set viking://resources/project-a --acl-mode restricted

Open source under the AGPL-3.0 License. Font licenses