Security Guide
This guide is for smart-contract authors. It focuses on design choices that can make a contract unsafe even when the runtime, package provenance, and registry controls are working correctly.
For package evidence and release controls, use:
What Can Go Wrong
Most contract security issues come from one of these patterns:
| Risk | Example |
|---|---|
| Over-broad invocation | A write method uses anyUser or any when only an organization should call it. |
| Permission escalation | A caller uses a contract to reach a bucket the caller should not be able to affect. |
| Unbounded execution | A public method loops over unbounded input and exhausts fuel. |
| Unstable ABI | Callers and indexers cannot rely on input, output, or event shapes. |
| Data leakage | Events or return values include plaintext, secrets, keys, or private metadata. |
| Unsafe C2C dependency | A nested call introduces rollback, readiness, or authorization behavior the author did not expect. |
| Migration loss | Upgrade migration overwrites, deletes, or transforms state without enough guards. |
| Stale evidence | Package, manifest, provenance, or registry evidence no longer matches the code being deployed. |
The rest of this guide turns those risks into authoring rules.
Authorization
Choose the narrowest invoke.allow policy that matches the method.
| Policy | Use for | Be careful because |
|---|---|---|
user | One specific user. | User ids are operationally specific and may need rotation or ownership transfer. |
organization | Team, app, or tenant-scoped methods. | Everyone resolved to that organization may call. |
contract | Exact contract-to-contract callbacks. | The caller contract id must be stable and reviewed. |
anyUser | Public user-facing methods. | Any authenticated user can call. Avoid for writes unless business rules are intentionally public. |
anyContract | Reviewed extension points for all contracts. | Any deployed contract can call. Treat as an integration surface. |
any | Methods intentionally public to users and contracts. | Broader than anyUser; tooling should make reviewers pause. |
system | Platform-controlled flows. | Do not use unless the platform flow is explicitly designed for it. |
Example organization-only method:
{
"invoke": {
"allow": [
{ "principalType": "organization", "principalId": "org-noos" }
]
}
}Example exact C2C callback:
{
"invoke": {
"allow": [
{ "principalType": "contract", "principalId": "contract-report-caller" }
]
}
}Rules:
- Prefer
userororganizationfor writes and admin-like methods. - Use exact
contractprincipals for C2C callbacks. - Use
anyContractonly for deliberate, reviewed extension points. - Treat
anyas a production-risk exception unless the method is intentionally public to both users and contracts. - Do not assume a contract caller is a human. In nested calls, reason about both the root caller and the immediate caller contract.
- Keep governance/admin operations out of contract methods unless governance has approved that contract and every caller path.
For the full authorization model, see Authorization.
Bucket Safety
Bucket-sensitive host operations require several gates to line up:
method manifest declares bucket access
AND deployed contract principal has bucket permission
AND caller has bucket permission where the host operation requires it
AND local payload availability satisfies the manifest when requiredMetadata-only read:
{
"bucketId": "bucket-reports",
"access": ["bucket:read_metadata"],
"availability": "metadata"
}Local encrypted payload availability:
{
"bucketId": "bucket-reports",
"access": ["bucket:read_encrypted"],
"availability": "encrypted_payload_local"
}Rules:
- Declare only the bucket permissions a method actually needs.
- Prefer
availability: "metadata"unless the method truly depends on local encrypted payload availability. - Remember that readiness can fail on nodes missing required bucket permissions or local payloads.
- Do not use a contract as a way to bypass caller bucket access.
- Never emit plaintext, decrypted payloads, DEKs, private keys, auth tokens, or sensitive metadata in events or return values.
- Review the bucket index schema before writing encrypted-record metadata or public indexes.
For buckets and permission details, see Data Buckets and Authorization.
Resource And Fuel Safety
NOOSChain does not charge gas, but every contract call has deterministic protocol limits: fuel, memory, host calls, argument size, event size, return size, and write effects.
Good pattern:
const max = min(input.items.length, 50);
for (let i = 0; i < max; i += 1) {
processItem(input.items[i]);
}Bad pattern:
while (true) {
pollUntilSomethingChanges();
}Rules:
- Bound every loop by an input limit, manifest rule, or fixed constant.
- Cap arrays and maps in the ABI.
- Avoid recursion unless depth is explicitly bounded.
- Prefer predictable storage writes over many small dynamic writes.
- Keep events and return values compact.
- Use method
maxFuelas a local circuit breaker for public or expensive methods. - Split large workflows across multiple transactions.
- Do not depend on wall-clock time, randomness, filesystem access, network access, or node-local configuration.
For method limits, see Manifest Methods.
ABI And Schema Discipline
The manifest ABI is the caller contract. Treat it like a public API.
Good input shape:
{
"type": "object",
"properties": {
"id": { "type": "string" },
"status": {
"type": "string",
"enum": ["draft", "submitted", "approved"]
},
"formatVersion": { "type": "integer" }
},
"required": ["id", "status", "formatVersion"]
}Rules:
- Include
formatVersionfor inputs and stored values that will evolve. - Make required fields explicit.
- Use enums for workflow states.
- Avoid hidden defaults that depend on time, randomness, or external services.
- Keep output schemas stable.
- Document event schemas in the manifest.
- Treat breaking ABI or event changes as a package version change.
For ABI fields, see Manifest Methods.
Events And Returns
Events are durable execution records for observers and indexers. Return values are structured call results for the transaction CALL_CONTRACT receipt.
Rules:
- Use stable topic names, such as
report.savedormigration.completed. - Include ids, public status, and correlation fields.
- Keep payloads small and bounded.
- Return one structured value per successful call.
- Do not include secrets, plaintext encrypted payloads, private keys, auth tokens, or sensitive operational metadata.
- Assume events and returns may be retained and indexed.
For AssemblyScript helpers, see AssemblyScript SDK.
Contract-To-Contract Safety
C2C calls are useful for composition, but they introduce dependency and rollback behavior.
Caller manifest:
{
"calls": [
{
"contractId": "contract-report-target",
"methods": ["summarize"],
"requiredBuckets": []
}
]
}Target method authorization:
{
"invoke": {
"allow": [
{ "principalType": "contract", "principalId": "contract-report-caller" }
]
}
}Rules:
- Declare every outbound target and method.
- Keep nested calls shallow.
- Avoid dependency cycles.
- Treat nested calls as all-or-nothing: if the target fails, the parent call rolls back.
- Avoid broad target auth when the method is intended for one caller contract.
- Include target bucket requirements on the caller edge.
- Review target readiness and deactivation risk before relying on a target.
For C2C manifest fields, see Manifest Calls.
Migration Safety
Contract state is isolated by contract id. Upgrades usually deploy a new contract id and then migrate selected state.
Rules:
- Version state keys or stored values, for example
reports:v1:<id>or{ "formatVersion": 1 }. - Pin expected source and target code hashes.
- Pin expected source and target manifest hashes.
- Dry-run migration payloads before submitting.
- Use resumable migration jobs for large state moves.
- Keep batches small enough for fuel and write-effect limits.
- Prefer schema-transform migrations when declarative transforms are enough.
- Use contract-defined WASM migrations only when transformation logic needs contract code.
- Keep old packages, manifests, registry entries, and migration receipts for replay and incident review.
- Test rollback assumptions before deactivating the old contract.
Good migrated value:
{
"formatVersion": 2,
"sourceContractId": "contract-report-store-v1",
"id": "report-001"
}For migration flows, see Manifest Migrations and Publishing.
Package Handoff
This guide does not duplicate package, provenance, and registry rules. Before asking an operator or reviewer to approve a package, hand off enough evidence for those pages to be checked:
- package directory;
noos-contract.json;- manifest;
- WASM artifact;
- package provenance signatures, if required;
- tests and review notes;
- registry payload or registry entry id, if required;
- migration plan and receipts, if upgrading.
Use:
- Publishing for the transaction workflow;
- Packages And Provenance for the integrity chain;
- Package Provenance for signatures and policy;
- Contract Registry for network approval.
Review Checklist
Authorization:
- [ ] Every method has the narrowest practical
invoke.allow. - [ ] Write/admin-like methods do not use broad caller policies without review.
- [ ] C2C target methods use exact
contractprincipals or deliberateanyContractextension points.
Data and buckets:
- [ ] Every bucket host operation has a matching manifest bucket declaration.
- [ ] Contract principal bucket grants are minimal.
- [ ] Caller bucket access is considered where host checks require it.
- [ ] Events and returns contain no plaintext, secrets, or keys.
Execution limits:
- [ ] Every loop is bounded.
- [ ] ABI inputs cap arrays and large values.
- [ ] Public or expensive methods have suitable
maxFuel. - [ ] Tests cover traps, host errors, and fuel exhaustion where relevant.
ABI, events, and returns:
- [ ] Inputs and stored values include version fields where they may evolve.
- [ ] Required fields and enums are explicit.
- [ ] Output and event schemas are documented and stable.
C2C:
- [ ] Caller manifests declare outbound
calls. - [ ] Target methods authorize the intended caller contract.
- [ ] Dependency cycles and deactivation risk are reviewed.
Migrations:
- [ ] Source and target hashes are pinned.
- [ ] Migration dry-run has been reviewed.
- [ ] Large migrations use resumable jobs.
- [ ] Receipts and old package artifacts are archived.
Package/release handoff:
- [ ] Package descriptor matches WASM and manifest hashes.
- [ ] Provenance requirements are understood.
- [ ] Registry requirements are understood.
- [ ] Publishing checklist is complete.