The Semantic Layer, From ScratchChapter 12
Chapter 12

Governance: same question, different answers

Users, roles and permissions in Stardog, and how one switch makes the PII graph vanish for marketing while HR still sees it.

Two colleagues paste the same SPARQL into Studio at the same moment: "show me Jolynn Bithany's name and SSN." Priya from HR gets Jolynn Bithany | 609-38-5573. Marco from marketing gets Jolynn Bithany | with an empty cell. No error, no "access denied": for Marco, the part of the graph that holds SSNs simply doesn't exist. Nobody wrote a second query, a masked view, or a GRANT in Aurora. By the end of this chapter you'll have built exactly that, and your Chapter 14 agent inherits it for free: an LLM running as marketing that asks for SSNs gets nothing to leak.

12.1Five terms, broken down

TermWhat it isWhy it existsTiny example
UserA login on the Stardog server (username + password).Every request is made as someone.stardog_admin, and soon hr_user
RoleA named bundle of permissions that users are assigned to.You change a policy in one place, not per person.marketing
PermissionA triple of action + resource type + resource.To say exactly what may be done to what.read + db + kit-c360
Named-graph securityA database option that makes each named graph a separately permissioned resource.So access can be narrower than "the whole database."read on named-graph:kit-c360\virtual://aurora_c360_safe
Sensitive propertyA property whose values are masked for users without explicit permission.Hides one column-like property wherever it appears, without splitting graphs.:ssn shows as a masked string for marketing.

12.2The Stardog security model

Stardog's docs define a permission as an action on a resource of some resource type. The valid actions are read, write, create, delete, grant, revoke, execute, and all. The resource types you'll meet most:

Resource typeWritten as (CLI)Controls
dbdb:kit-c360querying / updating a database
named-graphnamed-graph:kit-c360\<graph IRI>one graph, only if named-graph security is on
virtual-graphvirtual-graph:virtual://namemanaging a virtual graph's definition
data-sourcedata-source:data-source://namemanaging a JDBC data source
sensitive-propertiessensitive-properties:kit-c360seeing unmasked sensitive values
metadata, admin, dbms-admin, stored-query, icv-constraints…database options, admin tasks, and the rest

Your server (read with a read-only API call) has two roles. cloud has every action on every resource type and belongs to stardog_admin and to your Stardog Cloud portal login. It looks like it covers everything, but once named-graph security is on it reads no graphs at all; build step 6 shows this and how to keep your own access. reader has read on db, named-graph, virtual-graph, data-source, metadata, stored-query and icv-constraints, all with the wildcard *. (* means "every resource of that type": every database, every named graph, and so on.) In Studio → Security, select the cloud role and its Permissions table shows seven rows, one per action (EXECUTE, GRANT, WRITE, CREATE, READ, REVOKE, DELETE), each with resource type * and resource *: everything. The USERS pane also lists two accounts you didn't create: admin, marked superuser (Stardog Cloud's built-in account; you never log in as it), and your Stardog Cloud portal login (your email address), which also has the cloud role.

hr_user user marketing_user user role: hr role: marketing PERMISSIONS (action · type · resource) read · db · kit-c360 read · named-graph · …\virtual://aurora_c360_safe read · named-graph · …\virtual://redshift_c360 read · named-graph · …\urn:…:model (×2) read · named-graph · …\virtual://aurora_c360_pii (…\ = kit-c360\) hr only
Figure 12.1: Users get roles, and roles hold permissions. Both roles share the "safe" permissions. Only hr gets the PII graph.

12.3Named-graph security, and why virtual graphs are covered

By default a user who can read a database can read every graph in it. The option security.named.graphs changes that: "If enabled, named graphs are an explicit resource type in Stardog's security model." It's false by default and on kit-c360 today, and can be changed while the database is online. Once it's on:

Do virtual graphs count? Yes. The docs' Virtual Graph Security page says "Accessing virtual graphs is controlled the same way as regular named graphs": with named-graph security off, all virtual graphs are accessible, and with it on, "users will be able to query only the virtual graphs for which they have been granted access." So the permission for our PII graph is read on named-graph:kit-c360\virtual://aurora_c360_pii. (That exact string isn't printed on the page; it follows from the named-graph format. If Studio rejects it, tell Claude.) The same page distinguishes query forms:

How the query names the graphUser lacks permission →
GRAPH <virtual://aurora_c360_pii> { … }treated as empty, no error
FROM <virtual://aurora_c360_pii>treated as empty, no error
SERVICE <virtual://aurora_c360_pii> { … }an error

This is why Chapter 8 split Aurora into aurora_c360_safe and aurora_c360_pii: security works per graph, and the PII mapping holds only :ssn and :cardNumber, so hiding that graph hides exactly those two properties.

SELECT ?name ?ssn … customer:1 same text, two users hr_user (role hr) marketing_user (role marketing) virtual://aurora_c360_safe ✓ virtual://redshift_c360 ✓ virtual://aurora_c360_pii ✓ virtual://aurora_c360_safe ✓ virtual://redshift_c360 ✓ virtual://aurora_c360_pii: dropped, no error name · ssn Jolynn Bithany · 609-38-5573 name · ssn Jolynn Bithany · (empty)

Same SPARQL, two roles, two result sets. HR sees the SSN. For marketing the PII graph was silently removed from the query's dataset, so the OPTIONAL part finds nothing.

Figure 12.2: Named-graph security is applied while the query is planned. The PII virtual graph isn't hidden after the fact. For marketing it's never queried at all.

12.4The alternative: sensitive properties

Splitting graphs means designing mappings around security. Stardog's other tool works per property: you list property IRIs in the database option security.properties.sensitive (named groups go in security.properties.sensitive.groups). Users without read on sensitive-properties:kit-c360 (or sensitive-properties:kit-c360\GroupName for a group) get the row with the value masked, by default as a SHA-256 hash. kit-c360 has a friendlier mask in security.masking.function: the string "Insufficient privileges to view this value".

So with security.properties.sensitive = tag:stardog:api:ecomm:ssn, marketing would see Jolynn Bithany | Insufficient privileges to view this value instead of an empty cell, arguably clearer because they learn the value exists. The blog describes this route as tagging :ssn as m:SensitiveProperty in the model (m: = urn:stardog:modules:); the server docs describe the database option, not that tag, so if you try it, set the option.

12.5Why this beats per-warehouse grants, and where it doesn't

Grants in each warehouseNamed-graph security in Stardog
Where policy livesAurora GRANTs + Redshift GRANTs, kept in sync by handOne place, stated in business terms (the "PII graph")
Cross-warehouse questionsEach engine enforces its half; the join happens in someone's codeOne query, one policy, applied before any SQL is sent
An agent writing arbitrary queriesNeeds its own DB credentials per warehouseRuns as one Stardog user and can't reach what that user can't read
Adding a third source (say, Athena)A third grant systemOne more graph to permission

The honest limit: Stardog reaches Aurora and Redshift through the data sources' own logins (Chapter 7), so the warehouses see one service account, not Priya or Marco. Keep that account least-privileged (read-only on schema c360) and treat Stardog's roles as the per-person layer on top; warehouse controls stay as defense in depth. (Stardog can pass through a user's own credentials for some sources, such as Databricks with Microsoft Entra ID, but not in our setup.)

12.6Build step 6: two roles, two users, one query

Users and roles live in Studio → Security (the padlock in the left sidebar), available on your Free tier. Per the docs: + in the ROLES pane to add a role; select a role → Add Permission (action, resource type, resource); + in the USERS pane to add a user; select a user → Assign Role. And "When adding permissions via Studio, you can omit the resource type prefix," so you type kit-c360\virtual://aurora_c360_safe, not named-graph:kit-c360\….

Check yourself

What are the three parts of a Stardog permission? Give one from this chapter.

Action, resource type, resource. For example read · named-graph · kit-c360\virtual://aurora_c360_pii (CLI form named-graph:kit-c360\virtual://aurora_c360_pii).

Does named-graph security cover virtual graphs? What's your source?

Yes. The docs' Virtual Graph Security page says access to virtual graphs "is controlled the same way as regular named graphs."

Marketing runs GRAPH <virtual://aurora_c360_pii> { ?c :ssn ?ssn }. Error or empty? And with SERVICE?

GRAPH (and FROM) give an empty result with no error, because the graph is silently dropped from the dataset. SERVICE gives an error.

Why did Chapter 8 split Aurora into two virtual graphs?

Named-graph security grants whole graphs. Putting only :ssn and :cardNumber in aurora_c360_pii lets you hide exactly the sensitive properties by withholding one graph.

How would sensitive properties change marketing's result, and why aren't they our main control?

Marketing would get the row with the SSN masked ("Insufficient privileges to view this value" here). The docs call the feature not production-ready (full-text search and zero-length paths can bypass it) and don't say whether it covers virtual graphs.

Does Stardog security replace Aurora and Redshift grants?

No. The warehouses see only Stardog's data source login, which you keep read-only and least-privileged; Stardog roles are the per-person layer on top.

Chapter recap

Paste it to Claude with what you saw (the counts or results), so we can confirm it matches before the next build step relies on it.