Bucket Permissions
Bucket permissions are consensus-state rules. Any transaction that mutates bucket access or bucket records must pass deterministic permission checks during block execution. API checks improve read security and errors, but block execution remains authoritative because future transactions may arrive from peers.
Permissions decide who may read metadata, fetch encrypted bytes, write records, or administer a bucket. They do not decrypt data by themselves. For how permissions interact with user keys, bucket keys, record DEKs, and envelopes, see Key Model.
Permissions
NOOSChain currently defines:
bucket:read_metadata: view bucket metadata and record metadata/indexesbucket:read_encrypted: view encrypted payload bytesbucket:write: add encrypted recordsbucket:admin: manage access rules and implies every other bucket permission
Actors
Execution derives authority from transaction.envelope.signerPublicKey, not from payload fields. The signer public key maps deterministically to users.public_key; the user's organization comes from users.organization_id. GENESIS is a system actor and may perform genesis/system operations.
createdByUserId and createdByOrganizationId remain part of payload and state, but they are not authorization proof. For record writes and bucket creation, non-system transactions must have creator fields that match the resolved signer user and organization.
Smart contract support adds a contract actor for deployed contract instances. A contract actor is not a normal user and does not sign transactions directly. It is resolved from contract instance consensus state during contract execution and has a contractId, organizationId, and ownerUserId.
Ownership
The bucket creator user and creator organization have implicit bucket:admin. This ownership is stored in the bucket row itself, so replay can derive it without needing an explicit access-rule row.
Access Rules
bucket_access_rules grants permissions to a user, organization, or contract. Organization rules apply to every actor in that organization. Contract rules apply to one deployed contract instance. Permission evaluation is deterministic: system actor, implicit owner/admin, direct user or contract rule, then organization rule. bucket:admin implies all permissions.
Access rules can be added, updated, and removed by protocol-versioned transactions:
ADD_BUCKET_ACCESS_RULEcreates a new rule.UPDATE_BUCKET_ACCESS_RULEfully replaces the permission list on an existing rule. The payload must match the existingbucketId,principalType, andprincipalId.REMOVE_BUCKET_ACCESS_RULEdeletes an existing explicit rule and removes its SMT state leaf.
All three operations require bucket:admin on the target bucket, except for GENESIS/system transactions. Permission lists are normalized before storage and state hashing: duplicates are removed, strings are sorted, and unknown permissions are rejected. Empty update permission lists are invalid; use REMOVE_BUCKET_ACCESS_RULE instead. Contract principals must refer to an existing contract instance.
For smart contract calls, the intended v1 rule is that both the caller and the contract must be authorized for sensitive bucket operations. The contract's manifest controls who may invoke each method, and bucket access rules control what the contract principal may do once invoked.
Removing an explicit admin rule is allowed because the bucket creator user and creator organization retain implicit admin through the bucket state itself. Removing permissions affects future authorization immediately, but it does not rewrite past records or blocks.
Operators can inspect explicit access rules through GET /node/observability/bucket-access-rules or the Admin GUI Access Rules page. That observability view is read-only and can be filtered by bucket, user, or organization. It does not create, update, or remove rules; mutations still go through the signed transaction path above.
The operator CLI can build these signed transactions with noos tx, for example:
npm run noos -- tx build --type ADD_BUCKET_ACCESS_RULE --payload-file rule.json --signer-public-key="<admin-public-key>" --signer-private-key-path admin.key --output rule-tx.json
npm run noos -- tx submit --file rule-tx.json --yesFor guided bucket initialization, operators can use recipes:
npm run noos -- recipe init:template bucket --output bucket.json
npm run noos -- recipe init:create-bucket --payload-file bucket.json --signer-public-key="<writer-public-key>" --signer-private-key-path writer.key --dry-run
npm run noos -- recipe init:template bucket-access-rule --output rule.json
npm run noos -- recipe init:assign-bucket-principal --payload-file rule.json --signer-public-key="<admin-public-key>" --signer-private-key-path admin.key --yes
npm run noos -- recipe init:template bucket-policy --output policy.json
npm run noos -- recipe init:update-bucket-policy --payload-file policy.json --signer-public-key="<admin-public-key>" --signer-private-key-path admin.key --yesRecipes validate and sign the same consensus transactions; they do not bypass bucket permission checks.
ADD_ENCRYPTED_RECORD payload files may contain encrypted payload bytes. They are not plaintext, but they are still sensitive local operator files and should be handled accordingly.
Enforcement Points
CREATE_BUCKET requires the signer to be system or the same user as createdByUserId, and that user must belong to createdByOrganizationId.
ADD_BUCKET_ACCESS_RULE requires bucket:admin on the target bucket. The principal must exist and every permission must be one of the known permission constants.
UPDATE_BUCKET_ACCESS_RULE and REMOVE_BUCKET_ACCESS_RULE also require bucket:admin. Update state hashes include the normalized replacement permissions. Removal emits a delete state change for the access-rule leaf, so replay and SMT roots reflect the permission removal deterministically.
ADD_ENCRYPTED_RECORD requires bucket:write, and creator fields must match the signer unless the actor is system. Public index validation and encrypted payload state hashing rules still apply.
Read APIs use the temporary x-noos-public-key header to resolve an actor:
GET /buckets/:idrequiresbucket:read_metadataGET /buckets/:id/records/searchrequiresbucket:read_metadata- including encrypted payload bytes requires
bucket:read_encrypted
Record search can filter on public metadata with index.<key>=<value> query parameters. Those filters are validated against the bucket index schema and executed through typed record_indexes rows; see Record Public Indexes.
This header is a development placeholder. Production authentication should replace it with signed requests or session-bound identities, but the server must still resolve to the same deterministic public-key/user model.
The permission layer never decrypts data. It gates metadata, encrypted bytes, and whether application actors can read bucket-key metadata. For node replication, bucket replication policy plus verified node membership controls whether the node may receive ciphertext and, for per_bucket_key, the matching bucket-key envelope metadata. encrypted_payload remains availability-layer data; consensus state and replay depend on payload_hash, metadata, key envelopes, public indexes, creator identifiers, and transaction hash.
Encrypted upload requires bucket:write. Encrypted payload retrieval for application actors requires bucket:read_encrypted; metadata inspection requires bucket:read_metadata; active bucket-key reads require bucket:read_encrypted or bucket:admin, except for verified node replication requests allowed by the bucket replication strategy. Decryption is explicit and client-side in the intended production model.
If bucket:read_encrypted is removed, future authorized-only sync and backfill skip ciphertext for that principal. Ciphertext already stored locally is not physically deleted yet, but current API permissions still control access: without current bucket:read_encrypted, record APIs return encryptedPayload: null. Future payload pruning or garbage collection can physically remove old ciphertext without changing consensus state.