Skip to main content

Context Trust Model

Context attributes are the request-time values Vengtoo evaluates alongside subject and resource data. Unlike subject.attributes or resource.attributes, which are persisted in the Vengtoo control plane and gated behind write permissions, context is attached to each individual /access/v1/evaluation call and discarded once the decision is returned.

That makes context powerful (it can express things like "the user's current IP" or "the current time") and also dangerous: if an attacker can influence a context value, they can influence the decision. This page explains the trust hierarchy so you source each attribute from a place your policies can safely rely on.

The trust hierarchy

From most trusted to least trusted:

  1. Server-derived (Vengtoo-owned): Values Vengtoo generates itself. Nothing in the request can change them.
    • time: stamped automatically by Vengtoo from its own server clock on every evaluation call. You do not need to pass context.time; if you do, your value is used instead (useful for testing, not recommended in production). The built-in Time Window guardrail and any timeOfDay ABAC conditions read this value; they work without any SDK change.
  2. Vengtoo-stored attributes: subject.attributes and resource.attributes persisted in Vengtoo's control plane. Trusted because write access requires a valid API key or client credentials. When the same attribute is also passed in the evaluation request, the stored value wins: request-time attributes supplement stored ones but cannot override them. This means policies always evaluate against the authoritative value in the control plane, not whatever the caller sends.
  3. Caller-sent context: Anything in the context object of the evaluation request. Trusted only when your backend sets it from trusted state. Never trusted when it originates from an end-user request body.

The request flow

Your backend is the Policy Enforcement Point (PEP): it's the trust boundary where you verify the caller, build the context, and enforce the decision. The Vengtoo Agent is the authorization engine: it evaluates and returns the verdict.

The end user never talks to the agent directly. Your backend decides which values survive the crossing.

Per-attribute sourcing

AttributeSource fromNever source from
ipX-Forwarded-For parsed at your edge / load balancerrequest body
timeserver clock on your backendrequest header or body
mfa_verifiedyour IdP or session store, after verifying the user completed MFAa raw boolean field on the request
geoserver-side GeoIP lookup on the client IP, pass the resolved country code as context.georequest body
env, api_versioncompile-time or config-loaded constant on your backendrequest query/body
Custom (session_risk, request_id, ...)your backend's trusted internal stateanything the caller controls

A complete production context block, for reference:

"context": {
"ip": "203.0.113.42",
"geo": "US",
"env": "production",
"mfa_verified": true,
"session_risk": "low"
}

time is omitted here intentionally: Vengtoo stamps it automatically.

Bad: echoing user input into context

// DON'T — attacker sets env=dev and bypasses a production-only DENY policy
var body struct {
Env string `json:"env"`
}
_ = json.NewDecoder(r.Body).Decode(&body)

vengtoo.Evaluate(ctx, vengtoo.EvaluationRequest{
Subject: vengtoo.Subject{ID: userID},
Resource: vengtoo.Resource{ID: resourceID},
Action: "read",
Context: map[string]any{"env": body.Env}, // untrusted!
})

Good: sourcing from trusted backend state

// DO — env is a process-level constant, not caller input
const env = "production" // or loaded from your deployment config

// mfa_verified comes from your own IdP/session store, not from the request
mfaVerified := session.MFAVerified(userID)

vengtoo.Evaluate(ctx, vengtoo.EvaluationRequest{
Subject: vengtoo.Subject{ID: userID},
Resource: vengtoo.Resource{ID: resourceID},
Action: "read",
Context: map[string]any{
"env": env,
"mfa_verified": mfaVerified,
"ip": realClientIP(r), // from X-Forwarded-For at the edge
},
})

Same pattern in Node:

// Bad
await vengtoo.evaluate({ ..., context: { env: req.body.env } })

// Good
const env = process.env.APP_ENV ?? "production"
await vengtoo.evaluate({
...,
context: {
env,
mfa_verified: await session.isMFAVerified(req.user.id), // from your IdP/session store
ip: req.ip, // Express with `trust proxy` configured
},
})

Subject and resource attributes

Stored values win on conflict

subject.attributes and resource.attributes can be passed in the evaluation request or stored in Vengtoo. When both exist, the stored value wins.

AttributePreferred sourceWhy
subject.attributes.*Stored in Vengtoo via the Management APIStored values win on conflict; callers cannot override them
resource.attributes.*Stored in Vengtoo on the resource recordSame guarantee: stored value is authoritative

Request-time attributes are merged with stored values and are useful for passing attributes that aren't stored in Vengtoo (e.g., a session-computed risk score). If the same key appears in both, the stored value takes precedence. This prevents a caller from escalating their own attributes to bypass a policy condition.

Design principle

For attribute-based evaluation, Vengtoo evaluates rules against what your backend sends: your backend assembles the domain context, Vengtoo renders the verdict. This keeps attribute checks fast and free of relational complexity. Your backend already knows which department a subject belongs to, which tier a resource is in, whether MFA was completed: it passes those facts in; Vengtoo applies the rule.

Rule of thumb

If a policy would behave differently for env=dev vs env=prod, ask: can the user send a request that flips env to dev? If yes, you have a trust bug in your PEP, not a policy bug.