diff --git a/reference/components/javascript-environment.md b/reference/components/javascript-environment.md index b9aecda3..52b3a5b5 100644 --- a/reference/components/javascript-environment.md +++ b/reference/components/javascript-environment.md @@ -85,6 +85,10 @@ An object whose properties are the tables in the default database (`data`). Each See [Database API](../database/api.md) for full reference. +:::note Server-side `tables.*` reads run in a trusted context +Calls to `tables.X.get()`, `tables.X.search()`, and similar methods from within resource code (or Operations API SQL) execute in a **trusted system context** and do **not** re-apply the target table's `allowRead` guard or role-level `attribute_permissions`. This is by design — server-side code is responsible for its own authorization checks. A computed attribute or custom resource that cross-reads a protected table will pull the raw data into the response regardless of the caller's role. If you expose data from a protected table through a custom resource, apply authorization explicitly (e.g. check `getCurrentUser()` and enforce the relevant restrictions before returning the data). +::: + ### `databases` An object containing all databases defined in Harper. Each database is an object of its tables — `databases.data` is always equivalent to `tables`. diff --git a/reference/database/schema.md b/reference/database/schema.md index 904e1389..ece65002 100644 --- a/reference/database/schema.md +++ b/reference/database/schema.md @@ -174,6 +174,10 @@ type MyTable @table @export(name: "my-table") { The optional `name` parameter specifies the URL path segment (e.g., `/my-table/`). Without `name`, the type name is used. +:::warning `@export` is a routing directive, not an access control +Omitting `@export` removes the REST/MQTT route for a table (callers get 404), but it does **not** protect the data. The table still exists in the database and its records remain fully accessible to administrators via the Operations API and SQL. For data confidentiality, use role permissions (`attribute_permissions`, `read: false`) rather than relying on the absence of an export route. +::: + ### `@sealed` Prevents records from including any properties beyond those explicitly declared in the type. By default, Harper allows records to have additional properties. @@ -196,6 +200,10 @@ type InternalConfig @table @hidden { } ``` +:::warning `@hidden` does not restrict data access +`@hidden` only suppresses a type or field from generated API specs and MCP tool schemas. The underlying data is returned on **all** read surfaces — REST, SQL, and the Operations API — for any user with table-level `read` permission. Do not use `@hidden` as a confidentiality control. To restrict which users can read a field or table, use role `attribute_permissions` with `read: false`. +::: + `@hidden` is also available as a [field directive](#hidden-field-directive) to suppress individual attributes. ## Documenting Types and Fields @@ -326,7 +334,7 @@ type Event @table { ### `@hidden` (Field Directive) -Suppresses the field from MCP tool descriptors and the OpenAPI document. The attribute still exists in the table; data is still queryable through other interfaces subject to RBAC. Use this for fields that should not appear in introspectable surfaces. +Suppresses the field from MCP tool descriptors and the OpenAPI document. The attribute still exists in the table; data is still returned on all read surfaces (REST GET, SQL, Operations API) for any user with read permission on the table. Use this for fields that should not appear in generated specs or tool schemas, not to restrict data access. ```graphql type Customer @table { @@ -340,7 +348,7 @@ type Customer @table { } ``` -`@hidden` is a metadata-visibility directive, not access control: `attribute_permissions` on roles remains the data-access enforcement mechanism. +`@hidden` is a metadata-visibility directive, not access control: `attribute_permissions` on roles remains the data-access enforcement mechanism. A field marked `@hidden` is still readable by any role with table `read` access — to prevent a role from reading a field value, set `read: false` in `attribute_permissions` for that role. ## Relationships diff --git a/reference/users-and-roles/overview.md b/reference/users-and-roles/overview.md index cc4f11bb..3a8e0670 100644 --- a/reference/users-and-roles/overview.md +++ b/reference/users-and-roles/overview.md @@ -173,6 +173,10 @@ Each table entry defines CRUD access: - `DELETE` is not an attribute-level permission. Deleting rows is controlled at the table level. - The `__createdtime__` and `__updatedtime__` attributes managed by Harper can have `read` permissions set; other attribute-level permissions for these fields are ignored. +:::note Attribute `read: false` — filter side-channel +Setting `read: false` on an attribute prevents the **value** from appearing in any response body (REST, SQL, Operations API, GraphQL all omit or reject it). However, the attribute can still be used as a **filter predicate** by that role — e.g. `GET /Table/?salary=95000` returns 0 or 1 rows, revealing whether any record holds that exact value. Binary-search enumeration of the restricted column is possible without ever reading a value directly. If preventing any inference from query results is a requirement, the application must reject or ignore filter conditions on `read: false` attributes in a custom resource handler. +::: + ## Role-Based Operation Restrictions ### Databases and Tables