Skip to content

Commit f47f221

Browse files
authored
Merge pull request #279 from onflow/josh/entitlements-guidance
Update guidance for capabilities and entitlements
2 parents f359024 + 8705671 commit f47f221

7 files changed

Lines changed: 213 additions & 64 deletions

File tree

docs/anti-patterns.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,12 @@ Accidentally exposed fields can be a security hole.
8383
### Solution
8484

8585
When writing your smart contract, look at every field and function and make sure
86-
that require access through an [entitlement](./language/access-control.md#entitlements) (`access(E)`),
86+
they require access through an [entitlement](./language/access-control.md#entitlements) (`access(E)`),
8787
or use a non-public [access modifier](./language/access-control.md) like `access(self)`, `access(contract)`, or `access(account)`,
88-
unless otherwise needed.
88+
unless you are making a deliberate design decision to allow completely open and unrestricted access to read that field or call that function.
89+
90+
The only functions that should be `access(all)` are `view` functions and the only fields that can be `access(all)` are basic types like numbers or addresses.
91+
Complex fields like arrays, dictionaries, structs, resources, or capabilities should always be `access(self)`.
8992

9093
## Capability-Typed public fields are a security hole
9194

docs/design-patterns.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -501,23 +501,36 @@ transaction(provider: Address, name: String) {
501501
}
502502
```
503503

504-
## Check for existing capability before publishing new one
504+
## Check for existing capabilities before issuing or publishing new ones
505505

506506
### Problem
507507

508-
When publishing a capability, a capability might be already be published at the specified path.
508+
When issuing or publishing a capability, a capability might be already be issued or published at the specified path for the desired capability type.
509509

510510
### Solution
511511

512-
Check if a capability is already published at the given path.
512+
Check if a capability is already issued and/or published at the given paths.
513513

514514
### Example
515515

516516
```cadence
517517
transaction {
518518
prepare(signer: auth(Capabilities) &Account) {
519-
let capability = signer.capabilities.storage
520-
.issue<&ExampleToken.Vault>(/storage/exampleTokenVault)
519+
var capability: Capability<&ExampleToken.Vault>? = nil
520+
521+
// get the capability to the vault at the given storage path if it exists
522+
let vaultCaps = account.capabilities.storage.getControllers(forPath: /storage/exampleTokenVault)
523+
for cap in vaultCaps {
524+
if let cap = cap as? Capability<&ExampleToken.Vault> {
525+
capability = cap
526+
break
527+
}
528+
}
529+
530+
if capability == nil {
531+
// issue a new capability to the vault since it wasn't found
532+
capability = account.capabilities.storage.issue<&ExampleToken.Vault>(/storage/exampleTokenVault)
533+
}
521534
522535
let publicPath = /public/exampleTokenReceiver
523536

docs/language/access-control.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,20 @@ In Cadence, access control is used in two ways:
1212
A user is not able to access an object unless they own the object or have a reference to that object. This means that nothing is truly public by default.
1313

1414
Other accounts cannot read or write the objects in an account unless the owner of the account has granted them access by providing references to the objects.
15+
16+
This kind of access control is covered in [capabilities] and [capability management].
17+
18+
2. Access control within contracts and objects, using access modifiers (`access` keyword).
19+
20+
This page covers the second part of access control, using access modifiers.
1521

1622
:::warning
1723

18-
Remember that in this case, `private` refers to programmatic access to the data with a script or transaction. It is **not safe** to store secret or private information in a user's account. The raw data is still public and could be decoded.
24+
Remember that in this case, `private` refers to programmatic access to the data with a script or transaction. It is **not safe** to store secret or private information in a user's account. The raw data is still public and could be decoded by reading the public blockchain data directly.
1925

2026
:::
21-
22-
This kind of access control is covered in [capabilities] and [capability management].
23-
24-
1. Access control within contracts and objects, using access modifiers (`access` keyword).
2527

26-
This page covers the second part of access control, using access modifiers.
28+
## The `access` keyword
2729

2830
All declarations, such as [functions], [composite types], and fields, must be prefixed with an access modifier using the `access` keyword.
2931

@@ -36,6 +38,14 @@ access(all)
3638
fun test() {}
3739
```
3840

41+
:::danger
42+
43+
If you prefix a function with `access(all)`, you are likely granting complete and open access for **anyone using any account to call that function.** It is critical that you properly use [entitlements] to restrict sensitive functions to the accounts that need access.
44+
45+
For example, if you create a vault for your users and give the `withdraw` function `access(all)`, anyone can drain that vault if they know where to find it.
46+
47+
:::
48+
3949
## Types of access control
4050

4151
There are five levels of access control:
@@ -46,9 +56,11 @@ There are five levels of access control:
4656

4757
For example, a public field in a type can be accessed on an instance of the type in an outer scope.
4858

49-
- **Entitled access** — the declaration is only accessible/visible to the owner of the object, or to [references] that are authorized to the required [entitlements].
59+
- **Entitled access** — the declaration is only accessible/visible to the owner/holder of the object, or to [references] that are authorized to the required [entitlements].
5060

5161
A declaration is made accessible through entitlements by using the `access(E)` syntax, where `E` is a set of one or more entitlements, or a single [entitlement mapping].
62+
63+
An entitled field acts like an `access(all)` field ONLY if the caller actually holds the concrete resource object and not just a reference to it. In that case, an authorized reference is needed.
5264

5365
A reference is considered authorized to an entitlement if that entitlement appears in the `auth` portion of the reference type.
5466

docs/security-best-practices.md

Lines changed: 140 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,148 @@ This is an opinionated list of best practices that Cadence developers should fol
88

99
Some practices listed below might overlap with advice in the [Cadence Anti-Patterns] article, which is a recommended read as well.
1010

11+
## Access Control
12+
13+
Do not use the `access(all)` modifier on fields and functions unless absolutely necessary. Prefer `access(self)`, `access(contract)`, `access(account)`, or `access(SomeEntitlement)`. Unintentionally declaring fields or functions as `access(all)` can expose vulnerabilities in your code.
14+
15+
When writing definitions for contracts, structs, or resources, start by declaring all your fields and functions as `access(self)`. If there is a function that needs to be accessible by external code, only declare it as `access(all)` if it is a `view` function:
16+
17+
```cadence
18+
/// Simplified Bank Account implementation
19+
access(all) resource BankAccount {
20+
21+
/// Fields should default to access(self) to be safe
22+
/// and be readable through view functions
23+
access(self) var balance: UFix64
24+
25+
/// It is okay to make this function access(all) because it is a view function
26+
/// and all blockchain data is public
27+
access(all) view fun getBalance(): UFix64 {
28+
return self.balance
29+
}
30+
}
31+
```
32+
33+
If there are any functions that modify state that also need to be callable from external code, use [entitlements] for the access modifiers for those functions:
34+
35+
```cadence
36+
/// Simplified Vault implementation
37+
/// Simplified Bank Account implementation
38+
access(all) resource BankAccount {
39+
40+
/// Declare Entitlements for state-modifying functions
41+
access(all) entitlement Owner
42+
access(all) entitlement Depositor
43+
44+
/// Fields should default to access(self) just to be safe
45+
access(self) var balance: UFix64
46+
47+
/// All non-view functions should be something other than access(all),
48+
49+
/// This is only callable by other functions in the type, so it is `access(self)`
50+
access(self) fun updateBalance(_ new: UFix64) {
51+
self.balance = new
52+
}
53+
54+
/// This function is external, but should only be called by the owner
55+
/// so we use the `Owner` entitlement
56+
access(Owner) fun withdrawFromAccount(_ amount: UFix64): @BankAccount {
57+
self.updateBalance(self.balance - amount)
58+
return <-create BankAccount(balance: amount)
59+
}
60+
61+
/// This is also state-modifying, so it should also be restricted with entitlements
62+
/// In this case, we can use two entitlements to be more specific
63+
/// about who can access (Owner OR Depositor)
64+
access(Owner | Depositor) fun depositToAccount(_ from: @BankAccount) {
65+
self.updateBalance(self.balance + from.getBalance())
66+
destroy from
67+
}
68+
}
69+
```
70+
71+
## Access Control for Composite-typed Fields
72+
73+
Declaring a field as [`access(all)`] only protects from replacing the field's value, but the value itself can still be mutated if it is mutable. Remember that containers, like dictionaries and arrays, are mutable and composite fields like structs and resources are still mutable through their own functions.
74+
75+
:::danger
76+
77+
This means that if you ever have a field that is a resource, struct, or capability, it should ALWAYS be `access(self)`! If it is `access(all)`, anyone could access it and call its functions, which could be a major vulnerability.
78+
79+
You can still allow external code to access that field, but only through functions that you have defined with `access(SomeEntitlement)`. This way, you can explicitly define how external code can access these fields.
80+
81+
:::
82+
83+
# Capabilities
84+
85+
## Issuing Capabilities
86+
87+
Don't issue and publish capabilities unless absolutely necessary. Anyone can access capabilities that are published. If public access is needed, follow the [principle of least privilege/authority]: make sure that the capability type only grants access to the fields and functions that should be exposed, and nothing else. Ideally, create a capability with a reference type that is unauthorized.
88+
89+
When issuing a capability, a capability of the same type might already be present. It is a good practice to check if a capability already exists with `getControllers()` before creating it. If it already exists, you can reuse it instead of issuing a new one. This prevents you from overloading your account storage and overpaying because of redundant capabilities.
90+
91+
```cadence
92+
// Capability to find or issue
93+
var flowTokenVaultCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>? = nil
94+
95+
// Get all the capabilities that have already been issued for the desired storage path
96+
let flowTokenVaultCaps = account.capabilities.storage.getControllers(forPath: /storage/flowTokenVault)
97+
98+
// Iterate through them to see if there is already one of the needed type
99+
for cap in flowTokenVaultCaps {
100+
if let cap = cap as? Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault> {
101+
flowTokenVaultCap = cap
102+
break
103+
}
104+
}
105+
106+
// If no capabilities of the needed type are already present,
107+
// issue a new one
108+
if flowTokenVaultCap == nil {
109+
// issue a new entitled capability to the flow token vault
110+
flowTokenVaultCap = account.capabilities.storage.issue<auth(FungibleToken.Withdraw) &FlowToken.Vault>(/storage/flowTokenVault)
111+
}
112+
```
113+
114+
## Publishing Capabilities
115+
116+
When publishing a capability, a published capability might already be present. It is a good practice to check if a capability already exists with `borrow` before creating it. This function will return `nil` if the capability does not exist.
117+
118+
```cadence
119+
// Check if the published capability already exists
120+
if account.capabilities.borrow<&FlowToken.Vault>(/public/flowTokenReceiver) == nil {
121+
// since it doesn't exist yet, we should publish a new one that we created earlier
122+
signer.capabilities.publish(
123+
receiverCapability,
124+
at: /public/flowTokenReceiver
125+
)
126+
}
127+
```
128+
129+
## Checking Capabilities
130+
131+
If it is necessary to handle the case where borrowing a capability might fail, the `account.check` function can be used to verify that the target exists and has a valid type:
132+
133+
```cadence
134+
// check if the capability is valid
135+
if capability.check() {
136+
let reference = capability.borrow()
137+
} else {
138+
// do something else if the capability isn't valid
139+
}
140+
```
141+
142+
## Capability Access
143+
144+
Ensure capabilities cannot be accessed by unauthorized parties. For example, capabilities should not be accessible through a public field, including public dictionaries or arrays. Exposing a capability in such a way allows anyone to borrow it and to perform all actions that the capability allows, including `access(all)` fields and functions that aren't even in the restricted type of the capability.
145+
11146
## References
12147

13148
[References] are ephemeral values and cannot be stored. If persistence is required, store a capability and borrow it when needed.
14149

15-
When exposing functionality, provide the least access necessary. When creating an authorized reference, create it with only the minimal set of entitlements required to achieve the desired functionality.
150+
When exposing functionality in an account, struct, or resource, provide the least access necessary. When creating an authorized reference with [entitlements], create it with only the minimal set of [entitlements] required to achieve the desired functionality.
151+
152+
# Accounts
16153

17154
## Account storage
18155

@@ -28,18 +165,6 @@ Therefore, avoid passing an entitled account reference to a function, and when d
28165

29166
It is preferable to use capabilities over direct account storage access when exposing account data. Using capabilities allows the revocation of access and limits the access to a single value with a certain set of functionality.
30167

31-
## Capabilities
32-
33-
Don't issue and publish capabilities unless really necessary. Anyone can access capabilities that are published. If public access is needed, follow the [principle of least privilege/authority]: make sure that the capability type only grants access to the fields and functions that should be exposed, and nothing else. Ideally, create a capability with a reference type that is unauthorized.
34-
35-
If an entitlement is necessary to access the field or function, ensure it is only used for the particular field or function, and not also by other fields and functions. If needed, introduce a new, fine-grained entitlement.
36-
37-
When publishing a capability, a capability might already be present. It is a good practice to check if a capability already exists with `get` before creating it. This function will return `nil` if the capability does not exist.
38-
39-
If it is necessary to handle the case where borrowing a capability might fail, the `account.check` function can be used to verify that the target exists and has a valid type.
40-
41-
Ensure capabilities cannot be accessed by unauthorized parties. For example, capabilities should not be accessible through a public field, including public dictionaries or arrays. Exposing a capability in such a way allows anyone to borrow it and to perform all actions that the capability allows.
42-
43168
## Transactions
44169

45170
Audits of Cadence code should also include [transactions], as they may contain arbitrary code, just like in contracts. In addition, they are given full access to the accounts of the transaction's signers (i.e., the transaction is allowed to manipulate the signer's account storage, contracts, and keys).
@@ -58,14 +183,6 @@ Use [intersection types and interfaces]. Always use the most specific type possi
58183

59184
If given a less-specific type, cast to the more specific type that is expected. For example, when implementing the fungible token standard, a user may deposit any fungible token, so the implementation should cast to the expected concrete fungible token type.
60185

61-
## Access control
62-
63-
Declaring a field as [`access(all)`] only protects from replacing the field's value, but the value itself can still be mutated if it is mutable. Remember that containers, like dictionaries and arrays, are mutable.
64-
65-
Prefer non-public access to a mutable state. That state may also be nested. For example, a child may still be mutated even if its parent exposes it through a field with non-settable access.
66-
67-
Do not use the `access(all)` modifier on fields unless necessary. Prefer `access(self)`, or `access(contract)` and `access(account)`, when other types in the contract or account need to have access, and use entitlement-based access for other cases.
68-
69186
<!-- Relative links. Will not render on the page -->
70187

71188
[Cadence Anti-Patterns]: ./design-patterns.md
@@ -76,4 +193,5 @@ Do not use the `access(all)` modifier on fields unless necessary. Prefer `access
76193
[transactions]: ./language/transactions.md
77194
[principle of least privilege/authority]: https://en.wikipedia.org/wiki/Principle_of_least_privilege
78195
[intersection types and interfaces]: ./language/types-and-type-system/intersection-types.md
79-
[`access(all)`]: ./language/access-control.md
196+
[`access(all)`]: ./language/access-control.md
197+
[entitlements]: ./language/access-control.md

docs/tutorial/03-resources.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ We've already imported the `HelloResource` contract for you and stubbed out a `t
179179

180180
To prepare:
181181

182-
1. Create a `prepare` phase with the `SaveValue` authorization [entitlement] to the user's account.
182+
1. Create a `prepare` phase with the `SaveValue` authorization [entitlement] to the user's account. This authorizes the transaction to save values or objects anywhere in account storage. You'll learn more about entitlements in the next lesson.
183183
1. Use `create` to create a new instance of the `HelloAsset`.
184184
1. Save the new resource in the user's account.
185185
1. Inside the `transaction`, stub out the `prepare` phase with the authorization [entitlement]:
@@ -213,7 +213,7 @@ Paths in the storage domain have type `StoragePath`, and paths in the public dom
213213

214214
Paths are **not** strings and do **not** have quotes around them.
215215

216-
Use the account reference with the `SaveValue` authorization [entitlement] to move the new resource into storage located in `/storage/HelloAssetTutorial`:
216+
Next, use the account reference with the `SaveValue` authorization [entitlement] to move the new resource into storage located in `/storage/HelloAssetTutorial`:
217217

218218
```cadence
219219
acct.storage.save(<-newHello, to: /storage/HelloAssetTutorial)
@@ -364,6 +364,7 @@ In real applications, you need to check the location path you are storing in to
364364
// Existing code...
365365
}
366366
```
367+
This [entitlement] makes it so you can borrow a reference to a value in the account's storage in addition to being able to save a value.
367368
1. Add a `transaction`-level (similar to contract-level or class-level) variable to store a result `String`.
368369

369370
- Similar to a class-level variable in other languages, these go at the top, inside the `transaction` scope, but not inside anything else. They are accessible in both the `prepare` and `execute` statements of a transaction:

0 commit comments

Comments
 (0)