Authorization Model
Vengtoo's authorization model draws a clear line between what is being protected (Resources and Resource Types) and who is attempting access (Subjects and Roles). Policies are the rules that tie the two sides together.
Vengtoo follows a deny-by-default model. If no policy explicitly grants access, the request is denied. You don't need to create DENY policies to block access — only to override existing ALLOW policies at equal or higher priority.
Core entities
| Entity | Scope | Owned by |
|---|---|---|
| Tenant | Root | — |
| Resource Type | Tenant | Tenant |
| Namespace | Tenant | Tenant |
| Resource | Namespace | Namespace |
| Subject | Tenant | Tenant |
| Role | Tenant | Tenant |
| Policy | Tenant | Tenant |
Mental model
A Namespace is a logical grouping of resources. How you draw the boundaries is up to you — by product domain, team, environment, or whatever maps to your system. Every tenant has a default namespace — resources created without an explicit namespace are placed there automatically.
Namespace does not factor into authorization evaluation. It is an organizational tool for the dashboard and API filters, not an authorization boundary.
A Resource is the thing being protected — an API endpoint, a document, an invoice, a tool call. Every resource has a Resource Type that defines what actions are valid on it. Every resource belongs to exactly one namespace; if you don't specify one, it is assigned to the tenant's default namespace automatically.
Policies describe the protection rules. A policy targets specific resources, an entire resource type, or both. Targeting a resource type covers every instance — one policy for the document type applies to doc_1, doc_2, and any document created in the future.
Roles and Subjects are pure identity. They answer the question "who is attempting access?" and carry no scope of their own. They exist at the tenant level and can be reached by any policy the tenant defines.
Where scope lives
Scope lives only on policy→resource-type and policy→resource links. Nowhere else.
- A policy linked to a resource type matches every resource of that type — past and future.
- A policy linked to a specific resource matches only that resource.
- Both can be combined in one policy (union — either match grants access).
- Namespace, Roles, Subjects, and Groups carry no scope. Namespace in particular has no effect on authorization evaluation.
Policy "editors-can-read" → linked to Resource Type: document → covers doc_1, doc_2, any future document
Policy "viewers-read-only" → linked to doc_1, doc_2 → covers only those two resources
Policy "compliance-review" → linked to (nothing) → draft state, covers nothing
Actions and policies
Actions are defined on the resource type. When you create a policy, you select a resource type and pick which of its actions the policy covers — for example, a document type with actions create, update, write lets you create a policy that only covers create.
You also choose the scope: all instances of that type, or specific resources only.
Evaluation flow
When a client calls /access/v1/evaluation, the policy engine resolves the decision like this:
- Collect candidate policies. A policy is a candidate if it is:
- linked to this specific resource, or
- linked to this resource's type (covers all instances of that type). A policy with no links at all is in draft state — it matches nothing.
- Filter by subject assignment. The calling subject must be assigned to the policy directly or via a role.
- Match action and conditions. The requested action must be in the policy's
actionslist (or["*"]); ABAC conditions (time, attributes, request context) must pass. - Apply DENY override via priority. If an applicable DENY policy exists at equal or higher priority, the decision is DENY; otherwise an applicable ALLOW wins.
Examples
Example 1 — Resource-type policy (most common)
Resource Type: document (actions: read, write, delete, share)
Resources: doc_1, doc_2, doc_3 (all type: document)
Policy: "editors-can-read"
effect: ALLOW
actions: ["read"]
linked to: Resource Type: document
Evaluation:
(alice, read, doc_1) → ALLOWED (type: document, policy covers all documents)
(alice, read, doc_3) → ALLOWED (same — any document, including newly created ones)
(alice, write, doc_1) → no match (wrong action — needs a separate policy)
One policy covers every document — present and future. Alice's write attempt finds no matching ALLOW policy.
Example 2 — Resource-specific + DENY override
Policy: "viewers-read-only"
effect: ALLOW
actions: ["read"]
linked to: doc_1, doc_2 (specific resources)
Policy: "block-outside-hours"
effect: DENY
actions: ["*"]
conditions: time NOT between 09:00-17:00
linked to: Resource Type: document
priority: 80
Evaluation at 20:00:
(bob, read, doc_1) → DENIED (DENY policy matches at priority 80, overrides ALLOW)
Evaluation at 14:00:
(bob, read, doc_1) → ALLOWED (DENY condition fails — within business hours)
The DENY is type-wide (all documents, all hours outside 09:00-17:00). The ALLOW is resource-specific. At 20:00 the DENY wins; at 14:00 the DENY condition fails and the ALLOW applies.
Example 3 — AI agent with guardrails
Resource Type: runtime (actions: execute, read_output, kill)
Resources:
├── python_sandbox (type: runtime)
├── node_sandbox (type: runtime)
└── production_shell (type: runtime)
Subject: agent_copilot (type: agent)
Role: "safe-executor" groups [Policy A, Policy B]
Policy A: "sandbox-execute"
effect: ALLOW
actions: ["execute", "read_output"]
linked to: python_sandbox, node_sandbox (specific resources)
Policy B: "no-production"
effect: DENY
actions: ["*"]
linked to: production_shell (specific resource)
priority: 100
Evaluation:
(agent_copilot, execute, python_sandbox) → ALLOWED (Policy A)
(agent_copilot, execute, production_shell) → DENIED (Policy B, priority 100)
(agent_copilot, kill, python_sandbox) → no match (no ALLOW for kill)
The agent can execute in sandboxes but is hard-blocked from production. The kill action has no matching ALLOW policy, so it defaults to no access.
Example 4 — Draft policy (zero-link)
Policy: "compliance-review"
effect: DENY
actions: ["delete"]
conditions: requires_approval == false
linked to: (nothing)
Status: DRAFT — this policy exists but matches nothing.
Once linked to a resource type or specific resource, it activates.
A developer can save this policy while iterating on conditions. It is invisible to the evaluation engine until at least one link is added.
Example 5 — One role, multiple resource types
Resource Type: invoice (actions: read, write, approve)
Resource Type: report (actions: read, export)
Role: "finance-admin" groups [Policy X, Policy Y]
Policy X: effect=ALLOW, actions=["read","write"], linked to Resource Type: invoice
Policy Y: effect=ALLOW, actions=["read"], linked to Resource Type: report
Subject: carol, assigned role "finance-admin"
Evaluation:
(carol, write, invoice_123) → ALLOWED (Policy X covers all invoices)
(carol, read, report_789) → ALLOWED (Policy Y covers all reports)
(carol, write, report_789) → no match (Policy Y only allows read on reports)
Carol's single role covers two resource types with different action sets. No duplication needed.
Behavioral reference
- Zero-link policy (draft state) — A policy with no resource-type links and no resource links is valid but in draft state. It does not match any request during evaluation. Activation is implicit: the moment at least one link is added, the policy becomes active. (See Example 4 above.)
- Resource-type-wide and resource-specific both present — When a policy links to a resource type and to specific resources, semantics are Union (OR). The policy protects any resource from either set. There is no intersection mode.
- Namespace is organizational, not an authorization boundary — Namespace links on a policy are stored for dashboard filtering only. They have no effect on evaluation. Access rules are expressed through resource-type and resource links exclusively.
- Cross-type policy access — A single policy can link to multiple resource types. A role can group policies across any number of types. This is how "platform administrator" style access works without duplicating roles.
- DENY policies — Follow exactly the same link rules as ALLOW policies (resource-type link, resource link, or both). DENY's only distinguishing behavior is the priority-based override during evaluation.
- Uniqueness — All tenant-level entities enforce per-tenant uniqueness on their name. Resource types are shared across the entire tenant — if two teams need a
documenttype with different semantics, use distinct names likebilling-documentandhr-document. - DENY + conditions — Conditions scope when a policy takes effect. No special handling is needed for DENY + conditions; the condition gates whether the DENY is applicable in a given request. (See Example 2 above.)