Skip to content

ACL API ​

ACL is queried and managed through the dedicated /api/v1/acl endpoints for shared resources under viking://resources/.... See resource access control for inheritance rules.

Endpoints ​

MethodPathBehavior
GET/api/v1/acl?uri={uri}Read direct, inherited and effective permissions
PUT/api/v1/aclSet direct entries and/or inheritance mode
POST/api/v1/acl/grantSet one principal's direct level
POST/api/v1/acl/revokeRemove one principal's direct grant
DELETE/api/v1/acl?uri={uri}Clear direct entries and restore inherit mode

HTTP

http
GET /api/v1/acl?uri={uri}
PUT /api/v1/acl
DELETE /api/v1/acl?uri={uri}
POST /api/v1/acl/grant
POST /api/v1/acl/revoke

All ACL operations require manage and return 403 without it; account ADMIN has implicit manage.

GET returns the ACL report directly in result. The report contains uri, acl_mode, direct_entries, inherited_entries, and effective_entries. Mutation endpoints return the report directly.

Example set_acl body:

json
{
  "uri": "viking://resources/project-a",
  "acl_mode": "restricted",
  "entries": [{"principal": "user:bob", "level": "read"}]
}
  • entries replaces direct grants; omission preserves them; [] clears them.
  • acl_mode: inherit combines direct and inherited grants; restricted uses direct grants only. Omission preserves the mode.
  • Supply at least one field. Inherited and effective entries are read-only.
  • Principals are user:{id}, group:{id}, or user:*; levels are read, write, or manage. Duplicate principals retain the highest level.
  • grant_acl takes {uri, principal, level}, revoke_acl takes {uri, principal}, and resetting uses DELETE /api/v1/acl?uri={uri}. Incremental changes run inside the server's existing lock.

Creation and content writes ​

POST /api/v1/resources, POST /api/v1/fs/mkdir, and POST /api/v1/content/write accept a top-level acl field, alongside tags and tag_mode where supported:

json
{
  "acl": {
    "acl_mode": "restricted",
    "entries": [{"principal": "user:bob", "level": "read"}]
  }
}

Omitting ACL gives new nodes empty direct grants and inherited parent permissions; existing nodes retain their ACL. Explicit ACL requires inherited manage for a new node, or the existing node's own manage. Authorization and validation precede content mutation. Write access alone cannot change ACL. Creators receive no extra grants.

Imports apply direct ACL only to the final import root. Descendants inherit it; automatically created parent directories receive no direct grants. Re-importing unchanged content still applies an explicit ACL update.

The account's acl.enabled defaults to false: namespace rules apply and ACL is ignored for access decisions. When enabled, the shared root permanently grants user:* = manage. Supplying ACL neither enables the account switch nor implicitly selects restricted mode.

ACL stays in the context index and follows existing asynchronous processing and wait semantics. Temporary inconsistency is accepted. Standalone ACL mutations require an existing context record. Vectorless records and empty-file support are outside this change.

Case Aliases and ACL ​

When localfs runs on a case-insensitive filesystem, ACL is resolved from the requested URI while the underlying filesystem may resolve a URI that differs only by letter case to the same real file or directory. If the alias URI is authorized, later operations such as read, stat, ls, write, rm, cp, and mv may act on the real object.

Example:

bash
# Bob's directory grants access only to Bob
ov acl set viking://resources/bob-private --acl-mode restricted --entry user:bob=manage

# Alice reading the exact path should get 403
ov read viking://resources/bob-private/private.md

# On case-insensitive localfs, the alias directory may hit the same real directory
ov read viking://resources/BOB-PRIVATE/private.md
ov ls viking://resources/BOB-PRIVATE
ov write viking://resources/BOB-PRIVATE/private.md --content changed --mode replace

The same risk applies to system-generated files that are reachable through public URIs in the shared resource tree, such as .abstract.md and .overview.md. For example, if the real file is viking://resources/bob-private/.overview.md, viking://resources/BOB-PRIVATE/.OVERVIEW.md may hit it. Internal databases, queues, and lock files that are not exposed through public URIs are not directly in scope for this URI alias issue.

On case-sensitive systems, BOB-PRIVATE and bob-private are different paths; a missing alias target returns NOT_FOUND and does not trigger this issue.

CLI

bash
ov acl get viking://resources/project-a
ov acl set viking://resources/project-a --acl-mode restricted --entry user:bob=read
ov acl grant viking://resources/project-a --principal user:bob --level write
ov acl revoke viking://resources/project-a --principal user:bob
ov acl rm viking://resources/project-a

ov mkdir viking://resources/project-a --acl '{"acl_mode":"restricted","entries":[{"principal":"user:bob","level":"read"}]}'
ov add-resource ./docs --to viking://resources/docs --acl '{"acl_mode":"restricted","entries":[]}'
ov write viking://resources/project-a/a.md --content hello --mode create --acl '{"acl_mode":"inherit"}'

SDK ​

python
acl = {"acl_mode": "restricted", "entries": [{"principal": "user:bob", "level": "read"}]}
client.mkdir(uri, acl=acl)
client.add_resource("./docs", to=uri, options={"acl": acl})
client.write(file_uri, "hello", options={"acl": acl})
report = client.acl_get(uri)
client.acl_set(uri, [], acl_mode="restricted")
client.acl_grant(uri, "user:bob", "read")
client.acl_revoke(uri, "user:bob")
client.acl_delete(uri)

Async Python uses the same method names. TypeScript provides aclGet(uri), aclSet, aclGrant, aclRevoke, and aclDelete; creation options accept acl, while mkdir accepts it as its third argument. Go provides ACL(ctx, uri), SetACL, SetACLMode, GrantACL, RevokeACL, and DeleteACL, with ACLSpec values for creation.

Open source under the AGPL-3.0 License. Font licenses