Our engine never sees your warehouse password
datahashi

Data roles and row-level security

Deny-by-default grants, metric access, and parameterized row-level security.

Updated

datahashi denies access by default. A consumer cannot query an entity, dimension, measure, or metric unless a Data Role grants it.

flowchart TD
    Req["Bearer token<br/>principal agent-01 · role sales_eu"] --> G
    subgraph G["Governance enforcement"]
        direction TB
        S1["1 · Catalog grants<br/>revenue granted: YES<br/>Customer.region granted: YES<br/>raw Order.amount in governed mode: NO"]
        S2["2 · Row-level security<br/>inject WHERE customer.region = current_user.region<br/>claim resolves to 'EU'"]
        S1 --> S2
    end
    G --> SQL["SQL with bound filter<br/>WHERE customer.region = $1 · args: EU"]

Policy and compile meet in two places:

  • Grants decide whether a metric or dimension appears in the catalog.
  • Row filters add parameterized predicates to the SQL. Values are never concatenated as text.

Studio seats vs data roles

Platform permissions and query entitlements are separate:

Dimension Studio seat (authoring_role) Data Role (policy_roles)
Scope Console / Control Plane Query engine / catalog
Values owner, editor, viewer Defined in policy.yaml (for example finance, sales_emea)
Grants Edit models, invite members, publish Query specific metrics and entities
Applies to Human users in the console Humans, MCP credentials, and SQL credentials

An owner in the console cannot query protected finance metrics unless their principal also holds the matching Data Role.

Declare data roles

You write data roles in the engine file policy.yaml. The console uses the same format. This file is not the policies: block of the Query Runner config. The runner config names physical tables. This file names model members.

roles:
  sales_representative:
    description: Access to core sales metrics and customer dimensions for EMEA
    grants:
      - kind: metric
        target: revenue
      - kind: metric
        target: order_count
      - kind: metric
        target: avg_order_value
      - kind: entity
        target: Customer
        dimensions: [id, name, region, plan_tier]
        row_filter: "region = current_user.region"
      - kind: entity
        target: Order
        dimensions: [id, customer_id, status, order_date]
        measures: [revenue, order_count]

  finance_auditor:
    description: Global access to all entities and metrics
    grants:
      - kind: metric
        target: "*"
      - kind: entity
        target: "*"
        fields: ["*"]

Grant types

  • kind: metric. A named metric, or * for every metric. The metric is granted only when every entity its measures live on is also granted.
  • kind: entity. Name columns under dimensions and measures. fields accepts only ["*"], or you omit it. Any other fields list is refused at boot.
  • Wildcard. fields: ["*"], or no fields / dimensions / measures keys, exposes every dimension and every measure of the entity.

Row-level security

A row_filter predicate limits the rows a principal can see.

  • Bound parameters. Claim values are bound as parameters. They never enter the SQL text.
  • Several roles. The engine unions grants. It ANDs row filters. A second role cannot widen the first role’s rows.

Row-filter forms

Exactly four forms are accepted. Anything else fails the request with 403. The filter is never skipped.

  • region = current_user.region (also != and <>). The claim holds exactly one non-empty value.
  • region in current_user.regions (also not in). Keywords are lowercase. An empty list means zero rows.
  • status = 'active' (also != and <>). The literal applies to every principal.
  • deleted_at is null. is not null is not supported.

The left side is a dimension of the granted entity. A static ENGINE_API_KEY carries no claims. A claim-bound filter then fails every query made with that key. Use a literal filter with a static key, or use a JWT.

Attribute sources

When the identity token cannot carry claims, datahashi can call an Attribute Source over HTTPS at request time. The service returns a claims dictionary for the subject. If the lookup fails, the query is rejected.

Next: PostgreSQL wire protocol · Write your first model.