From 7c58572f2ab121de007cbae4d69965eeaadcbf90 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 21:32:18 +0200 Subject: [PATCH 01/12] docs(security): port security best practices 1:1 from portal Replaces AI-generated security docs with verified portal content, adds 8 missing topic files, 2 new prerequisite pages, and fixes a confirmed double-spend bug in the inter-canister-calls guide. Changes: - Replace: inter-canister-calls.md, access-management.mdx, canister-upgrades.md, dos-prevention.md, data-integrity.md - Add: overview.md, data-storage.md, decentralization.md, https-outcalls.md, misc.md, observability.md, resources.md, formal-verification.md - Add: references/message-execution-properties.md (prerequisite referenced by inter-canister-calls.md) - Add: guides/canister-calls/idempotency.md (prerequisite for safe retry patterns in inter-canister calls) - Fix sidebar order conflicts (now matches portal ordering 1-14) - Fix MDX HTML comment syntax in access-management.mdx - Add security and reference diagram images to public/img/ - encryption.mdx flagged for separate security team review (new content not from portal, not changed here) --- docs/guides/canister-calls/idempotency.md | 102 ++ docs/guides/security/access-management.mdx | 497 ++++------ docs/guides/security/canister-upgrades.md | 350 +------ docs/guides/security/data-integrity.md | 904 +++++++++++------- docs/guides/security/data-storage.md | 94 ++ docs/guides/security/decentralization.md | 76 ++ docs/guides/security/dos-prevention.md | 360 +------ docs/guides/security/encryption.mdx | 2 +- docs/guides/security/formal-verification.md | 34 + docs/guides/security/https-outcalls.md | 97 ++ docs/guides/security/inter-canister-calls.md | 605 +++++------- docs/guides/security/misc.md | 280 ++++++ docs/guides/security/observability.md | 22 + docs/guides/security/overview.md | 22 + docs/guides/security/resources.md | 39 + .../message-execution-properties.md | 78 ++ .../references/example_highlighted_code.png | Bin 0 -> 32553 bytes .../img/docs/references/example_orderings.png | Bin 0 -> 34582 bytes public/img/docs/retry_idempotency.png | Bin 0 -> 55032 bytes .../security/example_trap_after_await.png | Bin 0 -> 70597 bytes .../security/ii_mobile_delegation_chain.png | Bin 0 -> 95362 bytes sidebar.mjs | 1 + 22 files changed, 1868 insertions(+), 1695 deletions(-) create mode 100644 docs/guides/canister-calls/idempotency.md create mode 100644 docs/guides/security/data-storage.md create mode 100644 docs/guides/security/decentralization.md create mode 100644 docs/guides/security/formal-verification.md create mode 100644 docs/guides/security/https-outcalls.md create mode 100644 docs/guides/security/misc.md create mode 100644 docs/guides/security/observability.md create mode 100644 docs/guides/security/overview.md create mode 100644 docs/guides/security/resources.md create mode 100644 docs/references/message-execution-properties.md create mode 100644 public/img/docs/references/example_highlighted_code.png create mode 100644 public/img/docs/references/example_orderings.png create mode 100644 public/img/docs/retry_idempotency.png create mode 100644 public/img/docs/security/example_trap_after_await.png create mode 100644 public/img/docs/security/ii_mobile_delegation_chain.png diff --git a/docs/guides/canister-calls/idempotency.md b/docs/guides/canister-calls/idempotency.md new file mode 100644 index 0000000..b46fb51 --- /dev/null +++ b/docs/guides/canister-calls/idempotency.md @@ -0,0 +1,102 @@ +--- +title: "Safe Retries and Idempotency" +description: "Design idempotent canister APIs to enable safe retries for ingress calls and bounded-wait inter-canister calls, preventing double-spend and other correctness issues." +--- + +In the case of network issues or other unexpected behavior, ICP clients (such as agents) that issue ingress update calls may be unable to determine whether their ingress request has been processed. For example, this can happen if the client loses connection until after the request's ingress expiry ends and the request's status is removed from the system state tree. + +Similarly, canisters that call other canisters using bounded-wait calls may be unable to determine whether the call was successful or not. + +This can be risky as the callers (external users or applications for ingress messages, or canisters for inter-canister calls) might decide to retry the transaction, potentially leading to serious security vulnerabilities such as double spending. + +Thus, it is important to design and/or use canister APIs such that it is possible to retry requests safely, even when the ICP provides no information about previous request attempts. This page describes general approaches that both the canister authors and clients can adopt to enable safe retries. + +## Idempotent canister APIs + +A canister endpoint is idempotent if executing it multiple times is equivalent to executing it once.[^1] Whenever an endpoint is idempotent or can be made idempotent by the developer, this provides an easy way to implement safe retries. + +Given an idempotent endpoint, you can implement retries from an external application by retrying the call until you observe a certified response, either a replied or rejected status; see the illustration below. If such a response is ever observed, it's sure that the transaction has been executed at least once, which, thanks to idempotency, has the same result as executing it exactly once. However, the application may not be willing to wait for a response indefinitely, and a timeout could be implemented. Upon timeout, an error should be displayed to the user instructing them to wait until the latest message that has been sent has expired (as defined by the request's `ingress_expiry`) and then manually check the status of the transaction. Ideally, timeouts should be rare and not occur during normal operation. + +![Retrying an idempotent call](/img/docs/retry_idempotency.png) + +The situation is similar for bounded-wait inter-canister calls. Given an idempotent endpoint, the calling canister can keep retrying until a response other than `SYS_UNKNOWN` is observed or give up after a timeout if waiting indefinitely is not an option. + +Below are two approaches to making endpoints idempotent: sequence numbers and (time window) ID deduplication. + +### Update sequence numbers + +An endpoint can make use of sequence numbers to provide idempotency by taking a sequence number parameter in addition to other parameters. In the extreme case, a canister could keep a single expected sequence number for every endpoint, and a call could only be accepted if it contained the next expected sequence number, causing the expected sequence number to be incremented upon call execution. This trivially implies that any call can only be executed once. More practically, an expected sequence number is kept for each caller principal, or, in the case of ledger-like canisters, each ledger account. Note that Ethereum implements this mechanism. + +The advantages of this approach are: + +1. Sequence numbers are simple to implement and understand. +2. When applicable, it has a modest memory footprint because only the next expected sequence number must be stored (for example, per active account). + +The approach also has some disadvantages: + +1. It limits the throughput. When per-caller sequence numbers are used, it means that the caller can generally perform only one ingress call per consensus block, translating to a throughput of about 1 ingress call per second for that user. The situation is better for inter-canister calls, as the requests (if delivered) will be delivered in the order in which they were sent. Thus, the calling canister can issue multiple requests simultaneously, using appropriate sequence numbers. Under normal load, all requests should be delivered. However, under heavy load where the system may drop some requests, requests that follow such a dropped request may become invalid. + +2. It limits concurrency. The user has to sequentialize all their calls. This is straightforward to do when the user is another canister, but it can be much more difficult when the canister is called through ingress messages. In particular, it's complicated when the user is using multiple clients or devices to access the canister, for example. This concurrency problem also makes the approach inapplicable to cases where anonymous users are allowed to trigger update calls. + +3. If the sequence number is stored per user or per account, tracking them for too many users can exhaust the canister memory, even if each individual number is small. This could, e.g., be exploited by an attacker to exhaust the memory. The approach is thus best suited for cases where the user has to pay for the usage in some way (e.g., the ledgers usually require a fee to both create an account and transfer funds), which thwarts attackers by requiring them to invest significant funds in an attack. + +### ID deduplication + +Another approach to idempotency is to make the calls uniquely identifiable on the receiving canister side (e.g., by using user-chosen IDs, sequence numbers, or a combination of several argument fields) to make sure a given call is executed at most once. The canister then deduplicates calls before executing them; if a call with the same ID has been executed previously, the new call is simply ignored (potentially returning the result of the previous call). Thus, the user can safely keep retrying the call until they get a response. + +For example, the ICRC ledger standard provides deduplication in this way. Using identical values for all call parameters, including the `created_at_time` and `memo` parameters, when issuing a transaction makes the transaction call idempotent by deduplicating calls with the same parameters. + +However, a naive implementation of this approach can exhaust the canister memory, as all successfully executed IDs need to be kept around forever. Thus, the deduplication is usually time-limited to a certain time window. For example, the ICP ledger uses a 24-hour window, and the ICRC standard defines a configuration parameter `TX_WINDOW` that determines the window length. + +Moreover, the ICP/ICRC ledgers use the `created_at_time` parameter to limit the validity period of a call. Roughly, the call is only considered valid if its `created_at_time` is not in the future and at most 24 hours in the past.[^2] This avoids the problem where the deduplication window expiring would allow a retried call to succeed again. + +But even with this improvement used in the ledgers, the time window approach implicitly assumes that the client will be able to get a definite answer to their call within the time window. For example, after the 24 hours expire, the user cannot easily tell if their ledger transfer happened; their only option is to analyze the ledger blocks, which is somewhat tedious and has to be done carefully to avoid asynchrony issues; see the section on [queryable call results](#queryable-call-results). + +Relying solely on a time window for deduplication does not guarantee bounded memory usage. In theory, an unlimited number of updates could occur within the time window, though in practice, this is constrained by the scaling limits of the ICP. The ICP/ICRC ledgers thus also define a maximum capacity: a limit on the number of deduplicated transactions (i.e., deduplication IDs) that can be stored in their deduplication store. Once this capacity is reached, further transactions are rejected until older transactions expire from the deduplication store at the end of the time window. Yet another extension of the approach is to guarantee deduplication for the stated time window as above but keep storing deduplication IDs even beyond that window, as long as the capacity is not reached. This way, the clients obtain a hard deduplication guarantee for the time window and a best-effort attempt to deduplicate transactions even past the window. + +An alternative is to do away with the time window and store the deduplication data forever. This requires storing this data in multiple canisters in order to prevent exhausting canister memory, similar to how the ICP/ICRC ledgers store transaction data in the archive canister. This shifts the tedious part of querying the deduplication data (e.g., ledger blocks) from the user to the canister. + +Summarizing, the advantages of this approach are: + +1. It can support high throughput. +2. It requires no synchronization on the part of the user and supports use cases like multiple devices. + +The disadvantages are: + +1. It is more complicated to implement than sequence numbers. +2. If a time window is used, it usually implicitly assumes that the user learns the call outcome within the time window. +3. The memory usage can grow fairly high with high supported throughput and long deduplication windows. For example, supporting 100 transactions per second with a deduplication window of 24 hours can require hundreds of megabytes of heap space. This can be mitigated by using multiple canisters to store the deduplication data, at the expense of further implementation complexity and higher latency. + +## Other approaches to safe retries + +In the absence of idempotent endpoints, or even in addition to them, clients may be able to use other endpoints to make their retries safe. + +### Queryable call results + +If the canister, in addition to the update endpoint, also exposes a query that can inform the user of the result of the update, the client can also use this for safe retries as follows: + +1. Attempt to perform the update. +2. If the result of the update is unknown (e.g., not present in the ingress history anymore, or a `SYS_UNKNOWN` error is returned for an inter-canister call), query the call result endpoint to determine whether the update was applied or not. Moreover, one needs to ensure that the previously sent call cannot be applied in the future. If both of these are true, the call might be retried or safely reported as failed. + +In practice, this pattern may be more complicated. For example, the ICP ledger exposes a `query_blocks` method that can be used to implement the above pattern for transfers initiated as ingress messages: + +1. Call the `query_blocks` method on the ledger to determine what the last block (as specified in the `chain_length` field of the response) currently is. Let's call this `last_block`. +2. Attempt to perform a transfer. This ingress message includes an `ingress_expiry` field. +3. If the result of the transfer is unknown, ensure that the transfer will not be applied at a later point: + - If using ingress messages, call the `read_state` endpoint on the ledger canister to obtain the `/time` branch of the system state tree. Repeat this until the reported time exceeds the `ingress_expiry` time. + - If using inter-canister calls, perform all subsequent calls (`query_blocks`) listed below from the same canister that initiated the transfer. The [ordering guarantees](../../references/message-execution-properties.md) then ensure that the transfer cannot happen later. +4. Call the `query_blocks` method on the ledger again to retrieve all ledger blocks since `last_block`, and check that the `timestamp` also exceeds the `ingress_expiry` time. In case of failure, retry until a result is obtained. Then, scan through the returned blocks to determine whether the transaction has been included or not. + +### 2-step transfers + +Another approach applicable to ledgers (such as ICRC-1 or ICP) is to perform transfers in two steps: + +1. First, transfer the tokens to an intermediate subaccount of the sender that's specific to this transaction. For example, if the transaction has a unique ID, the client can hash the ID to obtain a subaccount. The transferred amount should be the desired amount plus the ledger transaction fee. +2. If the result of the above transfer is unknown, query the balance of the transaction-specific subaccount. Like in the [queryable call result](#queryable-call-results) approach, if using ingress messages, this should be repeated until the `timestamp` accompanying the response exceeds the `ingress_expiry`. If the balance is 0, the transaction can safely be reported as failed, or it can be retried (starting from step 1). If the balance is at least the expected balance, one can proceed. +3. If the transfer to the transaction-specific subaccount succeeded (as determined either by the transfer result or by the balance query above), the client sends another transfer from the transaction-specific subaccount to the desired target account. This can be repeated as many times as necessary until a result of the call is known. Once a result is known, the overall transfer can be declared as succeeded, even if this step fails with an error, as this signifies that some previous attempt to transfer the money to the target succeeded. + +[^1]: "Equivalent" is meant from the user perspective here. Multiple executions may trigger changes such as those in the canister's cycle balance, but they are not relevant for the user. + +[^2]: More precisely, the ledger also allows for a small time drift of `created_at_time` into the future, which has to be taken into account when clearing the deduplication window. + + diff --git a/docs/guides/security/access-management.mdx b/docs/guides/security/access-management.mdx index 8c756eb..d9bf321 100644 --- a/docs/guides/security/access-management.mdx +++ b/docs/guides/security/access-management.mdx @@ -1,375 +1,250 @@ --- -title: "Access Management" -description: "Control who can call your canister with guards, caller checks, and controller management" +title: "Security Best Practices: Identity and Access Management" +description: "Security best practices for authentication, anonymous principal rejection, ingress message inspection, and session management." sidebar: - order: 1 + order: 3 --- import { Tabs, TabItem } from '@astrojs/starlight/components'; -Every canister method is callable by anyone on the internet. Without explicit access checks, any user or canister can invoke any of your public functions. This guide covers the patterns you need to restrict access. +## Make sure specific user actions require authentication -## Checklist +### Security concern -Use this as a quick reference when securing your canister: +If this is not the case, an attacker may be able to perform sensitive actions on behalf of a user, compromising their account. -- [ ] Reject the anonymous principal (`2vxsx-fae`) in every authenticated endpoint -- [ ] Check the caller inside each update method: not just in `canister_inspect_message` -- [ ] Use the `guard` attribute (Rust) or guard functions (Motoko) to enforce access rules -- [ ] Add a backup controller so you never lose canister access -- [ ] Use `canister_inspect_message` only as a cycle-saving optimization, never as a security boundary +### Recommendation -## How caller identity works - -When a canister receives a message, the network includes the caller's principal. This identity is provided by the system: it cannot be forged or spoofed. You access it with: - -- **Motoko:** `shared({ caller })` pattern on public functions -- **Rust:** `ic_cdk::api::msg_caller()` - -Every principal is one of these types: - -| Type | Format | Example | Meaning | -|------|--------|---------|---------| -| User | Varies (self-authenticating) | `wo5qg-ysjaa-aaaaa-...` | Human with a cryptographic identity | -| Canister | 10 bytes, ends in `-cai` | `rrkah-fqaaa-aaaaa-aaaaq-cai` | Another canister making an inter-canister call | -| Anonymous | Fixed | `2vxsx-fae` | Unauthenticated caller: no identity | -| Management | Fixed | `aaaaa-aa` | IC management canister (system calls) | - -## Reject anonymous callers - -Any endpoint that requires authentication must reject the anonymous principal. Without this check, unauthenticated callers can invoke protected methods. If your canister uses the caller principal as an identity key (for balances, ownership, etc.), the anonymous principal becomes a shared identity anyone can use. - - - - -```motoko -import Principal "mo:core/Principal"; -import Runtime "mo:core/Runtime"; - -// Inside persistent actor { ... } - - func requireAuthenticated(caller : Principal) { - if (Principal.isAnonymous(caller)) { - Runtime.trap("anonymous caller not allowed"); - }; - }; - - public shared ({ caller }) func protectedAction() : async Text { - requireAuthenticated(caller); - "ok"; - }; -``` - - - +- The caller of every canister call can be identified. The calling [principal](../../references/ic-interface-spec/index.md#principal) can be accessed using the system API's methods [`ic0.msg_caller_size` and `ic0.msg_caller_copy`](../../references/ic-interface-spec/canister-interface.md#system-api-imports). If an identity provider such as Internet Identity is used, [the principal is the user identity for this specific origin](../../references/internet-identity-spec.md#identity-design-and-data-model). If some actions (e.g., access to user's account data or account-specific operations) should be restricted to a principal or a set of principals, then this must be explicitly checked in the canister call. An example in Rust can be found below: ```rust -use ic_cdk::update; -use ic_cdk::api::msg_caller; -use candid::Principal; - -fn require_authenticated() -> Result<(), String> { - if msg_caller() == Principal::anonymous() { - return Err("anonymous caller not allowed".to_string()); - } - Ok(()) -} - -#[update(guard = "require_authenticated")] -fn protected_action() -> String { - "ok".to_string() +// Let pk be the public key of a principal that is allowed to perform +// this operation. This pk could be stored in the canister's state. +if caller() != Principal::self_authenticating(pk) { ic_cdk::trap(...) } + +// Alternatively, if the canister keeps data for different principals +// in e.g., a map such as BTreeMap, then the canister +// must ensure that each caller can only access and perform operations +// on their own data: +if let Some(user_data) = user_data_store.get_mut(&caller()) { + // perform operations on the user's data } ``` +- In Rust, the `ic_cdk` crate can be used to authenticate the caller using `ic_cdk::api::caller`. Make sure the returned principal is of type `Principal::self_authenticating` and identify the user's account using the public key of that principal. See the example code above. -The Rust `guard` attribute runs the check before the method body executes. If the guard returns `Err`, the call is rejected. This is more robust than calling guard functions inside the method: you cannot forget to add it. Multiple guards can be chained: +- Do authentication as early as possible in the call to avoid unauthenticated actions and potentially expensive operations before authentication. It is also a good idea to [deny service to anonymous users](#disallow-the-anonymous-principal-in-authenticated-calls). -```rust -#[update(guard = "require_authenticated", guard = "require_admin")] -fn admin_action() { - // both guards passed -} -``` +- Do not rely on authentication performed during [ingress message inspection](#do-not-rely-on-ingress-message-inspection). - - - -## Owner and role-based access control - -There is no built-in role system on ICP. You implement it yourself by tracking principals in your canister state. - - - - -The `shared(msg)` pattern on an actor class captures the deployer's principal atomically. No separate init call, no front-running risk. Use `transient` for the owner since it gets recomputed from `msg.caller` on each install/upgrade. - -```motoko -import Principal "mo:core/Principal"; -import Set "mo:core/pure/Set"; -import Runtime "mo:core/Runtime"; - -shared(msg) persistent actor class MyCanister() { - - transient let owner = msg.caller; - var admins : Set.Set = Set.empty(); - - func requireOwner(caller : Principal) { - if (Principal.isAnonymous(caller)) { - Runtime.trap("anonymous caller not allowed"); - }; - if (caller != owner) { - Runtime.trap("caller is not the owner"); - }; - }; - - func requireAdmin(caller : Principal) { - if (Principal.isAnonymous(caller)) { - Runtime.trap("anonymous caller not allowed"); - }; - if (caller != owner and not Set.contains(admins, Principal.compare, caller)) { - Runtime.trap("caller is not an admin"); - }; - }; - - public shared ({ caller }) func addAdmin(newAdmin : Principal) : async () { - requireOwner(caller); - admins := Set.add(admins, Principal.compare, newAdmin); - }; - - public shared ({ caller }) func removeAdmin(admin : Principal) : async () { - requireOwner(caller); - admins := Set.remove(admins, Principal.compare, admin); - }; - - public shared ({ caller }) func adminAction() : async () { - requireAdmin(caller); - // ... protected logic - }; -}; -``` +## Disallow the anonymous principal in authenticated calls - - +### Security concern -```rust -use ic_cdk::{init, update}; -use ic_cdk::api::msg_caller; -use candid::Principal; -use std::cell::RefCell; - -thread_local! { - static OWNER: RefCell = RefCell::new(Principal::anonymous()); - static ADMINS: RefCell> = RefCell::new(vec![]); -} +The caller from the system API (e.g., `ic0::api::caller` in Rust) may also return `Principal::anonymous()`. In authenticated calls, this is probably undesired and could have security implications since this would behave like a shared account for anyone that does unauthenticated calls. -fn require_authenticated() -> Result<(), String> { - if msg_caller() == Principal::anonymous() { - return Err("anonymous caller not allowed".to_string()); - } - Ok(()) -} +### Recommendation -fn require_owner() -> Result<(), String> { - require_authenticated()?; - OWNER.with(|o| { - if msg_caller() != *o.borrow() { - return Err("caller is not the owner".to_string()); - } - Ok(()) - }) -} - -fn require_admin() -> Result<(), String> { - require_authenticated()?; - let caller = msg_caller(); - let is_authorized = OWNER.with(|o| caller == *o.borrow()) - || ADMINS.with(|a| a.borrow().contains(&caller)); - if !is_authorized { - return Err("caller is not an admin".to_string()); +In authenticated calls, make sure the caller is not anonymous and return an error or trap if it is. This could be done centrally by using a helper method. An example in Rust can be found below: +```rust +fn caller() -> Result { + let caller = ic0::api::caller(); + // The anonymous principal is not allowed to interact with the canister. + if caller == Principal::anonymous() { + Err(String::from( + "Anonymous principal not allowed to make calls.", + )) + } else { + Ok(caller) } - Ok(()) -} - -#[init] -fn init(owner: Principal) { - OWNER.with(|o| *o.borrow_mut() = owner); -} -// Unlike Motoko's shared(msg) pattern which captures the deployer automatically, -// the Rust #[init] requires passing the owner explicitly at deploy time: -// icp canister deploy backend --argument '(principal "your-principal-here")' - -#[update(guard = "require_owner")] -fn add_admin(new_admin: Principal) { - ADMINS.with(|a| a.borrow_mut().push(new_admin)); -} - -#[update(guard = "require_owner")] -fn remove_admin(admin: Principal) { - ADMINS.with(|a| a.borrow_mut().retain(|p| p != &admin)); -} - -#[update(guard = "require_admin")] -fn admin_action() { - // ... protected logic: guard already validated caller } ``` - - +## Do not rely on ingress message inspection -Always include admin revocation (`removeAdmin`). Missing revocation is a common source of bugs: once granted, admin access should be removable. +### Security concern -## Controller checks +The correct execution of [`canister_inspect_message`](../../references/ic-interface-spec/canister-interface.md#system-api-inspect-message) is not guaranteed because it is executed by a single node, and if that node is malicious, it can simply skip this check. In that case the update call would be executed without any message inspection checks. -Controllers are the principals authorized to manage a canister (install code, change settings, stop/delete). The controller list is managed at the IC level, not inside your canister code. +Also note that for inter-canister calls, `canister_inspect_message` is not invoked. - - +### Recommendation -**Motoko** provides `Principal.isController` to check if a principal is a controller of the current canister: +Your canisters should not rely on the correct execution of `canister_inspect_message`. This in particular means that no security-critical code, such as [access control checks](#make-sure-specific-user-actions-require-authentication), should be solely performed in that method. Such checks **must** be performed as part of an update method to guarantee reliable execution. Ideally, they are executed both in the `canister_inspect_message` function and a guard function. -```motoko -import Principal "mo:core/Principal"; -import Runtime "mo:core/Runtime"; +## Use a well-audited authentication service and client-side ICP libraries -// Inside persistent actor { ... } +### Security concern - public shared ({ caller }) func controllerOnly() : async () { - if (not Principal.isController(caller)) { - Runtime.trap("caller is not a controller"); - }; - // ... - }; -``` +Implementing user authentication and canister calls yourself in your web app is error-prone and risky. For example, if canister calls are implemented from scratch, there may be bugs around signature creation or verification. - - +### Recommendation -In Rust, there is no built-in `is_controller` function: checking controllers requires an async call to the management canister. See [inter-canister calls](../canister-calls/inter-canister-calls.md#making-calls) for inter-canister call patterns. +- Consider using an identity provider such as [Internet Identity](https://github.com/dfinity/internet-identity) for authentication, and use the ICP JavaScript agent for making canister calls. - - +- You may consider alternative authentication frameworks on ICP for authentication. -**Managing controllers with icp-cli:** +## Set an appropriate session timeout -```bash -# View current canister settings including controllers -icp canister settings show backend -e ic +### Security concern -# Add a backup controller -icp canister settings update backend --add-controller -e ic +Currently, Internet Identity issues delegations with an expiry time. This expiry time can be set in the auth-client. After a delegation expires, the user has to re-authenticate. Setting a good value is a trade-off between security and usability. -# Remove a controller (warning: removing yourself locks you out) -icp canister settings update backend --remove-controller -e ic -``` +### Recommendation -Always add a backup controller. If you lose the private key of the only controller, the canister becomes permanently unupgradeable: there is no recovery mechanism. +See the [OWASP recommendations](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-expiration). A timeout of 30 minutes should be set for security-sensitive applications. -## `canister_inspect_message`: cycle optimization only +The auth-client supports [idle timeouts](https://github.com/dfinity/agent-js/tree/main/packages/auth-client#idle-management). -`canister_inspect_message` is a hook that runs on a single replica before consensus. It can reject ingress messages early to save cycles on Candid decoding and execution. However, it is **not a security boundary**: +## Don't use fetchRootKey in the ICP JavaScript agent in production -- It runs on one node without consensus: a malicious boundary node can bypass it -- It is never called for inter-canister calls, query calls, or management canister calls +### Security concern -Always duplicate real access checks inside each method. Use `inspect_message` only to reduce cycle waste from spam. +`agent.fetchRootKey()` can be used in the ICP JavaScript agent to fetch the root subnet threshold public key from a status call in test environments. This key is used to verify threshold signatures on certified data received through canister update calls. Using this method in a production web app gives an attacker the option to supply their own public key, invalidating all authenticity guarantees of update responses. - - +### Recommendation -```motoko -import Principal "mo:core/Principal"; +Never use `agent.fetchRootKey()` in production builds, only in test builds. Not calling this method will result in the hardcoded root subnet public key of the mainnet being used for signature verification, which is the desired behavior in production. -// Inside persistent actor { ... } -// Method variants must match your public methods +## Integrating Internet Identity on mobile devices - system func inspect( - { - caller : Principal; - msg : { - #adminAction : () -> (); - #addAdmin : () -> Principal; - #removeAdmin : () -> Principal; - #protectedAction : () -> (); - } - } - ) : Bool { - switch (msg) { - case (#adminAction _) { not Principal.isAnonymous(caller) }; - case (#addAdmin _) { not Principal.isAnonymous(caller) }; - case (#removeAdmin _) { not Principal.isAnonymous(caller) }; - case (#protectedAction _) { not Principal.isAnonymous(caller) }; - case (_) { true }; - }; - }; -``` - - - +A [short presentation](https://www.youtube.com/watch?v=iRmpCkzC6iI&t=1863s) can be found as part of the November 2024 global R&D. -```rust -use ic_cdk::api::{accept_message, msg_caller, msg_method_name}; -use candid::Principal; - -#[ic_cdk::inspect_message] -fn inspect_message() { - let method = msg_method_name(); - match method.as_str() { - "admin_action" | "add_admin" | "remove_admin" | "protected_action" => { - if msg_caller() != Principal::anonymous() { - accept_message(); - } - // Silently reject anonymous: saves cycles - } - _ => accept_message(), - } -} -``` +### Security concern - - +Internet Identity has a standardized way for web applications to request authentication of a user. This [client authentication protocol](../../references/internet-identity-spec.md#client-authentication-protocol) allows a client dapp frontend to obtain a delegation signed by the Internet Identity for a locally generated session key pair. Using this delegation in combination with the session key allows the dapp frontend to make authenticated calls towards the backend canister. Such calls need to be digitally signed by the session private key. The IC will verify the signature and verify if there is a delegation (or chain of delegations) from the II key to the session public key. -## Debugging identity +The II client authentication protocol leverages the browser's `postMessage` API to communicate between the client origin and the II origin. This protocol allows II to authenticate the origin of the authorization request using the hostname. -When troubleshooting access control issues, it helps to know which principal your canister sees. A simple `whoami` endpoint returns the caller's identity: +:::note +As part of the client authentication protocol, a dapp can specify an alternative origin by following the [alternative frontend origins](../../references/internet-identity-spec.md#alternative-frontend-origins) requirements. +::: - - +Upon successful authentication, II will return a delegation for the principal derived from the users' II for the specific frontend origin. This serves two purposes. First, a client dapp can't use this delegation on other dapps to impersonate the user. Second, multiple client dapps can't correlate user behavior across dapps, thereby reducing privacy. A dapp with a different frontend origin won't be able to request authentication for your dapp, which provides protection against certain phishing attacks. -```motoko -// Inside persistent actor { ... } +When integrating a mobile application with II, the implementation is not straightforward since a mobile app can't call the `postMessage` API. It is tempting to create a simple "proxy" web frontend served by the dapp as shown in the sequence diagram below. The mobile application can load this proxy to complete the normal II authorization flow. Upon completion, this proxy web app provides the delegation back to the mobile app. - public shared ({ caller }) func whoami() : async Principal { - caller; - }; +**Naive implementation sequence diagram:** +```mermaid +sequenceDiagram + actor U as User + participant MA as Mobile App + participant PWA as Proxy web app + participant II_FE as II Front-end + participant II_BE as II Back-end + participant BE as Back-end + activate U + U -> MA : 1. Login + activate MA + MA ->> MA : 2. Generate session key pair + activate PWA + MA ->> PWA: 3. Load /?sessionPublicKey=
+ activate II_FE + PWA ->> II_FE: 4. Standard II client auth protocol + Note over PWA,II_FE: II will create a delegation for the session
key generated by the mobile application. + U ->> II_FE: 5. Authenticate with passkey + activate II_BE + II_FE ->> II_BE: 6. getDelegation(frontEndHostname) + II_BE -->> II_FE: 7. <> + Note over II_BE,II_FE: Delegation can leak through
replica or boundary nodes + deactivate II_BE + II_FE -->> PWA: 8. Return the delegation
using postMessage API + deactivate II_FE + PWA -->> MA: 9. Return the delegation + Note over PWA,MA: Delegation can leak through
insecure return mechanism + deactivate PWA + U ->> MA: 10. Authenticated call + activate BE + MA ->> BE: 11. Update call using the delegation + Note over II_BE,BE: Delegation can leak through
replica or boundary nodes + BE -->> MA: <> + deactivate BE + deactivate MA + deactivate U ``` -
- - -```rust -use ic_cdk::query; -use ic_cdk::api::msg_caller; -use candid::Principal; - -#[query] -fn whoami() -> Principal { - msg_caller() -} +However, without any precautions, this proxy would happily accept malicious requests to authenticate the user and might return the delegation back to an attacker. + +Such an attack would start by phishing the user by means of a malicious mobile or web application. The user is asked to authenticate through II. However, instead of using II directly, the attacker abuses the open proxy to authenticate the user for the dapp, under which the vulnerable proxy is running. The attacker would generate a session key and ask the proxy to use the session public key in the II authentication protocol. Through this method, II issues a signed delegation for the user's II derived for the frontend origin of the proxy. This delegation could leak to the attacker, who can use it to impersonate the user. For example, if the attacker can trick the proxy to redirect to the malicious application (e.g., by registering Android deep links or iOS custom schemes), it could directly obtain the delegation. Furthermore, the delegation could leak through an insecure communication channel between the proxy and the mobile app or through observation of the IC state. + +The attack requires four conditions: +1. An attacker can provide a session key to be used in the II client authentication protocol. +2. The client authentication protocol is initiated for a target frontend hostname. +3. The user completes the II authentication protocol. +4. The attacker can obtain the delegation, which is signed by the II canister. + +Conditions 1, 2, and 3 can be satisfied by convincing the user to initiate an authentication flow with a session public key that is chosen by the attacker by loading the proxy from an attacker-controlled mobile or web application. Concretely, an attacker would execute a phishing attack where a victim is directed to the proxy from an unsuspicious application. For example, the victim is convinced that the attacker is issuing an airdrop. The victim has to download a corresponding malicious mobile app that requires II authentication. This malicious mobile app would load the proxy (step 3) similarly to how the legitimate mobile app would. The malicious app would ask the proxy to authenticate the user for an Condition 2 is met for any dapp that exposes such an open II authentication proxy on their domain. session key. The victim might not realize they are completing an authorization flow for a different dapp origin. + +Condition 4 can be satisfied by controlling a replica or boundary node that can observe the delegation in step 7. Alternatively, the delegation could leak in step 9 by using an HTTP GET parameter in a URI pointing to the IC. In such cases, if the mobile app that should receive the URI isn't installed, the browser loads the web app by making a request to the URI. Boundary and replica nodes would again receive the delegation as part of the URI. Condition 4 can also be met if the mobile app issues a request to the IC in step 11 without verifying the delegation obtained in step 9. + +Finally, condition 4 can also be satisfied if the delegation is returned insecurely from the proxy frontend to the mobile app. For example, by using Android deep links or iOS custom schemes, which can be intercepted by a malicious app. + +### Recommendation + +In the standard integration between a client web app and the II web frontend, the origin of the client is verified **before** starting the client authentication protocol. Unfortunately, loading the URI of the proxy app in step 3 does not provide any information about the mobile application. Therefore, the proxy frontend is unable to authenticate the client. This creates an open endpoint for attackers to use, as described in the previous section. + +This risk can be addressed by adopting the following remediations shown in the sequence diagram and explained further below. + +**Secure integration sequence diagram:** +```mermaid +sequenceDiagram + actor U as User + participant MA as Mobile App + participant PWA as Secure Proxy web app + participant II_FE as II Front-end + participant II_BE as II Back-end + participant BE as Back-end + + activate U + U -> MA : 1. Login + activate MA + MA ->> MA : 2. Generate session key pair + activate PWA + MA ->> PWA: 3. Load /?sessionPublicKey=
+ PWA ->> PWA: 4. Generate intermediate session key + Note over PWA: This key never
leaves the proxy
front-end + activate II_FE + PWA ->> II_FE: 5. Standard II client auth protocol
using intermediate session key + Note over PWA,II_FE: II will create a delegation for the
intermediate key and not for the attacker
chosen session key. + U ->> II_FE: 6. Authenticate with passkey + activate II_BE + II_FE ->> II_BE: 7. getDelegation(frontEndHostname) + II_BE -->> II_FE: 8. <> + II_FE ->> II_FE: 9. Construct the delegation chain + Note over II_FE: The proxy front-end creates a
delegation from the intermediate
key to the mobile app session key
and combines it with the
delegation from the II canister key
to the intermediate key. + II_FE -->> PWA: 10. Return the delegation
using the postMessage API + deactivate II_FE + PWA -->> MA: 11. Return the delegation chain using
an app link (Android)
or universal link (iOS)
as part of the fragment
using associated domains + Note over MA,PWA: Protect the delegation chain
from being leaked to the web
server by using a URI fragment
instead of a GET parameter. + MA ->> MA: 12 Verify the delegation chain
against the session
key from step 2 + Note over MA: Verify the delegation chain before using it
to avoid leaking a delegation with an
attacker controlled session key to the IC +MA -->> U: <> + U ->> MA: 13. Authenticated call + activate BE + MA ->> BE: 14. Update call using the verified delegation chain + BE -->> MA: <> + deactivate BE + deactivate MA + deactivate U ``` -
-
- -Call it to verify which identity is being used: +* Introduce an intermediate session key that is generated and stored by the web app proxy frontend. +* Initiate the II client authentication protocol using this intermediate session key. By using a new session key that the attacker can't control, the delegation issued by II would no longer be usable by the attacker if it were stolen in step 8, as the attacker doesn't have access to the intermediate session private key. +* [Create a delegation chain](../../references/ic-interface-spec/https-interface.md#authentication) to allow the mobile application to use their session key. The delegation chain consists of two delegations as shown in the figure below. The first one delegates from the II canister key to the intermediate key and is generated by the II canister. The second one delegates from the intermediate key to the mobile app public key and is signed by the proxy frontend's intermediate session private key. Note, this means the intermediate key can impersonate the user. Since the proxy frontend is served from the IC, it can be trusted to handle this key properly. It is up to the developer to ensure the confidentiality of this key. For example, using the WebCrypto API to create unextractable keys as is used internally by the ICP JavaScript agent. Ideally, this intermediate key is short-lived to reduce the risk of exposure. -```bash -icp canister call backend whoami -``` +![Delegation Chain](/img/docs/security/ii_mobile_delegation_chain.png) +* Return the delegation chain to the mobile app using [app links](https://developer.android.com/training/app-links) on Android and [universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content) on iOS. These mechanisms bind the domain name/hostname to the mobile app, which prevents an attacker from using a malicious mobile app to receive the delegation chain. The domain-to-mobile app binding occurs through a JSON file that has to be hosted under the `/.well-known` directory of your web application. See [iOS](https://developer.apple.com/documentation/xcode/supporting-associated-domains) +and [Android](https://developer.android.com/training/app-links/verify-android-applinks) documentation for details. +* Return the delegation chain to the mobile app using a [URI fragment](https://www.w3.org/DesignIssues/Fragment.html) (everything following the # in the URI). The browser will load the URI if the mobile app linked to the app/universal link isn't installed on the mobile device. The benefit of URI fragments is that they are not included in the request to the server if the browser were to resolve the URI. A URL parameter or path would be included in such a request, and therefore it would leak the delegation chain to the proxy app backend (most likely the IC boundary and replica nodes). A URI fragment is still available to the mobile app for extraction. +* Verify the delegation chain in the mobile application before using it in an IC message. The mobile application likely uses an agent that does not verify whether the session key generated in step 2 corresponds to the delegation found in the delegation chain returned in step 11. Using such an agent to make a signed update call would simply create a message with the provided delegation chain and sign it with a mismatching key. Obviously, the IC would reject such a message as the signature does not correspond to the delegation chain, but the delegation chain would already have leaked to the boundary and potentially replica nodes where an attacker could steal it. +* Optionally, the proxy frontend could explicitly warn the user that it is about to sign in with II for your dapp. It could include the dapp's name and logo. This might alarm the user who is being phished since the pretense used by the attacker would likely not match with the purpose of your dapp. For example, the attacker claims the authentication is required as part of an airdrop while you are running an unrelated decentralized exchange. When the proxy dapp is opened, the user would see your dapp's logo and abort the sign-in. -## Next steps +For more information, view an [example implementation in the form of a Unity app](https://github.com/dfinity/examples/tree/main/native-apps/unity_ii_deeplink). The following pieces of that codebase are most important: -- [Security concepts](../../concepts/security.md): understand the IC security model -- [Canister settings](../canister-management/settings.md): configure controllers and freezing thresholds -- [DoS prevention](dos-prevention.md): rate limiting as an access control mechanism +* Generation of the intermediate session key [in index.js](https://github.com/dfinity/examples/blob/main/native-apps/unity_ii_deeplink/ii_integration_dapp/src/greet_frontend/src/index.js#L25). +* [Authentication using the intermediate session key](https://github.com/dfinity/examples/blob/main/native-apps/unity_ii_deeplink/ii_integration_dapp/src/greet_frontend/src/index.js#L26-L36) instead of the mobile app public key. +* [Generating the delegation chain](https://github.com/dfinity/examples/blob/main/native-apps/unity_ii_deeplink/ii_integration_dapp/src/greet_frontend/src/index.js#L48-L57) by combining the delegation obtained from II with a delegation created by the frontend. +* [Returning the delegation chain using an applink/universal link](https://github.com/dfinity/examples/blob/main/native-apps/unity_ii_deeplink/ii_integration_dapp/src/greet_frontend/src/index.js#L71). +* Returning the delegation chain [using a URI fragment](https://github.com/dfinity/examples/blob/main/native-apps/unity_ii_deeplink/ii_integration_dapp/src/greet_frontend/src/index.js#L73). +* The example is currently being improved whereby the delegation chain will also be verified in the mobile app before using it. -{/* Upstream: informed by dfinity/icskills (skills/canister-security/SKILL.md, dfinity/portal) docs/building-apps/best-practices/general.mdx */} +{/* Upstream: dfinity/portal — building-apps/security/iam.mdx */} diff --git a/docs/guides/security/canister-upgrades.md b/docs/guides/security/canister-upgrades.md index 7e8ba7a..d5323c0 100644 --- a/docs/guides/security/canister-upgrades.md +++ b/docs/guides/security/canister-upgrades.md @@ -1,350 +1,52 @@ --- -title: "Secure Upgrades" -description: "Upgrade canisters safely: pre/post hooks, stable memory, Candid compatibility, snapshot rollbacks, schema evolution, and testing" +title: "Security Best Practices: Canister Upgrades" +description: "Security best practices for canister upgrade hooks, panics during upgrades, and timer reinstatement." sidebar: - order: 2 + order: 9 --- -Canister upgrades are one of the highest-risk operations in production. A bad upgrade can corrupt state, make the canister permanently non-upgradeable, or break clients. This guide covers the patterns and checks you need to upgrade safely. +## Be careful with panics during upgrades -## Checklist +### Security concern -Use this before every production upgrade: +If a canister traps or panics in `pre_upgrade`, this can lead to permanently blocking the canister, resulting in a situation where upgrades fail or are no longer possible at all. -- [ ] Take a snapshot immediately before upgrading -- [ ] Run the upgrade locally first with `icp deploy` -- [ ] Verify data survives: write → upgrade → read -- [ ] Check Candid interface compatibility. No removed methods, no breaking type changes -- [ ] Avoid `pre_upgrade` hooks that serialize large state (use stable structures instead) -- [ ] In Motoko, use `persistent actor` (which eliminates the need for pre_upgrade hooks): avoid manual `pre_upgrade`/`post_upgrade` -- [ ] Confirm you have a backup controller (cannot recover from a trapped `post_upgrade` without one) -- [ ] Add a rollback plan: snapshot ID recorded, restore procedure tested +### Recommendation -## How upgrades work +- Avoid using `pre_upgrade` hooks if possible. Panics in the `pre_upgrade` hook prevent upgrades, and since the `pre_upgrade` hook is controlled by the old code, it can permanently block upgrading. -When you run `icp deploy` on an existing canister, the IC executes the following sequence: +- Panic in the `post_upgrade` hook if the state is invalid so that one can retry the upgrade and try to fix the invalid state. Panics in the `post_upgrade` hook abort the upgrade, but one can retry with new code. -1. **Stop** the canister (waits for in-flight messages to complete) -2. Run `pre_upgrade` on the old code (if defined) -3. Replace the Wasm module with the new code -4. Run `post_upgrade` on the new code (if defined) -5. **Restart** the canister +- [Test the upgrade hooks](https://mmapped.blog/posts/01-effective-rust-canisters.html#test-upgrades) (from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html)). -Stable memory is preserved through steps 2–4. Heap memory is cleared when the new Wasm loads. If `pre_upgrade` or `post_upgrade` traps, the upgrade fails with different consequences: +- See also the section on upgrades in [how to audit an Internet Computer canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister) (though focused on Motoko). -| Hook | Trap result | -|------|-------------| -| `pre_upgrade` | Upgrade cancelled. Old code still running. State intact but may need attention. | -| `post_upgrade` | New Wasm installed but initialization failed. Canister may be in an inconsistent state. | +- See [current limitations of the Internet Computer](https://wiki.internetcomputer.org/wiki/Current_limitations_of_the_Internet_Computer), section "Bugs in `pre_upgrade` hooks." -Both scenarios leave the canister in a difficult state. Prevention is far better than recovery. +## Reinstantiate timers during upgrades -## Stable memory patterns +### Security concern -### Motoko: use `persistent actor` +Global timers are deactivated upon changes to the canister's Wasm module. The [IC specification](../../references/ic-interface-spec/canister-interface.md#global-timer) states this as follows: -The `persistent actor` declaration automatically stores all `let` and `var` fields in stable memory. No serialization, no upgrade hooks, no instruction-limit traps. +> "The timer is also deactivated upon changes to the canister's Wasm module (calling install_code or uninstall_code methods of the management canister or if the canister runs out of cycles). In particular, the function canister_global_timer won't be scheduled again unless the canister sets the global timer again (using the System API function ic0.global_timer_set)." -```motoko -persistent actor Counter { - var count : Nat = 0; +Upgrade is a mode of `install_code`, and hence the timers are deactivated during an upgrade. - public func increment() : async Nat { - count += 1; - count; - }; +This could result in a vulnerability in certain cases where security controls or other critical features rely on these timers to function. For example, a DEX that relies on timers to update the exchange rates of currencies could be vulnerable to arbitraging opportunities if the rates are no longer updated. - public query func get() : async Nat { count }; +Since global timers are used internally by the Motoko `Timer` mechanism, the same holds true for the Motoko Timer. As explained in the [pull request](https://github.com/dfinity/motoko/pull/3542) under "The upgrade story," the global timer gets discarded on upgrade, and the timers need to be set up in the `post_upgrade` hook. - // transient: resets to [] on each upgrade: correct for caches, transient logs, and reset-on-upgrade counters - transient var recentCallers : [Principal] = []; -}; -``` +This behavior is different when [using Motoko](https://github.com/dfinity/motoko/pull/3542) and implementing `system func timer`. The `timer` function will be called after an upgrade. In case your canister was using timers for recurring tasks, the `timer` function would likely set the global timer again for a later time. However, the time between invocations of `timer` would not be consistent as the upgrade triggered an "unexpected" call to `timer`. -**Key rules:** +Using the Rust CDK, the recurring timer is also lost on upgrade as explained in the API documentation of [set_timer_interval](https://docs.rs/ic-cdk/0.6.9/ic_cdk/timer/fn.set_timer_interval.html). -- All `let`/`var` fields persist automatically. No `stable` keyword needed -- `transient var` for caches or counters that should reset on upgrade -- Do not write manual `pre_upgrade`/`post_upgrade` hooks. The runtime handles everything -- If a persistent field's type changes incompatibly, the upgrade traps. See [Schema evolution](#schema-evolution). +### Recommendation -### Rust: use stable structures +- In Motoko canisters, global timers should be set up in the actor initializer for canister installation or reinstallation. Canister-wide timers should be set in the `post_upgrade` hook for upgrades, as timers do not survive upgrades and must be explicitly set up thereafter. -In Rust, use [`ic-stable-structures`](https://docs.rs/ic-stable-structures/latest/ic_stable_structures/) to store data directly in stable memory. Data lives there from the start. No serialization step on upgrade. +- See the Motoko documentation on [timers](../../languages/motoko/icp-features/timers.md). -```rust -use ic_stable_structures::{ - memory_manager::{MemoryId, MemoryManager, VirtualMemory}, - DefaultMemoryImpl, StableBTreeMap, StableCell, -}; -use std::cell::RefCell; +- See the Rust documentation on [set_timer_interval](https://docs.rs/ic-cdk/0.6.9/ic_cdk/timer/fn.set_timer_interval.html). -type Memory = VirtualMemory; - -// Each structure must have its own unique MemoryId: never reuse IDs -const USERS_MEM_ID: MemoryId = MemoryId::new(0); -const COUNTER_MEM_ID: MemoryId = MemoryId::new(1); - -thread_local! { - static MEMORY_MANAGER: RefCell> = - RefCell::new(MemoryManager::init(DefaultMemoryImpl::default())); - - static USERS: RefCell, Memory>> = - RefCell::new(StableBTreeMap::init( - MEMORY_MANAGER.with(|m| m.borrow().get(USERS_MEM_ID)) - )); - - static COUNTER: RefCell> = - RefCell::new(StableCell::init( - MEMORY_MANAGER.with(|m| m.borrow().get(COUNTER_MEM_ID)), - 0u64, - ).expect("Failed to init counter")); -} - -#[ic_cdk::post_upgrade] -fn post_upgrade() { - // Stable structures auto-restore: no deserialization needed. - // Re-initialize timers or transient state here if required. -} -``` - -> **Warning:** Each `MemoryId` must map to exactly one data structure for the lifetime of the canister. Reusing a `MemoryId` for a different structure after an upgrade corrupts both. Keep a written record of your `MemoryId` allocations and never reorder them. - -### Avoid `pre_upgrade` serialization - -The serialization-based upgrade pattern is common in older Rust code but is fundamentally fragile: - -```rust -// DO NOT DO THIS in production -#[ic_cdk::pre_upgrade] -fn pre_upgrade() { - // If STATE is large, this hits the instruction limit and traps. - // A trapped pre_upgrade prevents the upgrade: canister stays on old code. - ic_cdk::storage::stable_save((STATE.with(|s| s.borrow().clone()),)).unwrap(); -} -``` - -When `pre_upgrade` traps due to instruction exhaustion, the canister cannot be upgraded. The `skip_pre_upgrade` flag (an emergency escape hatch via the management canister's `install_code` API (see [Management canister reference](../../references/management-canister.md#install_code)) bypasses the hook) but anything the hook would have saved is lost. Use stable structures so the upgrade path cannot brick itself under load. - -## Candid interface compatibility - -The IC checks your new Wasm module's Candid interface against the old one before completing the upgrade. If the new interface is not backward-compatible, the upgrade is rejected. - -**Safe changes:** - -| Change | Why it is safe | -|--------|---------------| -| Add a new method | Existing clients don't call it | -| Add optional parameters to an existing method | Old clients send no value; IC substitutes `null` | -| Remove trailing parameters from an existing method | Old clients send extra values; IC ignores them | -| Return additional values from a method | Old clients ignore extra return values | -| Change a parameter type to a supertype | Old values remain valid inputs | -| Change a return type to a subtype | New values remain valid for old clients | - -**Breaking changes (upgrade rejected or clients break):** - -| Change | Why it breaks | -|--------|--------------| -| Remove a method | Clients calling it get errors | -| Add a required (non-optional) parameter | Old clients don't send it | -| Change a parameter type to an incompatible type | Old clients send invalid values | - -**Example: safe evolution:** - -```candid -// Before -service counter : { - add : (nat) -> (); - get : () -> (int) query; -} - -// After: safe: optional param added, new return value, new method -service counter : { - add : (nat, label : opt text) -> (new_val : nat); - get : () -> (nat, last_change : nat) query; - reset : () -> (); -} -``` - -icp-cli checks Candid compatibility during deploy and prompts for confirmation if it detects a potentially breaking change. Use `--yes` in automated pipelines after manually verifying compatibility: - -```bash -icp deploy my-canister -e ic --yes -``` - -## Snapshot-based rollback - -Always take a snapshot immediately before a risky upgrade. If the upgrade causes unexpected behavior, you can restore the previous state within minutes. - -```bash -# 1. Stop the canister and create a snapshot -icp canister stop my-canister -e ic -icp canister snapshot create my-canister -e ic -# Note the snapshot ID printed in the output -icp canister start my-canister -e ic - -# 2. Deploy the upgrade -icp deploy my-canister -e ic - -# 3. Verify correctness -icp canister call my-canister health_check -e ic - -# 4a. If everything works, clean up when no longer needed -icp canister snapshot delete my-canister -e ic - -# 4b. If something is wrong, stop and restore -icp canister stop my-canister -e ic -icp canister snapshot restore my-canister -e ic -icp canister start my-canister -e ic -``` - -Snapshots capture the full canister state: Wasm module, Wasm heap memory, stable memory, and chunk store. Restoring from a snapshot brings back all of this state atomically. - -See [Canister snapshots](../canister-management/snapshots.md) for listing, downloading, and the state transfer workflow. - -## Schema evolution - -Upgrading canister code sometimes requires changing the shape of stored data. The rules differ by language. - -### Motoko - -When upgrading a `persistent actor`, the runtime checks that every persistent field's current type is compatible with the value stored in stable memory. Incompatible changes cause the upgrade to trap. - -**Safe changes:** - -- Add new `let` or `var` fields with initial values. The runtime initializes them on upgrade -- Add optional record fields (e.g., change `{ name : Text }` to `{ name : Text; email : ?Text }`) -- Widen a field's type (e.g., `Nat` → `Int`) - -**Unsafe changes (upgrade traps):** - -- Remove or rename a persistent field -- Narrow a field's type (e.g., `Int` → `Nat`) -- Change a non-optional field to an incompatible type - -If you need to make an unsafe change, migrate the data in two upgrades: add the new field alongside the old one, upgrade once (both fields present), then upgrade again to remove the old field. Test this two-step process locally before deploying to mainnet. - -### Rust - -Rust stable structures use serialized bytes on disk. Schema evolution safety depends on the serialization format and versioning strategy. - -**Adding fields safely with Candid encoding:** - -```rust -use candid::{CandidType, Decode, Deserialize, Encode}; -use ic_stable_structures::storable::{Bound, Storable}; -use std::borrow::Cow; - -#[derive(CandidType, Deserialize, Clone)] -struct UserV2 { - id: u64, - name: String, - created: u64, - // New optional field: safe to add: old records deserialize with None - email: Option, -} - -impl Storable for UserV2 { - // Unbounded avoids write failures when struct grows. - // Bounded requires a fixed max_size; if encoded size exceeds it after - // adding fields, writes trap. - const BOUND: Bound = Bound::Unbounded; - - fn to_bytes(&self) -> Cow<'_, [u8]> { - Cow::Owned(Encode!(self).expect("failed to encode UserV2")) - } - - fn from_bytes(bytes: Cow<'_, [u8]>) -> Self { - Decode!(&bytes, Self).expect("failed to decode UserV2") - } -} -``` - -**Rules:** - -- Use `Option` for new fields: Candid deserializes absent fields as `None`, so old records remain readable after the upgrade -- Use `Bound::Unbounded` unless you have a strict size requirement -- Never reorder `MemoryId` allocations across upgrades: same effect as changing a field type -- For breaking schema changes, use a versioned enum and migrate records lazily on read - -## Testing upgrades locally - -Never upgrade on mainnet without first verifying locally that data written before the upgrade is still readable after. - -**Motoko:** - -```bash -# Start local network -icp network start -d - -# Deploy initial version -icp deploy backend - -# Write data -icp canister call backend increment '()' -icp canister call backend increment '()' -icp canister call backend get '()' -# Returns: (2 : nat) - -# Modify source code, then redeploy -icp deploy backend - -# Verify data survived -icp canister call backend get '()' -# Must still return: (2 : nat) -``` - -**Rust:** - -```bash -# Start local network -icp network start -d - -# Deploy initial version -icp deploy backend - -# Write data -icp canister call backend add_user '("Alice")' -icp canister call backend get_user_count '()' -# Returns: (1 : nat64) - -# Modify source code, then upgrade -icp deploy backend - -# Verify data survived -icp canister call backend get_user_count '()' -# Must still return: (1 : nat64) -``` - -If the count drops to zero after upgrade, your data is not in stable memory: review your storage declarations before touching mainnet. - -For advanced scenarios (upgrade rollbacks, schema migrations, concurrent call safety), use [PocketIC](../testing/pocket-ic.md) to script multi-step upgrade scenarios in a controlled environment. - -## Controller safety - -You cannot upgrade a canister without a valid controller. Losing all controller keys leaves the canister permanently frozen at its current code: there is no recovery path on the IC. - -```bash -# Check current controllers -icp canister settings show my-canister -e ic - -# Add a backup controller before any risky upgrade -icp canister settings update my-canister --add-controller -e ic -``` - -For production canisters: - -- Maintain at least two controllers (primary identity + hardware wallet or multisig) -- For fully onchain governance, add an SNS or DAO canister as controller and remove personal principals - -See [Access management](access-management.md) for detailed controller management patterns. - -## Next steps - -- [Data persistence](../backends/data-persistence.md): stable structures and upgrade patterns in depth -- [Canister lifecycle](../canister-management/lifecycle.md#upgrade-a-canister): the full upgrade sequence and install modes -- [Canister snapshots](../canister-management/snapshots.md): create and restore snapshots -- [Testing strategies](../testing/strategies.md): test upgrade scenarios before deploying to mainnet -- [Access management](access-management.md): manage controllers and prevent lock-out - - + diff --git a/docs/guides/security/data-integrity.md b/docs/guides/security/data-integrity.md index ad7d37d..8818067 100644 --- a/docs/guides/security/data-integrity.md +++ b/docs/guides/security/data-integrity.md @@ -1,455 +1,629 @@ --- -title: "Data Integrity" -description: "Protect data confidentiality and authenticity in canisters using vetKeys encryption, identity-based encryption, certified variables, and signature verification." +title: "Security Best Practices: Data Integrity and Authenticity" +description: "Security best practices for certified variables, asset certification, and protecting data authenticity on ICP." sidebar: - order: 3 + order: 5 --- -Data on the Internet Computer faces two distinct threats: **confidentiality** (unauthorized parties reading data) and **authenticity** (verifying that data hasn't been tampered with). This guide covers the IC mechanisms that address both: vetKeys for onchain encryption, certified variables for cryptographic data authenticity, and signature verification for external data. +## Certified variables -For a conceptual overview of how these fit into the IC security model, see [Security model](../../concepts/security.md). For a deeper look at the vetKeys cryptographic protocol, see [vetKeys](../../concepts/vetkeys.md). +### Security concern -## Onchain encryption with vetKeys +ICP offers three modes of operation for canisters: `update`, `query`, and `composite_query`. For the sake of simplicity, we will club `composite_query` under queries for the rest of this section. -Canister state on standard application subnets is readable by node operators. If your application stores private data (notes, messages, files), you must encrypt it before storing. vetKeys (verifiably encrypted threshold keys) give canisters access to cryptographic key material derived by a threshold quorum of subnet nodes. No single node ever holds the raw key. +Update calls are slow and expensive but provide integrity guarantees as their responses include a threshold signature signed by the subnet. -The core workflow: +On the other hand, query calls are fast since a single replica formulates the response, but **there is no integrity guarantee, since the response can be manipulated by a single replica or boundary node.** For example, if the NNS dapp fetches proposal information from the governance canister via query calls and the responding node is malicious, it can mask an ill-intentioned proposal that causes irrevocable damage as innocuous by modifying the proposal payload in the response and mislead voters into voting yes. Another consequence of query calls is that users can't rely on [canister_inspect_message](../../references/ic-interface-spec/canister-interface.md#system-api-inspect-message) as a guard. **This makes query calls, in their raw form, unfit to serve data for security-critical applications.** -1. The client generates an ephemeral **transport key pair** -2. The canister calls `vetkd_derive_key` on the management canister, which derives a key encrypted under the client's transport public key -3. The client decrypts the result with its transport private key to obtain the raw vetKey -4. The client uses the vetKey to encrypt or decrypt data locally +### Using certified variables for secure queries +In certain use cases, there is a third option whereby query results can return data that has been certified by the subnet in an earlier update call. This is the concept of certified data, and it requires changes to the update call to create the certification, the query call to return the certificate, and the frontend to verify the certificate. Using certified data provides the best of both worlds with query-like response times and update-like certified responses. -No key material ever leaves the subnet in plaintext. The canister never sees the raw key. +Some examples of certified variables are asset certification in [Internet Identity](https://github.com/dfinity/internet-identity/blob/b29a6f68bbe5a49d048e12bc7a3263a9f43d080b/src/internet_identity/src/main.rs#L775-L808), [NNS dapp](https://github.com/dfinity/nns-dapp/blob/372c3562127d70c2fde059bc9c268e8ae858583e/rs/src/assets.rs#L121-L145), or the [canister signature implementation in Internet Identity](https://github.com/dfinity/ic-canister-sig-creation). -### Prerequisites +:::tip +Certified variables are an advanced feature that require careful implementation of authenticated data structures and verification on the canister and client sides, respectively. **If the client doesn't require fast response times, call the query method as an update call (replicated query).** The response would be certified by the subnet, and a single malicious or boundary node can't modify the response. +::: -**Rust:** - -```toml -[dependencies] -ic-cdk = "0.19" -ic-vetkeys = "0.6" -ic-stable-structures = "0.7" -``` - -**Motoko** (`mops.toml`): - -```toml -[dependencies] -core = "2.0.0" -``` - -**Frontend:** - -```bash -npm install @dfinity/vetkeys -``` - -> **API stability:** The `ic-vetkeys` crate and `@dfinity/vetkeys` package are published but their APIs may still change. Pin the versions above and check the [DFINITY forum](https://forum.dfinity.org) for migration guides before upgrading. - -### Key names and environments - -| Key name | Environment | Cycle cost (approx.) | -|----------|-------------|----------------------| -| `test_key_1` | Local + mainnet (testing) | ~10B cycles | -| `key_1` | Mainnet (production) | ~26B cycles | - -Use `test_key_1` during development and mainnet testing. Switch to `key_1` for production. `vetkd_public_key` does not cost cycles; only `vetkd_derive_key` does. - -### Rust implementation - -The `ic-vetkeys` crate provides a high-level `KeyManager` that handles access control and stable storage. For simpler use cases, you can also call the management canister directly. +:::tip +ICP also provides replica signed queries, where query responses are signed by the answering replica node; however, it doesn't have the same security guarantees as an `update` call and only protects from malicious boundary nodes. Replica signed queries are enabled by default on both the ICP Rust agent and the ICP JavaScript agent. +::: -**Using `ic-vetkeys` KeyManager (recommended):** +### What is certified data? +Aside from update calls, the subnet certifies (creates a threshold signature) a part of the canister data every round. This is stored in the state tree under the label `certified_data`. However, since it's certified every round, the amount of data that can be stored in `certified_data` is limited to 32 bytes. Hence, when you modify the state of your canister during an update call, if you can convert the state into a unique representation that can fit into 32 bytes, you can store it under `certified_data`, and it will be certified. Naturally, this can be done by computing a hash of the data structure of the canister state. This is also why certified variables are difficult to implement. Depending on your data structures, you will need to develop a different kind of hashing function. -Initialize the `KeyManager` with stable memory and a key ID in the `init` hook: +Subsequent query calls can return the data as-is, including the signature on the `certified_data`, which the frontend can verify with the IC root public key. This means that data aggregation or other calculations can't be done in query calls, as there would be no way to produce a signature over that newly created data. There are two workarounds: either this data is precomputed in the update call or all raw data is sent to the frontend, which verifies it and does the calculations. Combining these features, a canister should be able to certify a variable in a query response with this [design](https://medium.com/dfinity/how-internet-computer-responses-are-certified-as-authentic-2ff1bb1ea659). -```rust -use ic_stable_structures::memory_manager::{MemoryId, MemoryManager}; -use ic_stable_structures::DefaultMemoryImpl; -use ic_vetkeys::key_manager::KeyManager; -use ic_vetkeys::types::{AccessRights, VetKDCurve, VetKDKeyId}; - -thread_local! { - static MEMORY_MANAGER: std::cell::RefCell> = - std::cell::RefCell::new(MemoryManager::init(DefaultMemoryImpl::default())); - static KEY_MANAGER: std::cell::RefCell>> = - std::cell::RefCell::new(None); -} +On a high level, in your canister: +1. Choose an [authenticated data structure](https://cs.brown.edu/research/pubs/pdfs/2003/Tamassia-2003-ADS.pdf) like Merkle trees to store a value in canister memory. +2. In the **update** call: + - Perform the computation and store the result in the Merkle tree. + - The lookup path for the result must act as its `key`. Ideally this `key` should be the parameters provided by the caller in the query method. + - Recompute the Merkle proof (`root_hash`) + - Store the `root_hash` as the canister's certified data. + - Return the `key` as response. +3. In the **query** call: + - Fetch the result from the Merkle structure using the query parameters as the lookup path. + - Fetch the current `certified_data` for the canister. + - Compute the witness for the result using the same lookup path. The Merkle witness provides proof of inclusion that the requested result exists in the Merkle tree under the given path. + - Return `(result, certified_data, witness)` as the response. -#[ic_cdk::init] -fn init() { - let key_id = VetKDKeyId { - curve: VetKDCurve::Bls12381G2, - name: "key_1".to_string(), // "test_key_1" for local + mainnet testing - }; - MEMORY_MANAGER.with(|mm| { - KEY_MANAGER.with(|km| { - *km.borrow_mut() = Some(KeyManager::init( - "my_app_v1", key_id, - mm.borrow().get(MemoryId::new(0)), - mm.borrow().get(MemoryId::new(1)), - mm.borrow().get(MemoryId::new(2)), - )); - }); - }); -} -``` +The rest of the section shows an example canister, which can serve a certified response for a `query` using `certified_data` that is verified in the frontend. The examples are written in Rust and Motoko, but the overall design can be implemented in other languages. -Expose the two endpoints callers need: one to retrieve an encrypted key, one to retrieve the verification key: +### Building a canister with certified variables +Let's consider the following canister interface: -```rust -use candid::Principal; -use ic_cdk::update; - -#[update] -async fn get_encrypted_vetkey(subkey_id: Vec, transport_public_key: Vec) -> Vec { - let caller = ic_cdk::caller(); // capture BEFORE await - let future = KEY_MANAGER.with(|km| { - km.borrow().as_ref().expect("not initialized") - .get_encrypted_vetkey(caller, subkey_id, transport_public_key) - .expect("access denied") - }); - future.await -} - -#[update] -async fn get_vetkey_verification_key() -> Vec { - let future = KEY_MANAGER.with(|km| { - km.borrow().as_ref().expect("not initialized") - .get_vetkey_verification_key() - }); - future.await -} -``` - -**Calling management canister directly (lower level):** - -Retrieve the public key (no cycles required): - -```rust -use ic_cdk::management_canister::{ - VetKDCurve, VetKDKeyId, VetKDPublicKeyArgs, +```c +type User = record { + name: text; + age: nat8; }; -const CONTEXT: &[u8] = b"my_app_v1"; - -fn key_id() -> VetKDKeyId { - VetKDKeyId { - curve: VetKDCurve::Bls12_381_G2, - name: "key_1".to_string(), // "test_key_1" for testing - } -} +type CertifiedUser = record { + user : User; + certificate : blob; + witness : blob; +}; -#[ic_cdk::update] -async fn get_public_key() -> Vec { - let response = ic_cdk::management_canister::vetkd_public_key( - &VetKDPublicKeyArgs { canister_id: None, context: CONTEXT.to_vec(), key_id: key_id() } - ).await.expect("vetkd_public_key call failed"); - response.public_key +service : { + "set_user": (User) -> (nat64); + "get_user": (nat64) -> (CertifiedUser) query; } ``` -Derive a key for the authenticated caller (`key_1` costs ~26B cycles; `ic-cdk` attaches them automatically): +The canister exposes the following service: +- **set_user**: The caller provides a `User` object to the canister. The canister records it and serves a corresponding `index` for the entry as the response. Since `certified_data` can only store 32 bytes of data, it uses a specialized data structure from `ic_certified_map` to store the `User` data. + - The data structure internally stores the data in a `HashTree` (or [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree)) and records the `root_hash` of the data structure in the `certified_data`, which is 32 bytes. + - The `root_hash` cryptographically guarantees that only one tree can correspond to that hash. The `root_hash` is also referred to as the Merkle proof. +- **get_user**: The caller provides a `index: nat64` to the canister and gets a certified response for the corresponding `User`. The `CertifiedUser` response must have the following structure for verifying the response: + - **user**: The actual response. + - **certificate**: The payload for verifying the signature on the `certified_data`. ICP provides the system API `data_certificate()` for this. + - **witness**: Allows for the final verification of the response to be completed with the requested input and `certified_data`. -```rust -use ic_cdk::management_canister::{VetKDDeriveKeyArgs, VetKDCurve, VetKDKeyId}; +You can find an example implementation of the canister below. -#[ic_cdk::update] -async fn derive_key(transport_public_key: Vec) -> Vec { - let caller = ic_cdk::api::msg_caller(); // MUST capture before await - let response = ic_cdk::management_canister::vetkd_derive_key( - &VetKDDeriveKeyArgs { - input: caller.as_slice().to_vec(), - context: CONTEXT.to_vec(), - transport_public_key, - key_id: key_id(), - } - ).await.expect("vetkd_derive_key call failed"); - response.encrypted_key -} -``` - -### Motoko implementation - -Motoko uses the management canister directly. Define the request/response types and declare the actor interface: +**Motoko:** ```motoko +import CertifiedData "mo:core/CertifiedData"; import Blob "mo:core/Blob"; -import Principal "mo:core/Principal"; +import Nat8 "mo:core/Nat8"; +import Debug "mo:core/Debug"; import Text "mo:core/Text"; - -persistent actor { - - type VetKdCurve = { #bls12_381_g2 }; - type VetKdKeyId = { curve : VetKdCurve; name : Text }; - type VetKdPublicKeyRequest = { canister_id : ?Principal; context : Blob; key_id : VetKdKeyId }; - type VetKdPublicKeyResponse = { public_key : Blob }; - type VetKdDeriveKeyRequest = { input : Blob; context : Blob; transport_public_key : Blob; key_id : VetKdKeyId }; - type VetKdDeriveKeyResponse = { encrypted_key : Blob }; - - let managementCanister : actor { - vetkd_public_key : VetKdPublicKeyRequest -> async VetKdPublicKeyResponse; - vetkd_derive_key : VetKdDeriveKeyRequest -> async VetKdDeriveKeyResponse; - } = actor "aaaaa-aa"; - - let context : Blob = Text.encodeUtf8("my_app_v1"); - // "test_key_1" for local + mainnet testing, "key_1" for production - func keyId() : VetKdKeyId = { curve = #bls12_381_g2; name = "key_1" }; - // ... -``` - -Implement the public key and key derivation endpoints: - -```motoko - public shared func getPublicKey() : async Blob { - // vetkd_public_key does not require cycles - let response = await managementCanister.vetkd_public_key({ - canister_id = null; context; key_id = keyId(); - }); - response.public_key +import Nat64 "mo:core/Nat64"; +import Array "mo:core/Array"; +import CertTree "mo:ic-certification/CertTree"; +import CV "mo:cbor/Value"; +import CborEncoder "mo:cbor/Encoder"; +import CborDecoder "mo:cbor/Decoder"; + +actor CertifiedVariable { + + type User = { + name : Text; + age : Nat8; }; - public shared ({ caller }) func deriveKey(transportPublicKey : Blob) : async Blob { - // caller captured before the await; key_1 costs ~26B cycles - let response = await (with cycles = 26_000_000_000) managementCanister.vetkd_derive_key({ - input = Principal.toBlob(caller); - context; - transport_public_key = transportPublicKey; - key_id = keyId(); - }); - response.encrypted_key + type CertifiedUser = { + user : User; + certificate : Blob; + witness : Blob; }; -}; -``` -### Frontend: decrypt and use the vetKey + stable var count : Nat64 = 0; + stable let cert_store : CertTree.Store = CertTree.newStore(); + let ct = CertTree.Ops(cert_store); -The frontend generates a transport key pair, sends the public half to the canister, receives the encrypted derived key, and decrypts it locally. - -Generate a fresh transport key pair each session, then request and decrypt the vetKey: - -```typescript -import { TransportSecretKey, DerivedPublicKey, EncryptedVetKey } from "@dfinity/vetkeys"; + public func set_user(user : User) : async Nat64 { + count += 1; + let path : [Blob] = [Text.encodeUtf8("user"), blobOfNat64(count)]; + ct.put(path, encodeUser(user)); + ct.setCertifiedData(); + return count; + }; -// 1. Generate an ephemeral transport key: new one each session -const transportSecretKey = TransportSecretKey.fromSeed(crypto.getRandomValues(new Uint8Array(32))); -const transportPublicKey = transportSecretKey.publicKey(); + public query func get_user(index : Nat64) : async CertifiedUser { + let certificate = switch (CertifiedData.getCertificate()) { + case (?certificate) { + certificate; + }; + case (null) { + Debug.trap("Certified data not set"); + }; + }; -// 2. Request encrypted vetkey and verification key from the canister -const [encryptedKeyBytes, verificationKeyBytes] = await Promise.all([ - backendActor.get_encrypted_vetkey(subkeyId, transportPublicKey), - backendActor.get_vetkey_verification_key(), -]); + let path : [Blob] = [Text.encodeUtf8("user"), blobOfNat64(index)]; -// 3. Decrypt the vetkey using the transport secret -const vetKey = EncryptedVetKey.deserialize(new Uint8Array(encryptedKeyBytes)) - .decryptAndVerify( - transportSecretKey, - DerivedPublicKey.deserialize(new Uint8Array(verificationKeyBytes)), - new Uint8Array(subkeyId), - ); -``` + let value = switch (ct.lookup(path)) { + case (?value) { + value; + }; + case (null) { + Debug.trap("Lookup failed"); + }; + }; -Use the vetKey to derive a symmetric AES-GCM key and encrypt/decrypt data: - -```typescript -// 4. Derive a 256-bit AES key from the vetKey material -const aesKey = await crypto.subtle.importKey( - "raw", - vetKey.toDerivedKeyMaterial().data.slice(0, 32), - { name: "AES-GCM" }, - false, - ["encrypt", "decrypt"], -); - -// 5. Encrypt data -const iv = crypto.getRandomValues(new Uint8Array(12)); -const ciphertext = await crypto.subtle.encrypt( - { name: "AES-GCM", iv }, - aesKey, - new TextEncoder().encode("secret message"), -); - -// 6. Decrypt data -const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, aesKey, ciphertext); -``` + let user : User = decodeUser(value); + let witness = ct.encodeWitness(ct.reveal(path)); -### Common mistakes + let certifiedUser : CertifiedUser = { + certificate = certificate; + witness = witness; + user = user; + }; -- **Reusing transport keys across sessions.** Generate a fresh transport key pair for each session. If an attacker ever learns the transport secret, they can decrypt all keys derived while that secret was in use. -- **Using derived key bytes directly as an AES key.** The `encrypted_key` field from `vetkd_derive_key` is an encrypted blob. After decryption, call `toDerivedKeyMaterial()` before using for AES: do not use the raw bytes directly. -- **Putting secret data in the `input` field.** The `input` field is sent to the management canister in plaintext and serves as a key identifier (e.g., a user principal or document ID). Never use it for actual secret data. -- **Inconsistent context values.** The `context` field on the canister and on the frontend must match exactly. A mismatch causes silent decryption failure. + return certifiedUser; + }; -## Identity-based encryption (IBE) + func encodeUser(user : User) : Blob { + let bytes : CV.Value = #majorType5([ + (#majorType3("name"), #majorType3(user.name)), + (#majorType3("age"), #majorType0(Nat64.fromNat(Nat8.toNat(user.age)))), + ]); -IBE lets you encrypt to an identity (such as a user's principal) without the recipient being online or having registered a key. Anyone who knows the canister's derived public key can encrypt to any principal. The recipient later authenticates to the canister, obtains their vetKey, and decrypts locally. + let #ok(encoded_user) = CborEncoder.encode(bytes); + return Blob.fromArray(encoded_user); + }; -This is useful for private messaging, sealed auctions, and any case where you want to encrypt data "to" a principal who will retrieve it later. + func decodeUser(bytes : Blob) : User { + let #ok(#majorType5(map)) = CborDecoder.decode(bytes); + let name_tag = Array.find<(CV.Value, CV.Value)>(map, func x = x.0 == #majorType3("name")); + let age_tag = Array.find<(CV.Value, CV.Value)>(map, func x = x.0 == #majorType3("age")); + + let name = switch (name_tag) { + case (?name_value) { + let #majorType3(name) = name_value.1; + name; + }; + case (null) { + Debug.trap("Decoding failed for name"); + }; + }; -> **Access control:** If you implement IBE without using `KeyManager` or `EncryptedMaps`, your canister must verify that `caller == recipient_principal` before calling `vetkd_derive_key`. Without this check, any caller can request any derived key and decrypt messages meant for someone else. The `ic-vetkeys` library handles this automatically. + let age = switch (age_tag) { + case (?age_value) { + let #majorType0(age) = age_value.1; + Nat8.fromNat(Nat64.toNat(age)); + }; + case (null) { + Debug.trap("Decoding failed for age"); + }; + }; -**TypeScript IBE example: encrypt (sender side):** + return { + name = name; + age = age; + }; + }; -```typescript -import { IbeCiphertext, IbeIdentity, IbeSeed } from "@dfinity/vetkeys"; + func blobOfNat64(n : Nat64) : Blob { + let byteMask : Nat64 = 0xff; + func byte(x : Nat64) : Nat8 { + Nat8.fromNat(Nat64.toNat(x)); + }; + Blob.fromArray([ + byte(((byteMask << 56) & n) >> 56), + byte(((byteMask << 48) & n) >> 48), + byte(((byteMask << 40) & n) >> 40), + byte(((byteMask << 32) & n) >> 32), + byte(((byteMask << 24) & n) >> 24), + byte(((byteMask << 16) & n) >> 16), + byte(((byteMask << 8) & n) >> 8), + byte(((byteMask << 0) & n) >> 0), + ]); + }; -// No canister call needed if the public key is already known -const recipientIdentity = IbeIdentity.fromBytes(recipientPrincipalBytes); -const ciphertext = IbeCiphertext.encrypt( - derivedPublicKey, recipientIdentity, - new TextEncoder().encode("secret message"), - IbeSeed.random(), -); -const serialized = ciphertext.serialize(); // store this onchain (ciphertext, not plaintext) +}; ``` -**TypeScript IBE example: decrypt (recipient side):** - -```typescript -import { TransportSecretKey, DerivedPublicKey, EncryptedVetKey, IbeCiphertext } from "@dfinity/vetkeys"; - -// Recipient authenticates to the canister to obtain their vetKey -const transportSecretKey = TransportSecretKey.fromSeed(crypto.getRandomValues(new Uint8Array(32))); -const [encryptedKeyBytes, verificationKeyBytes] = await Promise.all([ - backendActor.get_encrypted_vetkey(subkeyId, transportSecretKey.publicKey()), - backendActor.get_vetkey_verification_key(), -]); -const vetKey = EncryptedVetKey.deserialize(new Uint8Array(encryptedKeyBytes)) - .decryptAndVerify( - transportSecretKey, - DerivedPublicKey.deserialize(new Uint8Array(verificationKeyBytes)), - new Uint8Array(subkeyId), - ); - -const decrypted = IbeCiphertext.deserialize(serialized).decrypt(vetKey); -// decrypted is Uint8Array containing "secret message" -``` +**Rust:** -### Deriving public keys offline +```rust +use candid::CandidType; +use ic_certified_map::HashTree; +use ic_certified_map::{leaf_hash, AsHashTree, Hash, RbTree}; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use std::cell::Cell; +use std::cell::RefCell; + +#[derive(CandidType, Serialize, Deserialize, Clone)] +struct User { + name: String, + age: u8, +} -You can derive the canister's public key for a given context without making a canister call. This is useful for IBE encryption when the recipient is offline: +impl AsHashTree for User { + fn root_hash(&self) -> Hash { + let user_serialized = serde_cbor::to_vec(&self).unwrap(); + leaf_hash(&user_serialized[..]) + } + fn as_hash_tree(&self) -> HashTree<'_> { + HashTree::Leaf(Cow::from(serde_cbor::to_vec(&self).unwrap())) + } +} -```typescript -import { MasterPublicKey, DerivedPublicKey } from "@dfinity/vetkeys"; +#[derive(CandidType)] +struct CertifiedUser { + user: User, + certificate: Vec, + witness: Vec, +} -// Derive offline from the known mainnet master public key -const masterKey = MasterPublicKey.productionKey(); -const canisterKey = masterKey.deriveCanisterKey(canisterId); -const derivedKey: DerivedPublicKey = canisterKey.deriveSubKey( - new TextEncoder().encode("my_app_v1"), -); -// Use derivedKey for IBE encryption without any network calls -``` +thread_local! { + static INDEX : Cell = Cell::new(0); + static TREE: RefCell>> = RefCell::new(RbTree::new()); +} -For complete IBE and encrypted storage examples, see: -- [Password manager](https://github.com/dfinity/examples/tree/master/rust/vetkeys/password_manager): encrypted key-value storage with `EncryptedMaps` -- [Encrypted notes app](https://github.com/dfinity/examples/tree/master/rust/vetkeys/encrypted_notes_dapp_vetkd): per-user encrypted note storage -- [IBE example](https://github.com/dfinity/examples/tree/master/rust/vetkeys/basic_ibe): identity-based encryption with Internet Identity principals +#[ic_cdk::update] +fn set_user(user: User) -> u64 { + let index = INDEX.with(|index| { + let count = index.get() + 1; + index.set(count); + count + }); -## Certified variables for data authenticity + TREE.with_borrow_mut(|tree| { + match tree.get(b"user") { + Some(_) => { + tree.modify(b"user", |inner| { + inner.insert(index.to_be_bytes(), user); + }); + } + None => { + let mut inner = RbTree::new(); + inner.insert(index.to_be_bytes(), user); + tree.insert("user", inner); + } + } + ic_cdk::api::set_certified_data(&tree.root_hash()); + }); + index +} -Query calls on ICP run on a single replica and are not verified by consensus. A malicious or faulty replica could return fabricated data. Certified variables solve this: the canister stores a Merkle root hash in the subnet's certified state during update calls, and query responses include a subnet BLS signature proving the data is authentic. +#[ic_cdk::query] +fn get_user(index: u64) -> CertifiedUser { + let certificate = ic_cdk::api::data_certificate().expect("No data certificate available"); + + TREE.with_borrow(|tree| { + let user = match tree.get(b"user") { + Some(inner) => { + let user = inner.get(&index.to_be_bytes()[..]).expect("User not found"); + user.to_owned() + } + None => { + panic!("Tree isn't initialized"); + } + }; + + let mut witness = vec![]; + let mut witness_serializer = serde_cbor::Serializer::new(&mut witness); + let _ = witness_serializer.self_describe(); + tree.nested_witness(b"user", |inner| inner.witness(&index.to_be_bytes()[..])) + .serialize(&mut witness_serializer) + .unwrap(); + + CertifiedUser { + user, + certificate, + witness, + } + }) +} +``` -Use certified variables when: -- Query responses must be verifiable by clients without trusting any single replica -- You serve data that could change (balances, configuration, records) via fast query calls -- Your frontend needs to verify that data hasn't been tampered with in transit +### Verifying certified variables -For the full implementation guide, including Merkle tree construction, witness generation, and frontend verification, see [Certified variables](../backends/certified-variables.md). +Once you have the response `CertifiedUser`, for the integrity guarantee, the frontend must verify the certification in the response. This is broken down into several steps implemented in the Rust and JavaScript example below. -**Key rules:** -- `certified_data_set` may only be called during update calls (not query calls) -- You can only certify 32 bytes: build a Merkle tree and certify the root hash -- Re-certify data in `post_upgrade`: certified data is cleared on upgrade -- Clients must verify certificate freshness (the certificate embeds a timestamp; reject certificates older than ~5 minutes) +:::note +The example has some extra steps to set up the canister with some `User` data before verification. You can ignore the section marked between `// ==== START of canister data setup` and `// ==== END of canister data setup`. +::: -## Signature verification for external data +1. Verify the IC certificate: Recompute the `root_hash` of `certificate.tree` (pruned state tree with the canister's `certified_data`) and verify the `certificate.signature` with `root_hash` as the message, `certificate.delegation`, and the IC `root_key` as the public key. This confirms that the signature is valid for the current state tree. +2. Validate that the response is not stale by verifying the time at `/time` in `certificate.tree` is less than a certain delta of current time. The recommended delta is 5 minutes but should be adapted to the use case. +3. Recompute the `root_hash` of the witness and verify equality with the `certified_data`. The `certified_data` can be obtained from `certificate.tree` under the path `/canister//certified_data`. +4. Check if query parameters are in the witness. In this example, the lookup path is `/user/` and should be present in the witness. +5. Validate if the value found in `/user/` matches `user` from the response. +6. If all of the previous steps succeed, return `user` as the valid response. -When your canister receives data from external parties (signed messages, X.509 CSRs, or HTTP request signatures) it must verify the cryptographic signature before trusting the data. ICP verifies signatures on ingress messages automatically, but canister-to-canister or external data flows require manual verification. +**Rust (client-side verification):** -### IC ingress message signatures +```rust +use arbitrary::{Arbitrary, Unstructured}; +use candid::Encode; +use candid::Principal; +use candid::{CandidType, Decode, Deserialize}; +use futures::future::join_all; +use ic_agent::identity::AnonymousIdentity; +use ic_agent::Agent; +use ic_certificate_verification::validate_certificate_time; +use ic_certificate_verification::VerifyCertificate; +use ic_certification::hash_tree::HashTree; +use ic_certification::{Certificate, LookupResult}; +use rand::prelude::*; +use serde_cbor::Deserializer; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[derive(CandidType, Deserialize, Debug, PartialEq, Eq, Arbitrary)] +struct User { + name: String, + age: u8, +} -Every ingress call to a canister is signed by the caller's identity. The IC verifies these signatures automatically before the message reaches your canister: you do not need to verify them yourself. The `caller` principal in your canister method is already authenticated. +#[derive(CandidType, Deserialize)] +struct CertifiedUser { + user: User, + certificate: Vec, + witness: Vec, +} -For workflows that require additional independent verification (such as verifying a message offline or in a different context), the IC uses the following signature schemes: +static URL: &str = "http://localhost:41749"; +static CANISTER: &str = "a3shf-5eaaa-aaaaa-qaafa-cai"; +const MAX_CERT_TIME_OFFSET_NS: u128 = 300_000_000_000; // 5 min +const MAX_CALLS: usize = 10; + +#[tokio::main] +async fn main() { + + let agent = Agent::builder() + .with_url(URL) + .with_identity(AnonymousIdentity) + .build() + .expect("Unable to create agent"); + + // This should be done only in demo environments. + // When interacting with mainnet, hardcode the root_key. + agent + .fetch_root_key() + .await + .expect("Unable to fetch root key"); + let root_key = agent.read_root_key(); + + let canister_id = Principal::from_text(CANISTER).unwrap(); + + // ==== START of canister data setup + let mut rng = rand::thread_rng(); + + // Make MAX_CALLS to set_user + let mut get_user_calls = Vec::new(); + for _ in 0..MAX_CALLS { + let bytes: [u8; 16] = rng.gen(); + let mut u = Unstructured::new(&bytes[..]); + let temp_user = User::arbitrary(&mut u).unwrap(); + + println!("Calling set_user with {:?}", temp_user); + let response = agent + .update(&canister_id, "set_user") + .with_effective_canister_id(canister_id) + .with_arg(Encode!(&temp_user).unwrap()) + .call_and_wait(); + get_user_calls.push(response); + } + let results: Vec = join_all(get_user_calls) + .await + .into_iter() + .map(|result| { + Decode!( + result + .expect("Query call get_user failed") + .as_slice(), + u64 + ) + .unwrap() + }) + .collect(); + + // From response indexes, choose a random index for get_user + let index: usize = rng.gen(); + let index: u64 = *results.get(index % MAX_CALLS).unwrap(); + // ==== END of canister data setup + + println!("Fetching index {:?}", index); + + let query_response = agent + .query(&canister_id, "get_user") + .with_effective_canister_id(canister_id) + .with_arg(Encode!(&index).unwrap()) + .call() + .await + .expect("Unable to call query call get_user"); + + let certified_user = Decode!(&query_response, CertifiedUser).unwrap(); + + let mut deserializer = Deserializer::from_slice(&certified_user.certificate); + let certificate: Certificate = serde::de::Deserialize::deserialize(&mut deserializer).unwrap(); + + let start = SystemTime::now(); + let current_time = start + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_nanos(); + + // Step 1: Check if signature in the certificate can be validated with the + // root_hash of the tree in certificate as message and root_key as public_key + let verification_result = certificate.verify(canister_id.as_slice(), &root_key[..]); + + println!( + "Step 1: Digest match & Signature verification: {:?}", + verification_result + ); + + // Step 2: Check if the response is not stale with the given time offset MAX_CERT_TIME_OFFSET_NS. + let time_verification_result = + validate_certificate_time(&certificate, ¤t_time, &MAX_CERT_TIME_OFFSET_NS); + + println!("Step 2: Time skew: {:?}", time_verification_result); + + // Step 3: Check if witness root_hash matches the certified_data + let lookup_result = + certificate + .tree + .lookup_path([b"canister", canister_id.as_slice(), b"certified_data"]); + + let certified_data: [u8; 32] = match lookup_result { + LookupResult::Found(result) => result.try_into().unwrap(), + _ => panic!("Certified data not found"), + }; -- **Ed25519**: used by Internet Identity and many wallet implementations -- **ECDSA on secp256r1 (P-256)**: used by some hardware authenticators -- **ECDSA on secp256k1**: used by Bitcoin-compatible wallets + let mut deserializer = Deserializer::from_slice(&certified_user.witness); + let witness_decoded: HashTree> = + serde::de::Deserialize::deserialize(&mut deserializer).unwrap(); + let witness_digest = witness_decoded.digest(); + + println!( + "Step 3: Witness digest matches certified data: {:?} ", + witness_digest == certified_data + ); + + // Step 4: Check if the query parameters are in the witness + let witness_lookup: User = + match witness_decoded.lookup_path([b"user", &index.to_be_bytes()[..]]) { + LookupResult::Found(result) => serde_cbor::from_slice(result).unwrap(), + _ => panic!("user {} not found", index), + }; + + // Step 5: Check if the data found in Witness matches the returned result from the query. + println!( + "Step 4 & Step 5: Witness data matches User value: {:?}", + witness_lookup == certified_user.user + ); + + // Step 6: Return the result + println!("Result: {:?}", certified_user.user); +} +``` -To verify IC signatures independently (outside the IC, or as a second layer of validation), use the `ic-validator-ingress-message` Rust crate or the `@dfinity/standalone-sig-verifier-web` JavaScript library. See the [independently verifying IC signatures (Rust)](https://github.com/dfinity/ic/tree/master/rs/validator) documentation, or the [`@dfinity/standalone-sig-verifier-web` npm package](https://www.npmjs.com/package/@dfinity/standalone-sig-verifier-web) for the JavaScript path. +**JavaScript (client-side verification):** + +```js +import pkg from "@dfinity/agent"; +const { Actor, HttpAgent, Certificate, blsVerify, Cbor, reconstruct, lookup_path } = pkg; +import { IDL } from "@dfinity/candid"; +import { Principal } from "@dfinity/principal"; +import fetch from "isomorphic-fetch"; +import assert from "node:assert/strict"; + +const idlFactory = ({ IDL }) => { + const User = IDL.Record({ age: IDL.Nat8, name: IDL.Text }); + const CertifiedUser = IDL.Record({ + certificate: IDL.Vec(IDL.Nat8), + user: User, + witness: IDL.Vec(IDL.Nat8), + }); + return IDL.Service({ + get_user: IDL.Func([IDL.Nat64], [CertifiedUser], ["query"]), + set_user: IDL.Func([User], [IDL.Nat64], []), + }); +}; -### X.509 certificate handling +const canisterId = Principal.fromText("a3shf-5eaaa-aaaaa-qaafa-cai"); +const host = "http://localhost:35777"; -Canisters can act as certificate authorities using threshold signing keys. Because no single node ever holds the threshold private key, only the canister (via consensus) can sign certificates: this gives you a CA whose private key cannot be exfiltrated. +start().await; -The pattern: a canister generates a root CA certificate signed with its threshold Ed25519 or ECDSA key, then issues child certificates for CSRs submitted by external parties. Certificates can be verified by any standard X.509 tool. +async function start() { + const agent = new HttpAgent({ fetch, host }); + await agent.fetchRootKey(); -For a complete working example in Rust, see the [x509 example](https://github.com/dfinity/examples/tree/master/rust/x509), which demonstrates: + const rootKey = agent.rootKey.buffer; + let dummyUser = { name: "test_user", age: 21 }; -1. Creating a root CA certificate with a threshold signing key -2. Issuing child certificates from externally provided CSRs (in PKCS#10/PEM format) -3. Verifying ownership of the CSR before signing + const actor = Actor.createActor(idlFactory, { + agent, + canisterId, + }); -The key pattern for issuing a child certificate: + let index = await actor.set_user(dummyUser); + let certifiedUser = await actor.get_user(index); -```rust -// Verify the CSR signature before trusting its contents -verify_certificate_request_signature(&cert_req)?; + await verifyCertificate(certifiedUser, index, rootKey, canisterId); +} -// Verify the caller owns the key in the CSR -prove_ownership(&cert_req, ic_cdk::api::caller())?; +async function verifyCertificate(certifiedUser, index, rootKey, canisterId) { + const certificate = certifiedUser.certificate.buffer; + const witness = certifiedUser.witness.buffer; + const user = certifiedUser.user; + + const cert = new Certificate(certificate, rootKey, canisterId, blsVerify); + + // Step 1: Check if signature in the certificate can be validated with the + // root_hash of the tree in certificate as message and root_key as public_key + await cert.verify(); + console.log("Certificate verification succeeded"); + + // Step 2: Check if the response is not stale with the given time offset of 5m. + const te = new TextEncoder(); + const pathTime = [te.encode("time")]; + const rawTime = cert.lookup(pathTime).value; + console.log("Time skew: ", verifyTime(rawTime)); + + // Step 3: Check if witness root_hash matches the certified_data + const pathData = [ + te.encode("canister"), + canisterId.toUint8Array(), + te.encode("certified_data"), + ]; + + const certifiedData = cert.lookup(pathData).value; + let witnessTree = Cbor.decode(witness); + let witnessRootHash = await reconstruct(witnessTree); + console.log( + "Verify CertifiedData matches witness root_hash: ", + certifiedData.buffer === witnessRootHash.buffer + ); -// Sign the child certificate using the canister's threshold key -// (ed25519_sign or ecdsa_sign via management canister) -``` + // Step 4: Check if the query parameters are in the witness + const query_params = [te.encode("user"), bigEndian(index).buffer]; + const witnessData = Cbor.decode(lookup_path(query_params, witnessTree).value); + console.log("Witness data: ", witnessData); -This approach is used when you need to issue certificates to external systems that expect standard PKI infrastructure, while keeping the CA private key under threshold-protected control. + // Step 5: Check if the data found in Witness matches the returned result from the query. + assert.deepStrictEqual(witnessData, user, "Value matches response data"); -## Deploying and testing + // Step 6: Return the result + return user +} -### Local development +function verifyTime(rawTime) { + const idlMessage = new Uint8Array([ + ...new TextEncoder().encode("DIDL\x00\x01\x7d"), + ...new Uint8Array(rawTime), + ]); + const decodedTime = IDL.decode([IDL.Nat], idlMessage)[0]; + const time = Number(decodedTime) / 1e9; + const now = Date.now() / 1000; + const diff = Math.abs(time - now); + if (diff > 5) { + return false; + } + return true; +} -```bash -# Start a local network: test_key_1 and key_1 are provisioned automatically -icp network start -d +function bigEndian(n) { + let buf = new Uint8Array(8); -# Deploy your canister -icp deploy backend + for (let i = 7; i >= 0; i--) { + buf[i] = Number(n & 0xffn); + n >>= 8n; + } + return buf; +} +``` -# Test public key retrieval -icp canister call backend getPublicKey '()' -# Returns: (blob "..."): the vetKD public key for your canister +## Use HTTP asset certification and avoid serving your dapp through `raw.icp0.io` -# Test key derivation (requires a 48-byte transport public key blob) -# In practice, the frontend generates this using TransportSecretKey.fromSeed() -icp canister call backend deriveKey '(blob "\00\01\02...")' -# Returns: (blob "..."): the encrypted derived key -``` +### Security concern -### Mainnet deployment +Dapps on ICP can use [asset certification](https://learn.internetcomputer.org/hc/en-us/articles/34276431179412-Asset-Certification) to make sure the HTTP assets delivered to the browser are authentic (i.e., threshold-signed by the subnet). If an app does not do asset certification, it can only be served insecurely through `raw.icp0.io`, where no asset certification is checked. This is insecure since a single malicious node or boundary node can freely modify the assets delivered to the browser. -```bash -# Deploy to mainnet -icp deploy backend -e ic +If an app is served through `raw.icp0.io` in addition to `icp0.io`, an adversary may trick users (phishing) into using the insecure `raw.icp0.io`. -# Verify the public key is non-empty -icp canister call backend getPublicKey '()' -e ic -``` +### Recommendation -Confirm that: -- `getPublicKey` returns a non-empty blob (48+ bytes of BLS public key material) -- `deriveKey` returns a non-empty blob (encrypted key material) -- Different callers receive different derived keys (same caller + same input = same key; different caller = different key) +- Only serve assets through `.icp0.io`, where the boundary nodes enforce response verification on the served assets. Do not serve through `.raw.icp0.io`. -## Next steps +- Serve assets using the asset canister, which creates asset certification automatically, or add the `ic-certificate` header including the asset certification as, e.g., done in the [NNS dapp](https://github.com/dfinity/nns-dapp) and [Internet Identity](https://github.com/dfinity/internet-identity). -- [vetKeys concept guide](../../concepts/vetkeys.md): how the threshold key derivation protocol works -- [Encryption guide](./encryption.md): vetKeys encryption patterns including EncryptedMaps -- [Certified variables](../backends/certified-variables.md): full certified data implementation -- [Security model](../../concepts/security.md): IC security guarantees and threat model +- Check in the canister's `http_request` method if the request came through raw. If so, return an error and do not serve any assets. - + diff --git a/docs/guides/security/data-storage.md b/docs/guides/security/data-storage.md new file mode 100644 index 0000000..767bccb --- /dev/null +++ b/docs/guides/security/data-storage.md @@ -0,0 +1,94 @@ +--- +title: "Security Best Practices: Data Storage" +description: "Security best practices for canister data storage, stable memory, encryption of sensitive data, and backups." +sidebar: + order: 6 +--- + +## Rust: Use `thread_local!` with `Cell/RefCell` for state variables and put all your globals in one basket + +### Security concern + +Canisters need a global mutable state. In Rust, there are several ways to achieve this. However, some options can lead to vulnerabilities such as memory corruption. + +### Recommendation + +- [Use `thread_local!` with `Cell/RefCell` for state variables](https://mmapped.blog/posts/01-effective-rust-canisters.html#use-threadlocal) (from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html)). + +- [Put all your globals in one basket](https://mmapped.blog/posts/01-effective-rust-canisters.html#clear-state) (from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html)). + +## Limit the amount of data that can be stored in a canister per user + +### Security concern + +If a user is able to store a big amount of data on a canister, this may be abused to fill up the canister storage and make the canister unusable. + +### Recommendation + +Limit the amount of data that can be stored in a canister per user. This limit has to be checked whenever data is stored for a user in an update call. + +## Consider using stable memory, version it, and test it + +### Security concern + +Canister memory is not persisted across upgrades. If data needs to be kept across upgrades, you may serialize the canister memory in `pre_upgrade` and deserialize it in `post_upgrade`. Using `pre_upgrade` and `post_upgrade` methods is not recommended and should be avoided. The available number of instructions for these methods is limited. If the memory grows too big, the canister can no longer be updated. + +### Recommendation + +- Stable memory is persisted across upgrades and can be used to address this issue. + +- [Consider using stable memory](https://mmapped.blog/posts/01-effective-rust-canisters.html#stable-memory-main) (from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html)). Take note of the discussed disadvantages. + +- [Version stable memory](https://mmapped.blog/posts/01-effective-rust-canisters.html#version-stable-memory) (from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html)). + +- [Test the upgrade hooks](https://mmapped.blog/posts/01-effective-rust-canisters.html#test-upgrades) (from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html)). + +- See also the section on upgrades in [how to audit an ICP canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister) (focused on Motoko canisters). + +- Write tests for stable memory to avoid bugs. + +- Some libraries commonly used are: + + - [https://github.com/dfinity/stable-structures](https://github.com/dfinity/stable-structures) + + - [https://github.com/seniorjoinu/ic-stable-memory](https://github.com/seniorjoinu/ic-stable-memory) + +:::caution +Please note some of these libraries may be partially unfinished. +::: + +- See [current limitations of the Internet Computer](https://wiki.internetcomputer.org/wiki/Current_limitations_of_the_Internet_Computer), sections "Long running upgrades" and "\[de\]serializer requiring additional Wasm memory." + +- For example, [Internet Identity](https://github.com/dfinity/internet-identity) uses stable memory directly to store user data. + +## Consider encrypting sensitive data on canisters + +### Security concern + +By default, canisters provide integrity but not confidentiality. Data stored on canisters can be read by nodes/replicas. + +### Recommendation + +- Consider end-to-end encrypting any private or personal data (e.g., a user's personal or private information) on canisters. + +- The example dapp [encrypted notes](https://github.com/dfinity/examples/tree/master/motoko/encrypted-notes-dapp) illustrates how end-to-end encryption can be done. + +## Create backups + +### Security concern + +A canister could be rendered unusable and impossible to upgrade. For example, due to one of the following reasons: + +- It has a faulty upgrade process due to some bug from the dapp developer. + +- The state becomes inconsistent or corrupt because of a bug in the code that persists data. + +### Recommendation + +- Make sure methods used in upgrading are tested, or the canister becomes immutable. + +- It may be useful to have a disaster recovery strategy that makes it possible to reinstall the canister. + +- See the "Backup and recovery" section in [how to audit an Internet Computer canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister). + + diff --git a/docs/guides/security/decentralization.md b/docs/guides/security/decentralization.md new file mode 100644 index 0000000..a07e9f7 --- /dev/null +++ b/docs/guides/security/decentralization.md @@ -0,0 +1,76 @@ +--- +title: "Security Best Practices: Decentralization" +description: "Security best practices for decentralizing dapp control using SNS, governance, and reducing centralized trust." +sidebar: + order: 4 +--- + +## Use a decentralized governance system like SNS to put dapps under decentralized control + +### Security concerns + +If single entities or small groups control canisters, they can apply changes or updates whenever they like. If a canister, e.g., holds assets such as ICP, ckBTC, or ckETH on a user's behalf, this effectively means that the controller could decide at any time to steal these funds through methods such as updating the canister and transferring the assets to their account. + +Furthermore, the controller of canisters serving web content (such as e.g., the asset canister) could maliciously modify the web application to e.g., steal user funds or perform security-sensitive actions on the user's behalf. For example, if [Internet Identity](../authentication/internet-identity.mdx) is used, the user principal's private key for the given origin is stored in the browser storage, and a malicious app can therefore fully control the private key, the user's session, and any assets controlled by that key. + +Dapps are commonly reachable over their own custom domain name instead of ic0.app. These domains are registered with a DNS registrar by one of the developers. The developer can choose to have this domain point at a completely different web application, even one not hosted on ICP. Users will trust this domain and the app it serves. This could allow such a developer to steal funds, leak data, etc. + +A dapp might have privileged features that are only accessible to principals that are on an allow list. For example, minting new tokens, debugging functions, managing permissions, removing NFTs for digital rights violations, etc. This means that whoever controls that principal (such as the dapp developers) may have central control over these privileged features. + +For performance or privacy reasons, some components of a dapp may be hosted off-chain. These off-chain components often control principals used to interact with the onchain components and are usually controlled by a developer holding credentials to the off-chain cloud environment. On top of that, third party off-chain entities such as cloud providers can inspect and manipulate data in this environment if they choose. They could take ICP principal private keys out of this environment and call privileged operations on the canisters. Off-chain components can quickly lead to many additional centrally trusted parties. Depending on the value managed by a dapp, these parties could be tempted to act maliciously. + +### Recommendations + +In the following list, we first provide recommendations for centralized dapp control and then move to recommendations for increasingly decentralized settings. From a security perspective, more decentralization is favorable. The following list could also be used as a basis for assessing a dapp's level of decentralization. This is just a set of recommendations and may be incomplete. + +1. **The dapp uses central, off-chain components:** The application makes use of centralized components such as those running in the cloud. The owners of these cloud services have full control over the application and assets managed by it. Your application should likely be further decentralized by avoiding central components. But while you have them, [securely manage your keys in the cloud](https://cloudsecurityalliance.org/research/topics/cloud-key-management/). +2. **The dapp is controlled by the developer team:** Your project is not under decentralized control, for example, because it is in an early development stage or does not (yet) hold significant funds. In that case, it is recommended to manage access to your canisters securely and ideally not let individuals control the application. To achieve that, consider the following: + - Require approval by several individuals or parties to perform any canister controller operations. + - Require approval by several individuals or parties for any security-sensitive changes at the application level that are restricted to privileged principals, such as admin operations including permissions management, minting new tokens, removing NFTs for digital rights violations, etc. + - A helpful tool to achieve either of the above two points is the [orbit station canister](https://github.com/dfinity/orbit) which allows you to configure intricate policies for canister control. [Orbit](https://orbit.global/) also serves as an enterprise wallet where token funds are governed using policies. Ideally, individuals also manage their key material using hardware security modules, such as [YubiHSM](https://www.yubico.com/ch/store/yubihsm-2-series/) and physically protect these through methods such as using safes at different geographical locations. Some of HSMs support threshold signature schemes, which can help to further secure the setup. To increase transparency about the changes made to a dapp, consider using a tool like [LaunchTrail](https://github.com/spinner-cash/launchtrail). +3. **Full decentralization using a DAO**: The dapp is controlled by a decentralized governance system such as ICP's [Service Nervous System (SNS)](https://learn.internetcomputer.org/hc/en-us/articles/34084394684564-SNS-Service-Nervous-System), so that any security-sensitive changes to the canisters are only executed if the SNS community approves them collectively through a proposal voting mechanism. If an SNS is used: + - Make sure voting power is distributed over many independent entities such that there is not one single or a few entities that can decide by themselves how the [DAO evolves](https://learn.internetcomputer.org/hc/en-us/articles/34088279488660-Tokenomics#voting-power-and-decentralization). + - Ensure all components of the dapp are under SNS control, including the canisters serving the web frontends; see [SNS asset canisters](../governance/managing.md). + - Consider the [SNS preparation checklist](../governance/launching.md). Important points from a security perspective are tokenomics, disclosing dependencies to off-chain components, and performing security reviews. + - Rather than self-deploying the SNS code or building your own DAO, consider using the official SNS on the SNS subnet, as this guarantees that the SNS is running an NNS-blessed version and maintained as part of ICP. + - See also [verification and trust in a (launched) SNS](https://wiki.internetcomputer.org/wiki/Verification_and_trust_in_a_(launched)_SNS) and [SNS decentralization swap trust](https://wiki.internetcomputer.org/wiki/SNS_decentralization_swap_trust). + +An alternative to DAO control (3. above) would be to create an immutable canister smart contract by removing the canister controller completely. This can be achieved by setting the controller to a [black hole canister](https://github.com/ninegua/ic-blackhole). However, note that this implies that the canister can **never** be upgraded, which may have severe implications in case a bug is found. The complexity of ICP dapps and the fact that complex frontends are hosted onchain means that black holed canisters are rarely the right solution. The option to use a decentralized governance system and thus being able to upgrade smart contracts is a big advantage of the ICP ecosystem compared to other blockchains. + +:::note +Contrary to some other blockchains, immutable smart contracts need cycles to run, and they can receive cycles. +::: + +It is also possible to implement a DAO on ICP from scratch. If you decide to do this (e.g., along the lines of the [basic DAO example](https://github.com/dfinity/examples/tree/master/rust/basic_dao)), be aware that this is security critical and must be security reviewed carefully. Furthermore, users will need to verify that the DAO is controlled by itself. + +## Verify the control and level of decentralization of smart contracts you depend on + +### Security concern + +If a dapp depends on a third-party canister smart contract (e.g., by making inter-canister calls to it), it is important to verify that the callee satisfies an appropriate level of decentralization. For example: +- If funds or cycles are transferred to a third-party canister, one might require the canister to be controlled by a decentralized governance system, as otherwise these funds are centrally controlled. +- If inter-canister calls are made to a centrally controlled and potentially malicious canister, that canister could execute a denial of service attack on the caller or even trigger functional bugs; see [be aware of the risks involved in calling untrustworthy canisters](./inter-canister-calls.md#be-aware-of-the-risks-involved-in-calling-untrustworthy-canisters). + +### Recommendation + +If you interact with a canister that you require to be decentralized, make sure it is controlled by the NNS, a service nervous system (SNS) or a decentralized governance system, and review under what conditions and by whom the smart contract can be changed. + +## Don't load JavaScript or other assets from untrusted domains + +### Security concern + +Loading untrusted JavaScript from domains other than `.icp0.io` means you completely trust that domain. Also, assets loaded from these domains (incl. `.raw.icp0.io`) will not use asset certification. + +If they deliver malicious JavaScript, they can take over the web app or account. This could, for example, happen by reading the private key managed by the ICP JavaScript agent from the browser's local storage. + +Note that also loading other assets such as [CSS](https://xsleaks.dev/docs/attacks/css-injection/) from untrusted domains is a security risk. + +### Recommendation + +- Loading JavaScript and other assets from other origins should be avoided. Especially for security-critical applications, you can't assume other domains to be trustworthy. + +- Make sure all the content delivered to the browser is served and certified by the canister using asset certification. This holds in particular for any JavaScript, but also for fonts, CSS, etc. + +- Use a [content security policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to prevent scripts and other content from other origins from being loaded at all. See also [define security headers, including a content security policy (CSP)](./resources.md#web-security). + + diff --git a/docs/guides/security/dos-prevention.md b/docs/guides/security/dos-prevention.md index 946284d..b0d5208 100644 --- a/docs/guides/security/dos-prevention.md +++ b/docs/guides/security/dos-prevention.md @@ -1,344 +1,62 @@ --- -title: "DoS Prevention" -description: "Protect canisters from denial-of-service attacks with rate limiting, cycle drain protection, and resource management" +title: "Security Best Practices: Denial of Service" +description: "Security best practices for protecting canisters against DoS and DDoS attacks, noisy neighbors, and expensive calls." sidebar: - order: 4 + order: 8 --- -On ICP, [canisters pay for every message they process](../../concepts/cycles.md): including messages from attackers. Anyone on the internet can send update calls to your canister, and each call burns cycles even if your code ultimately rejects it. Left unmitigated, this lets an attacker drain your cycle balance by flooding your canister with messages. +## Protect against DoS and DDoS attacks -This guide covers the patterns that protect against denial-of-service (DoS) attacks: early message filtering, rate limiting, resource allocation, and cycle monitoring. +### Security concern -## Checklist +A denial of service (DoS) attack aims to make a system unavailable by overwhelming it with requests or data. A Distributed Denial of Service (DDoS) attack is a more sophisticated version, where the attack originates from multiple sources, making it harder to block. An attacker will typically search for operations that are free to be executed by anyone but which are expensive for the application in terms of certain resources such as storage, memory usage, network bandwidth, computing resources, etc. In the case of canisters, such attacks can aim to deplete cycles, making the canister unable to process legitimate requests. The reverse gas model means that a dapp needs to implement strategies to deal with this. -- [ ] Use `canister_inspect_message` to drop obviously invalid messages before Candid decoding -- [ ] Reject the anonymous principal in every endpoint that requires authentication -- [ ] Enforce per-caller rate limits or concurrency locks for expensive operations -- [ ] Set a conservative freezing threshold (90–180 days) -- [ ] Set explicit `wasm_memory_limit` to guard against memory exhaustion -- [ ] Set `wasm_memory_threshold` to receive an `on_low_wasm_memory` hook notification before the limit is hit -- [ ] Monitor cycle balances and alert on unusual consumption spikes -- [ ] Reserve compute or memory allocation for high-traffic canisters +### Recommendation -## Cycle drain attacks +To protect your canisters from DoS and DDoS attacks, consider the following strategies: +* **Bot prevention techniques**: Use methods like captchas or proof of work to ensure only legitimate users can access your canister. CAPTCHAs help verify that the user is human, while proof of work requires the user to spend computational resources to proceed, deterring automated attacks. [Internet Identity](https://github.com/dfinity/internet-identity) has a [captcha implementation](https://github.com/dfinity/internet-identity/blob/2bf92dc16371428a3dcc1115580a691842ec76df/src/internet_identity/src/main.rs#L517) that can serve as an example for implementing this in other projects. +* **Monitor cycles usage**: Regularly track your canisters cycles consumption and set alerts for any sudden spikes that may indicate an attack. +* **Ingress message charging**: While charging for ingress messages (external requests to the canister) is not natively supported, custom solutions could be implemented to make sure that any expensive actions have costs associated with them. +* **Filter ingress messages using inspect message**: Certain non-critical checks can be placed in the inspect message function to filter out ingress update messages before they are executed by all nodes of a subnet. Since this code only runs on a single node, the execution does not consume cycles, but it also shouldn't be relied upon for security-critical checks such as access control. However, they can efficiently reject certain ingress messages early. Read the corresponding [documentation](../../references/ic-interface-spec/canister-interface.md#system-api-inspect-message) and [security best practice](./access-management.mdx) carefully for the caveats. -Every ingress message (external call to your canister) costs cycles. The cost includes: +## Protect against noisy neighbors -- A base execution fee of 5M cycles per update message (13-node subnet), plus an ingress reception fee of ~1.2M cycles and 2,000 cycles per byte received -- Per-instruction fees for all code executed before a trap or rejection -- Candid decoding, which runs before your method body +### Security concern -This means an attacker can drain your cycles simply by sending many messages. The canister pays for Candid decoding and early checks even when it rejects the call. See [Cycles costs](../../references/cycles-costs.md#cost-table) for exact figures. +In a shared resource environment like the Internet Computer, multiple canisters can run on the same subnet. If one canister consumes too many resources (CPU, memory, etc.), it can negatively impact the performance of others on the same subnet. This is known as the "noisy neighbor" problem. -### Use inspect_message as a first-pass filter +### Recommendation -`canister_inspect_message` runs on a **single replica** before a message enters consensus. Code in this hook does not burn cycles, so it is an efficient place to drop messages that are obviously invalid: for example, calls from the anonymous principal to authenticated endpoints. +To mitigate the "noisy neighbor" issue, manage your canister's resource allocation effectively: +* **Memory allocation**: Memory can be reserved per canister by setting `memory_allocation`, ensuring that your canister can always allocate memory up to the requested `memory_allocation` and preventing other canisters from using up the subnet's available memory. Note that memory availability is not guaranteed beyond the memory allocation and thus monitoring actual memory usage against this value is important to avoid availability issues. +* **Compute reservation**: Similar to memory, computing power can also be reserved by setting `compute_allocation` to a value between 0 and 100, which denotes the percentage of one CPU core to be reserved for this canister. A value of 50 means that every 2 rounds, the canister will be scheduled to execute a message. This guarantees the minimal progress your canister can make, which protects against noisy neighbors. Both allocations are reserving resources for your canister on the subnet, which prevents the other canisters from using them. Hence, they come at a cost. Memory allocation is charged as if all that memory would be allocated. Compute allocation is currently charged at 10M cycles per percentage point. +Learn more about managing memory and compute resources in the [cycles costs reference](../../references/cycles-costs.md). +* **Subnet and canister distribution**: Implement a smart canister deployment strategy by monitoring the load on subnets. You can choose to deploy new canisters on less busy subnets or adopt a multi-canister architecture that balances the load across subnets. Be mindful to minimize inter-subnet communication for canisters that frequently interact with each other. Additionally, avoid deploying to known high-traffic subnets where possible, though keep in mind that resource usage can change unexpectedly with new dapps. -**Critical limitation:** `canister_inspect_message` is not a security boundary. It runs on one node and can be bypassed by a malicious boundary node. It is also never called for inter-canister calls, query calls, or management canister calls. Always duplicate real access control inside each update method. See [Access management](access-management.md) for the full access control pattern. +:::note +When the subnet grows above 750GiB, then the new reservation mechanism activates. Every time a canister allocates new storage bytes, the system sets aside some amount of cycles from the main balance of the canister. These reserved cycles will be used to cover future payments for the newly allocated bytes. The reserved cycles are not transferable, and the amount of reserved cycles depends on how full the subnet is. For example, it may cover days, months, or even years of payments for the newly allocated bytes. It is important to note that the reservation mechanism applies only to the newly allocated bytes and does not apply to the storage already in use by the canister. See more at [resource reservations](https://forum.dfinity.org/t/increasing-subnet-storage-capacity-and-introducing-resource-reservation-mechanism/23447). +::: -`inspect_message` has a budget of **200 million instructions**: do not perform expensive work here. Use it only to short-circuit calls that are structurally invalid (wrong caller type, missing required data). +## Handle expensive calls -**Motoko: inspect_message:** +### Security concern -```motoko -import Principal "mo:core/Principal"; +Some calls (update or query) might be expensive in terms of the memory or cycles they consume. For example, any function using chain-key signing or HTTPS outcalls is relatively expensive. See the [additional documentation](../../references/cycles-costs.md) on cycles cost details and for other examples. -// Inside the persistent actor { ... } +An attacker will target expensive calls to drain the cycles balance or available memory quickly. -system func inspect( - { - caller : Principal; - msg : { - #adminAction : () -> (); - #publicAction : () -> (); - #expensiveOperation : () -> (); - } - } -) : Bool { - switch (msg) { - // Admin and expensive methods: reject anonymous callers before Candid decoding - case (#adminAction _) { not Principal.isAnonymous(caller) }; - case (#expensiveOperation _) { not Principal.isAnonymous(caller) }; - // Public methods: accept all - case (_) { true }; - }; -}; -``` +### Recommendation -**Rust: inspect_message:** +* **Use captchas**: Expensive operations should require a captcha to be solved. Try to use a library to implement a captcha instead of a cloud service, as such a service would require HTTPS outcalls and isn't decentralized. +* **Use PoW (proof-of-work)**: Require a proof-of-work challenge to be solved by the client for any expensive operation. The parameters need to be carefully chosen to require sufficient computation per call to the expensive operation without creating too much impact for legitimate clients. Don't forget to consider clients on slow and older mobile devices while protecting against attackers on modern multi-GPU systems. Certain algorithms can limit the performance increase of GPUs to improve this uneven battlefield. +* **Charge for expensive calls**: You can require that certain expensive calls from other canisters include cycles to compensate for the resources consumed. In addition, one can charge for ingress messages. However, that is not currently supported by the protocol itself, and a custom solution, such as pre-paying a certain amount, would need to be designed. +* **Differentiate between update and query calls**: Expensive computations should generally be avoided for update calls unless absolutely necessary. While query calls are not authenticated, they are faster and less resource-intensive. To check whether a method was called as a query or update call, you can use `ic0.in_replicated_execution()`. -```rust -use ic_cdk::api::{accept_message, msg_caller, msg_method_name}; -use candid::Principal; +### Further recommendations -/// Pre-filter to reduce cycle waste from spam. -/// Runs on ONE node. Can be bypassed. NOT a security check. -/// Always duplicate real access control inside each method. -#[ic_cdk::inspect_message] -fn inspect_message() { - let method = msg_method_name(); - match method.as_str() { - // Admin and expensive methods: reject anonymous callers - "admin_action" | "expensive_operation" => { - if msg_caller() != Principal::anonymous() { - accept_message(); - } - // Silently reject anonymous: saves cycles on Candid decoding - } - // Public methods: accept all - _ => accept_message(), - } -} -``` +- Automatically monitor cycles consumption and set appropriate alerts for cycles consumption rate and balance. Sudden spikes in cycles consumption could indicate an attack. +- Implement early authentication and rate limiting for your canisters. +- Be aware of attacks targeting high cycles-consuming calls. +- See the "Cycle balance drain attacks section" in [How to audit an ICP canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister). -### Rate limiting and per-caller locking - -For expensive operations (chain-key signing, HTTPS outcalls, large state writes), enforce per-caller concurrency limits. Allowing the same caller to queue up many concurrent requests multiplies the cost of any single caller's flood. - -The CallerGuard pattern prevents concurrent calls from the same principal. While the guard is held, any second call from the same caller is rejected immediately: before any expensive work runs. - -**Motoko: per-caller concurrency lock:** - -```motoko -import Map "mo:core/Map"; -import Principal "mo:core/Principal"; -import Result "mo:core/Result"; - -// Inside the persistent actor { ... } - -let pendingRequests = Map.empty(); - -func acquireGuard(principal : Principal) : Result.Result<(), Text> { - if (Map.get(pendingRequests, Principal.compare, principal) != null) { - return #err("already processing a request for this caller"); - }; - Map.add(pendingRequests, Principal.compare, principal, true); - #ok; -}; - -func releaseGuard(principal : Principal) { - ignore Map.delete(pendingRequests, Principal.compare, principal); -}; - -public shared ({ caller }) func expensiveOperation() : async Result.Result { - // 1. Reject anonymous - if (Principal.isAnonymous(caller)) { - return #err("anonymous principal not allowed"); - }; - - // 2. Acquire per-caller lock: rejects concurrent calls from same principal - switch (acquireGuard(caller)) { - case (#err(msg)) { return #err(msg) }; - case (#ok) {}; - }; - - // 3. Do expensive work (async calls, etc.) - try { - let result = await someExpensiveCall(); - #ok(result) - } catch _ { - #err("operation failed") - } finally { - // Released in cleanup context: runs even if the callback traps - releaseGuard(caller); - }; -}; -``` - -**Rust: per-caller concurrency lock (CallerGuard):** - -```rust -use std::cell::RefCell; -use std::collections::BTreeSet; -use candid::Principal; -use ic_cdk::update; -use ic_cdk::api::msg_caller; - -thread_local! { - static PENDING: RefCell> = RefCell::new(BTreeSet::new()); -} - -struct CallerGuard { - principal: Principal, -} - -impl CallerGuard { - fn new(principal: Principal) -> Result { - PENDING.with(|p| { - if !p.borrow_mut().insert(principal) { - return Err("already processing a request for this caller".to_string()); - } - Ok(Self { principal }) - }) - } -} - -impl Drop for CallerGuard { - fn drop(&mut self) { - PENDING.with(|p| { - p.borrow_mut().remove(&self.principal); - }); - } -} - -#[update] -async fn expensive_operation() -> Result { - let caller = msg_caller(); - if caller == Principal::anonymous() { - return Err("anonymous principal not allowed".to_string()); - } - - // Acquire per-caller lock: Drop releases it even if the callback traps - let _guard = CallerGuard::new(caller)?; - - // Do expensive work: use Call::bounded_wait for inter-canister calls - // to avoid unbounded waits that would block canister upgrades - let result = do_expensive_work().await?; - Ok(result) - // _guard dropped here -> lock released -} -``` - -The guard releases automatically when it goes out of scope: including when an inter-canister call callback traps. Never use `let _ = CallerGuard::new(caller)?` (this drops the guard immediately, making locking ineffective). Always bind to a named variable (`let _guard`). - -### Proof-of-work and captchas for public endpoints - -For endpoints that must accept anonymous or unauthenticated callers: for example, a public registration flow. The per-caller lock pattern cannot apply. Instead, require the caller to prove they spent computational resources: - -- **Captcha:** Require solving a captcha before calling an expensive endpoint. Use a library-based captcha (not a cloud service) to keep the solution onchain and avoid HTTPS outcalls. -- **Proof of work:** Require the client to include a nonce that satisfies a hash challenge. The canister verifies the nonce in `inspect_message` before accepting the message. This imposes CPU cost on the caller proportional to the difficulty parameter. - -[Internet Identity](https://github.com/dfinity/internet-identity)'s [captcha implementation](https://github.com/dfinity/internet-identity/blob/2bf92dc16371428a3dcc1115580a691842ec76df/src/internet_identity/src/main.rs#L517) provides a working example. - -## Resource limit awareness - -The IC enforces hard limits on message execution. If your canister frequently approaches these limits, a flood of requests can make it unable to serve legitimate users: - -| Limit | Value | -|-------|-------| -| Instructions per update call | 40 billion | -| Instructions per query call | 5 billion | -| Instructions per `inspect_message` | 200 million | -| Max ingress message payload | 2 MiB | -| Wasm heap memory | 4 GiB (wasm32) | -| Wasm stable memory | 500 GiB | - -Source: [Cycles costs reference](../../references/cycles-costs.md#resource-limits). - -### Prevent memory exhaustion - -If users can store data without limits, an attacker can fill the 4 GiB Wasm heap or stable memory, causing allocation failures that corrupt canister state. Mitigations: - -- **Enforce per-user storage quotas**: track bytes stored per principal and reject requests that exceed the limit. -- **Validate input sizes**: check the size of user-provided blobs, text, or arrays before storing them. -- **Set a `wasm_memory_limit`**: configures a soft ceiling below the 4 GiB hard limit. When exceeded, new update calls trap instead of corrupting state. See [Canister settings](../canister-management/settings.md). - -```yaml -# icp.yaml: memory protection (settings nested under canister name) -canisters: - - name: backend - settings: - wasm_memory_limit: 3gib - wasm_memory_threshold: 512mib # triggers on_low_wasm_memory hook -``` - -### Paginate large queries - -Data queries that return unbounded result sets can exhaust the instruction limit for a single call. An attacker can exploit this by requesting a query that processes all stored data: - -- **Always paginate**: accept an optional cursor or offset and return at most a fixed number of results per call. -- **Avoid unbounded iteration**: do not iterate entire data structures in a single call unless the data set is provably bounded. - -## Freezing threshold as a safety net - -The `freezing_threshold` setting defines the minimum number of seconds the canister should be able to survive on its current cycle balance. When the balance drops below this reserve, the canister **freezes**: update calls are rejected. A frozen canister does not execute code, but it continues to pay for storage and compute allocation. - -The default threshold is 30 days. For production canisters holding valuable state, increase it to 90–180 days: - -```bash -# Set freezing threshold to 90 days -icp canister settings update backend --freezing-threshold 7776000 -e ic -``` - -Or via `icp.yaml`: - -```yaml -# icp.yaml: settings nested under canister name -canisters: - - name: backend - settings: - freezing_threshold: 90d -``` - -A conservative freezing threshold gives you time to detect and respond to a cycle drain attack before the canister is uninstalled. If cycles reach zero and the threshold expires, the canister is uninstalled: code and data are deleted permanently. See [Canister settings](../canister-management/settings.md) for full configuration details. - -## Noisy neighbor protection - -Multiple canisters share the same subnet. If a neighboring canister consumes excessive compute or memory, it can slow your canister's response times. You can reserve resources to protect against this: - -### Compute allocation - -Setting `compute_allocation` guarantees your canister a percentage of an execution core and ensures scheduled execution even when the subnet is busy: - -```yaml -# icp.yaml: settings nested under canister name -canisters: - - name: backend - settings: - compute_allocation: 10 # Guaranteed 10% of one execution core -``` - -A value of `10` means the canister is scheduled at least every 10 consensus rounds. Compute allocation incurs an ongoing rental fee (10M cycles per percentage point per second on a 13-node subnet). Only set it if you need guaranteed throughput under load. See [Cycles costs](../../references/cycles-costs.md#compute-allocation). - -### Memory allocation - -Setting `memory_allocation` reserves a fixed pool of memory for your canister, preventing other canisters from consuming the subnet's available memory: - -```yaml -# icp.yaml: settings nested under canister name -canisters: - - name: backend - settings: - memory_allocation: 4gib -``` - -Memory allocation is charged as if the full allocated amount were in use. Monitor actual memory usage to avoid paying for unused allocation. - -## Monitoring cycle consumption - -Cycle drain attacks appear as unusual spikes in consumption. Set up monitoring before deploying to mainnet: - -```bash -# Check current cycle balance -icp canister status backend -e ic - -# Check balance of a specific canister by ID -icp canister status -e ic -``` - -Key metrics to monitor: - -- **Balance**: alert when balance drops below a safe threshold (e.g., 2x the freezing threshold reserve) -- **Burn rate**: track cycles per day; a sudden spike indicates unexpected activity -- **Memory usage**: track growth over time; sudden jumps suggest user-driven data accumulation - -For production canister monitoring, consider automating balance checks with a heartbeat or timer canister that sends an alert notification when the balance approaches the freezing threshold. - -## Handling expensive operations safely - -Chain-key signing (threshold ECDSA/Schnorr), HTTPS outcalls, and Bitcoin API calls are significantly more expensive than standard update calls. These make attractive targets for attackers: - -- **Require authentication**: never allow anonymous callers to trigger expensive operations. -- **Apply per-caller locking**: use the CallerGuard pattern to prevent the same caller from queuing multiple expensive calls. -- **Charge callers**: for canister-to-canister calls, require the calling canister to attach cycles to cover the cost. The called canister accepts the cycles using `ic0.msg_cycles_accept` (Rust: `ic_cdk::api::msg_cycles_accept(max_amount: u128)`). -- **Differentiate update vs. query**: move expensive computations to update calls and use query calls for cheap reads. Check whether a method is running as a query or update with `ic0.in_replicated_execution()` (Rust: `ic_cdk::api::in_replicated_execution()`). - -## Next steps - -- [Access management](access-management.md): caller checks, anonymous principal rejection, and role-based guards -- [Inter-canister call safety](inter-canister-calls.md): TOCTOU vulnerabilities and the CallerGuard pattern -- [Canister settings](../canister-management/settings.md): freezing threshold, memory allocation, and compute allocation -- [Cycles costs](../../references/cycles-costs.md#cost-table): exact cost tables and resource limits -- [Security model](../../concepts/security.md): IC trust boundaries and threat model overview - - + diff --git a/docs/guides/security/encryption.mdx b/docs/guides/security/encryption.mdx index 3480abb..655618c 100644 --- a/docs/guides/security/encryption.mdx +++ b/docs/guides/security/encryption.mdx @@ -2,7 +2,7 @@ title: "Encryption with VetKeys" description: "Encrypt and decrypt data on ICP using VetKeys for privacy, key management, and identity-based encryption" sidebar: - order: 6 + order: 14 --- import { Tabs, TabItem } from '@astrojs/starlight/components'; diff --git a/docs/guides/security/formal-verification.md b/docs/guides/security/formal-verification.md new file mode 100644 index 0000000..c04f250 --- /dev/null +++ b/docs/guides/security/formal-verification.md @@ -0,0 +1,34 @@ +--- +title: "Security Best Practices: Formal Verification" +description: "Applying formal verification and TLA+ model checking to find and prove the absence of security bugs in ICP canisters." +sidebar: + order: 13 +--- + +Formal verification is the highest form of quality assurance for software. Given a specification of what the system should do, formal verification tools check whether this specification is satisfied by a model of the system. The unique advantage of formal verification is that it can not only find bugs but also formally **prove** their absence — including the absence of security bugs. This goes beyond what testing or manual audits can achieve. + +The proof is always relative to the model and the specification. Any simplifications and assumptions in the model, or omissions in the specification, may hide bugs and attacks. On the other hand, verification can require a lot of effort, and model simplifications can make it significantly easier. + +For a concrete example, when verifying the ckBTC minter canister, the DFINITY team used models that exclude the possibility of calling the `update_balance` canister method more than 2 times concurrently. This potentially misses attacks that require 3 or more concurrent calls to `update_balance` to trigger a bug. But we considered such bugs highly unlikely, and, in return, we were able to run a fully automatic verification process, which was much cheaper than other verification methods. + +There are many existing formal verification tools. While none of them take into account the specifics of ICP yet, many of them are general enough that they can be applied to canisters. In particular, the DFINITY team has made good use of the TLA+ toolkit to combat reentrancy bugs, a type of concurrency bug where one method of a particular canister is called while another one is still executing. + +These bugs are particularly difficult to find, as they can involve unexpected interactions of code scattered throughout a canister or even in different canisters. The number of such code interactions may be huge and thus difficult for humans to detect. These interactions are usually difficult to test automatically and systematically, which TLA+ can do. + +## TLA+ + +The Temporal Logic of Actions (TLA+) is a language for specifying and verifying complex systems. TLA+ comes with a set of tools for lightweight formal verification in the form of so-called model checking. Through model checking, it exhaustively (within bounds, such as the aforementioned 2 concurrent calls bound) explores all possible concurrent interactions of a model of the code — exactly the domain that is difficult to test — and finds bugs. + +Importantly, after building the model of the code, model checking runs with virtually no further human input, making it highly cost-effective. To illustrate with some made-up numbers: if the industry standard practices (such as testing and security reviews) eliminate 80% of the bugs, and "heavyweight" formal verification eliminates 99.99%, with TLA+ you can eliminate 90% with a fraction of the effort of the heavyweight verification. + +We have used TLA+ to create the following models that can be interesting for dapp developers: + +- NNS and SNS governance (focusing on interactions with the ledger canister). +- ICP ledger (focusing on block archival). +- ckBTC minter. +- SNS swap canister. +- People parties dapp. + +To find out more on why and how you can apply TLA+ to your canisters and dapps, including an in-depth guide to modeling canisters, refer to our series of blog posts ([1](https://medium.com/dfinity/eliminating-smart-contract-bugs-with-tla-e986aeb6da24), [2](https://medium.com/dfinity/weeding-out-the-bugs-with-tla-models-3606045bf24e), [3](https://mynosefroze.com/blog/2023-08-09-tla_for_canisters)). You can also look at the [DFINITY-produced TLA+ models](https://github.com/dfinity/formal-models) for examples and techniques. + + diff --git a/docs/guides/security/https-outcalls.md b/docs/guides/security/https-outcalls.md new file mode 100644 index 0000000..cfd3280 --- /dev/null +++ b/docs/guides/security/https-outcalls.md @@ -0,0 +1,97 @@ +--- +title: "Security Best Practices: HTTPS Outcalls" +description: "Security best practices for canister HTTPS outcalls: API keys, rate limits, idempotency, response consistency, and input validation." +sidebar: + order: 7 +--- + +## Do not store sensitive data such as API keys in canisters + +### Security concern + +Sensitive data is a broad term that varies depending on your application logic and behavior. Here is a non-exhaustive list of secrets that are typically considered sensitive, such as API keys or tokens: +* Secrets that allow interaction with non-public endpoints. +* Secrets that allow querying or modifying endpoints with confidential data. +* API tokens that are fee-based. + +By default, the data stored inside your canister is unencrypted. Therefore, if your canister is installed on a malicious replica, it can easily retrieve and steal your keys, tokens, and secrets in plain text. + +### Recommendation + +Make sure you don't store sensitive data inside your canister. + +See also: [data confidentiality on ICP](./misc.md#data-confidentiality-on-icp). + +## Ensure your canisters have a sufficiently large quota with the HTTP server + +### Security concern + +When an HTTPS outcall is performed, it is amplified by the number of replicas in the subnet. The target web server will receive not only one request but as many requests as the number of nodes in the subnet. + +Most web servers implement some sort of rate limiting; this is a mechanism used to restrict the number of requests a client can make to a web server within a specific time period, preventing abuse or excessive usage of their API(s). + +### Recommendation + +You should consider such rate limits when designing and implementing your canisters. Rate limits are enforced using different time granularities, e.g., seconds or minutes. For second-granularity enforcement, make sure that the simultaneous requests by all subnet replicas do not violate the quota. Violations may lead to temporary or permanent bans. + +See the [HTTPS outcalls guide](../backends/https-outcalls.mdx) for more details. + +## Only make HTTPS outcall requests to idempotent endpoints + +### Security concern + +As mentioned before, if an HTTPS outcall is performed, it is amplified by the number of replicas in the subnet. That means the queried endpoint will receive the same request several times. This is especially risky in requests that change the endpoint state, given that one HTTPS outcall could lead to unintentionally changing the endpoint state several times. + +### Recommendation + +Make sure the endpoints, called by an HTTPS outcall, are idempotent, such that the queried endpoint has the same behavior with the same request payload, no matter the number of times it is called. + +Some servers support the use of idempotency keys. These keys are random unique strings submitted in the HTTP request as headers. If used with the HTTPS outcalls feature, all requests sent by each honest replica will contain the same idempotency key. This allows the server to recognize duplicated requests (i.e., requests with the same idempotency key), handle just one, and modify the server state only once. Note that this is a feature that must be supported by the server. + +See the [HTTPS outcalls guide](../backends/https-outcalls.mdx) for more details. + +## Ensure HTTPS responses are identical + +### Security concern + +When replicas of a subnet receive HTTP responses, these responses must be identical. Otherwise, consensus won't be achieved, and the HTTP response will be rejected but still charged. + +### Recommendation + +Make sure the HTTP responses sent to the consensus layer are identical. + +Ideally, the HTTP responses returned by the queried endpoint would always be the same. However, most of the time this is not possible to control, and the responses include random data (e.g., the response includes timestamps, cookie values, or some sort of identifiers). In those cases, make sure to use transformation functions to guarantee that the responses received by each replica are identical by removing any random data or extracting only the relevant data. + +This applies to the HTTP response body and headers. Make sure to consider both when applying the transformation functions. Response headers are often overlooked and lead to failure because of failed consensus. + +See the [HTTPS outcalls guide](../backends/https-outcalls.mdx) for more details. + +## Be aware of HTTP request and response sizes + +### Security concern + +The pricing of HTTPS outcalls is determined by the size of the HTTP request and the maximal response size, among other variables. Thus, if big requests are made, this could quickly drain the canister's cycles balance. This can be risky in scenarios where HTTPS outcalls are triggered by user actions (rather than a heartbeat or timer invocation). + +### Recommendation + +When using HTTPS outcalls, be mindful of the HTTP request and response sizes. Ensure that the size of the request issued and the size of the HTTP response coming from the server are reasonable. + +When making an HTTPS outcall, it is possible — and highly recommended — to define the `max_response_bytes` parameter, which allows you to set the maximum allowed response size. If this parameter is not defined, it defaults to the hard response size limit of the HTTPS outcalls feature, which is 2MiB. The cycle cost of the response is always charged based on the `max_response_bytes` or 2MB if not set. + +Finally, be aware that users may incur cycles costs for HTTPS outcalls in case these calls can be triggered by user actions. + +See the [cycles costs reference](../../references/cycles-costs.md) for pricing details. + +## Perform input validation in HTTPS outcalls + +### Security concern + +HTTPS outcalls that use user-submitted data are susceptible to various injection attacks. This may lead to several issues, such as the ones previously mentioned. + +### Recommendation + +Perform input validation when using user-submitted data in the HTTPS outcalls. + +See the [OWASP Input Validation Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html) for more information. + + diff --git a/docs/guides/security/inter-canister-calls.md b/docs/guides/security/inter-canister-calls.md index 2f5414b..fa5ce3b 100644 --- a/docs/guides/security/inter-canister-calls.md +++ b/docs/guides/security/inter-canister-calls.md @@ -1,483 +1,342 @@ --- -title: "Inter-Canister Call Safety" -description: "Handle reentrancy, callback traps, and async safety in inter-canister calls" +title: "Security Best Practices: Inter-Canister Calls" +description: "Security best practices for handling traps in callbacks, message ordering, rejected calls, and untrustworthy canisters." sidebar: - order: 5 + order: 2 --- -Inter-canister calls are the most common source of security bugs on the Internet Computer. The async messaging model creates a class of vulnerabilities that do not exist in synchronous systems: state can change between an `await` and its response, traps in callbacks can skip security-critical operations, and calls to untrusted canisters can permanently block upgrades. +To understand the issues around async inter-canister calls, one needs to understand the [properties of message execution on ICP](../../references/message-execution-properties.md). Understanding these properties is a prerequisite for understanding the security issues discussed below. -This guide covers the specific patterns you must apply whenever your canister makes an inter-canister call. +This is also explained in the [community conversation on security best practices](https://www.youtube.com/watch?v=PneRzDmf_Xw&list=PLuhDt1vhGcrez-f3I0_hvbwGZHZzkZ7Ng&index=2&t=4s). -## Why inter-canister calls are dangerous +## Securely handle traps in callbacks -When your canister `await`s a call to another canister, the IC scheduler can interleave other incoming messages while your canister waits for the response. This means: +### Security concern -- State your canister read before the `await` may be different when the callback runs. -- A second call from the same user can arrive and begin executing before the first call's callback completes. -- If the callback traps, any mutations made in the callback are rolled back: but mutations made before the `await` are already committed. +Traps and panics roll back the canister state, as described in [Property 5](../../references/message-execution-properties.md#message-execution-properties). So any state change followed by a trap or panic can be risky. This is an important concern when inter-canister calls are made. If a trap occurs after an await to an inter-canister call, then the state is reverted to the snapshot before the inter-canister call's callback invocation, and not to the state before the entire call. -The code before `await` and the code after `await` execute as **separate atomic message executions**. Understanding this is the foundation of inter-canister call security. +More precisely, suppose some state changes are applied and then an inter-canister call is issued. Also, assume that these state changes leave the canister in an inconsistent state, and that state is only made consistent again in the callback. Now if there is a trap in the callback, this leaves the canister in an inconsistent state. -## Reentrancy and the CallerGuard pattern +Here are two example security issues that can arise because of this: -A reentrancy bug occurs when a second message from the same caller interleaves with a first message that is still in progress: that is, awaiting a response. In DeFi contexts this enables double-spending: the attacker calls `withdraw()`, waits for it to begin the inter-canister transfer, then calls `withdraw()` again before the first call updates the balance. +- Assume an inter-canister call is issued to transfer funds. In the callback, the canister accounts for having made that transfer by updating the balances in the canister storage. However, suppose the callback also updates some usage statistics data, which eventually leads to a trap when some data structure becomes full. As soon as that is the case, the canister ends up in an inconsistent state because the state changes in the callback are no longer applied, and thus the transfers are not correctly accounted for. + ![example_trap_after_await](/img/docs/security/example_trap_after_await.png) + This example is also discussed in this [community conversation](https://www.youtube.com/watch?v=PneRzDmf_Xw&list=PLuhDt1vhGcrez-f3I0_hvbwGZHZzkZ7Ng&index=2&t=4s). -The CallerGuard pattern prevents this by tracking which callers have an in-flight operation. When a second call arrives from the same caller, it is rejected before it can interleave. +- Suppose part of the canister state is locked before an inter-canister call and released in the callback. Then the lock may never be released if the callback traps. + Note that in canisters implemented in Rust with Rust CDK version `0.5.1`, any local variables still go out of scope if a callback traps. The CDK actually calls into the `ic0.call_on_cleanup` API to release these resources. This helps to prevent issues with locks not being released, as it is possible to use Rust's Drop implementation to release locked resources, as we discuss in [Be aware that there is no reliable message ordering](#be-aware-that-there-is-no-reliable-message-ordering). -### Motoko +### Recommendation -In Motoko, the guard must be released in a `finally` block. The `finally` block runs in cleanup context, where state changes are committed even if the `try` body trapped. If you release the guard inside the `try` body, a trap in the callback leaves the guard held forever. The caller is permanently locked out. +Recall that the responses to inter-canister calls are processed in the corresponding callback. If the callback traps, the cleanup (ic0.call_on_cleanup) is executed. When making an inter-canister call, ICP reserves sufficiently many cycles to execute the response callback or cleanup, up to the instruction limit. A fixed fraction of the reservation is set aside for the cleanup. Thus, a response or cleanup execution can never "run out of cycles," but they can run into the instruction limit and trap. -```motoko -import Map "mo:core/Map"; -import Principal "mo:core/Principal"; -import Error "mo:core/Error"; -import Result "mo:core/Result"; +The naïve recommendation to address the security concern described above would be to avoid traps. However, that can be very difficult to achieve due to the following reasons: -// Inside your persistent actor class { ... } -// Replace otherCanister with your canister reference. +- The implementation can be involved and could panic due to bugs, such as index out-of-bounds errors or panics (expect, unwrap) that should supposedly never happen. -let pendingRequests = Map.empty(); +- It is hard to make sure the callback or cleanup doesn't run into the instruction limit and thus traps, because the number of instructions required can in general not be predicted and may depend on the data being processed. -func acquireGuard(principal : Principal) : Result.Result<(), Text> { - if (Map.get(pendingRequests, Principal.compare, principal) != null) { - return #err("already processing a request for this caller"); - }; - Map.add(pendingRequests, Principal.compare, principal, true); - #ok -}; +Due to these reasons, while it is easy to recommend "avoiding traps", this is actually hard to achieve in practice. Therefore, code should be written so that it can deal even with unexpected traps due to bugs or hitting the instruction limits. There are two approaches: -func releaseGuard(principal : Principal) { - ignore Map.delete(pendingRequests, Principal.compare, principal); -}; +1. Perform simple cleanups +1. Utilize "journaling." -public shared ({ caller }) func withdraw(amount : Nat) : async Result.Result<(), Text> { - if (Principal.isAnonymous(caller)) { - return #err("anonymous caller not allowed"); - }; +In the first approach, the cleanup callback is used to recover from unexpected panics. This can work, but it has several drawbacks: +- The cleanup itself could panic, in which case one is in the initial problematic situation again. The risk may be acceptable for simple cleanups, but as discussed above, it is hard to write code that never panics, especially if it is somewhat complex. +- As of version 0.12.0, Motoko provides the `try`/`finally` feature to clean up temporary resource allocations in a structured way. Cleanup is used (as formerly) internally by Motoko to perform some state manipulations and now allows inserting programmer-written code also. If an execution path after `await` traps, all `finally` blocks in (dynamic) scope will be executed as a last-resort measure. Be aware that `finally` is not a magical construct to end all trap worries, as trapping in the `finally` blocks themselves can still leave your canister in an inconsistent state. Thus we recommend keeping your `finally` code clear and concise and paying special attention to reviewing it well. +- As discussed above, the Rust CDK has a feature that automatically releases local variables in cleanup, which [can be used to release locks](#recommendation-1). Since only one cleanup callback can be defined, any custom cleanup would currently have to implement that feature itself if needed, making this currently hard to use and understand. - // Acquire per-caller lock before any state reads or async calls. - switch (acquireGuard(caller)) { - case (#err(msg)) { return #err(msg) }; - case (#ok) {}; - }; +Instead, "journaling" is the recommended way of addressing the problem at hand. - try { - // Read state and make the inter-canister call here. - let result = await otherCanister.transfer(caller, amount); - #ok(result) - } catch (e) { - #err("transfer failed: " # Error.message(e)) - } finally { - // Runs in cleanup context regardless of success or trap. - // State mutations here are always committed. - releaseGuard(caller); - }; -}; -``` +### Journaling -### Rust +Journaling can be used for ensuring that tasks are completed correctly in an asynchronous context, where any instruction or async task can fail. Journaling is generally useful in any security-critical application canister on ICP. The journaling concept we describe here is inspired and adapted from journaling in file systems. -In Rust, the `Drop` trait releases the lock when the guard goes out of scope: including when the async function is cancelled or a trap occurs. Never write `let _ = CallerGuard::new(caller)?`: the leading underscore drops the guard immediately, making locking ineffective. Always bind to a named variable: `let _guard = CallerGuard::new(caller)?`. +Conceptually, a journal is a chronological list of records kept in a canister's storage. It keeps track of tasks before they begin and when they are completed. Before each failable task, the journal records the intent to execute the task, and after the task, the journal records the result. The journal supports idempotent task flows by providing the necessary information for the canister to resume flows that failed to complete, report progress for ongoing flows, and report results for completed flows. Retries can be initiated by calls, automatically on a [heartbeat](../backends/timers.mdx#heartbeats-legacy) or using [timers](../backends/timers.mdx). If the task flow was completed in a heartbeat or a timer, a user can take advantage of idempotency to check the result. -```rust -use std::cell::RefCell; -use std::collections::BTreeSet; -use candid::Principal; -use ic_cdk::update; -use ic_cdk::api::msg_caller; -use ic_cdk::call::Call; +Creating a record in the journal is called "journaling." For example, to make an unreliable async call to a ledger: -// Replace other_canister_id() with your canister's ID lookup. +1. Check the journal to ensure the transfer is not already in progress. If it is already in progress, go into recovery (see the [Recovery](#recovery) section below). Otherwise, journal the intent to call a ledger to transfer 1 token from A to B. The journaled intent should contain sufficient context to later identify what happened to the call. -thread_local! { - static PENDING: RefCell> = RefCell::new(BTreeSet::new()); -} + - An "in progress" transfer would show in the journal as an entry containing intent to do the transfer without an entry containing the result of the transfer call. -struct CallerGuard { - principal: Principal, -} +1. Call the ledger to transfer 1 token from A to B. -impl CallerGuard { - fn new(principal: Principal) -> Result { - PENDING.with(|p| { - if !p.borrow_mut().insert(principal) { - return Err("already processing a request for this caller".to_string()); - } - Ok(Self { principal }) - }) - } -} +1. Journal the result of the transfer. -impl Drop for CallerGuard { - fn drop(&mut self) { - PENDING.with(|p| { - p.borrow_mut().remove(&self.principal); - }); - } -} + - On failure, record the error. -#[update] -async fn withdraw(amount: u64) -> Result<(), String> { - let caller = msg_caller(); - if caller == Principal::anonymous() { - return Err("anonymous caller not allowed".to_string()); - } + - On success, record success. In order to commit the record, an inter-canister call can be made to an endpoint on the same canister that does nothing. Otherwise, a trap could erase the journaled result, complicating recovery. - // Acquire per-caller lock. Drop releases the lock when _guard goes out of scope. - let _guard = CallerGuard::new(caller)?; +1. Continue onto the next blocked task. - // Make the inter-canister call while the lock is held. - Call::bounded_wait(other_canister_id(), "transfer") - .with_args(&(caller, amount)) - .await - .map_err(|e| format!("transfer failed: {:?}", e))?; + - "Blocked tasks" are those that require step 3 to be completed before execution. - Ok(()) - // _guard dropped here: lock released -} -``` - -## State mutations before and after await + - A blocked task may depend on the success or failure recorded in step 3. -Because the code before `await` and the code after `await` are separate message executions, you must treat them independently when reasoning about consistency. + - Examples of blocked tasks: -**The critical rule:** If your canister mutates state before an `await`, that mutation is committed even if the callback traps. + - On failure, log the failure in a user-visible log, and if less than 5 failures have occurred, make a new transfer outcall with the same parameters. -### Example: deduct before transferring + - On success, update the internal accounting of assets to conform to the result of the transfer. -In a token transfer flow, deduct the balance before the inter-canister call rather than after. If the call fails, refund in the callback. This approach is safe: if the callback traps, the pre-deducted balance stays deducted (you can detect and remediate the stuck state. If you deduct after the call and the callback traps, the transfer happened but the balance was never deducted) funds are double-spent. + - Note that any independent task does not need to wait for any part of this flow. -**Motoko:** +The critical property of the journal is that at any point, if there is a failure, the journal is sufficient to determine what the next safe step should be. If, after step 1 (journal the intent), +there is a failure in step 2 or 3, and step 3 has not been completed, then the application should complete step 3 by finding out what happened to the call in step 2. If finding out what happened to the call is too difficult to automate, it can be done manually. The journal can indicate whether a manual intervention is necessary and the type of intervention that is necessary. +The fact that the intent has been journaled and the app knows not to reenter the flow until the result has been recorded means the journal acts as a lock on the critical section containing +the ledger outcall. The lock will not get stuck, assuming the application can always find out what happened to a call. Enough context about the call should be recorded in the intent to ensure +that this is the case. For the ICP ledger, an ID can be generated and recorded in the journaled intent, and the ledger can be called with the ID included in the memo so that the result of the +call can be queried later. -```motoko -import Map "mo:core/Map"; -import Principal "mo:core/Principal"; -import Error "mo:core/Error"; -import Result "mo:core/Result"; +### Journaling is robust to panics -// Inside your persistent actor class { ... } -let balances = Map.empty(); +Continuing the above example, consider a panic at any point. +1. If there is panic before the async outcall, then the journaled intent will be lost. No state change occurred internally, and no outcalls were made, so the app is in a safe state. The next step is to record a new intent. +1. If there is a panic after the async outcall and no self-call was used to commit the journal, the journaled result (step 3) will be lost. This means the app will need to determine the result and journal it before continuing to step 4. As long as it is possible to determine the result, the app can be brought back to a consistent state. -public shared ({ caller }) func transfer(to : Principal, amount : Nat) : async Result.Result<(), Text> { - // 1. Validate balance before the await. - let balance = switch (Map.get(balances, Principal.compare, caller)) { - case (?b) b; - case null 0; - }; - if (balance < amount) { - return #err("insufficient balance"); - }; +### Journaling and audit events - // 2. Deduct BEFORE the await: mutation is committed regardless of callback outcome. - Map.add(balances, Principal.compare, caller, balance - amount); - - // 3. Perform the inter-canister call. - try { - await ledgerCanister.transfer(to, amount); - #ok(()) - } catch (e) { - // 4. Refund on failure: the deduction persists even if this try/catch runs. - let currentBalance = switch (Map.get(balances, Principal.compare, caller)) { - case (?b) b; - case null 0; - }; - Map.add(balances, Principal.compare, caller, currentBalance + amount); - #err("transfer failed: " # Error.message(e)) - } -}; -``` +The journal can be used to augment the audit trail for recent events. However, it is probably too detailed for long-term storage. After a while, journal entries could be compressed and incorporated into long-term audit events. The process for creating audit events could itself be journaled. -**Rust:** +### Recovery -```rust -use std::cell::RefCell; -use std::collections::BTreeMap; -use candid::Principal; -use ic_cdk::update; -use ic_cdk::api::msg_caller; -use ic_cdk::call::Call; +The journal ensures the application knows that recovery from an error is needed and aids in making recovery decisions. In order to support the recovery process, the journal should support querying all unresolved tasks of a certain type and tasks of a certain type that resulted in an error. Given an intent, the journal should also be able to return the result if it exists and indicate if it does not exist. -// Replace ledger_canister_id() with your canister's ID lookup. +Note that recovery can often be complex to automate. In such cases, the journal can support a manual recovery process. +Extending the ledger example above, a recovery process could look as follows: -thread_local! { - static BALANCES: RefCell> = - RefCell::new(BTreeMap::new()); -} +1. There is a panic, and the status of the ledger call is unknown. However, the journal has recorded that a call to transfer with particular parameters and a memo has been made, including the deduplication timestamp of the transfer. +1. The app calls the ledger to determine whether a transaction with the journaled parameters has succeeded on the ledger. Due to the guarantee that any pair of messages that are both executed are always executed in the order issued, if the ledger indicates that the transaction has not occurred, then the transaction will never occur. +1. The app journals the result of the transfer call. +1. The app journals the intention to update internal state according to the result of the transfer call, then updates the internal state, and finally journals the result of the attempt to update the internal state. Journaling this step is still useful even if it does not contain outcalls, because outcalls may be introduced later, and the step could conflict with other processes that are not atomic. -#[update] -async fn transfer(to: Principal, amount: u64) -> Result<(), String> { - let caller = msg_caller(); - - // 1. Validate and deduct BEFORE the await. - BALANCES.with(|b| { - let mut balances = b.borrow_mut(); - let balance = balances.get(&caller).copied().unwrap_or(0); - if balance < amount { - return Err("insufficient balance".to_string()); - } - balances.insert(caller, balance - amount); - Ok(()) - })?; - - // 2. Make the inter-canister call. - let result = Call::bounded_wait(ledger_canister_id(), "transfer") - .with_args(&(to, amount)) - .await; - - if let Err(e) = result { - // 3. Refund on failure. - BALANCES.with(|b| { - let mut balances = b.borrow_mut(); - let current = balances.get(&caller).copied().unwrap_or(0); - balances.insert(caller, current + amount); - }); - return Err(format!("transfer failed: {:?}", e)); - } +Note that querying the ICP ledger or an ICRC ledger to determine whether a transaction has succeeded is not straightforward to automate, so it could be done manually. - Ok(()) -} -``` +### Example implementation of journaling -## Callback traps and security-critical cleanup +GoldDAO's GLDT-swap has an implementation of journaling. In their case, the journal entries are recorded in the "registry." Note that in GLDT-swap there is also a separate concept of "record," which is a permanent audit trail and is not used for journaling. Some error paths require manual recovery. See the following reference points: -A trap in an inter-canister call callback is particularly dangerous: the callback's state mutations are rolled back, but the pre-`await` mutations are not. A malicious callee can induce a trap in your callback to skip actions that should always run: like debiting an account. +- Registry (journal) structure: + - https://github.com/GoldDAO/gold-dao/blob/ledger-v1.0.0/canister/gldt_core/src/registry.rs#L18 + - https://github.com/GoldDAO/gold-dao/blob/ledger-v1.0.0/canister/gldt_core/src/lib.rs#L654 +- The registry is used in `notify_sale_nft_origyn` to record progress and enforce correctness of the flow. + - https://github.com/GoldDAO/gold-dao/blob/ledger-v1.0.0/canister/gldt_core/src/lib.rs#L910 + - Note that not all details of the flow appear in the registry. The amount of detail to include depends on one's goals for recovery. -To protect against this: +## Be aware that there is no reliable message ordering -1. **Keep callbacks minimal.** The less logic in a callback, the fewer opportunities for a trap. -2. **Use `finally` (Motoko) or `Drop` guards (Rust) for cleanup.** Cleanup that runs in `finally` or in `drop()` executes in cleanup context where mutations persist even after a trap. -3. **Avoid calling untrusted canisters** from callbacks that perform security-critical state changes. The callee can cause your callback to trap. +### Security concern -### Motoko: cleanup in finally +As described in the [properties of message executions on ICP](../../references/message-execution-properties.md), messages (but not entire calls) are processed atomically. In particular, as described in Property 4 in that document, messages from interleaving calls do not have a reliable execution ordering. Thus, the state of the canister (and other canisters) may change between the time an inter-canister call is started and the time when it returns, which may lead to issues if not handled correctly. These issues are generally called 'reentrancy bugs' (see the [Ethereum best practices on reentrancy](https://consensysdiligence.github.io/smart-contract-best-practices/attacks/reentrancy/)). Note, however, that the messaging guarantees, and thus the bugs, on ICP are different from Ethereum. -```motoko -import Error "mo:core/Error"; +Here are two concrete and somewhat similar types of bugs to illustrate potential reentrancy security issues: -// Inside your persistent actor class { ... } -// Replace otherCanister with your canister reference. +- **Time-of-check time-of-use issues:** These occur when some condition on global state is checked before an inter-canister call and then wrongly assuming the condition still holds when the call returns. For example, one might check if there is sufficient balance on some account, then issue an inter-canister call, and finally make a transfer as part of the callback message. When the second inter-canister call starts, it is possible that the condition that was checked initially no longer holds, because other ledger transfers may have happened before the callback of the first call is executed (see also Property 4 above). -var operationInProgress = false; +- **Double-spending issues**: Such issues occur when a transfer is issued twice, often because of unfavorable message scheduling. For example, suppose you check if a caller is eligible for a refund, and if so, transfer some refund amount to them. When the refund ledger call returns successfully, you set a flag in the canister storage indicating that the caller has been refunded. This is vulnerable to double-spending because the refund method can be called twice by the caller in parallel, in which case it is possible that the messages before issuing the transfer (including the eligibility check) are scheduled before both callbacks. A detailed explanation of this issue can be found in the [community conversation on security best practices](https://www.youtube.com/watch?v=PneRzDmf_Xw&list=PLuhDt1vhGcrez-f3I0_hvbwGZHZzkZ7Ng&index=2&t=4s). -public shared ({ caller }) func riskyOperation() : async () { - operationInProgress := true; // Committed immediately +### Recommendation - try { - await otherCanister.doSomething(); - // ... callback logic - } catch (e) { - // Handle error - ignore Error.message(e); - } finally { - // Runs in cleanup context: mutation persists even if callback trapped. - operationInProgress := false; - } -}; -``` +It is highly recommended to carefully review any canister code that makes async inter-canister calls (`await`). If two messages read or write the same state, review if there is a possible scheduling of these messages that leads to illegal transactions or an inconsistent state. -### Rust: cleanup via Drop +See also: "Inter-canister calls" section in [how to audit an ICP canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister). -```rust -use std::cell::Cell; -use ic_cdk::update; -use ic_cdk::call::Call; +To address issues around message ordering that can lead to bugs, one usually employs locking mechanisms to ensure that a caller or anyone can only execute an entire call, which involves several messages, once at a time. A simple example is also given in the [community conversation](https://www.youtube.com/watch?v=PneRzDmf_Xw&list=PLuhDt1vhGcrez-f3I0_hvbwGZHZzkZ7Ng&index=2&t=4s) mentioned above. -// Replace other_canister_id() with your canister's ID lookup. +The locks would usually be released in the callback. That bears the risk that the lock may never be released in case the callback traps, as we discussed in [securely handle traps in callbacks](#securely-handle-traps-in-callbacks). The code examples below show how one can securely implement a lock per caller. +- In Rust, one can use the drop pattern where each caller lock (`CallerGuard` struct) implements the `Drop` trait to release the lock. From Rust CDK version `0.5.1`, any local variables still go out of scope if the callback traps, so the lock on the caller is released even in that case. Technically, the CDK calls into the `ic0.call_on_cleanup` API to release these resources. Recall that `ic0.call_on_cleanup` is executed if the `reply` or the `reject` callback executed and trapped. +- In Motoko, one can use the `try`/`finally` control flow construct. This construct guarantees that the lock is released in the `finally` block regardless of any errors or traps in the `try` or `catch` blocks. -thread_local! { - static OPERATION_IN_PROGRESS: Cell = Cell::new(false); -} +**Motoko:** -struct OperationGuard; +```motoko +import Result "mo:core/Result"; +import Map "mo:core/Map"; +import Error "mo:core/Error"; +import Principal "mo:core/Principal"; -impl Drop for OperationGuard { - fn drop(&mut self) { - // Runs when the guard is dropped, even during cleanup after a trap. - OPERATION_IN_PROGRESS.with(|f| f.set(false)); - } -} +actor { -#[update] -async fn risky_operation() -> Result<(), String> { - OPERATION_IN_PROGRESS.with(|f| f.set(true)); // Committed immediately + let pending_requests = Map.empty(); - // _guard released (Drop called) when this function returns or is cancelled. - let _guard = OperationGuard; + private func guard(principal : Principal) : Result.Result<(), Error.Error> { + if (Map.get(pending_requests, Principal.compare, principal) != null) { + #err (Error.reject("Already processing a request for principal " # Principal.toText(principal))); + } else { + Map.add(pending_requests, Principal.compare, principal, true); + #ok; + }; + }; - Call::bounded_wait(other_canister_id(), "do_something") - .await - .map_err(|e| format!("call failed: {:?}", e))?; + private func drop_guard(principal : Principal) { + ignore Map.delete(pending_requests, Principal.compare, principal); + }; - Ok(()) -} + public shared ({ caller }) func example_call_with_locking_per_caller() : async Result.Result<(), (Error.ErrorCode, Text)> { + var guard_acquired = false; + try { + // Try to create a lock for `caller`, return an error immediately if there is already a call in progress for `caller` + switch (guard caller) { + case (#ok) guard_acquired := true; + case (#err e) return (#err (Error.code e, Error.message e)); + }; + // do anything, call other canisters + #ok + } catch e { + #err (Error.code e, Error.message e); + } finally { + // Release the guard (only requests that have created a lock) + if guard_acquired { + drop_guard caller; + }; + }; + }; +}; ``` -## Bounded vs unbounded wait - -The IC offers two kinds of inter-canister calls: - -| | `bounded_wait` | `unbounded_wait` | -|---|---|---| -| Timeout | 300 seconds (default) | No timeout | -| If callee is unresponsive | Returns `SYS_UNKNOWN` error | Waits indefinitely | -| Upgrade safety | Canister can be stopped and upgraded after timeout | Canister **cannot be stopped** while awaiting | -| Use for | Calls to external or untrusted canisters | Calls to your own canisters you control | - -**The upgrade safety issue:** A canister cannot be stopped (and therefore cannot be upgraded) while it has outstanding unbounded-wait calls. If the callee is malicious or buggy and never responds, your canister is permanently stuck. Use `bounded_wait` for any call to a canister you do not control. - -### Motoko: bounded vs unbounded - -Motoko does not yet expose a direct API to switch between bounded and unbounded wait. The `await` keyword currently uses unbounded wait. For calls to untrusted canisters, prefer the system-level API (available via Rust) or structure your application so calls to untrusted canisters only go out from canisters you can afford to sacrifice. - - - -### Rust: choose bounded_wait for untrusted canisters +**Rust:** ```rust -use ic_cdk::call::Call; -use candid::Principal; - -async fn call_trusted(canister: Principal, method: &str) -> Result { - // Use unbounded_wait only for canisters you control. - Call::unbounded_wait(canister, method) - .await - .map_err(|e| format!("call failed: {:?}", e))? - .candid() - .map_err(|e| format!("decode failed: {:?}", e)) +pub struct State { + pending_requests: BTreeSet, } -async fn call_untrusted(canister: Principal, method: &str) -> Result { - // Use bounded_wait for external or untrusted canisters. - // Default timeout is 300 seconds. Adjust with .change_timeout(seconds). - Call::bounded_wait(canister, method) - .await - .map_err(|e| format!("call failed: {:?}", e))? - .candid() - .map_err(|e| format!("decode failed: {:?}", e)) +thread_local! { + static STATE: RefCell = RefCell::new(State{pending_requests: BTreeSet::new()}); } -``` -## Response size limits - -All inter-canister call payloads (both requests and responses) are limited to **2 MB**. A request above 2 MB fails synchronously. A response above 2 MB causes the callee to trap. +pub struct CallerGuard { + principal: Principal, +} -When reading large datasets across canisters, use pagination: return chunks of data per call rather than everything at once. Keep individual payloads under 1 MB to leave room for encoding overhead. +impl CallerGuard { + pub fn new(principal: Principal) -> Result { + STATE.with(|state| { + let pending_requests = &mut state.borrow_mut().pending_requests; + if pending_requests.contains(&principal){ + return Err(format!("Already processing a request for principal {:?}", &principal)); + } + pending_requests.insert(principal); + Ok(Self { principal }) + }) + } +} -```motoko -// Paginated query: avoid returning unbounded data -// Requires: import Array "mo:core/Array"; import Nat "mo:core/Nat"; -public query func getItems(offset : Nat, limit : Nat) : async [Item] { - // Return at most `limit` items starting from `offset`. - // Caller makes multiple calls to retrieve all data. - Array.sliceToArray(items, offset, offset + Nat.min(limit, items.size() - offset)) -}; -``` +impl Drop for CallerGuard { + fn drop(&mut self) { + STATE.with(|state| { + state.borrow_mut().pending_requests.remove(&self.principal); + }) + } +} -## Caller identity across await points +#[update] +#[candid_method(update)] +async fn example_call_with_locking_per_caller() -> Result<(), String> { + let caller = ic_cdk::caller(); + // using `?`, return an error immediately if there is already a call in progress for `caller` + // warning: never use `let _ = CallerGuard::new(caller)?`, because this will drop the guard immediately + // and locking would not be effective + let _guard = CallerGuard::new(caller)?; + // do anything, call other canisters + Ok(()) +} // here the guard goes out of scope and is dropped + +mod test { + use super::*; + + #[test] + fn should_obtain_guard_for_different_principals() { + let principal_1 = Principal::anonymous(); + let principal_2 = Principal::management_canister(); + let caller_guard = CallerGuard::new(principal_1); + assert!(caller_guard.is_ok()); + assert!(CallerGuard::new(principal_2).is_ok()); + } -In Motoko, the `caller` is captured as an immutable binding at function entry via `public shared ({ caller }) func`. This is safe across `await` points. + #[test] + fn should_not_obtain_guard_twice_for_same_principal() { + let principal = Principal::anonymous(); + let caller_guard = CallerGuard::new(principal); + assert!(caller_guard.is_ok()); + assert!(CallerGuard::new(principal).is_err()); + } -In Rust, the current ic-cdk executor preserves caller across `.await` points via protected tasks, but this is an implementation detail: not a language guarantee. Bind `msg_caller()` before the first `await` as a defensive practice. + #[test] + fn should_release_guard_on_drop() { + let principal = Principal::anonymous(); + { + let caller_guard = CallerGuard::new(principal); + assert!(caller_guard.is_ok()); + } // drop caller_guard as it goes out of scope here + // it is possible to get a guard again: + assert!(CallerGuard::new(principal).is_ok()); + } +} +``` -```rust -use ic_cdk::update; -use ic_cdk::api::msg_caller; -use ic_cdk::call::Call; -use candid::Principal; +This pattern can be extended to work for the following use cases: -// Replace other_canister_id() with your canister's ID lookup. +- A global lock that does not only lock per caller. For this, set a boolean flag in the canister state instead of using a `BTreeSet` (Rust) or `Map` (Motoko). +- A guard that makes sure that only a limited number of principals are allowed to execute a method at the same time. + - Rust: Return an error in `CallerGuard::new()` in case `pending_requests.len() >= MAX_NUM_CONCURRENT_REQUESTS`. + - Motoko: Return an error in `guard` in case `Map.size(pending_requests) >= MAX_NUM_CONCURRENT_REQUESTS`. +- A guard that limits the number of times a method can be called in parallel. + - Rust: Use a counter in the canister state that is checked and increased in `CallerGuard::new()` and decreased in `Drop`. + - Motoko: Increase a counter in the `guard` function and decrease it in the `drop` function. +- A guard that makes sure that every task from a set of tasks can only be processed once, independent of the caller who triggered the processing. [View example project](https://github.com/dfinity/examples/tree/master/rust/guards). +- A lock that uses a different type than `Principal` to grant access to the resource. [View an implementation using generic types](https://github.com/dfinity/examples/tree/master/rust/guards). -#[update] -async fn process() -> Result<(), String> { - // Capture caller BEFORE any await: defensive practice in Rust. - let caller: Principal = msg_caller(); +Finally, note that the same guard can be used in several methods to restrict parallel execution of them. - Call::bounded_wait(other_canister_id(), "validate") - .with_arg(caller) - .await - .map_err(|e| format!("validation failed: {:?}", e))?; +## Handle rejected inter-canister calls correctly - // Use the captured binding, not msg_caller() again. - do_work_for(caller); - Ok(()) -} +### Security concern -fn do_work_for(_caller: Principal) { - // ... -} -``` +As stated by the [Property 6](../../references/message-execution-properties.md#message-execution-properties), inter-canister calls can fail in which case they result in a **reject**. See [reject codes](../../references/ic-interface-spec/https-interface.md#reject-codes) for more detail. The caller must correctly deal with the reject cases, as they can happen in normal operation, because of insufficient cycles on the sender or receiver side, or because some data structures like message queues are full. -## canister_inspect_message is not called for inter-canister calls +1. The call was issued as a bounded-wait (best-effort response) call, and the system responded with a `SYS_UNKNOWN` reject code. In this case, the caller cannot be a priori sure whether the call took effect or not. +2. The system responded with a `CANISTER_ERROR` reject code. This indicates a bug in the ledger canister. In this case, it is still possible that the call had a partial effect on the ledger canister. +3. The system responded with a `CANISTER_REJECT` reject code. This means that the call was explicitly rejected by the ledger canister. Normally, this indicates that the transfer didn't happen, but this depends on the ledger canister. The ICP ledger canister for example never rejects calls explicitly. -`canister_inspect_message` (Motoko: `system func inspect`) runs only for **ingress messages**: calls from external users arriving at the boundary nodes. It is never called for inter-canister calls. +### Recommendation -This means any access control you implement in `inspect_message` does not protect your canister from being called by another canister. Always duplicate access checks inside the method body itself. +When making inter-canister calls, always handle the error cases (rejects) correctly. Other than the `SYS_UNKNOWN` error code, these errors imply that the message has not been successfully executed. For `SYS_UNKNOWN`, follow the guidelines in the [safe retries and idempotency](../canister-calls/idempotency.md) document to handle this scenario correctly. -For full details on access control patterns, see [access management](access-management.md). +## Be aware of the risks involved in calling untrustworthy canisters -## Handling rejected calls +### Security concern -Inter-canister calls can be rejected for reasons beyond your control: the callee may have trapped, run out of cycles, been stopped, or the system may have rejected the message due to resource pressure. Unhandled rejections trap your canister. +- If inter-canister calls are made to potentially malicious canisters, this can lead to DoS issues, or there could be issues related to candid decoding. Also, the data returned from a canister call could be assumed to be trustworthy when it is not. -Always handle the error result of an inter-canister call. +- When a canister `C1` calls a canister `C2` using an unbounded-wait (guaranteed-response) inter-canister call, and `C2` stalls the response indefinitely by not responding, the result would be a DoS on `C1`. Additionally, since the call registers a callback on `C1`, `C1` can no longer be stopped because of the outstanding callback, and thus can no longer be cleanly upgraded. Recovery would require wiping the state of the canister by reinstalling it. Note that even if `C2` was trustworthy it could still stall indefinitely. This could happen due to a bug in `C2` (which may be unlikely to occur). But other causes could be a stall of the subnet hosting `C2` (assuming that `C1` and `C2` are on different subnets), or `C2` making a downstream call to an untrusted canister `C3`. -**Motoko:** use `try/catch`: +- In summary, this can DoS a canister, consume an excessive amount of resources, or lead to logic bugs if the behavior of the canister depends on the inter-canister call response. -```motoko -import Error "mo:core/Error"; -import Result "mo:core/Result"; +### Recommendation -// Inside your persistent actor class { ... } -// Replace otherCanister with your canister reference. +- Making inter-canister calls to trustworthy canisters is safe, except for the (possibly unlikely) case that there is a bug in the callee or its subnet that makes it stall for a long time. -public shared func callSomething() : async Result.Result { - try { - let result = await otherCanister.someMethod(); - #ok(result) - } catch (e) { - #err("call failed: " # Error.message(e)) - } -}; -``` +- Interacting with untrustworthy canisters is still possible by using a state-free proxy canister which could easily be re-installed if it is attacked as described above and is stuck. When the proxy is reinstalled, the caller obtains an error response to the open calls. -**Rust:** handle the `Result` from `Call::bounded_wait`: +- Sanitize data returned from inter-canister calls. -```rust -use ic_cdk::update; -use ic_cdk::call::Call; -use candid::Principal; +- See the "Talking to malicious canisters" section in [how to audit an ICP canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister). -// Replace other_canister_id() with your canister's ID lookup. +- See [current limitations of the Internet Computer](https://wiki.internetcomputer.org/wiki/Current_limitations_of_the_Internet_Computer), section "Calling potentially malicious or buggy canisters can prevent canisters from upgrading." -#[update] -async fn call_something() -> Result { - let response = Call::bounded_wait(other_canister_id(), "some_method") - .await - .map_err(|e| format!("call rejected: {:?}", e))?; - response.candid::() - .map_err(|e| format!("decode failed: {:?}", e)) -} -``` +## Make sure there are no loops in call graphs -## Summary checklist +### Security concern -Before shipping any canister that makes inter-canister calls: +Loops in the call graph (e.g., canister A calling B, B calling C, C calling A) may lead to canister deadlocks. -- **Reentrancy:** Apply CallerGuard (per-caller lock) to any method that makes an inter-canister call and reads or writes shared state. -- **State ordering:** Deduct or commit before `await`; compensate on failure in the callback. -- **Cleanup:** Use `finally` (Motoko) or `Drop` (Rust) for locks and cleanup that must always run. -- **Wait type:** Use `bounded_wait` for calls to canisters you do not control; `unbounded_wait` only for your own canisters. -- **Payload size:** Keep request and response payloads under 1 MB; paginate larger datasets. -- **Caller capture:** In Rust, bind `msg_caller()` before the first `await`. -- **Access control:** Do not rely on `canister_inspect_message` for inter-canister call security: always check the caller inside the method. -- **Error handling:** Always handle the `Result` of every inter-canister call. +### Recommendation -## Next steps +- Avoid such loops, or rely on bounded-wait calls instead, since these provide timeouts. -- [Inter-canister calls](../canister-calls/inter-canister-calls.md#making-calls): Basic inter-canister call patterns and the `Call` API -- [Parallel inter-canister calls](../canister-calls/parallel-inter-canister-calls.md): Running multiple calls concurrently and handling partial failures -- [Security concepts](../../concepts/security.md): IC security model and threat landscape +- For more information, see [current limitations of the Internet Computer](https://wiki.internetcomputer.org/wiki/Current_limitations_of_the_Internet_Computer), section "Loops in call graphs." - + diff --git a/docs/guides/security/misc.md b/docs/guides/security/misc.md new file mode 100644 index 0000000..2fa1560 --- /dev/null +++ b/docs/guides/security/misc.md @@ -0,0 +1,280 @@ +--- +title: "Security Best Practices: Miscellaneous" +description: "Miscellaneous security best practices: data confidentiality, secure randomness, endpoint validation, testing, reproducible builds, monotonic time, and floating point." +sidebar: + order: 11 +--- + +## Data confidentiality on ICP + +### Security concern + +When storing data on ICP, there are two levels of data access. + +1. Nodes are able to read all data that is stored on a subnet. This includes all messages sent to or from a canister, along with all data stored in a canister. This means a node could extract all data available to a canister. This will change with the implementation of TEE-based security for nodes. + +2. End user clients can only access whatever data that nodes and canisters have made available to them. If the subnet's nodes do not misbehave and leak data, clients can only read the responses to ingress messages and queries that they have sent. The canister decides what data is exposed to the client. + +Partial information on data that is stored in the subnet state tree will always leak. Therefore, data with a low-entropy value may entirely leak and be fully exposed, such as a Boolean value that can only be either "True" or "False." Leakage on data with high entropy is negligible. + +There are two types of user-related data that may be stored in the subnet state tree. The first is when a user sends an ingress message to a canister; the message hash and the response are both stored in the subnet state tree to be retrieved securely by the client. The ingress message should contain a high-entropy nonce that is implemented by the agent and typically not exposed to the user. The message response is determined by the canister and may not contain a high-entropy value. If the canister response consists of a low-entropy value, then the data may be leaked to users other than the ingress message sender. + +The second type of user-related data is certified variables maintained by a canister that are also exposed through the subnet state tree. If a canister places low-entropy data into the state tree, then the data may leak to users who should not have access to that piece of data. + +### Recommendation + +For developers that need to protect the confidentiality of their data against external users, they should ensure that data in the subnet state tree has a sufficient level of entropy. 128 bits is recommended. If the data does not have enough entropy itself, then adding some artificial data using randomness would be recommended. + +In particular, a canister can ensure that responses to ingress messages do not leak data to external users, other than the sender, by including high-entropy data in the response. Or, a canister can ensure that data in certified variables is not leaked by adding high-entropy data to the variables that should be kept confidential. + +Additionally, similarly to ingress message responses, a canister's private custom sections that contain low-entropy data could leak to unauthorized users. Therefore, a sufficient level of entropy for canister private custom sections should be used. 128 bits is recommended. If the data does not have enough entropy itself, then adding some artificial data using randomness would be recommended. + +## Using secure randomness in canisters + +Canister developers often require access to secure randomness in their canisters to perform certain operations. The requirements for a secure randomness source include: +* **Unbiased:** The value shouldn't be influenced by anyone. +* **Unpredictable:** The value is unknown to anyone before it is generated. + +ICP exposes the system API [`raw_rand`](../../references/ic-interface-spec/management-canister.md#ic-raw_rand) for this exact purpose, which accepts no input and returns 32 bytes of cryptographically secure randomness. It is always recommended to use `raw_rand` as a source of randomness in canisters and **avoid** using other sources with low entropy, such as current time. + +To illustrate the usage of `raw_rand`, two examples in Motoko and Rust can be found below, including the benefits and caveats around using them. + +### 1. Direct usage of `raw_rand` as the random number generator + +In this Motoko example, the canister provides the requested size of random bytes by calling `raw_rand`. However, it can only generate 32 bytes of secure randomness in a single message, and thus subsequent calls to the system API are required to fill the requested size. + +```motoko +import Random "mo:core/Random"; +import Array "mo:core/Array"; + +actor Randomness { + public func random_bytes(n : Nat) : async [Nat8] { + let byteArray : [var Nat8] = Array.init(n, 0); + let entropy = await Random.blob(); + var f = Random.Finite(entropy); + var i = 0; + loop { + if (i == n) { + return Array.freeze(byteArray); + } else { + switch (f.byte()) { + case (?byte) { + byteArray[i] := byte; + i := i + 1; + }; + case null { + let entropy = await Random.blob(); + f := Random.Finite(entropy); + }; + }; + }; + }; + }; +}; +``` + +#### Benefits: +- The random bytes is guaranteed to be secure. + +#### Caveats: +- The method doesn't scale when a large amount of random bytes is requested, as `raw_rand` must be called for every 32 bytes. + +### 2. Using `raw_rand` as seed for a pseudo random number generator (PRNG) + +In this Rust example, we seed the output from `raw_rand` in a known PRNG like ChaCha20 in the `init` and `post_upgrade` hooks and generate randomness by calling the `random_bytes` method. + +```rust +use candid::{CandidType, Principal}; +use rand_chacha::rand_core::{RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use std::cell::RefCell; +use std::time::Duration; + +thread_local! { + static RNG: RefCell> = RefCell::new(None); +} + +const SEEDING_INTERVAL: Duration = Duration::from_secs(3600); + +#[derive(CandidType)] +enum RngError { + RngNotInitialized(String), +} + +type RandomBytesResult = Result; + +async fn seed_randomness() { + let (seed,): ([u8; 32],) = ic_cdk::call(Principal::management_canister(), "raw_rand", ()) + .await + .expect("Failed to call the management canister"); + RNG.with_borrow_mut(|rng| *rng = Some(ChaCha20Rng::from_seed(seed))); +} + +fn schedule_seeding(duration: Duration) { + ic_cdk_timers::set_timer(duration, || { + ic_cdk::spawn(async { + seed_randomness().await; + // Schedule reseeding on a timer with duration SEEDING_INTERVAL + schedule_seeding(SEEDING_INTERVAL); + }) + }); +} + +#[ic_cdk::init] +fn init() { + // Initialize randomness during canister install or reinstall + schedule_seeding(Duration::ZERO); +} + +#[ic_cdk::post_upgrade] +fn post_upgrade() { + // Initialize randomness after a canister upgrade + schedule_seeding(Duration::ZERO); +} + +// This must always be an update method or the PRNG state won't be updated +#[ic_cdk::update] +fn random_bytes(size: u32) -> RandomBytesResult { + let mut buf = vec![0; size as usize]; + RNG.with_borrow_mut(|rng| match rng.as_mut() { + Some(rand) => { + rand.fill_bytes(&mut buf); + Ok(hex::encode(buf)) + } + None => Err(RngError::RngNotInitialized( + "Randomness is not initialized. Please try again later".to_string(), + )), + }) +} +``` + +#### Benefits: +- This method scales for large random bytes, as `raw_rand` needs to be called only once, and subsequent PRNG computation is local to the canister. + +#### Caveats: +- The `setup_randomness` must **always** be initialized in both the `init` and `post_upgrade` hook as `init` [is not invoked during a canister upgrade](../../references/ic-interface-spec/canister-interface.md#system-api-upgrades). +- The `init` and `post_upgrade` methods don't allow async calls, and thus a timer is immediately scheduled to seed the randomness. +- Once the seed is initialized, the outcome of all future `random_bytes` is predictable to anyone having the seed (node providers), as the PRNG is deterministic. This breaks the unpredictable property of secure randomness. Hence, to balance security vs. performance, we recommend frequently reseeding the PRNG on a timer. The example above already does this with a duration of **1 hour**. However, based on the sensitivity of their dapp, developers can choose an appropriate reseeding interval by setting `SEEDING_INTERVAL`. +- The `random_bytes` must **always** be an `update` method, so the PRNG can preserve the state and offer unique randomness on every request. + +## Verify that your canister doesn't export malicious endpoints + +### Security concern + +Malicious code that could for example be introduced through a library in a supply chain attack could maliciously export canister endpoints, potentially leading to exposure of sensitive data, malicious canister state changes, or denial of service. + +### Recommendation + +Verify in your CI pipeline that the endpoints a canister's WASM exports are legitimate and as intended. [The `ic-wasm check-endpoints` command can be used for that purpose](https://github.com/dfinity/ic-wasm/blob/main/README.md#check-endpoints). + +## Test your canister code even in the presence of system API calls + +### Security concern + +Since canisters interact with the system API, it is harder to test the code because unit tests cannot call the system API. This may lead to a lack of unit tests. + +### Recommendation + +- Create loosely coupled modules that do not depend on the system API and unit test those. See this [recommendation](https://mmapped.blog/posts/01-effective-rust-canisters.html#target-independent) (from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html)). + +- For the parts that still interact with the system API, create a thin abstraction of the system API that is faked in unit tests. See the [recommendation](https://mmapped.blog/posts/01-effective-rust-canisters.html#target-independent) (from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html)). For example, one can implement a 'Runtime' as follows and then use the 'MockRuntime' in tests (code by Dimitris Sarlis): + +```rust +use ic_cdk::api::{ + call::call, caller, data_certificate, id, print, time, trap, +}; + +#[async_trait] +pub trait Runtime { + fn caller(&self) -> Result; + fn id(&self) -> Principal; + fn time(&self) -> u64; + fn trap(&self, message: &str) -> !; + fn print(&self, message: &str); + fn data_certificate(&self) -> Option>; + (...) +} + +#[async_trait] +impl Runtime for RuntimeImpl { + fn caller(&self) -> Result { + let caller = caller(); + // The anonymous principal is not allowed to interact with the canister. + if caller == Principal::anonymous() { + Err(String::from( + "Anonymous principal not allowed to make calls.", + )) + } else { + Ok(caller) + } + } + + fn id(&self) -> Principal { + id() + } + + fn time(&self) -> u64 { + time() + } + + (...) + +} + +pub struct MockRuntime { + pub caller: Principal, + pub canister_id: Principal, + pub time: u64, + (...) +} + +#[async_trait] +impl Runtime for MockRuntime { + fn caller(&self) -> Result { + Ok(self.caller) + } + + fn id(&self) -> Principal { + self.canister_id + } + + fn time(&self) -> u64 { + self.time + } + + (...) + +} +``` + +## Make canister builds reproducible + +### Security concern + +It should be possible to verify that a canister does what it claims to do. ICP provides a SHA256 hash of the deployed WASM module. In order for this to be useful, the canister build has to be reproducible. + +### Recommendation + +Make canister builds reproducible. See this [recommendation](https://mmapped.blog/posts/01-effective-rust-canisters.html#reproducible-builds) (from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html)). See also the [developer docs on reproducible builds](../canister-management/reproducible-builds.md). + +## Don't rely on time being strictly monotonic + +### Security concern + +The time read from the system API is monotonic but not strictly monotonic. Thus, two subsequent calls can return the same time, which could lead to security bugs when the time API is used. + +### Recommendation + +See the "Time is not strictly monotonic" section in [How to audit an ICP canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister). + +## Rust: Avoid floating point arithmetic for financial information + +### Security concern + +Floats in Rust may behave unexpectedly. There can be undesirable loss of precision under certain circumstances. When dividing by zero, the result could be `-inf`, `inf`, or `NaN`. When converting to an integer, this can lead to unexpected results. (There is no `checked_div` for floats.) + +### Recommendation + +Use [`rust_decimal::Decimal`](https://docs.rs/rust_decimal/latest/rust_decimal/) or [`num_rational::Ratio`](https://docs.rs/num-rational/latest/num_rational/). Decimal uses a fixed-point representation with base 10 denominators, and Ratio represents rational numbers. Both implement `checked_div` to handle division by zero, which is not available for floats. Numbers in common use, like 0.1 and 0.2, can be represented more intuitively with Decimal and can be represented exactly with Ratio. Rounding oddities like `0.1 + 0.2 != 0.3`, which happen with floats in Rust, do not arise with Decimal (see https://0.30000000000000004.com/ ). With Ratio, the desired precision can be made explicit. With either Decimal or Ratio, although one still has to manage precision, the above makes arithmetic easier to reason about. + + diff --git a/docs/guides/security/observability.md b/docs/guides/security/observability.md new file mode 100644 index 0000000..2f63c53 --- /dev/null +++ b/docs/guides/security/observability.md @@ -0,0 +1,22 @@ +--- +title: "Security Best Practices: Observability and Monitoring" +description: "Security best practices for monitoring canister cycles, logs, and health indicators." +sidebar: + order: 10 +--- + +## Monitor your canister + +### Security concern + +Without monitoring, it can be hard to detect attacks or vulnerabilities that are being actively exploited. For example, a sudden increase in cycles consumption could indicate a DoS attack, while unexpected changes in canister state could indicate a security breach. + +### Recommendation + +- Monitor your canister's cycles balance regularly, set up alerts for sudden changes in cycles consumption, and add an endpoint to expose health indicators. See the [DoS prevention best practices](./dos-prevention.md) for more context on cycles monitoring. + +- Consider emitting logs for security-relevant events (e.g., access control failures, unexpected state transitions). Since logs are stored on-chain, they provide a tamper-resistant audit trail. + +- See [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html) for general patterns on canister observability. + + diff --git a/docs/guides/security/overview.md b/docs/guides/security/overview.md new file mode 100644 index 0000000..b636382 --- /dev/null +++ b/docs/guides/security/overview.md @@ -0,0 +1,22 @@ +--- +title: "Security Best Practices Overview" +description: "Introduction to the ICP security best practices for canister and web app developers." +sidebar: + order: 1 +--- + +This section provides security best practices for developing canisters and web apps served by canisters on ICP. These best practices are mostly inspired by issues found in security reviews. + +The goal of these best practices is to enable developers to identify and address potential issues early during the development of new dapps, and not only in the end when (if at all) a security review is done. Ideally, this will make the development of secure dapps more efficient. + +Some excellent canister best practices linked here are from [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html) and [how to audit an ICP canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister). The relevant sections are linked in the individual best practices. + +## Target audience + +The target audience for these documents is any developer working on ICP canisters or web apps and anyone who reviews such code. + +## Disclaimers and limitations + +The collection of best practices may grow over time. While it is useful to improve the security of dapps on ICP, such a list will never be complete and will never cover all potential security concerns. For example, there will always be attack vectors very specific to a dapp's use cases that cannot be covered by general best practices. Thus, following the best practices can complement, but not replace, security reviews. Especially for security-critical dapps, it is recommended to perform security reviews or audits. Furthermore, please note that the best practices are currently not ordered according to risk or priority. + + diff --git a/docs/guides/security/resources.md b/docs/guides/security/resources.md new file mode 100644 index 0000000..9da048c --- /dev/null +++ b/docs/guides/security/resources.md @@ -0,0 +1,39 @@ +--- +title: "Security Best Practices: Resources" +description: "Important security resources and further reading for ICP canister and web app developers." +sidebar: + order: 12 +--- + +Below are resources which cover security best practices for technologies you are likely using in your dapp. These best practices are equally important as our Internet Computer specific guidelines and should be studied carefully. They can be useful to reference when developing secure dapps or executing security reviews. + +## General +* [How to audit an Internet Computer canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister) by Joachim Breitner +* [OWASP application security verification standard](https://owasp.org/www-project-application-security-verification-standard/) +* [OWASP top ten](https://owasp.org/www-project-top-ten/) + +## Rust +* [Secure Rust guidelines](https://anssi-fr.github.io/rust-guide/01_introduction.html), in particular [unsafe code](https://anssi-fr.github.io/rust-guide/04_language.html#unsafe-code), [overflows](https://anssi-fr.github.io/rust-guide/04_language.html#integer-overflows) and [Cargo-audit](https://anssi-fr.github.io/rust-guide/03_libraries.html#cargo-audit) + * For overflowing operations, consider using `saturated` or `checked` variants of these operations, such as `saturated_add`, `saturated_sub`, `checked_add`, `checked_sub`, etc. See e.g. the [Rust docs](https://doc.rust-lang.org/std/primitive.u32.html#method.saturating_add) for `u32`. + +## Crypto +* [OWASP cryptographic failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) points out issues related to cryptography, or the lack thereof. +* [OWASP application security verification standard](https://owasp.org/www-project-application-security-verification-standard/) (see Section V6) +* **Use the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API).** Storing key material in the browser storage (such as [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) or [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)) is considered unsafe because these keys can be accessed by JavaScript code, e.g. in an XSS attack. To protect the private key from direct access, use Web Crypto's [generateKey](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey) with `extractable=false`. + +## Web security {#web-security} +* Resources for setting security headers: + * [securityheaders.com](https://securityheaders.com/) + * [Permissions policy generator](https://www.permissionspolicy.com/) + * [Content security policy evaluator](https://csp-evaluator.withgoogle.com/) and [strict CSP](https://csp.withgoogle.com/docs/strict-csp.html) + * [OWASP secure headers project](https://owasp.org/www-project-secure-headers/) +* [SSL server test](https://www.ssllabs.com/ssltest/) +* Don't use features that could lead to an XSS vulnerability, such as e.g. [@html in Svelte](https://svelte.dev/docs#template-syntax-html). +* **Log out securely.** Clear all session data (especially [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)), clear [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), etc. on logout. Make sure other browser tabs showing the same origin are logged out if the logout is triggered in one tab. This may not happen automatically when the ICP JavaScript agent is used, since the ICP JavaScript agent keeps the private key in memory once initialized. + +## Testing +These are some useful references for testing canisters which don't necessarily relate to security. However, having good test coverage is essential for developing secure applications. +* In [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html): [test upgrades](https://mmapped.blog/posts/01-effective-rust-canisters.html#test-upgrades), [make code target-independent](https://mmapped.blog/posts/01-effective-rust-canisters.html#target-independent) +* Consider [PocketIC](../testing/pocket-ic.md) for canister testing + + diff --git a/docs/references/message-execution-properties.md b/docs/references/message-execution-properties.md new file mode 100644 index 0000000..d22ad10 --- /dev/null +++ b/docs/references/message-execution-properties.md @@ -0,0 +1,78 @@ +--- +title: "Properties of Message Executions on ICP" +description: "The 11 properties of message execution on ICP, covering atomicity, ordering guarantees, inter-canister call delivery, and cycle handling for bounded-wait and unbounded-wait calls." +--- + +## Asynchronous messaging model + +ICP relies on an asynchronous messaging model. Compared to synchronous messaging like on Ethereum, this provides performance advantages because multiple calls can be executed concurrently — and also in parallel, when multiple canisters are involved. However, asynchronous message execution can also lead to sometimes unexpected or unintuitive behavior. Therefore, it is important to understand the properties of message execution. Potential security issues that arise in this model, such as reentrancy bugs, are discussed in the [security best practices on inter-canister calls](../guides/security/inter-canister-calls.md). + +The [community conversation on security best practices](https://www.youtube.com/watch?v=PneRzDmf_Xw&list=PLuhDt1vhGcrez-f3I0_hvbwGZHZzkZ7Ng&index=2&t=4s) also discusses the messaging properties. + +## Message execution properties + +To interact with a canister's methods, there are two primary types of **entry points** or **methods** that can be called, update and query methods. If the Rust CDK is used, these are usually annotated with `#[update]` or `#[query]`, respectively. In Motoko, updates are declared as `public func`, and queries use the dedicated keyword: `public query func`. + +These entry points can be called either by external users through the IC's HTTP interface, or by other canisters. ICP also supports additional [entry points](./ic-interface-spec/canister-interface.md#entry-points) such as heartbeats, timers, initialization or upgrade hooks. These cannot be called directly, but the properties listed in this document are still relevant for them, and in particular heartbeats and timers, as they behave like update methods that are called by the system. + +A **message execution** is a set of consecutive instructions that a subnet executes when a canister's method is invoked. The code execution for any such method can be split into several message executions if the method makes inter-canister calls. The following properties are essential: + +- **Property 1**: Only a single message execution is run at a time per canister. Message execution within a single canister is atomic and sequential, and never parallel. + +Note that parallel message execution over multiple canisters is possible — this property talks about just a single canister. + +- **Property 2**: Each downstream call that a canister makes, query or update, triggers a message. When using `await` on the response from an inter-canister call, the code after the `await` (the callback, highlighted in blue) is executed as a separate message execution. + + For example, consider the following Motoko code: + + ![example_highlighted_code](/img/docs/references/example_highlighted_code.png) + + The first message execution spans the lines 2-3, until the inter-canister call is made using the `await` syntax (orange box). The second message execution spans lines 3-5 when the inter-canister call returns (blue box). This part is called the _callback_ of the inter-canister call. The two message executions involved in this example will always be scheduled sequentially. + +:::note +An `await` in the code does not necessarily mean that an inter-canister call is made and thus a message execution ends and the code after the `await` is executed as a separate message execution (callback). Async code with the `await` syntax (e.g. in Rust or Motoko) can also be used "internally" in the canister, without issuing an inter-canister call. In that case, the code part including the `await` will be processed within a single message execution. For Rust, both cases are possible if `await` is used. An inter-canister call is only made if the system API `ic0.call_perform` is called, e.g. when awaiting result of the CDK's `call` method. In Motoko, `await` always commits the current state and triggers a new message send, while `await*` does not necessarily commit the current state or trigger new message sends. See [Motoko actors and async programming](../languages/motoko/fundamentals/actors-async.md) for details on `await` vs. `await*`. +::: + +:::note +In the Rust CDK, it is the `await` expression that triggers the canister call, rather than invoking the `call` function itself. That is, a call that is not `await`-ed is never executed. In Motoko, if the code does not `await` the response of a call, the call is still made, but the code after the call is executed in the same message execution, until the next inter-canister call is triggered using `await`. Also, multiple outgoing calls can be triggered in parallel from the same message execution; see the [parallel calls examples](https://github.com/dfinity/examples). +::: + +- **Property 3**: Successfully delivered requests are received in the order in which they were sent. In particular, if a canister A sends `m1` and `m2` to canister B in that order, then, if both are accepted, `m1` is executed before `m2`. + +:::note +This property only gives a guarantee on when the request messages are executed, but there is no guarantee on the ordering of the responses received. +::: + +- **Property 4**: Multiple message executions, e.g., from different calls, can interleave and have no reliable execution ordering. + + Property 3 provides a guarantee on the order of message executions on a target canister. However, if multiple calls interleave, one cannot assume additional ordering guarantees for these interleaving calls. To illustrate this, let's consider the above example code again, and assume the method `example` is called twice in parallel, the resulting calls being Call 1 and Call 2. The following illustration shows two possible message execution orderings. On the left, the first call's message executions are scheduled first, and only then the second call's messages are executed. On the right, you can see another possible message execution scheduling, where the first messages of each call are executed first. Your code should result in a correct state regardless of the message execution ordering. + + ![example_orderings](/img/docs/references/example_orderings.png) + +- **Property 5**: On a trap or panic, modifications to the canister state for the current message execution are not applied. + + For example, if a trap occurs in the execution of the second message (blue box) of the above example, canister state changes resulting from that message execution, i.e. everything in the blue box, are discarded. However, note that any state changes from earlier message executions and in particular the first message execution (orange box) have been applied, as that message executed successfully. + +- **Property 6**: Inter-canister calls are not guaranteed to be delivered to the destination canister, but they are guaranteed to be delivered at most once. When a call does reach the destination canister, the destination canister may trap or return a reject response while processing the call. + + There are many reasons why a call might not be delivered to the destination canister. Some of them are under the control of the canister developers, such as the destination having sufficient cycles to process the call. Others are not; the Internet Computer may decide to fail the call under high load. + +- **Property 7**: Every inter-canister call is guaranteed to receive a single response, either from the callee, or synthetically produced by the protocol. + + However, a malicious destination canister could choose to delay the response for arbitrarily long if it is willing to put in the required cycles. Also, the response does not have to be successful, but can also be a reject response. The reject may come from the called canister, but it may also be generated by ICP. Such protocol-generated rejects can occur at any time before the call reaches the callee-canister, as well as once the call does reach the callee-canister, for example if the callee-canister traps while processing the call. Thus, it's important that the calling canister handles reject responses as well. A reject response means that the message hasn't been successfully processed by the receiver but doesn't guarantee that the receiver's state wasn't changed. + +- **Property 8**: If the calling canister made an unbounded-wait (as opposed to a bounded-wait) inter-canister call, if the call is delivered to the callee and the callee responds without trapping, the protocol guarantees that the first such response will get back to the caller canister. Otherwise, the caller will receive a reject response (with the code never being `SYS_UNKNOWN`). + +- **Property 9**: If the calling canister makes a bounded-wait call, the system may generate a reject response and deliver it to the caller. This can happen even if the call is successfully delivered to the callee and the callee responds without trapping. But this can only happen if the reject response is `SYS_UNKNOWN`. + + Since, by property 7, the caller will only ever receive a single response, this means that the real response from the callee, if produced, will get dropped by the system if `SYS_UNKNOWN` is returned. + +- **Property 10**: If cycles are attached to an unbounded-wait call, the sum of cycles accepted by the callee, and those refunded to the caller equals the amount that the caller attached. + +- **Property 11**: If cycles are attached to a bounded-wait call, they may get lost whenever a `SYS_UNKNOWN` response is received by the caller. In particular, any cycles refunded by the callee are lost, and if the callee doesn't receive the call, all attached cycles are lost. If any response other than `SYS_UNKNOWN` is received, property 10 holds even for bounded-wait calls. + + Note that cycles do not necessarily get lost. Even if the caller receives `SYS_UNKNOWN`, it can happen that the callee still receives the sent cycles. + +For more details, refer to the [IC Interface Specification abstract behavior](./ic-interface-spec/abstract-behavior.md) which defines message execution in more detail. + + diff --git a/public/img/docs/references/example_highlighted_code.png b/public/img/docs/references/example_highlighted_code.png new file mode 100644 index 0000000000000000000000000000000000000000..e7b1a1f7770f30ae4da721265b630be5108e547d GIT binary patch literal 32553 zcmd42WmsLi7A}mla4l|yBE{WZ7Es)!xKrHS-L<$DcP|dbi(7GbclU2~pR@Pbz2D#a z(z)NM@3ejFG%EOkP$T2_6p~3=9lOQbJS_3=EF)BDLeS{M zNj!B{;OWg`aueaw4UB{dwdmr?tr?XV0<3B`j2s#a)wr#%9-;=Z2j1}mqhKiZux&7e z+OTaJ#98uE??CN)YiL2SsNt_hU^(9JE+d6?V1CJ;h0JU8VuyoG=m{QYr>75uaK1D6 zY(^HWUU>HPHdD5LjdePht$8_8zdR+Ptr!aKs*h_K_sx|1)n#xr4*8)vScOgr9g8NN zWn}5(xXl(H&h4L~^ypr&nGz(mR@~8~L=% zABu;aLzuRSk16g7CZ6cMn4xqCL17B0Wg^Fk*@W1Y^|(rjgVA`kaT12c7oUX*Xi$S- zsIP0f9VI2or#fgke9SuLlce8MehNduH0lrecp1=e*>riop;cW5fkvY*As#EhZ(v66 zDI{NQa5~T8NA+H|66E@RxAef*64fZVTMSh=spaDb${jkt%FVf z6j!kfR|PB=%ScipPv}rEOH?QvL3j(ipO6}Y^RsjFuxFTj?|l(IkU#=_^Wt&w;y#?A zby*NlLHG!N0DI`!g4k(?kOw?4^ptNN0;YD_gD%k)*5P`aNeH%oc$9c1n$3SEbw$M8 z5NLDI-|2F7(EEDx=HVUs(qB!2k!zGMLWGMalGLkbfXWn!BV6BuWaqPt9pkG;QC``E zeagogV{;ow8R*6}2Ym)1TwWduDu=@*GSlAfvEz)%zfqGQyH_A26^sRHYT-YBE_F2k`#p|{U zL11g>c(z5jU>JJfmK&a0ez51z(x{02{${8s@m)qZUKCn;=!{4s5LZGUsbFb-Xv)K} z2UzK0&m$oF0&OufAWXZ+ZQnWidf47v`|jlsw?Sv4LcM>l92ks4QW*Mf)3=FKCsY7E zQk0}df;m3ih=f!$d_)+FWalS!KKerVFVapD@mQsw(D{VT0gmt6f6{D|h39@LM*b41 znU^QekQfx4cd`G06*4m%E0=tq*^y5(;8X~ILTR789`ZyqI}Z})hcXklNidgz4i0`q zzhbqcwEt+=P&IrxOxZx%hC@fRlY!SfY)0s`EzBuE%@!xi_v*alp1|{?PS{yD{<-Qi z_iE7HmM)yKKU$aHy2v_eKL#^k8c+}j-;La@-;LXp><+LA6D7?w6S)rbU`kC9%aGLi zv@EeK&MRq2>5Ks@@gW|3&~IBjoWf2LI$v=LW{PMEZi-D2<)A&hG`u9d>{IBc@NkI& z^#Wnz#v!|}7SwVo{Bdl9sz${2tf_KYLOQamvep#3@9$y-6zZwiF$F&Hl1DIS*Jdfo zafvj`xmA>weyKdH;FeV>=9Y76KlGltD%CC}{#8G7JTw0VVr#;3#>SPw4=8q}tm5r-ZDcZ<)7QPAxl4sce|#&Y*2 zlBZE7Y^DSbagS=Qq1>ge4{s_D+NX(g$A4k(Cm)rUOAACcN@-YPSR=_P$))0_s*Y-^>QVWaMtj}&8rfPMqZPB45#U;| zp^}l3sl!^*7koXQneY9#!Gx1z!}Lqmc`GU&zgAcHr#psT!yymXH%J`k_b2${8K*q* z&5TBWevbYUO*uy-c$*@AO3}v0ZY~4D&OtymE+}PxEaef`^`pe_mUDLhL zUDadta_S=dLi5mRHGQ3MpKtD|_zspBB^x3elHccn`_bg-^wx{L-y~9RII>~)h3mzTZ(xjQ39k~1 z2wMmDm8zRx7=4l;3>Y1yMd&6lMEwbK1_$XI*JN29aqoPubku_rft}V!Q>mStfwqBZ zUsgn3pG?>i;T=0aqqp00JJb@CxvKH@1gDIpi4D<&wq6nXCg9W0t4 z%`@r`>%EE68>8&7ATDnXo48(x@*}@ z+lQmaR#W*>5)$he)W|v5?m{I#C(?ZxWf@G*nxl6{O%CTNdX(1YY0yQdUxM3_;@rp@|tiTH#oWx`7b|MAg{f=17Hcw2@(9g=4o*vTrE-7Qqd?`RNPFqik`J+%Kas$NgwI%WMc%p@|?6RmQv4I_Qr!V_`IT> zPJ7)tuf!SdwK(l3j|{fRW2BIhR~U$2M_(Fytca|T)Of2{wQo8Wm-!1{?i+k9iYj5X z1v~;AwiLyc#h*q4(;IDM+GgC+eV!IFGk6%zuvT9jZC1UyUut2~P^gLU_{twvPVc-j zi82RT{9EV+!aW?T(&t{D?OwOW`N-vp<*2jL_$$12ACJv!B+h08D4r*;w4R@pbPuvM zeN;QEz3lcGc6F1V2JY_K659wo8?GC=jxueQSL+db5qUk+pKhmHYwuvrT)fU7ZBGrS z28yx^y(qkN-uh0ZuIA3G?qD5YVf(b;)Lv-q?N9DU9F*{OH zfsKSum}3>G|B#Y+(5rnpzu2e z1w~KsE5Ii(FhMX$Q6Xhl@MCRQPs~32m#JdvVgMC@YAV7}SybLqPYm^dR7}3q7ydB& zn<6H-kQg+$f}nc>sZ8RksQZ~p8s^83$?0!*yR|MJE*|8`fq~oU#2e10MJG!O3mhzG z>2ONN5*Z{U004x)@83~u7b*aQG@~o4X}R+6y#G)D1w*Tqp@alcQK6u~{}cHkhWVmy zAU@3R6hi+u>pxK-K#K5xWWV!%#sUCnW3e&X)|&qk04fQs{`Chd)c>nONo3GAtd_pV zDTWUv(RnEGr6y3m^SU-CAml7{Y)+qx$x)>v4d@855nnLte|8Ozau7RM;fv2I#@)C) zx4T|JVShAMZR=<;b2k~vSP=igddPQ6i7%5zC^yLz4e`p)!y*RNal-1(@fzWDobv7M zCL&K{fbb??B@ggr^~1gUrhpS^dX(9+D}IHhO_5;{=1yA_)tR>bwS0l?m-{b9Eq!dL z^td~ZNpzq0?=QB-mb|Vf#XpKi6O3KeyV~wgydO$tAT}OK94vEfO3vVNn4)W3-gJ`T zh4M>(=Xdm+yXL%&xsZDFVr4&l13V)?hldUuEwB3OC`cWzP90?5f){zVtnWIvsIvEIu!OJk{1|zuMdBjw2 zKlh#@^3+PxRh@Ii>&Z%))W>>=;HxJj90&Xq#+%N!!7}!QOq-+5qe;vjG91Wser(34 zY3#SW&~w`%r_Z;(`Ux~Y28ey69d<`@H+n**$zpipwFV2D9TmR<1_lO{u69RDfKhnY zn-L6C`H~3$Zw`F4Xk+WErA^nz^DE9p?{3*J@@|jY#e+M6lR!s)99;3aN^1Bdz<4UQ zTqlD|Bmm`+by`N8^p1GlBhw&nHILAUpATaLhv-J9nktKb-M#iGF*7~^fP!nCXWi2b zap98d79~Ng@F7w*Tg5Tal;-~Hx3dtFZ@g~ZQ(BN8pg-Y49nO@A^SNJ3OpD?K5Ixu@uU*7sS37!;yo%C9 zmUD(X-`7`g*Q#<@0E`CB2i^UfUW|e{&0aIaC6}m+whw!_sD5fXZekU>o&2M#4?b@% zMYX2ml^|8vZHxrX3gsqjJ6vo(g^0OQvGhAvaNFZ$=ozDZD5uS5@+u3Chyg#Jv64HV zEPQ1@{6{k)eD z0;My#S->9JG$S6zLO&iM&rUO4By}R7$eNI>L|s8G#`K`pHLbrzC97fL^fQr6!1nsJ zxtMND{bUVB^tJs88c_GzMaHW07KkXLfhKpx7XA$$C79=GFr6%t`l~R%!nL6OTb{jG znb>s4^sO&hcb#`s(F1Rb+3dT{74G=bYNB7+J$#6GCV4VE$MsiDnWrN}z@3PoX{iP> zvpFK31|OFA)3#|J+?N6ytxSYII8ZJDsCj+(V@oZfoZQ(`qNQL5af!k_QQjQqeI z&3;lleION1&5J6FVwcjmC^}`O6;r;fIeX|WFbl+YSv=t0ds&Cn|8*KA@fhs1Qa3k1 zIUIVPVpk5QV_L*zv30O)ELLzCq(ojhZL6~dY{~>vAxik9d>=%7J zbt1x?l4H*U-$J(&snB;|z*95hn|FQGEK`5Vkj0RFrW=AAmjBtw6x+8;PdUC8QH7bI zQ&(J9u&WKS&HeEP=SBCQvI-me%EzmW?l(bz$B-j~OjFWgOWdO-AYRvN7LQBxY^Ao8 z$K7cG1T_5qWTZbdg1gwBszyjyM1)~vcdA=*<`tys_-| z`9@E@$tWJT^D$0L?OrZrmaA}#R$7l9>&|*tfa6g`;}oIGa&Cd{L|4aoH^Op~Q9;G$ z#zR&{j9}08W5n|;J`YEH*KxkPeb*wn?82EcHH;yZc6TRg?Us7_IxdE?t=>ogY(%;W zf9_)x5nmd~4jA$)K^FNo253{rZF9SNL`b8ZBbgI?`N1M33;Xgq!gW!9J?{D#u@sQ^ zjJ%q}6&a2V$ZKqFFL|kNu!K7!Knb7d#l7H#D5aY1w<7P{8lT&|+l%~mnyGa{Fs1v0 zCE=c8d8qb~0*Uk>mpaLQw0bDv>M{jXDyjo?$yO}WT_3-JlbJl1%)vx=PfWXe3WD8KyAZKOLx&`(N|tf zIIw3<3Fhwv{E|iVTC?bpLnzC`O#{;v8rm?N#bn~++P}Ehv>HuZTAgn%-?gBRA`!QG zx@_l2$IXso%?0410o}%4T)ytUHAofR;R)pyKk<`T$mMLDPU}2aGkV1wA`zyvU9_2p|nJC zqu43iY7LKi7d@nHAd()l5X-)X5T*XiexDiVLSineZcY&`{XAlQX_{Ih#J9$cI&%nR&0)>!;KsXptrdXDn)k7t+9(1GQ();1%DsVdi4Kw3_1|Z%?G?H0y1H#a3UQ?uuyCD$V?#?zdtv0H%mX zhPJ#WBRDeQJ`X$CY5ZO-?5HqZ-cKiu=4%~ZnnWc z)#I~AXV*fWVi8QIJ>-IgTnvpe`Xi$|byi+D@DQg_(ahE8Uxf4LiqBd7EjnXq|<* zh*i(W_Z*$uoP!dgIPGh!Tk)Uq>`u}Y*F1@f!c`r9A~D{`idB{(uNeHwFJZq~SXWnh zy$G~G(Nc*>;@$>W%ng)9uMjGYhB{o3F1ge`-RZXCG4sl{pQjmq;c~B^9vFu* zmBQUq*zxpU_G?L8EXrP|iFVt^q$mCAsnE5z>XJMDa24&xd!{m?bHy6I@Un|PVkfZqsv(^$A#E(pa_98uD5H}fwAc&f* zo$Y8_ao8wi8?4XVzyCz>`I^;MS!KQ;9AvUO-nQ>df2zS0N z))s)>d=gP#H*O;KiJm@#e!g(IuRiiVr;X%nf*}A#M^C%rIt^TDhw29bnYP+#yk)$u z?aI^Grwfl(Xk;E-rP8MfIi7DCBW!S71 zm7PwPH7+E|uecNBRZ5jV-t1-jaKDVeN?VQ=J*G%DZq6VzWj5_QM}?Jbq9o%9c(+Xx zn_rc3hTl{z+PpqLlsX;FjvaB{Ur)=uvoHji?-k09^-rz-2cL-EtF$_w*hg%+p+~Zk zVlMMu!R=&-Yj@3Jx`6qIuQu5kzIX<}X9Nmb;^Yqp)m})bqbpuUyPwBu^L=K}Yy|gu z8aVupchY?Yw}wvbtWaAVzd3Kq2z%HIXeui0@y42^^X?Gv^9p!9i z)~XW9E8CIPGp`brUq9AY!xCWbq#4ORliMCI- zMz{GEpurL|$ldMC(HmqEh|Wt0Ftu!XG?76`kBZ_PR3}P2ZKb6Gw$jk{X6Eu#%1eA| zJmlI~qMKP54UD?IE@8dbyfK<2A_;%;T)I{I@k}~sA-(#beu@Rg($Ea_H-;ls{2I$# zC8p7`m@bTRzZ&IW!`*#{tvnb{{w}6PlYD0`h8#IuO~4iblJxnPl3eqHpC^NQ^^b#r zOZdLL$uS24UqBuK$)#$vIL*vwn|5QT!2|4203|esDaCst-;n@Ih-^KAn31xlvrd7O z97%YG+hdItrY~z9d@lQMl8Txgq$eDnJp9Ml7A5N$ zY^9PHu3H1{#*Zgv3I#9oACRYTOhI1-YH<+}$AQeip*=h*` zkBp+ae_&ZU30*3X3EphyyQR#}kip~u!59|TPiXpvtg{KvIJh>;m{p5T(UVar8`y_c zLibK&(ZLyZ&~Xq#ed0f+Q^Tq$7cv+JCl4516eI6x)CP_wB~AH%4qu0$sneKxe~-X0 z0Wa@9S=l>8+~~14*6?^y(O$P^_=kl6EY1z#vF2ELDwIDbU27dAdBS0KfU=L zJr+7C#P}SwcC~lX(y@U{(bqK}UH}uWhIRfBf;Oj+2h=bLnAD4e2OVgrCCFtV*CU#` zcKmLQk7_63Jj?hAkS?kG4)?-5&ce0m;AG^J=gbHfu&Kgo!*$Cb#J z#L#FV8$mQMAw0da4N`k+CxsOaCaJ5*Zma*J&H!sljyXHxBE(3z-uB!wFg1oLRL|LT zd@*x-1~*#VQi|7Id;C@}cxoDas#-Dn)_6i5d!r9AM>fDTAkroSZH}ayyv)Ogn8&a` zZmAUOM9b}_kIP%Ff#x?*oUf&Zf9GYckU^wPl%NYa?@O zXlfQ1b>4Zoi_PF1eo=yUdT#&0>n7+9;zIq1&BFXUqd}t&8Uw6DXY1~}*RC&tXtTC= z0t6LKDIKicj7_0bV-J^Svl<%3(g*qG-y=P}UJO#Jq07neN9MxXhfH_OAbVkLzSLwOEiE$U1RqCDk9PM{ zPV!b;nW^JnFdJZqdkDb#jL%YGcvrCY?H*YVSO%6j8y7SqY#szfGquec;$*IDPsOD7 z%(`<0d#`2j_S*wn7&xNg7Qq;ZkHY0HkLz>M*r)WkXjU}G6as_jPA2#8_}2;LE# z4Ne+sESQZb6j|^RiwwIAB;@L|PD!5XnsIsD9EySVtebS!>#T`UO|Ei71S=-TG8WS@ zBf9)n)#*@3I>rRu_FrV|Wxl}s$D1QZ&`}*8vk~A0wBK0|dv>8B;;GlwNYpn z6hWk4=u5q+f{hVmpJFN$zOgd zk&Vn`7gnxHOMO3`!_%UzFmbl=&V+%ZcRYYp1ui~PQ)`x=iVvr&=o6g1P zEmM?#TAbM*tQF%8m}(MWM~~deD%-DEc(dJv_;TS|@WCQ5O4>)GYq;p5Ww2`5cW1f@ zL$1vr!n#cJyQMki`>NbDG}TCu@G!L zP|!qg=T$w6QS)aSgJJkEi`J(ofFUM0-xG2F_$?!Ytb3gIDpG}g5oNJ`fXHKH&q)iwN}8d({A@zB zPFe-#msO+L;%M$08$BFLtz0bszPQ?)3AO&BKa~EutJS%cnr~5ZD{U|I+$O^6u&0B5 z0c(-HzDi=enyW;6%|jV8Z#)$}A0hUWCx7qzWZ&NI(srriP)6XlC}*^jOorrNE=v+~ z$w1dtU7nl$YP7NY7o$T1K6J|r${TFFZF)l!nn+}imTPX($fw>XjZSZ`@z1i!d$^zQ za`6eW#!BXBHN7s3Gpvo^Rh&>o+vay;+9;rpLpbZaols$7Q>r>69PlKC9E>Fh7{#8i zX^@4ZI8#1#*E58Y7y9LTjeeeFGKSu?a%NQb<%RSK&ILzow~a1wF2fbwSBl~Uwxqzb zY~u7z{2WNx6gCVE{zCO%Q7G6=!)wPUX@sM+(d1+1KFSPmq!@w0>K_2R=i~tp zpiEl_i!>w3&>=qil4Z*aLd|)(#tx zm;LYyN%;s7wH(#66rt=`3(5S5v%0FT2!nm^&2Y!|M-mO`I3pF$X(u)yG`veI%c2EE zj!Wj)rvSIuPLReSUGPI;tWoZM$S~(Oj#Sv)-(-J)%b|Mq5YNR1Z;WNuS!e*BJ5BR& zc(HqO6Ciz_PnLJW16+VlS3FQ6k`7*?sq$5RbcJ@K@$h6TdOq*1eEXdjYK^;qcEJG| z5aBK4PBS8N5D;2SgKs| z4dm@|^bB8eprXmbTPNXCbYldEx82k|zCN7=ga^rj$9#f*$NtgJ_k?53Gq2C*QAzzS8eplMBb` z=q_fdJ9H}gEPA1OEHuC#NB@G3b&+Gsy{wV&gv(O;d=(G*JB0*s=Mj6;s)yEqOMJ4l zvO)%fC3yTHXiv^xI!az^o5j&--3vXu-6Ei`D)|8pkA`nLoo1-pCD=0XdfHCUk&o9V zm7!K&Hd#};GnYWeHnRI8j43I-?b7CEI9z6T|D$_G+$`Fdx+tgq4AhF?xzzEqu@4S~ zE4O+*zJ3l$kp5S@(`RFUR%31sZq+$Xlodc~Wv(5k*W!&o9#D4ddSr(=nu{Nhz7eTH z^W3jbm)h*t?Xn{6sE35OVqJN8dIwEclllSQ4dw{}lC9C+PNe)wVqbY3Z%DshfzN|7 zQWfWJs58&Ukr%;XK)7ho9H$@K1kv~7>+&Vrl;LsYj9^B8WIx-uuI$^A!s!iInI29a z4G_1!e=d(6oTJBTwVUiMva^mCkY24AFg^Y=y+;E#0oVNDNxLKdifM+fs-6oa)?O~; zt59UMy9#oQ$K)KRHmVEGdp!Lp-8`}o?t>(O0jAGk0g3o_sK{lOzKBiwJ+CCkl6E*N zflTj0D~h%bT!PTVPzma`=vHnHk!TqhJ&p`l7u6DU42@x3{f}2cv*qxIAEfohMa_>; z=1^_>Xu#E!g?j;1xjVQF(=aWj6aywhdLi4Obx<`YoM<2#Ip#8LE&L$Dc%U&U_*U(; z=!NDOzuE;LMUWH%k%(74M%l0)4<=%8XV!+bolOIFZk!SZ|c)gm;&I{el*-oLirhFCv+7J*UU^ zNws>&N&G?D%Bzk&dh9zIL!z#aC{CFGD-rjq#ZG9K!>c^x{Q0PoIHLXY!0n5BAjjD5 zW1%pyR^1$67>R~nr@d10GD(E8Exgn2jcMk#wZu9fzf8J~{~7y6tCKkXD?sEd(vdIf zmR_s5Fbcq6$CWT`{DCy16i=TBmxM^tj$UuPA44<1Uu=J)y7-|#3g7F?tz!{a7?>3N75MkRj#`*17O+K3L#+Px?*U>jh z-bftYDJ0Ihw*#N`ylW=KA$7`{P%VsVm_pMH32mPC$V$#&D@XA)lUN|c|X4EzJoY+UQJFL|7gZn(*P3es~b$eQgEP&ex` z8r58Z8_wt79xB{cRvVsHn5dFCGAbQ952zUUQD9H5hEh}MjF(aWN2^Wr^3bl*xVC7s)mi#9m8mL;1pFfx6W!cd*-}IZKNsV$90j0g*`P1CeAQ$i!5rG3W!}&4X?l zzC;lY1;{X>Vp;eKla1sxyGQ9KVsq=3dqm3cP0Sh(#s|ssTWZk|c+y%?J4BNAg!xp6Pz*ca+w*=6bk@4RwS8|h^~?3ks~v4$>KbHaG=oa$#G-d5Y-fN~ zR&;)xE>{+A2NIc2!$l#I=*Zo2xSzAV;1N~3hHPNjf*r!uOe6atYYH}*$y}!u=FPUt z=ybygrW6@=%c8oc1(t({LI#J%;VesZ@QC#Nm}?@^fgVfLool<}JdZ24Sx)Qa-FKA6 z8?6End+{-yoUECnmEn z+NpFB!iMVJk=*V0-S~43c>Id-We-qExR2H&mT$Pkv{8nyj#)tK7!;{q9ShGQ4Vx@$9krRLtQ!e4y6^ba*uH9blY8{ zP#FiKD6tC}_>K2aM%?vIV09$wau+V|$<-mTP3tUvNxu#;wj5vrOOiuUH+Udu>6Ra; zE-t+@^i?WuWekxNamp)C>(iEr6EP#F=k0>&L0$}hZAFl z5{X;6Hdg}TY~5Xi1E-%blAbW2Kv)cTzh5NXCE%#QF?_Z!qVo#lUkak%(WvgDX(rk3 z$KCy++8RyUBhQK&87za-FFd)rRk%LoG57ttTCQiZZeQ09brB!)t6E=V?^w}2kW3px zpZ0|Ho(4ZE%XoTvAjm#wnvD0`=R((%)iLIjxRvYsiJdGEnup|sG*%&$F$?T$7iiOV z5xm3v1+*ee)xxc^F`n?3vntn?uxy&YZn|z}`X8W5^&o1fmYI?s80s*eWO>B*Emtp5q<(&pk$~1Qe=?d2y z9?aC1K~eW`5C>_1E6qFBS8V(vIISOn9k<0eG zj<`2Cua`6cSJLx6cpkb(KAofU(Yc%jt3^hC9#v*i%YLzFXmChBN14E=^hDi1U{2sd zKQO@GcV~tEZ2kihB^WFy7-}2rI%1#jKLFKluv8WZ>vStBI`$_hiWv$*IFO~+Fex4L z9|YBJ=+n6<2n0n*YESk%GP25k|7e`xB}weg%Ue9}sjWQNpsVtrZ<-g%# zf8)Fa0QtX)Z9x}2wW0f8yOW+g8`to+3r$MMEj%3O@p95@l4=j{P-s} z4OFe|H0s8&6D8$1p%nF>ZO4W8o7YxyQ>iuAxuX@GM?k;$6PYF<2O2F|IXR_Mu|JS$ zBxX>709YiILl^=9Z{DxeD&fBoRE|QzVHl2^=<@%f^-Cx4LzE0OG%(nY5qQnn8BSJ8 zqSe?hDa!g5LgY?0%0INfZ$_=~4GMIB9}U_(36;4-!Z4J((Wz!o=f7Lw6Y{wy)*253 zLCmhDUqzU){(k8x1PI({K(>HS*5Pc0lt!J&OsBrSKCAUIXQ^TVycYHCu?D5xA-@Tg*#ss14(j}w!)j4m zL)JkG2P(ZNNPz!$Xs5s-aKiz_oVLCoxQr+W@O0(w_;h=c2Rg4ZZw{R6Q@&1NHirLg zy->(z;5dH#PaiU>pn;}%>ABSIe@E;9(GO89@Zva85sE9YsMYm?RKMPJZ!9w_S@EXL zpf8-&{o0ILtx{`Z2S6@c9rgw~Rlf$^CQX4*R|g$Wr+G!$-f4nk)}1fT*^)Vswcao8 z+zz{lzwuv0-uL=+GECxv?jZKu?jVFC(`x4v&58gzjoJ^|?e1T_UvAWfQdx?|7xAb$IctI4@aUuLq!fA?ruL zroZpqSZ;Rs4e1aLIr4)gn!s6hn$z})%bSS3BrK614&TP`Xw%ck?{9HR+e&` zt6hcH-JRt#mLNU+-#TyrnoZ(?UVP&W9rymSncQNa5xjl39sI-qg0=-C;tC^R(E_;7 zKtr8y51MNwLIE)DpE^uN(;Uy*?+zy&Vzp4^V!L_H+NS9~-us+)!PYw-D9Y#wJ%6`c z5C&nRIK19o?ylzzW4L-aEa&AdbcwyL6=Z6&Jx;m!;G_304vMlB3qE~ieGi3}!Mosv z@eU`p!F)Q{k{w^AN+*-qbWGakBS2}jl( z(-3`12@Y=;&pRekSAK7%a_C;GkbVjSh_PgTUcJ$R_e)tg7F}ryX6r*8THfZQWncd1C%H^+6+1o^ zf!F&y7J?XxG7u1fbKI?7{i4?X%VNE`tiYDyWQsLz#?7!!ew4W642B$U?mX%w^H}&hnzmCEQP-JL zm9cY@3=%t&0dDIC+YR9&wJM!}sJ7L_&ZPpL6)4|~Ka`4VV-z(f-p zu76fGW?l%6G(ZUWMQ`-LK=z8{B+Zj1a9C%~D{YxM7DY}PpQR=T1n&4^RT`sLvKgg9 zi^pMIp6rV79SpP#v0F)yx&Dfy*KWyoD&qYnbss>4t8|xDDIP#+(kYj)G+_1qS7Ps<4@GzJUkhZbrj6y}A6^^GT1*O^_CLht` z_L#|o@-aoc;pXLD3RY#QT;T1ban0=U`hfMzFRJVZAv|#0p9lsHM zJ=eL?)uQ&Ni%^8NkR<~d?mFLIVko3jatd%exw7N<$h43%zs3p$0S9Bh<3`2LNg$H+kWi>9UE6OeIhIu5eIiV0WI5? zxI0lCt0Xw^{8u9^+=!7+dPV?4E}BO7n=c>@E5S#D8T{=$-<++jQHO(UzkjxPl;3NB ztWy+YePtVjUphSt9h{Nt4tyWJDU}Z4`+b8Tbjtl+E)tu$@~C;wz6}*LWl+Da&|le` zhpTa%yAjjk%1gg4Zwit{x&{XTQ`F)ZWpApAfGKl~ZNRyqH-}}RNkga!^vVZ)H zQ5&~)e*x*NX8Q2o95{l^uElc+BJBJG6ZT`pUYCeL#@}Z2I7iLYH3i^FV_nW^C^a%RXBdAc-$G_5K_@Uc@ ziLfW)!z5)zbMPrZ!(+;;FLWK-R?)u5xA8YDe6Hygy;z)pzu+ab4fe~fafhiigoeX` zo-I=wj{t0OBVy^RhdT1Dv^X7MTExDqsylX*{Kts(LyYM+MQ+E%l=Gmm71rf)T;p3=9F5NnOMB2Xxh- zE&-iquxvx_dRE85%yRC=sTgdQa**OO>iM!CU(xDP?WsfJ@}QcGXK5c<)hNJZVY5T{ z3E{CDUpTfw5w*YEF12XI1_;_jHpFzkK3cSYuQqkffP~bEmlUn2zds$aDOd>I&P&X%Lmp}q90`@7 z=UZeHFkV}KI20g<-0S_sH+x+7%r}20;lKc~Pu+ixR=)Y~o;w&I3j&=Q{OwnPmTZZS zp#0AyPdic#w4QxZ|674TMC&G^Kjz@|g#+obC_>Kz9xm0j&=~%=%!9llzjP!&7ZbV~ zEo!yU>b?X_QcLh~*MC=Rn?On;%DK^HT*b5S0Pw27doMZ<7xzDn0F9UJG}&(wG+dL6ZqWOE?fi#pXlxzh?*sAIPi4AkwE2<)o*NE|klb1sST1vIX`# zL&{Y;eDDYeQZwNfCsg>ODa3L$c9jO#r$mx+zankjekzobo`W2LX^?_C;gXV)?k|{T z%gkI#?5+aJHB5_$vN?6WjeCfLfZZFbe!Y|`Xq+Cmv}~61u^^9uhgvaTqBKd2S&knn zjm-iGvbafq`$2=r#rcUJpenVRg=tjFNm^Gbrw226IQ*{e9ddgwz7k#X*{=JJ-+6-U z*xxq1?&QlO0-CHZWsRuo%j3IyP5 zuR%tYCM^2{P+WW(WIL7!2f_cF8gtAs=9?d+2-eR@VF(vW zXx7vUnaS-~ANwKOsEjsnrqD0?5O<{=oq#dkPzw-%tT>Nge6Q}6CG#lX@SSSFctsHe zPmF;|)1PrY9K6E~YlA7zbRS%~XG^R;VHtM6 zxm5V}c03nk0@U6DNnCce8tU27`_pUL5`q>ZB9-RTV!$keAmQ6zi7QD+cDpf}vjGx3 zsF!F1HF(kTb_akzXmStmobD;=rYe}_(!kTxFmwab(_H(VDcNe@;`i)5jV9krrW~e{ z^+X)^83^p|UPLE9ClZu0M#Ml}sfN2sYnMWO#-ra6EdMo}gB`}J6Ao6($FCNN=a1{JutEsUGP2B;O^+0~XCrunH+i??xx~)>X+9_=M8ArW0`y2C z!y&A{HUzL9#|I5=S0+u3-kHa-rZig^$de#JdJi}371AWcp#Be z7)X7nf|&#-lp9=~EaqoYJ}OFfdIcjMvYTA?qb`2Lc_|qlK4EvCi4xXKSFK2s^Tt@V z)iwCA{Sb?Iq}|51m3giN;^UspYBq8FB7@UiFKjqD)%-zd6;qf7)l18d+4ym)Q1OmYNeq3nuQSV7O#yvN;eIGK22%;C@O)}1D;0UHl^ zlYGGl`8zL?{TBAt86z@2qdAbci2x0MhQpkbc+7Aj-6nN%%x9^sL97#e9}35cSfb`i z)HB#0*;J@i{4;1qPYz@=l403hx4cWO9(@?|dknwAVt@uz3|YYQ^-!Bn3sBXzwI|~$ zIv2?Lc4@tR?d2b{@c6DUU=* z)cLe!<3`vyfn{Bq&OT7u1Vh=Qcox%$g!gyr!x>}QG6b{eAdCK&EQz_T@{)FzRF_W% z1Eu5xhhnR3A|WV$ARXf8252(dwIqklbRSwE{SwSM18mz?U9gLH1&UShA>7C@+_M(a z6%czTQMmD4U0?U3ir^6yXKu~{HfKqUuFokpOz@Kcam12!RIO}Gan;uMkYnpLgp8z* zUl}GKZN=ChVwMypR8_wBocWJBH&|5prx=cu&JR0{x)=0^%!kW3%z5{BN+31Tv!A-- zPVthwns+xXq~h_=Rw&BT?0^~2!M~SYqa|icpa|nyC4YBdrBm@Pj`Df#mNLmbY3hcH z)dc4U$S3e(Mqrwyp1#bP z&Sw2n!aEv`%wotXnZwHa6J>A!qg~t>*IO7b2Ma(TUSgf#i1m`%D5H`r03JzYYs;(O zpTs?PkKn`+Y)DYnZKN#_b5`Qzz90dtx91reM;sACelbuc@T0%X{K&5xxaL!_41hrn zusX#@j_jy{qq1Va9X#9+!3VKA8*HB&{GZ;U^U??{N2_{bqKoR@+a`umVVBW#T6xo@ zAarX-l12@0YrE{Q&PFd@WGwr#nSj;p%l23-$OxV(j16mN6~tO;yl$U+N`a3l%6IxM zj)3YaUluDFDgw2&&K)IXbyU8c2_@pr1-nemuD-wn)Fd4A_hUL^C}pke_vYbTk%sQ= zlh;GJSXPUf?}VZxIV;9N_ra8!^mRbnfQ##p%|b8C#D>1 zUckmV8grKkYsC#W9S$a{_ghjv&u{GSykmqP?c{lud;akCyOOl@^mHutcx{N{Xhs*= zBsO+K9;Ix>1+7?brJgOGZ$6M~h2TBOalhu2HNDfLWiYy<(h0Ty=mCrcCwDx`Z}N8} z_*E{<@_Jnu2O5ib^~T4ZG#uGV(RN{0JW~eTM99*xTsw)LF)gvE{B=6_V0EbqARQHX znD&mDQLFB~=IP0I9Glp;zCDE3{AM!T;$%TcYR;qKUX4t@MsLwCZrEtz-6KO)MNG*DgF`e6W8cKeP$xk@R6vZ_QtAjrU4{|^KvOnx$ zMryYwiSDp_+`6{U>Bk>|9uqv!UH~HfPDJn-;$g}>&Uvw2)fdcx%D|HTMtW}cI&#Z5 zFn%fAsIr>kEgyJ?AuLSyVlIES=dG~z2n`5Q2m=(t4Sy0Saw?i>!+N`7Zcg$UD zt}4Hku4YbeM3TmuwSYfCR<$wi0Ui;aqvbd73-i@B#_dVcgqLi+ z)>h=&LUnm|z!EpM{2TcLn8KElJ^sFj+Y* zQ8=V;KcV9s@OpV>k9|gaLphnRLJ!r%_s21dv%Vec+G0A3zz*wkylC|ir->Ser{%?f z^b*KH1|z9<-nQpCvO0Llcnz^xtgJ_|dW{q&nQlq$6H%K0} zwfGUGa0^m$;W%e8EgR|zivb}c#&$qIePgzSQfzP|N+gmq9b}Sb)0qL$zBuSgS;BZl z@6qaGT#OZ#iI)>k+5~I>f=rRb#HC6+{X59_z5AF6f^r~9?yIoKcqPcqUuRa|rxC(S z`2sKc7ggEC&kuX~vFImPRxBOK{SbU$E(wqYjbTJ4W0u1?X5;ohWE49#pC1LJ5SJfd zM;lMS2DG@F*6AS$lR?0ef)%}oBMkh{wGDwPMs$oWU=CqqXez_f?)J(};I>Y__gL6> z^pIJUSFp2J#m$SqSCz+devOk$hbLkAsK)9kmLh>9;m@-XDf)-JFunu4y`RVA_-_ob zSQqFwCF>V6EG3^2`qducccLy}c-T{k7H^w9c~Q;MGQ&MS%6+|9+P5a=0v90`OsJnx zapUBKs7ycOgmh=TIG|6>rYvI%F<%`ToXPRG%q%5rME9j07La-Z=|NQrX(;GQ+xp=9 z(3!o=hvoj3ijXke4r%tlzQ19dt4vvw9#IENY{#+fbNXyJU|nPwkulZhl=dJnBOq{6 zzYz+8td|!S!7o?74N0mw4b3`%mx+K=Y7=99X1ZjeqD8z2!*&WTb)+rC$ULz4m~1T+|{-TfnZY|JvX1^jBZFV&d@bPVQ*2jm?%(lb|x52 z{Uk0x5=!m3{sZWKTNFG>JF@BF)iAPuGQN4#NkY5%qbw&ulw%Gl`Q@;)tFIR-vC_g& zNDE|&c<(-Kw;N}dL#jr(vx;jwVh70<0<+fK?Z8g_pViXQT;L z)UPlSPxevvS5hB#l(R;%^pbWVaj7MHeXUEzk|QP^vU_NrK@XqE!c z&sJWjijOpcmv4+pjTq*MSw5Xi_X!Jm@o9BalpU$*gYwQ#OsTuVhp-!PbBNMsE}ISg zD+m*+bcOs%T>|nkq-uvN12tzw103!@CM`e;-Xom4e|}|?)&cU*6V;& zCncuZ4^_h=9C&(xb`ORHgmJeA502v!=!OuGMqp5gY~Pre3@se3`%e@+fdtPVi}(t@ z+lL_jOucFIXMBCuX+4o#0eNO7F`{<*nZgE7^oW$6!q$>EkRe|uSN^3x>REM=CfXg- zvq4*! z^=KgE3m7^9NL&dX@dJX7h_{K?`lZcKnhxMBTvm>K@skvgR|<&9t z<7Am9k42Kl6)hW=KRsi4yYWdNR;jTpQP@6pi)Qi%yY!U(j>`3mUKLG7q5jU#-ioF& z_z=1TAaate%Wn9AAa>wCz??hHi3!(k(6Ir(lbLgiHiB^Ux-y65{uEEy>z26hC>#jN z`1dyaKKtB8cUO;7?8M-iKJX|>;(D8ccv+2JQ16*S?y7w`e|MW>H^T?3s_A*$G|9r8 zX#fj{ToSPINsJ-wYaLD>FC0xsNuMWg+)vXGhHcRTO}$JE(~CH3`0vF9ottw9GqErl zS|eX`jgy@^4?W5o$SxB5gyQ1~MGhRJyEC^kfsF)E$Z19_rUu=wrGQE-AHez;K} zgWO9RZ8otjKOZ;rZlc|xr?pcRyxMmw#oMH44EWyjr$h%m1z#E+hl?y;F^IfSyQ!Hl zJA7%<$wtR@{nM0fJjbH|$o3NuJO+P0A%D;zeRtcdCu2{_CgooFMLVS{0v7j?{j2-*tszVuWZ)g_p7^zyD_!kT|#7>e>@M};l*NU zfn#Lb8jFk=+!!r7%W=2y#~^kOad`QRt{$%GyJCa;5zsXneJ;(-0sm$tj`fK)}`dqtH(-* z=UHNi6GwO}Cnud5jro%`5=%>2bUqwJ{x9cQnMb2*SQ#Yi}rqHZ#Q2i_AG<{iO@nF_U0KVX!U^y>>yM z?}acTjhi-|#7b8d{5RHB7Agob_?RPo?jq8+3jg9q+N9g+~RCeECQu~a2)75+ne<=d zkr?Lxz?*pbcEtOQPQ-K;u(vg{7c2T4`keb1I2B3zpt91x%ylfZq+eDqXlRl$yVPQR zN)gD=6Y|Dth|M`PswDt@hk|Rb3ceMBjjlb?$1&b$f+k3;>lzzR*1}-@)AYTRVyCl_ z)*A1$$Fi6zY_S!?K!{E-`O#ySjxV<|fD~ZnT3>K<#~n&*K7!0vCxpM~y?70DEwj5A z5dQN8*mkMwEY4@h5n*oOha>e&hU+H##AMV(CubRgW&wKS9imA1!jIkx582Vx2aVf&(qbX5 zzM>sz%pKmls_@u@*!Z+~NNSt~PlCRQLE7MjVNFU>$J?AohOG2pk+rj0o?ve!#fR9Z zRxT44A8{RuJ}KBlpRoLpj48(Y@W(m4Go49&Y6jDF5@Oac>@K}xnSCjXLx$Aa$MiV| z6%Iq=tEwF;)$Lw7%K?V?Ghw4VdC;l#bZ3okvJ5q8zvh)%?jy_ZE#~C2NL)c;s0nsKz&T-^@LK5sbet zYeQo9F`X$(%kWr&Yk``cI*VPfiK|c*9@iXglde2mI=y@tIar%#Hci++fZyF0g*n*H z7Xy9^Bcq=QIa7DY&jv4eiAaFAjTNsqt-sZILiHndhxHIn3kRauYrg~X!)Cj-PM1z1 z5%XO5)*K+R~>pAX}WkE>DNi;7J%cyEE;T~(t!dz)KN3eCOs6qNR&cjRdC=5rzj2>SFg4HFh z52z`K{?pD)3}dbMCGVRCXK-L#c6rvnCCoA*9z5vKF`+Njrtk3!d83QuWv8ORzrVv| zDf+-+dla-2K2Ux;gB zz(2vRczSM;%J)8}sY~oG8_FN(nOlu#bLofXmy_D?$C^2|1wF0#XD#vCS8ZoMVz3t$lO=A7e1~~NgQC0qfTgWO`Q%%T zCk9{PoseFcO=UWgwDRE^QpWwdKsfu0NBVkOg9m4yD|SjoUr|y9sC?y!#NXj8kXH2E!>D9h8dM*4S+Q;z> zuaal|$yOs%7q_svX7;nMUbvF%jlCfkpg=l+vv|$7x!U2%-al)=0|r96U%Sku(X^3&*X!K7a%P8O@-lWOD8tc0$GZ#1H#dp-SUMn z`+G#Nb@L|T_n$0#^V~BlM=`;yBKpKF)i`a%pgntC%Q|;=Xu3* zLvsd+!rbAW5-vY$dIJ1uhHWo6zb9e%5+q47AFz$EL5J(D>(&sZZl%(gg2$Q9T39Fy zT%c!6@tS4B-58hA)q{GgM^p6gU()ywU~w(3n>?H@ddj2RZ_4NVc!$TKn?~(vi}t7U zd9{IORsU11BSj#`xEHLgZxCy@7)`y%rOd`SNUI9R;*^dFYywnVin%Q2d`=yDARz#L)#L{AVR4V!9za>ia2ltj7_gA1~{g4w`jRQU>|fdm(f&*L&$+bxDF!27{2V+Zg)6 ze+#jsNx#aIk1%9&ST!yFr~F9*Re8dQ1(by0Kb!k+T{%+;luW63R6$j|+^^UCzva#a zSWt(?Y2xwNq(IV1zvobp&J;JOrj050|EQWGd7%xcWE7UpY0$D)8?v+3*vUm$Qsz0j z^}K#|fubDgcENI#^az73<7wyroLLT9tmRPw=ScnE2fu*~Ju%wrfRcyLmni;I8yDab zCKbkj@YJqjy8%y_Y-5;0K#7jy&-DKe+s1vbD9RQ!;vE?bhtJaX1-EE0Fn4!v6fsv| zV2Bc-JkriAQk{(9ZRf$fRiln|!egbb1$62!5^dzyrq6mvC3`+Hi3oj(4a!bqqRWc% zt9!J;MkgBgG-m9*P+E9zl_(yisKr*J`(WQ(8T}xqaf;h1pUaLL-_{9$$Hv9qPSy8v zoa!6aI2+g0Ohap5MNMzKJo?!;aS=y+;{k6`>dv<7{bnR}9kON2^2`(FJ_0H9gFL1w zm#p7lc_RpdJDV}4yrJ!^q3R-wAH?dR`A9i0CQ|s>9Q#XlL5^BWQ&ziByO=dg)0Zgu z%BAjNWWNtV>M3B2i0tGLXRytWWk2i|&+=(*m%rHJ0i^g8dGf=bGX=s3ejiZ?M$nKq z5rVn*09^olC`x;hzT1%ZE_wKY^4B-$0;KH^5L%*9DgLkjP-cac$K~|hD>=Csnxd)} z>$THtlhD$XK;?hW+{AtV0Ypki1k)Fke`$2wFS0bPyD2D5)MXR)vz2mWUjdEIO4%S^ zB#?QC+wu+*Vh#uXpoZK}U^9>KxIIcyOcy~k1rX+`q+xf~@JiKpvurqDLi$lIN7ys=6+Lt%^(TusTzbHmeW=;T@%Y5e1iozb-h+u&0YnOQr z+DyfOT96c=2!6(!6?GrSf4#Qm6ZvO^n_8hGye5M03kvMULeUc%(J$WglnFWlc_5S3 zl89QJW9f>;4j2%9ex>(YK&5E80V@3$k*?r8S%8cAs@MK#_)~tfk}%}MBW!QfkyFqp zqZ_oge0RS9jz1|DI6>i@HnL(eML*))-t1K^qbglZ)j8Qo)JV5_aYoHl`b8Dq3u6wrJ85RTh=~(nSw<~+h_dwW62_6L6 z)a04bLX($~b>?k)S|dyDH$RJ;tX$B41CoV-KQONDV*|2AP*`}eI(6(kCfDon{h!s2 zE`jZ3fYP$mW@W~?g3@_PufOLYI6noXqQkcZ8vY=5Wk;4O~?J_VYI6+C$PBw*)=s1r1?bcY4*0hzdmHZWW z44YLMKSBv=WNJ@8UU_^^ZLk)POq#8AxC*mrEnTLgS*yypyk(n5Vn^oQ&Hc7qguGm%IDp8CvA@#JDWlaJk-lRl%mknKyG~EvE5K~Zi_CvnO7l#JC~~tjaNxW z`>kl)7A0txeTBvjMYX$JHgbX_RhDCyK!surs42|j;btFrA_s#RC!qiKnR5DPC3y-k zAtAdPe_=i9*Q^{Kt^B-Cx_#?3^}y$7ZSX3s4$pU%-!ftASjXT2eL5$l_h`J<3^r>b zR9s$DtIkq^->R;~(>zKf7&(*t_*u37G9X1j#<}ND)-UdOhGm!p-?By~-{l>!NW?UcLto`u<8=(8@|Il#p5`o`rGm+ja}l2ey;x z?B*Mwov-eUGBN4)v{27pgtM&%(X{_+=x!LBr928UU16R^va(*!ooBvFm4CGEp<`-l zdV55t_Ep5zFd|0B{l`4crH~X^IdUclv#VrU&t$gZzm2t4n0EW0+AQQ@$nhNb5^~%3 zMTvY59j+M85bp)0XNV*-)3m?+uN@;olvGWM>@mKtU5hg4i6sK7vXGtDLi8Ea;io*Y z)6ejxu*Zk$RIhJV*&7+n;EmZBX^&gLcpejm*Ui`1Y9L~W#va4PJzz6ch-ilHhquZd^|y*owjME0g_5$SKr8tI-GX|%j+_I}9Z zc4<3&cyAs@27LlPgy(F=W@!RNDl=oPVXt>X&h1eecVIt>x^UPX=xx~d2&X|oMGI9{ z#c{}`oEKSCC2lBO$L{2_e--AvEHDg4h%5;Sx8p)2--tM#6-Z@m1Yy;re<;Ff{(a4P zNaSYdvgZbzg$k&%bu4PKDoE$cBzJhufoct5tFg3lc!z1+n`X))Lx?tM(?IlWsqFlI z5(o9BtH5<(qKs?Txk@ml8jaCvMi;OA(q}g{a%JWa`>ugYZ9q%5|4v)SS2lW-&4Fdc zN*u&UA|1D_d0Xq)tM?c=KsE)TQ8{<$X+}!w%AVYzGsLlvx*EdFaX#4$E!sw}+NiI| zA_=W!v(_zJj*jKnof--y#;y5MCcA%Bn4Fff_2@AdbqNr3Kq6O;ZIViIF_BK{(h>*~ zi7MfKSiw51i`R+&!aURuV`+^q-caR`wOmrI0LOArQFETh@-v)W7C z=fJxn5`)pth>^shOGSh+apH8ch@);?Xjjd3n7hSjR$me=xSm-t)PLgcKKT_{6=W20OvM|r`(fjqYFj{pBNsiIiXYamB|NE_Fwy&#VSGP- zk#iye&hiG;?S67J%lestO2h&knKAMEMVGIM#7ZT)&9w9~jpDE+6UbYV_&;P2#v&hT zYXHjG8ArLBO~*Z{eK&|lG=)YB9G zA^556t5ec<)fDl+A_Ww^3xOj%CB2L$eJ7aq54eOLSb`B6LWB`paIpUT)C&uf6hVV4 z>i_@Y{|6qA5;el!k23MRH~Ex6;r|)z2HJFW`$D}$qrU&n2L*aDAA_z7ry zH9~Q!SL4kxfqZ9T@bIe@*j$+my3o}?#&Hec^k;x1x`UrE>sccj4IA>qf{0*%Td zkd2U0u7w$#z{{twh(WK&uus-1!o*(%>5K`-U&1plRry zgwK0gpR5%o>)B%vJ-UJN={X4LpJTM0yQ9Q-F4r@v`79R@wH+59GQUVv5j+Pv$_PrC z_^xc3giHai`(7~-8)ig^^B~s=Cg$ZDbvyrOp@EOX6lm_d_T{lJG;m!2-Q`L*RZ0{9 z%&!2#F>La@7tI~oKwa!p5-Pxd*hTggG};ZIR~J4Cz#y}>m2RUl7XO{2N~l9eW=oL* zK`xe|c_t|uo(J@dw_f?I(gXTdL#mhWSWVw+S6U)Je5^h80MDcz3FW1Sc4>|}|M&#!)U+CaR6jQVXV#2&~NfQ}mjin~zeUc~tJ0qJit6iz zqA3)$A=W#Gz`~=l+yVGttgMcpHNG^!y~nbnE|f-A-5}HZ$f9apJ6B70dEL%7&#X^U@IU{n`o@utSBPB)eRn)RX2yA|a0IaOCBv&j`qoFWz>Ib8%fmkA zK?#t%W}1#>8$Ap}qN@@RGIO_k7B>NVwmwWYx;666n-yWpx3)>fr^L@gD@WBV93BN9 z>)c9O$6;fn-E~k!$Bm-#yq(_Kc9&7VqYDV3`_+3u+bNsSP($N?XQm8vX6o1w_}XD9 z_fpvUvJ|ryBJGZSiF{HxS8bC690jvcFV@L9h_J^gI&HNUo0$AT{U8qB^819ysg?%- z(9%1WCRLN9*V|=-@t1m9*)qLo$FJ-UzLy(a0O2OQ;zY@m(y(Z z6H;{5B3~cH00nwP2m!k!5E6RJF4BYyjUXoRoCBd_e9A#Q!#P-{UFD{u(*4#$9}Et} zq@I=A3QxKj{n|poqHzV;e~JiD2zl=xEO)kB5mE!IbCbfIlhgi!Ab11D$)t||hmMk@ z<(brc(e2q<62>BNX@Po_X$1l3W7!RHZqaV9 zo`D5<1oBLM<$@swO`x6zvE?M0G?9i6^%tQ9;{|ii`#p@`o}s;<>e=Y=!G>aU4JeZP@H^F2@}*LFulHLi$BgWGxmpS-;MMnB+Obv6Cov{4 z^t8-{dTH-hLdg{nLi$^oZ8^)D#A(gU%i&D4_zD4-qV$ZRR-)YB&(*>7&B$IB_&wVY zFh~*nJ=dotyi4{3#$ol%dNb zEh{~sN4=%$Z8uxesKAZrMg>=tudyCs5pGdaEc-Ko*;qEyzl7vBv-uMiI;A6R9{axZ zcS(PxA~`fX3i?GQ;6ttql>gUx(Em4Pb*Rt#ea1(Vo&<_AK=a zrdL+}PLF*fg}guhjvHVD>{7Y^V9|Aac82!P1$fGNl5R&XQ2qHrpy4qo!dm|Bs?qNl z{BIC=!2kD)!_P2+96EOF>>vL{RHO?40bh}y2mblu6O@R($g8bZ`}+)`FoCLB?#~xq zt`q|311huq{eKzz^9MY*zG%ce&+}D^qoLMfU@LkKRG_uM`_b=EUws=F9{zM=V?(j( z{i0^dN6dd{{$E^wv8S#C+9l zzOPnmUkDgc3Zu0Zk>4{COy_Qjg*eigEGgV!hHJnhN`ervl*eH5zP0=B zgQ%lf2qZ1YX!5w|)#+=jO9h6iNKgtE!U3iyT%AsbzZd5yPr4|04&^HW*BP()0l?E$ zLQ;fij$%6 z2P!MSxY_d{3F}=-{Cf*d&p+*x@|aJtKy>Sd3_>>l@RpBGf>hK^u1_L z6!zKwz42otxd8Avk}ir=wg*BwJ@BUmtaI4^sbiq;7YVfZ?3X}VGXZCj*po7@5z17Z^+BmX{D z@-XOi0c<5A{z0Ws9XmVwDd_B8cnI2Re{hRV@B@US6fn3MRCU%2Pa*-}odJl@ zisdH5nmVvlzhEg8n@SXTQhD!Jm`@w*FEk&5?Nbs!sgN!J@cP@TQ3%+;3tvR_@}eS1 zT@Qc0`XZm^y*>zFfdq)L`pz#Ck)i1J7sO=*eS%t?w=C;VqIvGFkBnIT0sGetMrj3j zqocv_w`o#R395>F+lf?}O%^dZ?9RmMAW{7d5)#4CRkU|{a*qITaS_MIw$#AuRm_>q z&ktnSalqgOCL`7K(WWF0mttY$>EUL#!w+pN?!RO5Q@zPXkYmyzsvpsQ`}=`X@w_aA=Ud0wak*wu>6 ztSrM?`v05&40;1nOZORqLf9b9M zyMlRW1kg|4O}>Zx{ksD!QiM*W7@ObQ8;EF*${0@9 z`860YoG#*WK#j!z+_`H7qD35Ns2;>5z#qpeE)*u^jlvR$%^%1PTRuMSDoIJfL>f^0 zeQ|vLP|{-0O+NL9#P2)1u@#(SFC;o%!os5yuX;jS6r|FPC}wmdLd&MEDwJ~E4s2H@ z9?3|u0a!SS?f~oq%0cp6XHNya1G?nX*n#&JNZJ0R$1&2z_r9wgL`>^c>Jrf zaBWV_`w!UD9uLm#?KZ(EbVBM>6CaS`l?!wpg!$Hsb+RyPQYK}W$;WzLy-s;788MwY zf`=V3jk!Xo{#+|GK~MZAK! z<&l9jgq<+#9@}YoyQ`u?$#@HgV1P}_bdn1FBc&(;5{vE#*5i=scSp&}Hs<(xbJ#J_%o0{-Jso7N)#tNs zqRq4cgC{P3W-|i^==5J0JhZKiy??#5opuGBgyFGD)2#W`w*kN-ydU2zyCz#$0CT%aG&>E;~3Ud7n^Hnh}Om{Y|l@=m>iCRMTKk5gVlbHnel zXpX6$zvPMRW??Ne9byvK)&a?W;;%absyZK-W0bp0v+km}lh~o*?xOt&HR^`<9Ug9b zbG$T?Su|TevXG%(mbk$a)7O7^lChKSlOe%=jg2;8d0^8zOgs{Y`Irxfoa6E-ArF)x zb3!4!xU!fXsp8FisRs@kQk1#MW-ukaHEE!8w5jxyG`Uy>2J$#ab z(R$^asipC02{sdk^z%L?tdIhU*aZ3Yvaewf#v!^2A#QiD4Ix27n+3VwL&IP9dGH2N zPNY~_FxXpPs9_0&*qe||Sx;pB7$ zk)$hubqvOl68B?d>B<%O5~3~W7-XXdr616(_p;^PpNam?@QqGBPO}$1kGej@mA<)` zeT^|X=hY|tS1~VgbJe&L!@_fqHktTQGol~lFmLj?ioFQgm!cZg++?mo-ILAAMI~?5 z;uW?E7cw&@r+U+^QRb==Jk-`-hFx;6xaY&NOAFl1%x@YaJ#qqeZy!?^MxGgXa`^cY z;>&6rbkVf2wMxl=1V$=ME2F(qF_$s8_KF zkVq(rGQZ)=s>sw(6_Tk}_4-m&^y=&O7vbmHpM+K2nz#KYPl}9+XunrY?oLj>BA(29 zHSpcNC_}3;_xyuW{tM02?p@HetI5?qj7 z*lN&j=y4U}XVyFBR&0G0&`|Rsh^8{_q$ak{MNSABf^+mp_XziJJ!;Sl)hyS{op__3l&c~41>)xL zUDw*<7v$I8@P%QOQFjALBY$I)bG65My-xii`7>&2KJWd_f-&v|$UK@u+Pp*K#605y zS$%C?z|rApoac9+Q|}kv1>QP7i^te>>;$_DG?T--@{HL8X|^q#VF)3aR>}3 z+~1fFE<3#v`eQEEGfXo_`Sk$}nK8vJOS`!A{V_>iz}xMt}IA8jzDDNZ~xN_ht<*-mwNd4*GPD}m>=A2ztij6i9GkEA=G(n$844PtukKn0ypjL(4X24 zdm4K@UC~m0qqCOTdGUfjm(_uGg*3-~>gkuJi0 z@L=(etK*_y`=1Jo4+Ly9lwu`U3;P#-88jI^4Z#gu645@crD;=tZk%pcN5q&_KdG{1 zeh~lSw{g8|@T?8NZqM)T!X){6k0Z)JW?o7@{OlYjSITr?#%L42!EtJ`)m z9OoCSa655DebauQPc&9s+&l2_JG_SNn~(PtW)=87^fSKe+E+fAIxM}waKXUnGQ`ro zWe;qNE+|AjeDw(Fy9JWXp|4zOHuSC!=OeVTpXgFm&q*$ki~wodl<{}0brzb5M9Ghn zYyo1V8E#)u;_O^`f~lO!4>yLa?6RKt)9r~l_xQ$|q{d~2FJTUt7} z*f_c_-nf*28(1&}eHSF82h4~MvZ4mlA)x=mR!h%S@0p6QxuZRosfDANC6}i?3_%A; z)KeH-+FQDs(s|myc5o5)6l3_?LKs{lZgVrx{cYlEC&r-nOpQ*)(b$*}_U#Lssse-NAoi3^uN=Fkx~ z1#%;vaPxBUaQ~Y&=qif1E39VgY57`T*47^I42&WESP&}uxBdTm@}Cj^tEb+7dI|~& z{CCg)dh-A6sqJFvEaPYohIAGGkGTHX`M)3jv!f_CV&(tEiGQ&9?_I!WacoiUf6Gi9 zJIO6h6NnLFE32vnzJZec{b>e&S-=PJ4L+hyrILjpAQlxPQQX?v;E3RekMpmRXwLV~Hx&ZW{>QH*au7B) zKDt&a6ILA3fBgnZ1qO!@p#0Yz@HCJQg#^++%4d3?4*9=-vGK)GnIuvE`?nuc4aOof z5UTh;$76wB;{K0ukg!-7CEIdvIf)4fTf2!Z&g_K&s`1RXwc3&V@Cdo#sc-tHER_m4qU3+_+V!-U1mxG@e<(Qc@Ww9 z?e>hDLeRnRJ#=rbHkrM&sUliJOhHl*g%G0q(y!Xkb2*Fz(m;`Qh9?^s%t**}Z#5bp(KpDIYto*f9DpnOu{(*-kdwtTK5 zM~{q+9|lWxbq6xH=+WV@^#xB>%!kq|6ald`ck|Dyj{p$~myHx@K!lYa`%NMgjMw?B z6(uhn30=M)uX80o99+CoX$xjTbW*$?6!`+EjK88tV&_C$m|-O`?jbH5=##h~05at! zRu3tF?!@zkl-HFLXD>xk3q2lW0d!+1;a%_~K(0zO^@TW?)LI9;Bup6e*`DTG_a(|+ z>P;5YPvgTCiB zh?sQ^4UQ{;WqwIAtxsSAOk?uf$Up}((l5I|ka7=P*p{IT#Qj?;aexYTY&%1KG`J`l zY-`U;0T&7iNa}CH{OFA#+(D*PXQwt*%DM99RRAw4yHdw$bbx9 zrjRQ<2Gl=Lq)+0hfEGM_a-)er{R+aZt8u^rNsgCDv4B2eWTDq-0}l<;1nl!X4i?d`R^Rp|vTEXf^FG_|qrE+h2>z}9vgxRsiqC$Q zf5x^jW38leI9k}N;FmNVYunN4TM5eZ+rtim^$1#j9)Cv9^7cw_JUm|Y7^`=&7CLN2 zjr|#`eDvPy?&f5K&tV?Q{^#9uVa4sKQb(iJwRm|NUjHem#N|>*zTfY2Xl&9%N-QDs7V$par0=2jNawd5&$GB>L7n$G zXy$X<)?!p_`kvu3EQH=C!?ECZu@Fux+nV9As1K9q$7zDyUX27KRohKbn(fxiIlSTX zDp;4Ib7(u-tNq!3d9=Fz=i+Dbvory(#Me8Vkpws5D-UQc6+o|F2J2K}-c|IW9B=#I z;c(TpUS|+c3u^(kT7EP7A%u1>bTEeEvEizWFT`b#cf8i|=IfKp5wkMqz+@|_&u1;bbK3+rw%cRZ}GeHWMQpEilM1f?2_Z=TaBwL?4 zLxnKm;Gi~OV`EEqkl#-ZUGh8`5?ueWIB|Eyil6<2e6wz`;C6s;MJO3;D7C2}P~ z*mj)B{8K1}>F>Q-L1P;CpMD+_%3Hj6!KirMc!KkEFMW@~v~{uZah`HKa>9ZG)zZuK z*{=^4u6{MCgfDRevt4YeAM3zaj@>^#_;zP(?02Ru#b|zj-gaQy8%K|B`>7$)0A@1% z{qE1tJBnuCPsF$W+17=ch0nWr*iLUb^~xkOy*4OP#e8bC_nI#j4Y-<5$EQP6-a8Ge zuH1*b7U}nin|4p^;q2FQ!omkSrDHy~JoE_9gOM0QNOajBdGw>8gNJp{D=-q~k|yXV z5T3Mz-tLM=$scFn*sQ(%M-Lj4nyUfr>gb_gf)iX2xPI_C<2@^~@|4VmGE`M&GjO;@w z&!YG7TAz;JA*uV}Qd`S6s99u`kPuZ=x^q9PGuVbNwmu6hD#1z&>ovOJ)ONnj?nPAS z5yYf!{zd2*-ha62*Y4|Azk078MeRN<#nrgWXFK*WtW;^a_%@P=vE))@zb^OYtTaGd z+=KpkTM$i#X-BxeD36E5-JgrvuWwX&ny|$}X#Gy(or>+}YQiO1@q2cTu{!C2;-FO$ z%)w2Xv?6p6TAo-#HwPb&87E}8-xkDOMrPz1|D=Ty@9uE9!|x4VU~7t-invj^Sy)@X zdafLw)gbx$z^{k&P@dt#th<}S>Iwq3m`QhG_g$Nk%`iJ`e6`4t&krCeQ=vKP1s#X9 zxWw2iw|@=-s2C<=LQJ*0ii=V&J_>iULV0|5Z+{;MM#C5J23J|u%a&06J7DDgXBXe@ z#72K+Xl(8HzpMrvCfda^pCgLVh2&`C{N->jg2Viwk+Lw+qfTNz({{9C@s!KhM3I%ZT*cO#$`}+44sgUA{dN?%sWu2{_ghT8#>2P zJi}+^8s+y--dy}$NLwa$4RxDm7txD+#4$P&{95G3Nl93XDR{LVhr8;b|3P^VO}FQX zGh6tRJ6hvSI#3Phh~$dFD0&q9Q{|xxfI)ijMIsqL#4dW zcWo2*z{ymw1glE$R(&6gy0bd>?MJR4EX(hlwOFIlYKZUrW1wDZMVZ4sa3Kks22DAg zxg$u+%VIb;LZO_TRE0~egsdt_J&u&cGgtahlxDdNjY&e&&1G0o1R3h0%cJZU;6Q>| zAGr3AfW4c($+TLi0qz`H19NZ-Wu|7UT583?^X6Ca_^vLGC4iy1&U3oW9dPyL*Y(YF zuPpEF&*O!vS!}flgb?!QAw&2C!kcufUHu#+=n;wxx4BAfP6M2^um6U~l`wzyg@&Dw^0i{_QNo0;w|dn8KME*3mqnp;xt z>cJOrRX%kb+mtpPM=$LJ);maahnk0FuassfV&b2U0zYA>D8x&;%`_qM?vn;ATOPF| zb!qgm(_ZxyTQS|kAM;|2y|5Kg{QH!tFk5$>6|O?qQ`( zn%)LsV;lw2(mkYX&xu$Xd_3Yh)Gr>78N_o-y}G!^h1F&{^3(r$n`t4@LA_F2P~U>x zj}l(CwW>6~#3$7O)opQ1C9l_iqyRG9paZi+SvV+u6uUFzwX(E)E=!dZO zYUSRiekQK0(coN9MVY3<*R>1oO8sw>ahkMRa|%(+Yn#t6TSLP78m8MG;GZxbj_k8Q zcll4nbeVyPHM!e{oBEM27Pgz>lgK{t=3P2G`=*@Cm8qXIMPS#|A=E5epbazmde68* z#iNr*zV{&D&gUedClZt74a#HvLhG)kUl+YK9o(PCmsy}nk2Of8--CJObep{v`3^gK z_sXWtX+-Rzx5{>+q^XR`>gm%kH|lk)aHZv*X3rzD)#5K%e!^u=UBz@+hW3jw@3bspkx95OfVySf9Ayh=BUSVJWnw^c_NsgYn>Lj zApCYe1~Ws;)6Wu^m2o9bLXMr@hLVI304SUK*8 z1hzKyo7!Z1d9?v8ypft*ws%lJX&kuIYBv@o^Tg-fWuB8mpU&eP@??1a%lRm@=Oa-&6edX?^0jIDgqZJ;Ca5nm_{CEDx1z&-0ux-Ty(VYiK(7 zlkbmb3_MT?ICG4`BI+>N9zGkTA~c|P*-0>hk(!Q-XXzf&IQY9<@Z<**pCJ);8|uED z9ie25gL^y|>z6BUxMP^5c>9K3BdFa|a3RpqCVn^x1RYw{ti}vQI<1;`UU|NxCKk~# z6iBWU`7`3m@%H9QZX~N-5Oc+JI6}x`Oyv=0FzO2C%g(!#th>n(+Kn`*8tRkC(}wovyEDQ0(m(~%6~+kAw6O7;I7U)1f&ugHbv7RH^*uX5V&n4K)U+rpj;c`ONcuHbI6pLV{ElXx%e}I!M z&vQkZ=DGLn?iS(lG8)}PeB~YNOA+z-x*n0t6(h!dtNYF0k8#j#%E&sbaH*{9C@&K5 zL%m7VCB;~Bu$ElJy@b~qnH@dDb0w-`fU8ljnnJ?ANhKXR*3hl^?%}hTpTy^DJOPgm zI~;#)6=o$p%JLn@%o1n|id~kArDSH;DJk0y!F$ZhG5+keKC;(+EM#Dl&@JYOqEcfP zRZ3b=BP_Rgt5UgW{&D^tzXmp5)S<}yDFd{Oh*n@Nq{F#-8j~>X5Z#UZHmLgSnQjwX zh~HD{(5)X|(o8a5l{S^(2-Ao|n@Vsg`Fo*)cdHQmccF{&?}Jx+Xaf>~pFOu7k5d`S zu4CSQ!vx(e4O$d;$%-YAxKVScTfR3CH~kJp44II^VOZQr@L4b_Eh(MApwL4?ay?W0 z-OUxozs4@IJ85@0GTdC~^CyJK3pNO~mxoz7bpc1jgmpNcQT~Oc`>f<(!Zx=SVkpBil{zs1#z7XQWuXa+~3C@TXHC0()I*ZOYqnlP8hV*`i! zu1&Aj>X#Rjx-TiH&Qh1(1X#NtArS@pz2XTS7+79Ka`z4I;NfgsigUj7kk8V#3CEm|J>ZroZ3^;W#dCxi_wPt>RJ28VU$C7Ayn_w=x|pTL8XwSr&%7guLH!S=441*-OZ#V^s5vD1p3W^^)Y)w0meJk=fjw zD*k8ek;e+yh^62M`~dFJTg`+V24aXOQTrR@4bxWiD>kg0;ALVf!`Dl0z&}$2p)`lU zaNN?}klS!a4#NvUDOQ6RI%pB_Mve1OFt z0AafW@BVy!WqQEv)sHxq9l0^AaMDTZf%a`MnL>8mxHm2yYQn=f#+8l?wWGJi;H~} zP2GZ-hJwN^KlL)8`FpW$DKv$5>t@!j^hpwG_0M_7Z+~Kp;`jVXAnugcnIO>NxuW16 z9Z2S`l{9}+&G=SSqrv0AA<}h6ZXK)h2^#hW*UaitZ77Yb%YHZoMw-L(RKBjd67}40 zq5`*_9~<^Nk*;HbrurI<9sSyuV$(+H!K=WreXMiHchbW^knhbqRJKtLLXGaYCnq0z zU}!6Z5ElA5IL!Uy5@rv#$}SWb5`0IT@_#N78(#~%5lKdtL;5A;GIT0~WZvW^|a%%wB?_H*tPlH@rNLY@p-cV6uM;sSNR#a3Oc_w-UJ!Oi5 z-OI`7;9L?#vHc!qs3h@#xOs(u&imsXx_f0R{NeYdZ5oAYlLZP7H>JpVOajY;lZ2$G zxc{WwnKsm+$vcT#-AcVBfk;JpMZDF7WitU>W^dU&Ik+P>#`~@g4dYdNO>U9cjJuX) zf(|WnFzWlRx|6MmLaN3f^T!f`5!|KZ&S9i^uO^Gu9}FJ%Oc~%(c>y0~tuV{K;%10A zFCs@X<>y$o6lyNU`rCk;TxNed>BEnf!q|wzwFe43b;{!B&A=h(KCl2wE%0a~lF3Cx zg1fb*S#-vPpEglkKGOJKnii9F_*1XFS!t03ErGzOWQ>n3W zDHtbaQRxy7z%^ywI%>L2OzxwXR-RxeS^l0nbPT1T8pgUk7HII}H*9k0!5tir=fEt; zX|vzx>dw1Q-BHZp-<9;^eO7@@u;g*iS_#>2I&jp496G}Bx)0v~;9`-G z#uKYOE)if!E$M6I@-8UAsV5fFdD9NTyAe1f==e1HCg>*yT#rHclZ+!>I0x5@s@~1$ z2*RF&j+Q~!QGNNqDASSekz99Pu#o!(e!pNsE3uLr`Pl7>A=n;!+ z`oH;!6<)ydQt<}lQ0U_Fma@?m=#&vis0C+|HU+>eEp1X^|HfW95ZG(mpo+630537* zEJ)h{5{fwz$GY|}0Qadfc=94sg%g1WAKMxH;6MUME>an}78L+KXH!0qa=rr?Y3dUB zGZDm%)O1pJfJ!gRhWR%jK-x0%?Q@=gvX85I~pDhW~+(4rp7x zVr4}E<`QqZnzNafH2CC|TC-wA%oNkiiYAO{AgjI*!CT?I`%gblyUR+chXylJ!c~~> z5H!rp`SGd5z1rFK3i1uY!q-hjJ}-I}Uw+c@Dp{<{Rg0Nks!P{^)dl9%{s7G@9Q|vk zU)d|P|INE zd92x{*64OwtvNd{HauGuzheiXn+rNtbMZ}nKqj@w%5ivrcUW_*!`zw<+FeX1!eZf# z3qRVkf@N?XS0*3>JwQWyU=FLH=50UkjSlWW#dnMkj0FO~lbbJ;qKe_fo27g@KV4C( ztu;YFL&`4x_=Y{nQS8U%s|wQL5yg!Cj%uSZ&~Jpeou>iljzVGAjC)=W7YSsm99=oV z0WtcF_1+Gn?-y}&DN}6R#=1j;*O7#ngx^+VrszR*<)6h8J`n|$bX@|D5hH<|C52=$ zy$`2Ai4|G}{J%Z7-xO8pC!%oBE)sC8;9kzj-OOHs`MlaJeGEk7s-I;q->@4rt_I5+ zdB~wI3|7?nnoI((yZeEk&)e0{W=mXdQk)tDEo~A3>Uf=W(hD2VetWkvtd;p)%pWT6b`?4ObnMwI}TO zvhigAkCepdn@Smx&tXh#~1HJ&?9J-aAkl<`?HWsxwUMGs2-wmkHudY<74BW zur9@;)c_5c?q=A3Ttn?d%*`!<4L`2qP4Y*Z?#P+mFn;+d;Y01RjwMGb&e-zW@#26~ z+p>k*^%gkC80#Me+RHodhODWy5rsbJ*apIp$vM;Z&!K$sDzWLX9mjd%nP6Yb?|mwL z&L(f6<0cg@ba{)ngv9+sKnpBl`f3;{_Se@yV1)mqdn_I(uyk>$pI_+f7s6A#5j@RQ z6NReD=5Qf70mnrSRk5=~2*}F9y%JZqCI{kwnQD_j)AHliib!7|GPL5kLhR3%@5 z`CL5ig|l=15=#W-D#+6Z%fwGMH*^kcEl4_ci(Sk!GRngfpVXIRwl9d6T)*v(0`5dl4=KJ&a>4JCeFh9;LK?w9xSn?IlHrdsYA8yd!acPgFtQyBopBl$y^^11-AtsKQ` zsE-~KykkU{C-;siCI)^RlkQM zjQr`V?@OUJ^Z@iG=!!^H?G{tMyY@-80C@jxOMTPtW_z4~Stdkio&seZb-j z;ErI1iBUxU9EH7dObtof34k`y$d9j7zz&X_dhO*PBFXnwc~t|}`J2l0@1dX5>h5L3 z56x3*3u$9gq%#Ktt~?>1gL`_($5WxplaQo_<0B@apSlVo4bo2J@PU#9SyrTi{Y>=j z>VHpGf9}f|W&_7p&7*{L{$W38I)}8x{oY4F>o52EYZ(1EOr7(VFlF94xdGFf(>ts0 z$X|Dq^${baFF`W=FT*+h4~DzXak?vq)C3#DdAQ&-@E4^STY9qRFsND1BdoOqpu{&; zmUBcBCpx1ML5q1Cpk3gl3Kd2_#!m9wiJ&3EQjF zt*eHSYkc`6=dZK330xbQ@{EOs?s)qYV|>!CAJrbH9IQ|+QFWEaJD^8GWw!1dkvoma z_v-O_gqB|0nt6KxXCP;~Vn@PXTEO=>&zwu0BYr0ZMv(~&D)Daf^2YIeW00^WtWH1k z7EzC&Fns-5B*C1P$r|kADsmlba4hla{j?KxNXgMwf3RSmE3p>;tDfqi4Di9x-ZGT| zi`^seKry|1Pa)X5Oq3NGd}Lz2dfG%ahdbAMW`=0AqhU!-& z31goDS6HgVN)=iWeg+i<`fYwgPrN1MBPwyH-C!06v_U9?5|;;Iaf{vt1R zdnahM--)HYj(2}ns}SLCZ!sl4Re)-Un-BPm%vae9iap>dLz~sdk@_T&_;oyu@oBcC zw90rn(wEz@A_^Ha9dB|su0Q;vM5G_VfQL}@vGG~=rbF8>--bGAaeWPXDgpa1o+lea z`2ZIhFE^K8)vI+dLYEzn2j`LSZmULK7T6vr0>=}uY(4dJ_sgwG9`)~A;)X?nTPDV@ zobr}N+XqW5B!S3~gIMu1&uh$$E2WP6YKtv~(}j)!1owh}w#MEriXbvhR0ta`s{hVS z9XNk7%tmjk{s=%6KF-q+Xj;(Wl`d=65kzFhe%QOVdnBOd0n&Hbj6sO1n=sLF*!%Z2 z=6kc%;fDyQ(CG#wnDKmpMd8@|V>4N#O<9&?bPw#8G2V|>Kq>F@d&Y%#a6A;inb-RE z#f#Ozeb(K$+;=BEW?RC4L)k8Mz@X|mSqL|1 z{5P6K^PXNEEQeWg6sN*@T?zFZlwBCoNmBCL#2GVcCB+0YPHvbkYaptZn7dhiz0oFl zS~xwe6d%kOT^MD%29!kpn)5?)%_<_hMo32`sPnbfU<;&`^ywsDO?}N^%LTq)KZHXi z8Yo*{FVqIQgxF*U8l1D&>I&|kZm61!IDd{+&6;-*kYeLYB$CSsg5}MWc?#Lf*FSl) z3dSEw>q`F}wE8P6z=5-*s5aNvRwGz{o9*#7E(nmWtX(}kpWbC@u5YsNQdUAgpv(csKdNGv!~N(F78~jD6t@U7k46a&78?A!JM@ zh!|iaWT|lPP_weCh8kpDI0@+_ZVp4#sRu90aN-J{rCoov`ou#5$vNb$kOO?@vv^%O z2YD(-fH|LMOG_mN3821F{;b>Bu&n#V*~_2~Y=>9%onO?i0`Js82++Os0x?EJYQ`*n zB?k4!c2|9Sk(R5CG!*0t@3U9AN_t2AT$1k7rWln)@zktP2ARH4XN2FjdM^{|^f883 z!g&ANo&RUuQsZvBcZNr-|FH?pHKu*@SSG_50<`=QCiXKGU6?Uq61ymQ0Daz%x@?jG zp%Y1nEuy|i=56clOeMOx{@(tA$Ith*i{8q^&{bSkEc;s>KSAtr*S&LHa`^qpK@9tfJE^1A+4Ci_4VC%gVO zv8t##TK=sVnc!_@H|3!%`2w+uYn1s8L_*0+Mb5W4T>zvGcqBZ|vU$sER zlsdX%gV@GM7I(6dd#W9Mv3Bu8y~3O00d5bx4k~T1IxS0ST41%79%oYpm9|_LNd}e} zHU5w+n{#NSKtby$9GQF| zkgmn1b5ISpj2uy0g|I_kw)Btgh$QC%>Gd`wh*bbOi;JuQatst8lYpou9RFe?}(j_l~`$Aor;^a|6 z?%^fK;0twnOnxMUJhmQAHws*{b^J$5@ruy;?6e5qG}Y3PB)Og}wr z*0xY_BvWi4>QxsO6SQm)Vswl)+9hH?Tg8UnMq7_4JOaf$TI)1oO;{%LT(WMzhBKJq zLm!)zI~tj?!;#$0;07??KW9^Xmi;78NX?3v;^>0TcBXL7k=puo^_ZO(8rfxGqhi30RG9iO>xj=cQ#d69TM!} z-y;BB@fxdV1B^?|&}dD5kofYv20-Dfg+-SUF&wl=w~$9nag2z)An?%pF~T#ST-q)! z#TV-yR#K@X#>OAQ9p1i1U{N^4sJ1F}kxOOfFDk8KtB*<1FgJwTOm8ug7M>*kTrhtt zwmate93>SfQoQ~|)L^EVfkV@gT<$$R#+!{a$6=U#0;e##e8o+w4%E9B_v+MLq`N88} zUfh1U*(MVa3C+{}SIH$D9B!^v!aI;qNv+^q=tvk28Me3hps60a?pOX`6K17K476rC zPsS9ZNoik`ySC`!>GJGey&3ReMvE?24 zxo3m*j@S}wvSsM&%&CzTJP6|Qnub`?6 z9R+RRB;4)I15UoAX@WGkrf=jMyev`E`Pr(fqET41X0sE!T|~dnqv{b)=3Sy9@&4!LSx@Fo)5(+Y&!p z6i|KTqgZy7?RQo@B5^n0u+ZY?qa0XeH#K}r;OxASYTX}*jEb^1v--Y;mG1@{x3mgY ziU6$Q0AQ7j-T#{v0lzVrXR{QH9^R+udb+s(yVTOHa-(bU>l7g^2~C(M!sk7VMxX*A zBgj~u#0t_dl4OKSfsSI)7K|>4>qU5 z$&3#KE>H(k8zUB!SRrJIg==O!U-?TtEqVA^2Z02+lsh;kz%dGn16Drqc^zALfg&xA zfI%Xmy{d^k&$mSEfgMq=a@5nA!{mA6U5zzid97RoI`|oU5vpBAkij!@zg@?bVF9-* zby(go6Neh2t~IX}lS8@)D^DFpg8CaMFE(25_GED^UE_X(T@X^+Mw{S*>pS1e<%nQY z0~p}I<6#1kT^S!OXHRjfG%#d6cq>lAZuyXJOM z^oT#UOF~$$!DeZD6a-Km3n>`M6q2QY?l)2$Oc7F!iBel^@USDb>KQ&Ab5zULIFCKU`aCD?H_wqMj4TbB1VVO;>8 zuM36Iqc|)eH72CRx6_f*HYB=3rH7;Ht{|?E;B0z`{8gjNIuK2XsQ2Ac-Su`cy7KM^ znUbb$W8e*hQjL05-V_Y|px1X;rso$pD6bam;8YURZn>_$&UF}qnVn7TDzWkU8aqQ(M!T? zb$0Q$V#>0nf3eJ%x&puh<1k7UkiaA^A#s6%K~0!&U@TPS-qIMU>F>TU26nm4xdA$R zH+g&M|M{i7MTD?Hp8xvBMs>8##@hzn+-#E?oWYY%S5l_+_O-E^!&@v569%84%d5;Q zN8#b-G!$sO?%Yy}!p}$=%wLK;=`%Oh2ku8asd9O`B2n*~CC{t%WIU-LKS32!tJ)hrKm42NP#?CCf?MgeWxc0CF4d5`mlH8MxN2iJr zJ!;q&97<=dn><-SZ~j~@QTt()MaaCItK7K7+ME! z;m$XE3$A>qR4@8q{81(XfRAtuRm}e`?B8U9HhBw<(+QZ=YPbkb4-l=ict0s(3u9e&(q12BtBplDK_HRpE6YpvoCvql)?G&!2{{8TLieEiI`aZ8yFlPm z@}>!Q#EWy|&zdmLv8bg?-2=IC8SuvTuP0fI2h9)?NS7)q2!V^WjwtAYG|*g$Ao`mN zWpn*@Hl#j6yLl@jg-dF^!u^Y}ZzXOKrMp5`;NY|b=P9CMLjh514ji?L5%J%$Iygo? zLM%`QpU@OFIA&P{qkOl8b|T>@z^Tq0(5RKF{r`A-9rr!51i@tY@4;|9syt9aI{wv2 zWb5a*Z{LCMc3-4Cnel#1{6pzBBG%#d&Utqd0=~p;;147ee{E1gfP}gUyv!09} zOqb_E{7%QlKR(6DO*U^2#p>2YnHL4c?&}~b7C?(MH%0MmICEa}(j!He55x$Sss^h1 z_w5NjtW6GRS;%GhaEEXF@1+m|T>2my_=hKU{fdt=m@-5>9_*TkyKJZ+%mP|7bzLUE zZ34t|DX3-q40~;Ipv_d=I$%$aMx~b;_}nb2TUP zbxyg4u47O6+;{aDaa(ZH{jaxj>;QqI?U_dr&n03JF3lJlhvqogun9FtC#oU{&P*?B zdFsv}vi+HOuh}A(m#KwaS$Ov5>s{6(8R3L6+tc>73u_>$3Agan{xUVp(c9CsBp3KF z>JsdRXuU=(I9=Kea9LFgR8q}uE*HIiQGW)t+3&}{8q_{N2bGWdAbxf|STq7f+No@& z!QP%&@Fx6Xkk!9VL|GEm9cTclka^R-9h zplPByF9d@iDT??Jb_=8~Hr6$E*SW)9Z?e_FXCS*jz{>@^sZ`0bLyW*)SXlUCMTvVG zmkvLCTlQd`2kt-liOLbg#6}wcD%7-CW%B`de`0+Q@OR~~@BptNp=znA_8HtqR6zrm z3V}N+1Uif@Y&c#nY!a+Oe)^s~L%jKr2g)B1VM%QXuGBH8z*PX{UFZ_iEFj7Qcq;Ie z(?HLfy`J&nN7=I%U1RLxX+yWM?KF(&KJpt$t3!mgh*%OruZ4uY}b zsena0>a{J`>Ve{oq~{sJeHE4iYqj70d=b6%;#9AZrn@$(UU*%bk>zN^-s za>m?bg9dX&>_mKNezf*p*EpWbsNph7hc9MM6#qGdJBn)@i3AeAGM|)^wsa_B>T961 zXf3$tnR~>Hl=JbLA+U<&FFJOL$gS4?(f^H5C#A>7#3;1S8r}EUKus3UIRz;40OkG1 z#MKt$*zkl%Gc3>;>7>MwtnV)EG}^ph9UEVTrDxn(54-{J9UvFR;Qg5W6h}4H+Njmt zWlW`nWktG1K)AwABuSJ8#fuDL_mY-AEot}*%Hw_-e837&wV;wAJlTuE_=Lxdkxjmh zh_ny3r3MRK{Y5T<-v*?IE3}bfjwVwCa28Gw`8{^0@!(LS$lP6KL8Z)%lN<&I#M)qX z$@4WZPCNZVlUHSVy2LqYNQYdv$Gnq#o$MR4#xGi3^x99eNnUEeSddCyz?y)W4#q*m)K??BNW9$v>C@-$Qofca0!l7ktAlh9e~hn5c;m9(YIYTh8;jNocIly0l2pH5zF!=Q~Zd`$;R zMX6jf69NmsB>A6+7Vo8jjS?|eQ75JI**Uo0bxXrs0V-$LdbYn{Km%R{>g~EWUa*7| zwnhyyE>xcgA?H{tpj3>n1BIHA}eh zRydxQj8VPj)PKetQpbd>N&DEbNb;RY^LgmUZJETl^hjUg%zj}x-yf2(rmXMVL+Hk* z+tH-z{w-Sro_3k~KWj;JRQ9t2nW|wlEXrta5?anRRodlSPp!qq*h-wz?EqT_eve;#y!4ni zr<~Bl_CU%ANFc`?cJxeoK&irNn~>JYxbyQT-u!;Q`_R1b5a+PYmpfQa*Z2OLpF}GR z#qYT8r5y3UnR*Q#?xZa8K{6wGUIy1W6DhR6sM64TbGJ^&!eBgtU}-DKU!@(#7dxjl zH&?A@{@MF)uEp-Y$<3BZJ;16?P!@~~+lrZQr(t6U3kezPhtEw!3X!9->`y0!zw7;d^#Ok@ zJ7;C@hlL6q-lKgA8RmH8Ch-m*A;aGM*O!vBLGe$l%}q=MK(4YT^UxWLHCaA01Mqk+ zyZB-qGXnFHS>ds^%Y<%c#Relo!T(QRQ2N`?NVj(OGZwhIhd7(JUI{^ecak3VgJ6wp z2GfsNWBfoev-O&2_;Ub0^@9UdM@cXg%O{eJ#EWV4PdEp+53X8T`d_h25$p>v7vR=s zS22hbW)BcoHYT0cyMQk+5)_Qm;=h85v12m1U?%wb_QNO*gN0%f`;&}_6KP35yi-R1 zj`<`Xy}6X;pP#X&*j@4CI-PXBss`_=n1wtNBK91~ZdtC(h%%-(Fc5D4nQe~!#`KJAi`L8}_D8pL5#yJ(h8^^GSjynqEakmI^FQw10ofHDtj~ z*9WE<{L7$?>Kk6mi(}8eEE<1(ZPp+7a_-#5xO*$FHb~@6d~5=3{ic(=ewEa`jlRuU zxNNX?KoE753+B#{>YtZFvqq^XBD8fJ&VCB2wr8?gBMSC2({Xb3e$I)LQ*5Qb)s?Dy zf2)aKy!%SpvKac7;zp5q-}*;$E~|9?T?T~q&=u4)YZwJyh`C6|RG3N%L0|K=@nAo> zs3Ya6tj%DVliBdwxVblfcmJ5CNZ7RfavKf`&)I*LQrP%5#h_!LZ0wxbw*%{bPwWvs zN`$trOH$&Z|2fxBh}I7eeT7B7ZMZ(`TbC8+_Mz!)5bv!?D7GzC>Cgn}_M{+%)2iW5iU7>!{(`!-00#yfL;nK4HHY zNIf;O)vB4X6nJVMD#SOAcunjRwfFVtO`0&}2R~xQpVB1=;)M{=NT+Wb9@dftTxoK( zHR_C{jA|Y|5&z$G{oy$L{Wt-_I$JO<_tj_P2}g!DVYGKW`ogHIqq$khwc|Y2bnC7f zgY0j%Rj?jUX@Z(dQ^L+ZxN_dLecdJThClXY=xjZYwHtz9{%f+flLmv9s!^I>iU__y zY*bkAKR3Z=H7(5ZYhxsjcfiLZ(-&eC=efb?(**i>5Z~ zFJl`Vc=d`cK~uJth(eZpe@#in4X2Bs;=$PIsmPM-5J??e7YCyooD{Z38K-w6hvfQ^ zn&<1<3vY$#hi92-4;?E#h0w-NrA1h{n`F<M($Yn5D)T$<@|9zms z-#zWIxOl_5%c|Ik>9~={x)j8OPH)x1N9rnCB)c84F<*T0y|PH9F{?NWmtt*YK4W#| z*<1HYoT)3e(K>Tl;x4C4cn@UMT5v zgc{)j1(8k4PB_D#Dx0yjF^rz&fPWbT_hNSjb9FhI4(Kbdob6r>58nC6qNgwtO*g~x zy{ATHZx|K(WavYQ7j9*%WNJ^YEVm?|i}q%BKdxy0Oj@+lms(TL>=LW0_T`qG_h%}> zcN=j!J(bDS3r0Enqne435CticSw>3lBxM3VEF;YMG@V;<7U&g*D$;8z)HhRO7uI$H z53?d=Mt>=7%)G3=EH%Hl70T#+Ly6a4dO#Y(BN{bzPYrv`g{h{gxcor@qt2%Lk4wix ziZgDycgDU+!`PaezX%jq;pzvqcG46h6I0b`;ibQ`VAQ-U8>L7`(sAj}v^&EdjB7^l zU(OFzc@};hRFx4k+ac8Shkim_KcmnMZq9{ohi!A!4IW;qoG>0OS~6Aa$`I#r#)%zu zzSsHPSXcNL2QeXd6K7Q~x*+&AiKW708Ed$~#kN1?o{o-=mQs{@yUh>foXk6gGp!hO zloR72VO3aI49?7lL=tHFgwgUoOVSFjYVHi@N#5ztiy`@qmfF0lvz!s)^}FHV&UQL* zxNPjr6Y%36Uur_d)?OLM?b}9J(waN)skM}!sr1w})@6@d8(>1jQBTKrpFS4C*{-RE zLUP5UG|$7VoO_1m+|aWoOJcDovW+<4_o8rY4NQo9w#IuWbc_&I}f(s?n|Fk$0`Ul(Xyk0aXLM11=**m zOqg1K)CaJ1e+Jj{$!VjVfQq7;Qyt>Z0b_yKzv)Av`M8!5shkL5}nis=H zJ18j8wQ!3dK$y8+kj*x=_i&*u^vBG~3x7(|NFbk-p z1&dIkAt&(a2iDeaJYHPn=VO2IyK!8=)QPbuxZuW3wqC-{Q-*Og;}FZs-R9>+PNdU5 zD_-bx$#L9wzmdXy{p8jc9tBq$-jnpJZ*`}K7zg8MLfN7aS`ej&ObVrrO?#8ct#T;KAN*WCj3KFX7hDa$_oX=nfeH>xBr@%yL@j=>s z&GX<@kkLaSS!iWdR3HrX7~4_PTwt(dXa(qnxjPFo0`AUmW!z%4> zUEhK1>^J!;lqd>Hl+NK0DDwM&ue`_E{ut*n>OaMcdhgNFf{DjaK_dl${iNuF^^i+- z6E#(;-uwM4Cr(LEY~O02#&3IU^d43w6hkk&&K7o9U5 zl@e@`_o$kuuc3^?um78n;z}-#6zAU`Ul^*=KX=L6`T{VOeSGKllu$JAwkYuGm4`J0 z7ZA(VLVy4Kh1L*Bv(R|q>-%4qr9m=ZAaY&C$@)Ps3ye}Yq+4=S2nU2EgSIT1&-=xF zjQOQYVkWR+vlQaZ7)Wy2LcuCSa0h)qLQgbsPzLp;@1%9DX3|yBhbagASt{Y{+Q#Fi zFpZQbNtkt#EumrMY&bErzXSxpp|EIvG7@27$aUz@ksy}qM^!08q+!>|eLZM=Q=aN- z;6~DFy^V!M-n~_+dGuTFMdz&_-tPoi@lCO^T_GRXV3i7qmx-JSxrZ0x;fnihe*fg8 z5#K>aNQ&mbfz`*R*C-VX3wiN5iX`^NQk)L!8fz`GE9l6E#kuo)Iy+nNor8F{DptIA zoCa|Op@L<`mI;Z4VDLiEdE*WSnId;2Lc7xBdHB5QJWx~!;RJ~1Q6-^L-`U3bn9L=% zc2feRvCC7gut1A0s?s2|QHK&DnBjs_l~VJwIv8eBTUR3gg`(i)T6>x0i_!BVar=LF ziwPVDm+MPPk%QCvN#$P>`3oOgZS5@56+AcVam8>}Dwn@X`=A8?2$p?VI-POzd&H^r zPaogjSme4@UC|WZB@&^|p~Wn_a6GVe{$0}5%6vU7%drwvZ0;EEiK`dkDIWE)pU{3u zlZ`?K(u#?SN^kcZFt)lW%+TkGVuFIgpP#*jG|@aE@7XBxbsj&W}b%P9%*wR(x>WnET#WMnlj1Ib$U z_S#H8IJ`^wR`;If{`W~Bf1lZo5pgq-I-lSMPw7XbKplS zaMa?3-S+bETBCXzS(yNnorB!5V`?){YiHX%HDZT-Lbx+73}-od28*Q9f^AWVbwO7T zcEz@jrKP#qjEFyxxSm(JM5;a@ychJ705H3s0l`LTV%pD7rUW3M9+4F5YusO#GKX>! z+}t8;hITx~`DRyoICMYC;)RYbj+;eGY6{8!zSnY{L=0aa@$7aQ)7?{V1B*{`T3z|8 ztCaDyVJSj0@y(nR0%*Pdab?W74irC;2sQ^yyfmrp=F;7gEyyyVyU7FQPhP*i7#zLU z;;EIYW|o~`gN)RcP4N9A0GfKA$Gg{`+M6nPiO!E(C~UPp2AJ}K-`4~yt$hLfSo>eF zL#rujknPkLHZ8{72P$qE-QlBSl`|NUkHw&^IIdCCxWE*0SsBk2r+FlS-tjK;=WzwU zedQbe({;;Kg-zuHw+Da3u5~MCbSbP|*uZt&vTVxtkSfO-$3J!aK~AjfAhBwP#Oj1o zK!;enElcsEbGog!U)ASzel584if{1C7w$)(1z@lNcy-9S8q8)VXY}>-m_vKZZ|FDO z)g2DO372RZr{}_K5U#pmT&ly6tBWmfT;5e0$hx%mX)-O12(k?)z=8&{)alg+?m6UC z-?fR;y<7aYdq_60;76>G+rp`D8?=MNehy>tS)Y;q@-E{VUW`OGHpjT{WiHlhp|9!t zPe!S>X#Y5yHTLD|rRux#D;X(v$9N%kH1n5H+FMp{&D|of-xWEz?n6pdHX9(zyvibd*-$zJhBgGNDpnA`C?$4Uy}(j@%gs*rr-;va60UuIPDCu}1XH`k zIZgAfH9d=pk7-3N4M2CbH!lPBa^6^l{55h2nIg>Fl1;^TOUG&e&eoyS(}`qpjyZdPZlqYlq!EEwA~R`4tiG zp{*mJ8H~fz$aXu7w!MNk0Hl6t*y56=V&z8W5H3Iuw&tQoH&1Qq|!;vi}YGX`sHKy)GGf# z~$zs=~$t~;&a zSBXTmzgcE?)Ug)_txvc-giv{gsQu-W04AE;edG}|Oes92=%iT-eZ-n?f2$FHnf-Hc zxIj}VJ2~cdQupr-X+3Q~C(>&16;8SNnCS> zUOByITG7{a&Kk(0Vx_ANw{std_?Nn#;ByTTDToFEg2D=Q{3UM_yx>~lm_^)*|E3{F z^V-vVQoib_j&?=H-k{Op+ArktUfWJUGh=h_SG5~+oSN*q`MZ-syxxyH2BW6-lvr$= zL#Ndb3dfd!A1b#>Qu-?m!_hZjb>em^42OH4_Kzs?aMaSyYsuE$5}F?u=B;yZoix3v zWh3si7KUpcTQ^iM%uZ%WiX26W#h%^m?3J$cjN-QWW`rP=89a!lB^Rxd#=m(3T{;Yn(KLG{o*6n3dg?{w>_l#cvViP zyjVasNCWTKT2fi^d72H+_jHgnPS>uXV#h6iKJIIKV`OD}_)@o)cSx82{NRH6(EIb1 zdh(cYI=uP^EzKk~0SXd=R*c<%hw>@@`k3d;6Ry^!%YE%y#`ZZM! zlgfUL>O6vHG9o>HJoq`)=mMr&m3<4|*SL<&amTOkRu+s}YYmS_M|!WF)A&6djMH!@ z2(YrjDKs242O8xlFXNj^eR5UuW#zp7e$V9T<6eb^V1)!k@Psq)Ce36#hEza1$@(;-*g8jKK!or z1$s0pCI2&WFZnmviX+Z@&Bh0cz6yM2i%humG88v;_uaT418fDtE?#EmQi7R@_HwE1 z@NLiYpe9M=@|STPc|vaL!wodvow4_6>4BSK6P4mjJTwCe(Wqy(2%p2)eS_YL>H(C3;S zy90{1`a|7Pr64O2d}T>XDy!qgQPGeyB|7xCge3AkRUIl#`4Izt>35Z&nVOJGI&D=M zxwE-gJStUHRV58-H>)zc5RV1evh$S!cN}NdM$0&m%*E{~MelxL&`Vg_qa8~n6EzOU zqFFmH9Pwcsapk&t?=|~k_5+D*paoO5KL2pGe1Gp;44~;LHOVNhUzIKbJ>eU#Evc{(+^Euj3$a zVNW@ZXHZ*+S=d{eD@l)Dr6%FJMGKy&GUq>&v=5bX%K<76ivGMkm3W)?R}Xf+uJ@?3 zvbB=7d1+~Bznduh9xkyInJ*Jv$N9cDz=h<(h&+;2D7qtzd5in@c?>6?IuA`+GC`yB z00cBzaDs*cz>BmZ34dz8ULUT$v!28MHxiVUZrrWER1t(r*BYA%IGUzjebN5(`)Nww zYh`BGPd61>t}E}%3IVinL=jkvb51KE+WdiuLE9ZyMQ6zUsJMX~b5eLkUvex?9b5NE z;~on20P2L7n;Lgk$@gOUc?_{rG68$4Fdu7|B3f<)M{N_ZSmjq8xgzz>-uy|cy#wbI zA6`i3*-{_lwf%_asd+{jhGg!wZ}&BHJ^NtsvYgvOWPY_48uxhC2p|zCRVGE5zlZwE- zuY+CvKW+Z3-C`(dlk%)|itJ^>bO`oOu``CD5ubjc*8B z)Z`;OjJsC?u=~)`r_U&8m1D!$E&~<&!T7Bqf=J>v$FMD06s!uN12z}30M_De_Z@L> z?F9*=HdJGB=s)kMNz3}EuVKMYU{+kK7IU74y}3!Ktf}`jH00WuQ9dYKFgG_pnE_B@ zHq^50uR?RKMxSj!$6DWzaYrknwU5x&(~=O+3IhG@8R%b`ZKVCaM|dXfq(+ZK_b@iP z@iwe=$y4*}v@u*MF$?EIvdBZxuLoK4$>+RDUB@U$ZO0~D?*o-RP<`i$h*U$yVI|z!BJKK5J zPy5*Zl{sw+3Ey+uP(GxocnBH~pjNM`poJT^&v4UgsH@%r8R-aK3F)9c-|0Xe3))&T zQw4os+UNTEE>6Elvv42ftMTv0`eR4fH55>&#}xk9W|vC??Lw4K!*iU!Nl(bd%7>5r zUGjm}LE<7qAbimW^!IE%zB`6j-r}4Ow8QFwCXp8={mwaxplKKBj3GPm*JX59gxtY5 znA6xyjC91zT7$fspW)h9%>-VvSusX=emniM}#_p)k?;{P?# zgJ+42*iA*B_a`}%S(1_YnOqF}{7DeAq-WUqw7^A~-wz5OJrKTPf`+$11?n;3r)Vd0 zKgYhov!LwYjqPLgdnvGNBvfgSoQjN^EYdl2_^@b)1uF!WeEc4jitU$Y)D3dCKs|oW zy^gQ!SwrJoo%>+8?PGn-dv#kN>cp!Z3x=sl851nnY@|X*D)ZmYxI!AYm=NG5>w^01 z$So1enI&E=cH4vIdL7yaS#P%_{@GSUL$gX=w;e`{dY;r1KR{$$GTDMcYHNgO!-iGU zRAjl;`tYdwp7#o1%)QjC3M#GqSHH>=)p_51oy>dgdND%#Fc(+!xyu~#euJ1qH zBqb4q-@|kaI%+Wgbk%d7ZPGZPknjA^t_1THo(ByuWyCyfpE!bOq0nvYedYC5q$bw+ z_HE7{1s}35AU8CWDqvZnYlwmVr&}$>tPmr0jpPE;&CRgi*AKSOoISPn)`6b-0H3#u)x(EQn#65yS3YK8&R2}or=06ZIB7>Kzll0Va6 zrMr2(m#E~inD$<+eNQo5<92C3EjcB{6x^VRy3-1+q6K!xc?AEsN-86?jeXeq*wSDN zTtBS!*P0F3r-cY}zo#7}#-eVWc$N$b>RL3BjiOoMaT&Ko%O{(E8AqS=ll1qW4vw&N zF%a1l+;S0APOu~CTrbY*3?VaEM^bH0YhTQ5TGG64P2V|c$4H_l=i((EWxuA!Z`U!G zl(!2-PD+t)VI4=~&LzNd6atNd;X(OD^Hb_mbZ8I_%C$Fjx54obao?$8wBHT0CNRfuP)GS3DUMWb~{ z;&^_R`Mo=4KxEy>=`D3Dz}-4$UhS&QSu!L#_$2VBBlpmo=2AUkY8VdDpID&N1`jDf zt+hIb!4%`LpJ&FuFa-#{8Fe~yruUTb82FTZ5%4r-*xifPc_v1eyv8d(4Ej-)~8__!aSOIAfwpxv z#(u_g(?@zTpCMhWjO4u+jI$FH6MH~B$hmDt3H?p|HeVEu5pcQh8B9VVVGfW1i$?}3 zH%a5k?qa;?l$M-o1O68?VSnoBYO)*W)MV@WSY(y(LYC>Oxj!|}V;k;`eQi|a)~~sz z4iLl}(dwW_*alSP%a<2+iwg@)`o>d`MO4JojGO}hmP7)K0H&TeAr#ll5pRR74XM*1 z3}uS*qhHc!Gz=v^|G_L{vyXR~b!x6)Aw?42nU=anT$<}2=VL`u>(BqRWEPj`%dn`v zEQwt%JBj?Ro}UPd=0>gqWadd7SlgTArZ%F7p*^mu9ec$73?jhnPtEBOK(MABMO&TsMVL1Q z&0JL7rF;|Km|zKS=33*@x&9L^VVYqCfp{P1fARNKK?+jY$Dig3u2;-5{BJ}=XUM&D%`Ujxe)krJ+Ymn?&q|7n3sgEs zIK^j6Fq$@{|0@FmLAYCJ1N|ZuCW85^fo*8>?LvSyBU#^{SI{^>fWqDZv zYgV%L{La)tO~Wyo^ix$@?`k#YIIhFl6>TW!1qmnblmy zSIYkhC6VozP3|dnNT78}qTWs!z*XeVH-*==4<6X8$?r7&iOpwfO{04)*`qrKx{AO% zKl7S1Zul#gs~_={Dqll*Hq_OPU@-4|ep{4JPJ5egj~m&>%C*60KRVXO{uEx|)ECWB zYbd>IwWg1(kjp_b&I>@_!VxaEN95}*Y^u(^WO!*?Z^aQ0J+qzJw!^~!w10s3ONSs_ z4Xw2WJ`j1?ihH9HIE=EE$vI(s6Gy3rb_7oNt)DSAg+^3NOlEsLg%t`0+c)Ka?naO< ziHL|um@2Td5Z&>#)hZuAxzZGi?9zn{GU2MJGSq>UHy*hA(p89yie^ttynQ|H+&(hd z3qKulmn-8W26hcqp{&Ss2AARL&ln8U2qt~A&>HN^Zeu+rhDXl(0?$97vWf}-EnqFC zItuG|&ozv^g+jJz2sLVjK_*zbyoT@8eE-<^X^IRjzA`Y*9%p!IRi(!f4^|$hqs&Jn zRXE~-(7oNd!Gl67vbcl=%fPFU;Ym%1g!Jd&vTM{v-&hQ(QP| zAxkX)z_{8SC*=nd_S0>S#1mFj@FM{-h&H&NBq}tpjM2fs6huJ#1)lAj!c9R z6^tg0HIufJ)5*{2it?J~>L#imctVDTtXi{Fe_TvFE?ZH-WT;-cP}=fl908316SQK{ zbLSf|_(?E;POcEaNrB*2QF~+joG`8k8#VdJ^_}gzj`C*Fw&N77K-)A zhSd|42$~DyfNWQ~j-H>eH&PT9Za#` zg;*~B*l{cD$gxOYOuQD=k&Dx>244m-xb zCU-tGF&UnlKJ-k%uY6y!+EfFkC0ToNT_BSycHtcQUhMJowTvJWBg#jK~sd5^x8)cnu`2!QpNeJ(o_TuuC2f>ShtMg+Yh zy5`{dcUf$Sh13JTju95T%xL*PN`gb}M@8Y_AYbL-GR-&3k7KSRb@#;`Xv^=fam%>$ z$7&fq$rbUxkbmt)07wiMkH|hEa}#^AY7CPis0f_H*T|WG0#D_wHAoQj63Jje#MPCe zU?+y}-LWl%3=!*nfwwa>>AX%(CNmyt*wmu}5j3!U5sAQ7dwAxO#c7x54RZ zYiZ@HitTx^No{5eiKn=&v%CHa#|f~~&)o@mB>Y(A5HZ2VGsw1(in#4b)=p&oX)^jP z^~WrZUpbxJoF?t=k6qVvUc;!#?<9>A-)98SpIrG9B?QUYV9#Dz*<S$&FRBI#(!XC68Ov+#+WiW2LCh+S_} z^NQr33|8otrY5Qjodf~&899n_BZZLKa3P)^vTA`U`~BZK2rP7zCr_McdvErA=EdjV zwUfvft@soyznm)?Ot*e{?u*EuYYS!i-Sg;lUJHjuK4@Y>ovdoqH3-F$YH{=S_MJ%x z);Jfzl_m9mMR7(LCt`wu%l7K@&~kB-*d@{Y%d+dmS6@FYAb~DA6Y4|FQFEo;KfjGQ zj$Ob6U4Lu(p`s}&z7EE0wOY}+6t+<;)#U5WJ!U>7&P(IEp}-JvU)pTDq)kE03JWnn zNeaY!k$SlN06mTP5m$2M*Wul9lEwUTF#wwhwJ93>zJ9hyao+qi?`Qjo_B`E4YUF|g zgn0+b#!@nuaCb4ioBF!(O2X^LojZoFU4D6s*l$!waQPghLEcO(vRJYV=VT(X9lo3^ z#uy?E6#p8eKRh(KP&dizb?w7b6m+gQLa%ocj0qsMXJD_93<*_Vg^Qs4{OrmsQek(=jRPlAuH3yb)7I+;iDdeHBy`9}h%Y`i)A5}VJ&}~od97VnTq|!@iKg;*m$p5&)`VlXY}2P-P=pp+mR^l;~{Wl!*3K z^93ncmGWODH|3Q%#?~a}l?BvV&>vGV1cBeb*5U}TKlHDXf<7}n5Fd_v*XY*OrZqs> z$0p;xyZ28Wc$h|m4Ow$)?&tSLrf7`~^&~4-1$i^}Pk-?^;hHSmr^Xat`ZAcb1 zA!%g3iL>h8ax(f*iy-DPZv4BzmwKMNcQ9xZ4(*^QVfg4_>Td209*r}fx~0}$a>$+9 zQ3-Ij`*Kq4G;ra#O#w6sr&%9=R;DluNHA zuO2-*C&@HBEt$iCkt8dLLw~T*CO=EEYfbc zXqO+4MagZsO2&G$#XzV$l7xsAxyb$)bjl&O2v7dFC02#vG<13VM3RFSh-@0aKLU)! z`b_!0Qq`T|Wat91|6yq7WxlP90aHCX&yq?JK|&ll&dw1JN5ct%ah zJpba2O)p>^RIz!A2j>IJ-ESM_Z2t|99@7cVo6{Y@UUWcG= zoT_s{g$&N5^HTk&J!1x_1htpLQw8{lFnGkefw=O4nzd(ruD?o)#R`kHFXk+ke5u#O z*ADvQLoapAF#q}PXxSMH&}R$Xb4Wu2v`QEqw*SkVZ$)NPxMiYYgDr$69qUl@&?@Cd z|7J&MD;!v0&?`9Y(m4TO55~VIqz9at6A8x&*M}Rw*FgYCZgCw)|F2-Z`+>WrCN0o1 z&&U+VC2M!uq?DBYv)u|D(YS9c#YBbJx@X;knkeL@Cn()(aCvwd_OTJlOT7Q3IZ?>p zi{8+JpbtD~h0rlz9qi40a5pTZ<=8X5fE}}sDx9#Uf?Tvtjw+q^$I~brEnXdOp}~8P zBim4j{{HXRif+V>XX#ZAfYTrqy(MFfQ_s5j9c3_Ib8e zBl*DP1;l1h!Z{fPp|d3J?QMus+>EU;u(A_veMz+3O82n!U>PzqHqNx>sK!|uptV^E z*B?*wQj+T;>k|x+o$p3`tY}gcW4o?~)o?K%_;eONlXqY`ES?FL$7rZJ^$y@?YYHHn z849;&qK`0hDi9im#4UpR=nHaYWYa|YPKtfp_8Nm0tdW@i2~}>>(Lwl$p;`H{ZRFzj z3Ia47)`VS4(i*1XIO-axu{F+SvkSljW1Ctknw};wpZTxKNyQ8NNR{??@5%r=oUF7p zPJJHiB~kZF+L7)I^vNxsU8BZf6*s=t(7X2U^P|>a60H5I}7-J*znD%k>du> z+dea;0!bP<v4`pII8Ly-i7q zd`BC9Ul0M5DDzO!ZG}i^qDCgqshbLk2l><_M8+8p9Xa?$*cC)~tk+n+?g>S_5JO3@ zRI?#D#?Euk&@B&lutng&7ovhC9^Cq$?_7qf{C|Ao7dLBmicnhw-i1c@Y(pgmz3tv) zzKR0;ba^3uCLLLvN7i5qj+&8ofLfDR~*gzChz}2pN%%rzecQlO5ohd}7!leS4&EUP4I-FsDd_bh+*+ z%A?_7JWSoO13$`Na{`jh#DTSrA1tSan{cmKQI$GDhr@{SG8Q{ zgaA{;>65-Eyx*1Sg3C?h#?^7ClTk@6-0t{zZBR?o14mp?MK{_&T|Q;`m|8Q;TsCTL z??NF=1rB!|zWMfwY&DfS_L_R!!^avb-lXyB<<;@=t-WlOeICocWV)M5B~(mw^yxW` zjm~ebOy@2sAJKXd#@2rfFLZ3VO;!eSM5OBp1^J7f?><@?BeY*`CHRp=kMmZ<&$ZV1 zvey}aq!I7zYoc*KE85E+hSTUqpUVR>JEycB#_4MykG7Kl+TBB=N zjZ3Psq0@86Au5w|Cpsu+u8U26x1S2B7lKF7wzSi~1|{vt$3H^`>CZ!JgcR`6H>lTS z>fD*m0_1h!t=}g$}AL!o?X+P6xj)^RgSMh;Ft4w-n@u|=z+Ce1{CRRf7L?K$r$-RB3Cb+TPkCEPV(q>q(q_`|0XkJ?Vw|f1$-3`t zNYv_9*iXv--?g^DtBb^t02Q-0=@^mp=t-i6fZAqDEGhkdV|s>;?TeP*)v}7 zd_8PJVTZ^ibXl+Qln!W01Y2S{FOq@5iI3{NVKAWoWKU2}s;5?K7!b)KAcELMPPldT zI$6z_RVb#~sJSKB(&2-xV_LLS!^0R1Gi7oNfNUl}FBKFN=sfYy62xR42RZn7Tli$h>4sbYS}$K^ z2E%|eCq_`q2u{0YrzBv3k}tyevv9b9;%6D zBj3Y|yCsV#UG07LfYCCZc!{`DK;miY`XP)OOe zR!<2&*d9X5$o{7i?1OKR>z80^cwf|*RJxERI9DyF3I-6AkYNfM54e`c({Ti17XITO zf5I0y;EM}Riwqe=ar2np1q7DF%_*n{ehLAnrktU4IFf8S5oEU`!N3_8$wHe}w&KY1 zA@F8E@{$Aa=TB>B%5@g{*IepyM^jLKN0i~vSe#0sY&vgwmD2n#5Uw{dooc52N9qr1 z|9}6M0p%hwa0&iv5dfFKzkj~)Fw1I8?y3|tD*x+GfxK{t;cXZSs{j3GOHLzu)U5w= z&i|g=2p%gPCm{8obN=V#!Cdf^rz=rh|JRvSK5)Qr9k-5T{=d$Q8UO#$Z6ELptNpR( Vf5-k3e)N|@SJP0VRLw5*{{Y^Rk6!=) literal 0 HcmV?d00001 diff --git a/public/img/docs/retry_idempotency.png b/public/img/docs/retry_idempotency.png new file mode 100644 index 0000000000000000000000000000000000000000..cd251ac853955634f13fc087edd69d73c5cbe9d2 GIT binary patch literal 55032 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 O9>6kH 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 z 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 z%0D#z)@%Ob&0IjcrO^w@*ts0WF`~AT`NFQ*9nYc6?rb*FIehxo5C5sZzewb}f*{+} RP8H~la6@y0^7F2D{tG&~4aNWf literal 0 HcmV?d00001 diff --git a/public/img/docs/security/example_trap_after_await.png b/public/img/docs/security/example_trap_after_await.png new file mode 100644 index 0000000000000000000000000000000000000000..f611f118435f2ab4abc149e3f79a1ed2c10fbe50 GIT binary patch literal 70597 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 xPA8dpHkfv|K|m1Si+A+CR*6?v*?J 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�O5d^QYE2Fn=CQY*>(0*xx^^{Pd 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 literal 0 HcmV?d00001 diff --git a/public/img/docs/security/ii_mobile_delegation_chain.png b/public/img/docs/security/ii_mobile_delegation_chain.png new file mode 100644 index 0000000000000000000000000000000000000000..3f12f22e2eca94e29e8356febca2028721f5e475 GIT binary patch literal 95362 zcmeGEWmp`|);0`d!68_1cXuba1rP3S!QGwU8r&g3Ah=6#0%4FK!QCymyS+WRt}XZT z?fw7zvFBipX}YVbtE*NmInPxc`Cd^P1rZ++0s;a>=ADEJ1Oyx*1O!wJJQQ%opi+k( z_}cfnV_F6YvGs`RfL#?47jL^N5UfTgW&j1+XVswn+p7elu@BP z1-gl3rKaVor6A95>R`ugZ02BM&g^OD2(AV3){`GNv@>@#CiS$lwRhq76eRysf*&{r zpJpK^{Zqu%Mvz=f;XSFigR?m)H!~|UE4dINDJkh&XEO_a6$z=os{{WDl7Db@b>wGZ z@$m3q_TXT4aJFP&vMJ^imvz6H0%|K7^e+*V7%%Ff*01*j^- z#>>w7mgPTQ`j1t$|5cTn3%KXMYW~}mziYl_0T0)|4c}jL^XDuuIYNkUS^hcELWpdX ze9jOMA`mhXqH3Oyhsz%6cG}t}4@mS;zeN*B@n9(BK25@`2O7b#b8~Xy4Ju2ZQ^Lc~ zy~CiSz-Sx96E!JRutvj2rhNxX6$u3kgX}4M{cWuMqC!W#?Rv!~yzELpV2Z=03cbsv zif`qHXN7GC@A;wY<>6%yGYDRS6bd~;1cn^#|2f63$xGLm#;r=t-d_1-sG3m0nZv9(*61o_^X~29*!q@s3j3toFCm0wiqO{l24HW z#$t{EYV~2|W{wQ`Q^0L_z~`vPi@m(VPaP&&8}NiWP{Z-{xmOiG&|ncLew@X>+{Oz~ zr`UEG`_Pm*8@;9WWHEf&*X%k^seXI{ZkpD4S;P`>_fs!3KHzb4vAQuC=pz}8BZwpS zEEuPKE&lm;eD2+_C3Q*eiSKUPuTW;!=Zm}-yZW@OA+%pjTR$P`%-I z{Q-~tT~CW$S83?ycVhv$9KA)x8IOy=sZ7U`EcCfoFu%X<^7iwM`d`<-oW6cxz1VEu z6+H18bs-L5aSTGLQF0fBI}n2*uVFxlVB(1X31#fbD%p7&%JN<^f>MZl%-uZgKn=#7 z3Q{(LmqV=VJqI?tYRIdNizMN@q(ijeL3eLISpGft2^mvjP<=1OCN#-ycj?#MeG!9L zqt|zbDamak_;T1m@Pr?rOpAYs5*F}mwE`ljpUDew96 zt7NN}Bb($kcdMNMS1L6WIxjqoge^SrwmJOhscz@KdbKP@%MU7MrJ%MkV+ZHrGm zERVrxiSh}(cZf4h@D&JCQo=gI_sGTVBiDCma0( z9iF!Nk>Q-f;^&z(cnKpK4AQUcP6MT!I1GolJZbG)RPk@m-xb4?xuugA`?T-~)Os}X zLD?u8u}eynJzp8VTn*CX*{T!1?vKg8ENXWj(igjhmdBd&B4Sc$t?@<$LCHNqP>y0CUC*^q)Z_B%Q;rcTN%?6-7CMbdRI_$cj=!Hx0E{~= zWH8e1yQRoRzb5ep$w5FH`X#k({2n=Mk{m?ssNfQSl0*2Va+D&EQOkNvbm9p&lJv^C z+GqLafhG4tZNMr8@mWNXIUgPCk2tcifai=o;swtfeh_&6cPQTbewAdc5XQ zX<$y!Y>(MQ^J_w8!paB<){VGfSn=dqP6spQBhwnWrH#H~vO3rb#^Fxi04iitkoafB-hq3PrsZJ;>9D^ zbLQPoeGQ3)N2zykZu2F4{HYv^u4M@&j~-8I<0y~i!(1!=V>FAoNqCUowDbghZW#u| zSUs=z9Aheg>%YU1w;mIr6-I}T+kRcqf*1NjNusLZ-H>J`r_fHisXbu6;@28` zTSgk00OkJ%vN$5ApRwsFtLu(HH-J^nfB&mLJ(;PxCj$*&u`;{o2#%%!CEK=g#mpX2 z`7VrTLiK}GGOhamaXa0UxjhfR@PRPEH0ah%i~PXTZr> zKS{vKMXm>rTTLe*Ai9{WSgRqDqy?A5g@BaA5O!C<<+=oeB*tV zJ{BxEXY_7OOvS(x%0pc!8z&5ohC2(WUm zc5f`#W~gtX-S>oHi$K=kGs?yVLz_VpM=|c&K5)cPn zsM)UPt5;rgkr58(+bjWILA@Bt)67vM4|DzUuNFnS6^B>kQ)uc%Dg!8Ls9_$jJ3?+s z0DUx>F?x9hcp~M7Pu2nu6@x|9S?IhCFuWWFO*Z)>ea!B)XYl+DN2S+*%|*jNBNiJ+ z@`)BAE7uq3AX{ z2(i~>smx!r$;%ePQ=YAJs_kNIi)7`s2PV6Kfw)JjSsV2sWA^-xf7y`> zp1r6*rsWLs=S*yiD8e!eHL%|0MXS-<@!b{3>qvPOHyZ*z0c*32HH3}38ja=ZdZ2q`WNXU3d9~N8*cPGTgo$$Ri_m#};aMeZ zrQN5?4b+nkPe+URd>1t2faS0(-A%x*aFE$kD>3F8w>x%xU@90T0&^V&A$bM(aA}dL zv5RO-=<1SX{!a&n%{$F=pE?TkHsB96pqeZ@H{MjgQ)^tTi?~@{7`cA~I88z@Qt4$} zT|MIRs3bxtBL}1;;a$Kj2K1Mg z0>1$dFbgB_HRA1DnholU=FPXz!Vw6QXU0e}P4G89oB7_Ha}P!a&WOrQYqp z|0z}k22}{s>1-#+PTcIK;G>}r7kD-ZASXb4qfV%m_rsP#y-N92#P&=Tfmui* zLigqXJgcV&@6}p3;ks6=V}N@rL2+|pow63@@cX))J1iB3wVv`Fmud8aP^>lV4_iVp)|&+%7~jZ zzlKQ~@u07rdXjRrf;w(Vm|IcB!H9TcmRhVhf|9jz|ah#19 z5O^styDkGG{HM|Xrv(uh4RN3Z7a;@H|84qT#et9`vdqRg|G?tEv_M{i50ofh*P{DB zo%>e{5#5<+@6_~@ko3WV`Jbc2X>bXYe%XIK{QplsXF@~@_A~Wa$y7vm^hx55ThE0!nE1)~fvHu>#I=k$oy@rN(Ks2giv2Fx*jtzHWZu|Ll=cXAcI7`_|? z&`BsyW<2p38K`dLUS#k30-TQ8Xo56x%j*LA?Mr|GdzKcySI#_S>nG%hEYo>TZEoKN zaL~p?fF}!hjG%~nr?#9I-RNfEv9K$2(;_XnuS_uyVB%d8)0L4|c7LpZZFnG^ihG2z zlnM5L_L%p~(H8v(8RaRM+N%b@>eZXhn-=SSVxL0=d*>t>HS;UMhxxH*023+nvTc%> z7Wd33O_foiPeXu28M~}Phyce(@JYqW^uoW*-4jg> zhr~2^K=cy844i5>`1oZ@>G_N2^xiNw+*`a_*lN!{hGI_5uKQ^g0C1{!ua+abvI|}Q z+*g8OHs-T(@jMs*G$)j%3VI?FDSiQX+xL^p??}tJIrgty%WB%7EvnFySz;8XZw?YG$G_Z-B7(&oA z`lof*LI}zsE3-e`4;Q-3|EYiMjMFyP$L-(f7$#%gJQ?qIXo|G^x;fxz&<$WW7Jp3Q zzxd4wt~nlzqT0m^QjNM*xq#7e;-eQZNk+7gkO{z_0OWEY6^L;YLi2VV%*(|K9Df6I zJ^Rh}FsOQez|H;m?3f%BH@8u$(iK1*FX4?7~yuz0PwBQUkzUngbZTD{ zEUm#d1@Vi#QMdi)Kt*)>WAJYJ?+6dfGX>TS@5flaEqrAkaN`lsOeB5*5cAc2_JmvR z8v6gN!TSN`@F=&@<<|$2Ur~og$L0`l4N-_-%=+&~0>++*x(pHJ8z95BvvLk5R|X|u z7To8HQCUF6Q!`zPruZS-lNeM!36v?0;_*=z;@SM7-b1#5KDWl=?jan1AzIAY{U)^j zxF`e3=~I^stLV<>#+Il*H!zY$W9{^mE*hJGMxh$jO}WmO-uc?hA=&Yo>O>cNzh?AU z+`t!C_`$*+rPs=)=i%2EFYLlcZa&QZm*qrLB7 zsqf-{+Fbi-_{>f*$6pOBao?F8LqLb#uD(13_z0wO$XzJX+(Ewp%)$Afl7C{ZjVa@G zt=skrzHz8U-ahF7? z5iHqfw3zi^YL2jtKGY2erF0mX<1CrFYbwMtH}i=7UIqwO6r8@%uVD7W?euY1xGNRjz(A+1%ou&P z;tzYym9-ht)y`bf4z?UScquVkB+u(4aY^=j z7Jdm@lQXlIOo~h$3g(04MwOgeP81lbJd*AV}e3Clsc}Wv+ zPE)uEBqG4YP{X2c7JfelsLmn62sXLFrmUBR*-i^z&a(R%G|*@Kk9D&a%zjC`bYHXC zElLA6X#9JcDnXNg>hzOmH*N4bC^xt&19R6{ZVOgl@x1JghOWQua8(bk87tuJNl7F7 z;&l9$bPGW-;<-~!7_Cd5ljAq(T>3U<#;!$x;^w|`8w!wpVW!h;uYJSL3Vq#^oo1x@ zkhFJRhDLdfS89|v6W3va5j^vl+`ka*Y?C~Vlf-(e96jCvio~lnuE?0a`wFmO<`otA zvTXR?mDsSbF-S8%fnL!Qy7qIa!=u(MPJeexP8T)Vee#mDC18@4U)83N(0#Sf6+K%7 z!zXSMSN?}~{i7CYoS_8d)y#ceUgI8Lt8JUs2++9_yc!CMe3FeUG|p(XG#u)?UkxVR zRQDE&?6P%ruN;H-B3)-wnlILD-*|I}jMFk8De+TLk}U_X?m3R5uH+K%9>`2HGs+Xh z87XW~8D8d09ZSG`=`-*sd;-s5Jnie(lIrR%Q3fyfvlmjl+_pD#-NwJe z%!Gsnuc|x*cdpt&M}!Ah!V2XpFV8m*Kiz3m&+i{Y^;K6da&Kr?<$5CmQMpq~1;IK~ z5n$5Z8IO;Ui8{YF+R_r9J+bri0bv>{p#5rW;BoU~Hg_~)p^zb9V}d|&r(udsAwd?GQ#Cdp>c@6io;O$~ z*!pGY49}u8amiy1H<9mDuzSkM&Rb_Yj`h^2zT$_iJYP36q-OJjq+8^5>hT6jiwRJS z7iyfCQ-dxuXWOMr=uqzW8$-*_^gB!wog>P;&wv5{?&%k6qt}aD76mrY{3c&ai*ThP zEBVFl+kus0&uig_S88s|B10X^VNHWtheF%cb&Awgqqe0^I-=}>{JHD`Z&_0)OW}=m z*L0=*_G%FYovDmRL=UW(KF7ULx|T0y>&dl(C$ zQf2S=`}rl+7D+h;ngZq{#{H;AXHv|-zIjSw1EHfDFR?_&A7^%Pab+hR<11$nbfJMp zg${MJ_BA-0M9Fspvot(}Tm2!s>en;$;%vB__=0J$5^;hv?(YI*_(IgCxS#1B8YaOM z9+Ixm0fAF;nOkzKpCe=BFO1JF9KTTRoEzhAUtlx|6&tbtHoPprIYV@O&-*&Ip3DHG z>HP%Er6Pia2oyPvji}UC2^tC?!a%vEyGmWt*`-;(%M|m}2C&eJ3?_MYkJisSZu?j? zLXk!a9VKaN>Rg&nlZoW_WQu@1pXF~TF4cQrXOzzee@r!4a>Xfct-E7gqQ#()?JaYf z)zpDXqM?51*~zK(j)EA0b!T3aRU&L*zHiZ9LTgDk{hL!UjHi~o2flAR^Ik%Qjz>;z zxz`O}5!~_%WI-=ZroUEGHoaRq^hKFm{SCzW|B~Scc+Yc!J!CYHtVW3Pn%9^Qaqq_s zDFd^pPxvUDJJf#ijuRr`!3d$_^K)#5_eXO^yVdK12mCa8?0+RO;KSr_=F(@=k>k~e zcz!k5Scgq04YHhMJ(Yg~=NtfvY3viSfX%Mq(rfrSQom#SKHGehiwB zmWy#x6Ba)LXZa*4^2@B2#v&uc8r$5J^mPy6a$t>ydvX&v%|9#N_qb!EmYX$UzYkM9 zB237&;1{j8OHS}6UbLyuyykTI?~9M{1-Lsz@VJo2@B{TyS=i;@0P&1M}QN!DHemFMiYxv_Hh&WBb+gl zYj_Obp3_QSLC--aLl6GAA1ub=Fl27;k{csVkKAshG#{8#{f8LOk^FJ(1i;$SA4vTB zK4mc;^)e$=Mdcy~)K{2x zIKNAk{OH#{$eqxO*r|7h(JA8bO5(XfQT{%qdXOdSU zthlIE?%$<0Xqqmqbt|$SCZf+PhUsvBtfr(e!C#$cHSPQbVYbEf2~U17LFy$Iw+Nlw z`V*HcJNJiRdB&trBswk`X#>E?`Vd*^Z>uLlZpQa}^aBlpIP9aook=Z!SisJdWN#Zb`xSsxLh|$=IP3%&WBUws4Ss&Ve*TY|0Kg<** z9ai-^(j+C25)0pfmgjRl=Z9Zm4y){oF$3^Q_d8-I+z)AjpJ!W(&yXSPRKFd-LzzY< zSE%p2&u1+!`)OJqtej~x6jeA_=O_<>89o1r(>(hsdYUZb*hZK`YYD=2;u}l_{7wjJ znc^IafT}x_7l0)ks|c+@zJ7mbW@tBaJ__XE{m~MDpMS^&37YTO9gp|tBseC%`39$J zw_YxORo%F2v2Ydp;T_f{?h=rHhz?0sx2EXPis|EF+MAw;RbJ~Ei`(yP{)ALS7&zC4 zA>T6hE8c6^G`*>FTR1|&NJeRu|9vRD#>Q;Tz@94$07c+7QlMx`1oh6T2XkV;cYN7D zcag$}yBq-rUUv*AZFyY64tykm3Wp5QO|*U#Xefm;ts!aw@A_xs>e5I9Q!TF;ZWjtPgMDeN6G>;cD6r1YwaEOivZ2-TU!ROi7^I_GX~8Nq^iV(2<&_&g%V8`AI5zDeZde`rb~ z7b6rhQnC*a*hKK@U7RqU@B}g*(g6sW7PlZYMyKW)O0hd;w8K*Alf^~3S*>1@ft*PGf_3ZN4`Cm)A-=-I zpx9i8%gR-8O^w#eNriI-E6u;@!Qc>y$Dk~}Q*47?23(^&S$7dk%f%RP?Xn$dJULY; zyTVtPj)RR8zYFo6_7M9Lh1j?+b)c5N7CeI-9zpF-wg~FCck)L# zS8sW|amp4&UozD^e|;q0nsF1|Rr7d{V{HvMaTM^l&(8oR(_MJ`eACqh_Z;`ZB^I2l z!U^ewfXu-$gY@R8PxjQ9l$bhz4|Z6an!R`SzBIPd#1one)Hq$aMQD zPAZ_-a=jiXYZLk~K)Yi0L(+OhEtwZfYz`cYW1eJVVUqrCK!tanmo@Y6sl(Qw#Li<>|xP0vXmf13-z)`aJ zusv;DZ9cLLLy1)yzY1RZ-K;0-BW7p679LGhxR@Zb;7d}{V0lIq5ghZUpNgWE7X zE0aq06Gsw8>pcknKqze~fwftzJUiafBq+a&wQqD%BYyV zrnSQlL>_|m7Qo5CC%j^JPUn~VsVU&}i`ANk@+@C&GA0SctD0LV|MA=SOvJGB=K@9b z$pPZK0qHvRn9bfWHs*Q;MmZID6lGOBH&rGx?VUs1?G#hqrdOY?l@Q*6Ol(UVl7?)T zC(N9z+9zO!1$k8H%g<`gV=^{Ivw8qh0QYzu1SeHgy&h6h+5f3AC5-5T zUJL_u@2`w~62oX^gGVJ1-UPS)QnU{tt)EZE{7{AGL7o@uKS14fQfk_I=@XRuk z&fyZl-G~(uKcl46DPX2{$|C$?#)>ObqzONs>S$Gw0ZY28Zn-muk;Im)OKQ?LHbEbw zhowdLhTUNc1o}`PEHSd9# zR;|8C-f0NU@b0YntTc9T>AZ1GEpvm}K-(gno;P+}kGA$iy_vjDufSOOgZzwB5Uh&R zFVmK>x7vTX+<$>)4@yQ~qskcHVe=^rQFhh*s15Y)F^0%5p912=+0q7#OxDp! ztrZ_t=Sn}zb^Zd$rjV`$JFEpNnLGu{hBMSju5U7+zOJ@t(2zhDodK1a*zV%-5f3$A zeSIzH^gBk6lsGslj6-U?$)R_mDQJ(s?l}b^LZ|rrE{S4Al`L56Rcl;m(8YqfG>j(i za(25wHk<)@yptaFV(NT)icc?9mgN*~Gn&$vfg54`D>m<3XTl_py8>Ta4VU}P_DVg0fj8IfwvZ%V3x z1PpFrxgU5_J@c7Aw!zAKY2)9wy$?Q)S7+R82O&oXHx{#iX7WFwrg-H)CWLZUux!p< zQbL=$=1tS^x_(Xyd}3`hTY;U|j=$hod~4yPEpEC=hfTp@`Xg%e0{^$0-9&Of%=O#v z*!`I!4Xgq}6&lVp*DOauhm5^vDVNk?ILP@-)_djuhN|0P+`hPHEm-XL(%?}{Z`hz0 z7a#0p4VznRo8?ED;5sx-8zo?=)iddg%F}S69%MrbE~OOh(U}k57?Y@S(A+tYu_+mG z^eGsqBlpERbxmn%)7sSsObcybNkJtb6|kq_&&WIasnJP`nOxQN@knXgSHLSh2j={g z_)w-OQFCo78DZ9cQG6?hdEw>GrC3`qdQgnSi8bcM-v3tcI0Qti&D@XA_eg6WiDc=r zxGv;XnG}J6Sb?O_v5kp3=$0??La$ISlhh9X3K5RDfd0fJBEJ(q@c`7Gm4hdEt_d~r zD{w>>0w%*L>79JnOkpS~{_ZlFoT2It;`#Aq!cQK;fsxFqyFUXm=LZu5rO}Y?nFUJl zB8epmOTWvoBhG5d-cdzi#*%8?nrI&;+ia|jDK(nX<;TDa=u2guCf_yR4_)`%{9Wy6 z7<;kv5v5cdl(Wl~Z}T~CG~JMJCK!6MXtUj-2F-TAb?SB;eKjuGn@#q#+VtYAcb`I~ z+^@{s-za&N*L@7r)~Uiq9(~zNpfv3b%x&->Pllrodqp%8Vs@u9x;}5yByS-aN2jjq zszmic2_l|fu`@py?)ewkokL%?-+l{a(>8Z$tHFX(M;iCE|F%A><#1V}LOuwV9B=+u zw>vSnR)A5*;t5+c8ZleWioNdruB8-HW^KmGR~YX5)8ro+0N-XizOcto)q_>MPwC{f z$qlb+;?3}0Ty0ws+0f+PJsR34QROD2*V88C@QXDDx0{L{{d#Z+w zD0WK-Rp{o}kY0_x5{(~PIJd^V>YG30cgpibGVm67@W=^!kWLGUnPOucEMtk*pQZ#X z*AkqR#}4xUwTZATCU8Bb#QNO9s%v=Rp79CLIh?%pF-gT1MptDeOcT*oT)gn{B zZ*{L4H#$(+zr~Jx0CI}|FW)DrN=>|W;|Dl#g4bH9Y>Nr=*1L>S+%|I> zPczAUb5JNeA46M-a+KGTyvjso%5Z}!-zJ2~GoSRL7 zIO@GRS7sMhNUtXV?MtSOF*RHfkt0nt{mOYe!PHuFn&Qo;2(uJ=q4O=rXgyT#iQq4P zE4Mo+UDFg_ArqfqPe#dm!kraAIrf%7^ZRRMH(UIUx)#&3&Wf&>q7~u$NwHZ;1W9bu@G+ht8q=_Q<%aI^8<^Mm;B?{I4DA zCF~+5U&*SRv8i19*~fU}`u^7%$cZB}%13NI2>)0uz)YlK%zZB?d;aujD}Q&M)}_Z< z_!;6X(RKz5%x(HL#&tv)*N5t3qVBMi+N(f(y&!m<-V-bh%6_?Z_itx=0&z@4$7Rd! z$0A%G#hC0`pUi|JE4v+VUN|tFZK`=TSKuswiH}PQ;*YnY465RTg4*q#G!Fnt}lBMcwReOrYUo4gT#SW zY!VvmeN~C0HLpKA1L^*eh+&5_Ypt9`NKG@dLeo}xrP)4TPi^^ViBc6B1gT_u{KDwF z_!lA!Y+se{_T(X9Zm#m%Lo0^C%<$n1l8-(HNII|H;?|h5q>@%mQ2Z+%WXWJIf^MlJ za-Q977D0>33?_s7X>1yWC!w=QB|__y5BWF{XvI%&R(`@0dXZdyhC6zRrq%DStV}D- z@fNh>Wz9MKS7!4%_|P#1XT~4h*53AErea&fuU113G{MAV#+){2+p`+|?Q{oHU(OxhP6xzebYN8J&x>-0)_qsl{ z5+WH!8rLpDjBAjIS$t#Irz(tY#G~t&O!b$&Zq)A2l>wh$RC}80-2eA1} z_`+Xwg~E8?+bomYxdc}V)Aia&eC-)Hcy**^hCeMWEB9;%cM0Cl6JM^BjyTh1EA5}d zvj%KkB3!)Zu+n0=0X70K#^K3%;o6$Lx1ba_4Z&1Q-(3hdHLtS+Z@{#pVX=Q8MRWMN zHSw<`d^hTvUI}&V8cFrTJ#;hCTJ{U2hC1pTe)p{eX?EkI9Xj@4ZcA-`OQZO8TqvD4 z-*QU*-C5OLG1SeM!rv?fGfE-MjGm0~Ga7uaiRX%(de?>p>9l5Vj;p;TG^PIYqQiRw z!1?n@e_0A$WVmZj!^$DOf=9w9LN8PmsTzPs9ei$x)v)Qy_V&Xnr@b#OAExjI;H2uG zQ;zCBWGQ9gKRKNowU;r3&KJnn*wb2KvLu$upUA<$WYk-#M1Jh*{UR;hCHjTWXaQdn zo4wXhGfw1cM)at_n^plsR}mHXUGB>kCobPyo~W04->P zpQvdTh+xqoi{O>KpK*NcJ85RjmFwWFb!z}6~ zZ=g-V2xHh)x*KLj6VofY%zc-=)~4^9xTTFGpkPiZ-ChtbAO;cZ$}udA$`tIEi22Zd zEFi|}Y!F;cWjFf%E6L$-7^Mifp-^1i3i>Tz?{+(etF>&G^f8Nu91p%nBB~FpRNnaw&wm5Mz-}}O73ZlBAv)8aeSftYO%cnIyG@JbDYGQe z=BT3n<6Qt_QJ53E*e(SGx+O6DhXW=`fg=XEeQLU`SKmQUyOzTXaYd|Kil8uBo#_!w zUX8lgGEhNLX!Z3ii%rNG$9r1olAx|$-g(m%jDN`}d_{J5lY6V4SLK5q>0Qh#KC=B#7ENI+++LoLv&GOvyhA+)&W1>L8hHeaFKTR*_#n32PV&r&w=md+dJJy zeW!YgXZ?q+ocf7)w)9mf^cS0b-?Y-aa^YnPq&2o$%-&iar26$Hn=Uqqx#YqiU2Y(E zR0)o954<7pdYGuE>O%H?f_tJ9LUG>X7LEFTqJnlYbmuk~kjs>{a#_s$otLi4?4Sb_ zR>JRUjEb9HmW7y2QnKtfVCbeoA-bkLg6aAf{(#Oq03&~b7ZX;>aac5hZ}iBONq~Jb zV=-6JJ)LWLT_bpD4j)ae8ha~OZuB~R(j1w!6`0pTn2S17j0I0S>B3m-xF58G0^18a zK^{7&7Z8pb8)jcd>TOagS9t2@cj$PC+k)kBmPw9g{aQ~!W}C=9DNX}u3&R!5jHbK) zwi~8N#wa|pjKD{7w7%GXLgw*fztQ61l$3Eh3tpo3__p zq9nHxX1y1sRVM5T0zv{>I_-u%27|LG@D6)w4gSH7??YKG9XJO-f(diFoZssdauIFf ziAq_blF)c#$rmKWmrpfmt)b2cWb^*WrHNMqDxLYCT7Sr{{0?(kgm3$dvaI(;Xi@nZ zr{?mn2v5Q&4}sNm$0h1gx<1mK)SQTJ673pD1X{UmbHZ`~xfUxQ7P0uzwFOIj8?)mT z!5h)QVEC%-lJ$ZnwVPC@+M%W_a47t8H`cru?>F}((1~BLUqh%x#JyqppwI$>v>BS9 zvKZ{S6MSa)=B8!xOx`|DZ~BqVr+w4KIv(VJjq$U@sAt5dzW}YoCTYevmQ_+KMec~f zJ>te`ud|$vG|;M{AC&g*Hr4^*ux9&(psk+~yWU7izK8H2BY4LJ*j|2Y0i+Rte#nin z{@Tl8R2#~1l;o!6Q*tLB_|$ugi>#5_oTiPis0Ob)lgNBRqNVe>BnbS@3wPHmVy31C zG@_3}3lneVrb6Mf%iCUx4`T`I&}QHS%L*QWKOXCqngFwjOVz!aDX$ z7SVzSMY;U&R5E%=@r{G`WY#zJ)C>-&nd>9UfZM(3S^HF7Fs!96WpufRj z<&59P8Zxuh18fvGxq#IpyDR=4E;on;svsz4#1Yun{-Yt!cv*7-7Y}6!w82JY<;WBN zVan&XI7w@50!Dpf%vXXg{$G}y({K50f^e;e&C|7iI-oUAmK?u+2-y~CGs6@*c8d1_ zveP*3{a+qd^D@48wc1Y1es=g`VT;>1*bm@=BY3td`}pEL7_=o+-Ui zH>HvCsmbnmMxO|K3Y?w?@cwQ?4gAtK^OTDx@96$rJBSKZ!u}sn~Zgs#ZT)^q4@V92k?!YTI^nbt= zIasa77_p&eN}QOUqrOpET+}*Gzy%LcJigQEI-_#=VB1Hg(kC##lxMB8TwOY4msp}{8yjf_zzX;CbrK`r$M&Bt)@&DaE24$RAqY(sU|zTEjbX~Qg)`<#vu0nQj$7?8 z31miU@X1j+hhaenrGzZ z#{c97c{Rky4%^yVDmy_08t`d@{Bea_Azq$_xggs z{!LO|E2J-0Tc^h$XX=b1{;kTVR|x5Lc8vncuhHI|#Yrhk$kSf{ua!9f0$4HACZ#Y0 z`*A#i<#V>sBwmEZAuQ(a2kAo|VNx+MZ|+^n^(Faj`ZBsSeyEmvp<;|BSbq)m5i?OP zO0~ZGP~SCEcXs3X@hytitK69CDb$@3KeMjMoMVYk>Z1e47-*edjO-I;cCY*ECvX+x zFn+i63QVYpodRs9W$bR4!j-+#XSB0EU*d6*B}$&f)A=32jjprkU)T~ckJv?5>M+;g zfdkZ{8?L@xSdbr{kCm*p0YBfJA5tnbHc~#cPB6kF29hKvb4&x{FeaTCMNUo1XHJ*PHJv)w2m?>;G=MX_$ z(Kx!6tL$ts2#reA6L{BkDuPAVsU!xi5Q#6Hen!RVg`Gz=5)>D4;ojZ!xy^$WNTO0kAXjU zVrjwn8{-j(=pIE=Z-LxE+q3b7jh@xIK#IsgR}tN3?agMG2kx0L-4bA zcaV|Fv@goCp*1h|r>8nTN1AvKss$~Ydd5!jxAhh*cVCAep#Y@^3mG!w_dBZlI%dg0t`u5YVC;Z{n9s?XAnRiqKCAx~@bP>v- z!+zO?Vj>cdO+-W-e0b6B^VTBRv_K}4Y54n0NtAjgt#SfU=8xm%PrsUMOhnYyhl8i9 zAcIBtIK2D4M8wx%y^?8mD2?&8zBjGV7ng)GY-C#3pWh)9!s=u46`-kq{Os}mAcu3I zIGe7GyJS{obt1uwtBU$a=D}^AiSv9PE8gi{-ee;#edSsjq~#Km%ln0j`k`5RB2n@3 z2D>Fndtx30EOuFrG2XbNAfvuKQ`BjT)^^ahjm)njYm}*WKRj8-($Uuy+s@u1P90ot zwmEBjJvf+%&ze6uFp@_WuS zfH^$HD|>E{h}Vs_hG<1!KS2ow6gwklhCS3Kf@}*KNjnpbbgqvLO|!@QT`Oo{D`OK( zwRl1@Xx&sy#3_-6259Fmle5ZL;RfkXPii8#eKz(&zZRU04^$bdUhjzv9ujgS<6G5a z8MN?G;lhFDt$0&ypP6KdTQocN)$tHu^%#C%g$8|JQfW-$Cy&NRhiq-CEwzQk&S7Q< zp=Myg8pi$jEr02ii99=ptG1OCBu<}ZA&RVO@fBxo_vo-4_e(23qe=i%nsQwjn~omK zuNtm{v8eaKa-{83)A=&BaaxR>Dis)~<8&63E@HjN;)>RbzIV}3DTQ_wq-j2Svdd{W zcqHx#hldPsNE+o6_lJy7YI2jX@(JU%*ZHe)57yrAdELXm1uWsh9XTU8;5f5c5P53J zqx0O2Lh0K4?U+3%tpylZHoT9zLC?6c2+M@dV;l(Ogu64uJgIqy|~mjNCq=2vO+_8{GHOSx*BaS^!KzHRYiVz*0W+LrwO)mB43(o z@n}za!t=Xo&oFG#TRBw_Z2guA-_V6*alH)1Y>V(z!lB+u@ML@tc7U=h~&odm^FuIAo%zeyBqgQdNrOf`RUVgU?lBf)eWf zP+n>T#mNM(S;vh;J|$>EK_n#1axF(l>et2OKvK@MInkx14wpz~m6X|4r9o*YF)obQ zV}#OrEd@=SdU3B56_SfewIXB+e3x9JScx4GK(qY8D1?$FC50 zZ!LWke>lc?6o4TTx&iL!3swXo* z)NUd&zW1u1VJ`aEjAI&MV0C7&$Sfl6DwL{zt;Vz=;_-|p{wV<2J1?5{UHJ<1U`{tp zliNL4Q6P`$-4*S1E_jG}>}H-YZ+5iuyR*0Q{-lc_p@9EPm?{AIiVeAHb<89xYfvSb zVUcWqI=|E27Ky$ji4rH?0JF6{dsz(HKo*Yc=~z)yL)02O-zT#z^(Z5^uwH`m2XhQ& zzvX7kZfW5A*W982Vmzte70hY1Gv1HXD@p1MIIn!t@4;y?R8yv3@$t5Z=0@;@#MmcT zr;YjrZf|PRlB&?(RCVZ~yqsgHk|)31#w#|C5-!9-ZFEA)_3T8(EU;Bju35rfo0c6P zrJq-}xQyAtHc$vQo}&`0Zq3a0X#GfPrnjH!A#cTL0Ru{&V4PkTEOYtPFx$RHLcH{&Lv2|USYfY&NfmE++;Y& z{>KeEqP&bnkA&HnaZorbmij=Q+0q)vnl(Q9nRd>&bw^XRn18P@#R0=}ie|1pGWNAj z8TL54FrE5cyjAqJpQ8Wk;-Be<7E4nN5;1rK)i6ef%6T24FDurtM#nG)>g44<-z3Hw zY0DH^T1D+Fq955*N&NW?+QAj3s;KsN!d0;yjd*PsI4Ih z#5t(MH@PY>3TI@AFk!~-a&3PyA?v7w{8hJ!7zD2|gJ22%5+L0<55tenW>{1*6RCSo zB3tj{OLs(G1>*ChuXaCVr%b)x{IpxMkue{ziWuw0bP;!~`wik)n(^MH&#abJaAl^k2i*-d0_@1?N(qjGRZmT812tMsmura*zP;Z{++yJ=)#5t1F z+#>POyMP4oQGEPWQ$8U`u&crUiSlkaU-n%{psBed(;@NyuykSC(CRh|&v^+R<;?Kd zjbx(MIxaV!nQjyQJ+6WSWrO&bq&Eh7nwl8@JKejY1bn9ZxGr1wk8slo0&HfDo9eqz zi{}zg94RQPt^JFW zwLZ!=*5lE2J*%KH;~csi&s=Oh@>#BO;bfkRO z0mxAwKs7y2^8aWwy}0PLu1j2LZ8`@3;IyK3K$VN{l_=m{JmZS63tmT$e2;apT_D_4 zZSK{St!o3TX)}Wk_VNfWAo1`m;h+5RjU?>R$uC=x2OuNLNaJq|CO11_+P@?Mdtf(Go2A^5-+VQoDi$EbLu7|*0IsEN z8Va7dMXSsr*V8f*r1SQ3?pa!r#@mh^&9Kx1HR5q~T%TwePJ`WSD!zxSG^_3as*bMt zX>TlweZr=vq#39Z+=@VVpsRV-`cb?m2d(n6%$L=2Ro6t%yl^@~V>vDOPV0Sw)+xHP zMpP=TAI~j)mBdc(ziw-eOO<=rINaP>Dtl=9|FosOp3DKtEnuvFWw-J;T%OY^&{r#EMj7s zjCqz>5Mg!_vcIwCwN*qxx*qkZWvYFV$(pk{m%~u7na_3`BLK!P8h@z{>XG_QkW@^{ z8LK+D1{C6@VycdZZ0ZoT(Qg7&3|x0^%uEfWEwVkm3}d~*M%nqWYINcyUW_w$Hhf0B zeUUtmRS@q0Wgn*uQMzrrOP6pS>kBz4rR_f$-8_E_FJ=3F0TCui?A1~9+3(Q3J8QH9 z(@q~R4Hn_hIY5oePVyba)b_>U5Jw&Bdm{-Xy8wid?VW*I6F~js4XBVcughL%#~{i$ z53hEksjC9hq%U5Vfca zBm+_-L9uRwwlvh9zkho8W-isY>BJjp`t?4R^iEf1RSxHC;)l3Zyc-QJ9o@gDQR%^7 zijFCM!gD`B(NvBgS=G5-?lVIHN3cQ+tn$D?JwW_1MLb2DbZW=uq4AnEI?EdC= z*B>2PIvXd^G5>~^;+%F8Ee@Rm^8Ff0FewI*sm}6sKgwMzvjXB!kNy(W!DEpK4h(OXyS?GRWA0dkmzI7~R8$WxO(v_=UZCV)X-*g>LD%Bi?TPRh2+-G+$W@*~I&en5xsI5^g z$OKT(W?>x?8f#l_sIHwkw$P|tlZd+(Z$^sePl*dk=3g-9o6sg9?JH!KPrLi`4iAyBXJRsu@EfXHgS za${zPQ+NDE$9MZl=WdO+w>0)&Q9;FP0qlxmQGGfXl>M}6X+=|ZE60(C^n9`B1^8hg zx60cmpLiA!PkIM~`br=-pqTyHjJ^VOO%NvUawILDpB{Aykx~P%>U^A3#}=hk8p=QM zn2u~Lrn;^BpFO=v+bjAVDaK7~P-uL0yjQgD(QrRhnu706bMXQMMSKAXK`DBDe|8ET z8nZM|R3zGe$6Y6Y4l+}Az6}vro+VtvTvtdbAmJgQg^hLR!p^TlByyn#>7;*OFaH3f za#lzvA5z0WgU1<5U>?HSLl#GA4qff>n3u2OS7pCRCDi${oP)e=A~A^4;ParxxrNVS z?S|gm07HG_xZp$?;M;SQpU#wovP(1v&w){#-b1;X!$3$5x5)wD&EPv+Cm>L0h&{6# z^re1EEud&IWCXuRLqbR_?58)l>hG(3tBed*aiEzI=|2xbNiGGDk&A+14tF2 z5gMwl#B%{>Xfq7LfwOTk-qFR8Xt@j~o@6wOVyl-E+D!_sHd9|DV<;-zv_U8(^H-!< zB_{1K@I*+W4;|CF3h11RwjJ~ZVHkDu4kNliYDnX^^6~tjmChqpWu4*4P4PHFa+_K@ zKJGy%ws6-o0#yW@fqQd0)|5Wxn5o5a*6{Lknxm8#BLs;S_+_NUOClMQWlxi$U*6@0 zNyG9%VFdZ2?Pr3n%|x8Zb;8Sq2kow;E6aXscDk}fJ*0rBN`l;*D-gq0j1G`boik{Q zg(0L2DT=?{Y46HGTa8M9bw@lg{b|>^Q0zR>*c)fVE+9IE~*@C`v0 z!BaK+nzsN$%JzFi`L}tMwG- z!qe#29qQ+kO24rYmbG}d_b-p2enNAv`ZOBHC5r+lS4Ax8o(j8RtOQ_l(qS2#Bo~sDo?y43hoab*`JiNuJrd z%E}`CU%a*k21kSE-x@Jte+Y3sM&He6_c;b#MXV;8@AIhuKoeFG$|M9VuA+rx#`_

Svd3Pw}P1ZS0jEC)+&WfQZYRoC9eI$#Em8I)RZ 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%0Anw#!!--foMa8-E=gMvz`ExGDc$Zdv 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 literal 0 HcmV?d00001 diff --git a/sidebar.mjs b/sidebar.mjs index 4ef125b..e99f28e 100644 --- a/sidebar.mjs +++ b/sidebar.mjs @@ -129,6 +129,7 @@ export const sidebar = [ { slug: "references/cycles-costs" }, { slug: "references/subnet-types" }, { slug: "references/execution-errors" }, + { slug: "references/message-execution-properties" }, { slug: "references/http-gateway-spec" }, { slug: "references/candid-spec" }, { slug: "references/internet-identity-spec" }, From 3db41fac686ced4cb4109c6659cffde7a34b5cd0 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 21:45:16 +0200 Subject: [PATCH 02/12] docs(security): drop redundant prefix from page titles --- docs/guides/security/access-management.mdx | 2 +- docs/guides/security/canister-upgrades.md | 2 +- docs/guides/security/data-integrity.md | 2 +- docs/guides/security/data-storage.md | 2 +- docs/guides/security/decentralization.md | 2 +- docs/guides/security/dos-prevention.md | 2 +- docs/guides/security/formal-verification.md | 2 +- docs/guides/security/https-outcalls.md | 2 +- docs/guides/security/inter-canister-calls.md | 2 +- docs/guides/security/misc.md | 2 +- docs/guides/security/observability.md | 2 +- docs/guides/security/overview.md | 2 +- docs/guides/security/resources.md | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/guides/security/access-management.mdx b/docs/guides/security/access-management.mdx index d9bf321..fcd43f5 100644 --- a/docs/guides/security/access-management.mdx +++ b/docs/guides/security/access-management.mdx @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Identity and Access Management" +title: "Identity and Access Management" description: "Security best practices for authentication, anonymous principal rejection, ingress message inspection, and session management." sidebar: order: 3 diff --git a/docs/guides/security/canister-upgrades.md b/docs/guides/security/canister-upgrades.md index d5323c0..3e14816 100644 --- a/docs/guides/security/canister-upgrades.md +++ b/docs/guides/security/canister-upgrades.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Canister Upgrades" +title: "Canister Upgrade Security" description: "Security best practices for canister upgrade hooks, panics during upgrades, and timer reinstatement." sidebar: order: 9 diff --git a/docs/guides/security/data-integrity.md b/docs/guides/security/data-integrity.md index 8818067..d954a35 100644 --- a/docs/guides/security/data-integrity.md +++ b/docs/guides/security/data-integrity.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Data Integrity and Authenticity" +title: "Data Integrity and Authenticity" description: "Security best practices for certified variables, asset certification, and protecting data authenticity on ICP." sidebar: order: 5 diff --git a/docs/guides/security/data-storage.md b/docs/guides/security/data-storage.md index 767bccb..ae96d78 100644 --- a/docs/guides/security/data-storage.md +++ b/docs/guides/security/data-storage.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Data Storage" +title: "Data Storage" description: "Security best practices for canister data storage, stable memory, encryption of sensitive data, and backups." sidebar: order: 6 diff --git a/docs/guides/security/decentralization.md b/docs/guides/security/decentralization.md index a07e9f7..47bf921 100644 --- a/docs/guides/security/decentralization.md +++ b/docs/guides/security/decentralization.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Decentralization" +title: "Decentralization" description: "Security best practices for decentralizing dapp control using SNS, governance, and reducing centralized trust." sidebar: order: 4 diff --git a/docs/guides/security/dos-prevention.md b/docs/guides/security/dos-prevention.md index b0d5208..5e79c88 100644 --- a/docs/guides/security/dos-prevention.md +++ b/docs/guides/security/dos-prevention.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Denial of Service" +title: "Denial of Service Prevention" description: "Security best practices for protecting canisters against DoS and DDoS attacks, noisy neighbors, and expensive calls." sidebar: order: 8 diff --git a/docs/guides/security/formal-verification.md b/docs/guides/security/formal-verification.md index c04f250..7ec216c 100644 --- a/docs/guides/security/formal-verification.md +++ b/docs/guides/security/formal-verification.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Formal Verification" +title: "Formal Verification" description: "Applying formal verification and TLA+ model checking to find and prove the absence of security bugs in ICP canisters." sidebar: order: 13 diff --git a/docs/guides/security/https-outcalls.md b/docs/guides/security/https-outcalls.md index cfd3280..efc5a3c 100644 --- a/docs/guides/security/https-outcalls.md +++ b/docs/guides/security/https-outcalls.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: HTTPS Outcalls" +title: "HTTPS Outcall Security" description: "Security best practices for canister HTTPS outcalls: API keys, rate limits, idempotency, response consistency, and input validation." sidebar: order: 7 diff --git a/docs/guides/security/inter-canister-calls.md b/docs/guides/security/inter-canister-calls.md index fa5ce3b..a5d5d8f 100644 --- a/docs/guides/security/inter-canister-calls.md +++ b/docs/guides/security/inter-canister-calls.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Inter-Canister Calls" +title: "Inter-Canister Call Security" description: "Security best practices for handling traps in callbacks, message ordering, rejected calls, and untrustworthy canisters." sidebar: order: 2 diff --git a/docs/guides/security/misc.md b/docs/guides/security/misc.md index 2fa1560..cfd0d4a 100644 --- a/docs/guides/security/misc.md +++ b/docs/guides/security/misc.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Miscellaneous" +title: "Miscellaneous Security Practices" description: "Miscellaneous security best practices: data confidentiality, secure randomness, endpoint validation, testing, reproducible builds, monotonic time, and floating point." sidebar: order: 11 diff --git a/docs/guides/security/observability.md b/docs/guides/security/observability.md index 2f63c53..f7a70a4 100644 --- a/docs/guides/security/observability.md +++ b/docs/guides/security/observability.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Observability and Monitoring" +title: "Observability and Monitoring" description: "Security best practices for monitoring canister cycles, logs, and health indicators." sidebar: order: 10 diff --git a/docs/guides/security/overview.md b/docs/guides/security/overview.md index b636382..d9ffb2e 100644 --- a/docs/guides/security/overview.md +++ b/docs/guides/security/overview.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices Overview" +title: "Security Overview" description: "Introduction to the ICP security best practices for canister and web app developers." sidebar: order: 1 diff --git a/docs/guides/security/resources.md b/docs/guides/security/resources.md index 9da048c..6e299ba 100644 --- a/docs/guides/security/resources.md +++ b/docs/guides/security/resources.md @@ -1,5 +1,5 @@ --- -title: "Security Best Practices: Resources" +title: "Security Resources" description: "Important security resources and further reading for ICP canister and web app developers." sidebar: order: 12 From d00082c4b72cfb961e5112c62abb4f1b7a05a728 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 21:49:20 +0200 Subject: [PATCH 03/12] docs(security): remove AI-generated encryption page pending security review --- docs/concepts/vetkeys.md | 1 - docs/guides/security/encryption.mdx | 410 ---------------------------- 2 files changed, 411 deletions(-) delete mode 100644 docs/guides/security/encryption.mdx diff --git a/docs/concepts/vetkeys.md b/docs/concepts/vetkeys.md index e27d660..cc14ef6 100644 --- a/docs/concepts/vetkeys.md +++ b/docs/concepts/vetkeys.md @@ -99,7 +99,6 @@ The vetKD management canister API is live on mainnet. The `ic-vetkeys` Rust crat ## Next steps -- [Encryption with VetKeys](../guides/security/encryption.md): implement encrypted storage, IBE, and the full end-to-end key derivation flow - [Chain-Key Cryptography](chain-key-cryptography.md): the threshold cryptographic foundation that vetKeys build on - [Security](security.md): where vetKeys fit in the broader canister security model diff --git a/docs/guides/security/encryption.mdx b/docs/guides/security/encryption.mdx deleted file mode 100644 index 655618c..0000000 --- a/docs/guides/security/encryption.mdx +++ /dev/null @@ -1,410 +0,0 @@ ---- -title: "Encryption with VetKeys" -description: "Encrypt and decrypt data on ICP using VetKeys for privacy, key management, and identity-based encryption" -sidebar: - order: 14 ---- - -import { Tabs, TabItem } from '@astrojs/starlight/components'; - -VetKeys enable canisters to derive cryptographic key material on demand so that clients can encrypt and decrypt data without the canister ever seeing the raw key. This guide covers the complete flow: exposing vetKD endpoints in a canister, generating a transport key pair on the frontend, and using the derived key for symmetric encryption. It also covers higher-level patterns: the `EncryptedMaps` abstraction for encrypted key-value storage, and identity-based encryption (IBE) for sending encrypted messages to a principal. - -For background on how the vetKD protocol works, see [VetKeys](../../concepts/vetkeys.md). - -## Prerequisites - - - - -Add the following to `Cargo.toml`: - -```toml -[dependencies] -candid = "0.10" -ic-cdk = "0.19" -ic-vetkeys = "0.6" -ic-stable-structures = "0.7" -serde = { version = "1", features = ["derive"] } -serde_bytes = "0.11" -``` - - - - -Add `ic-vetkeys` to `mops.toml`: - -```toml -[package] -name = "my-vetkd-app" -version = "0.1.0" - -[dependencies] -core = "2.0.0" -``` - - - - -Frontend (TypeScript): - -```bash -npm install @dfinity/vetkeys@0.4.0 -``` - -## Step 1: Expose vetKD endpoints in the backend canister - -The backend canister wraps the management canister's `vetkd_derive_key` and `vetkd_public_key` methods and enforces per-caller key isolation. The context passed to both API methods encodes the domain separator and the caller's principal, so each caller's keys are cryptographically separate and only that caller can retrieve them. - - - - -```motoko -import Array "mo:core/Array"; -import Blob "mo:core/Blob"; -import Nat8 "mo:core/Nat8"; -import Principal "mo:core/Principal"; -import Text "mo:core/Text"; - -persistent actor { - - type VetKdCurve = { #bls12_381_g2 }; - - type VetKdKeyId = { - curve : VetKdCurve; - name : Text; - }; - - type VetKdPublicKeyRequest = { - canister_id : ?Principal; - context : Blob; - key_id : VetKdKeyId; - }; - - type VetKdPublicKeyResponse = { - public_key : Blob; - }; - - type VetKdDeriveKeyRequest = { - input : Blob; - context : Blob; - transport_public_key : Blob; - key_id : VetKdKeyId; - }; - - type VetKdDeriveKeyResponse = { - encrypted_key : Blob; - }; - - let managementCanister : actor { - vetkd_public_key : VetKdPublicKeyRequest -> async VetKdPublicKeyResponse; - vetkd_derive_key : VetKdDeriveKeyRequest -> async VetKdDeriveKeyResponse; - } = actor "aaaaa-aa"; - - let domainSeparator : [Nat8] = Blob.toArray(Text.encodeUtf8("my_app_v1")); - - // Encodes domain separator + caller principal so each caller's keys are isolated. - func callerContext(caller : Principal) : Blob { - Blob.fromArray( - Array.flatten([ - [Nat8.fromNat(domainSeparator.size())], - domainSeparator, - Blob.toArray(Principal.toBlob(caller)), - ]) - ) - }; - - func keyId() : VetKdKeyId { - { curve = #bls12_381_g2; name = "test_key_1" } - // Use "key_1" for production - }; - - public shared ({ caller }) func getPublicKey() : async Blob { - let response = await managementCanister.vetkd_public_key({ - canister_id = null; - context = callerContext(caller); - key_id = keyId(); - }); - response.public_key - }; - - public shared ({ caller }) func getEncryptedVetKey( - input : Blob, - transportPublicKey : Blob, - ) : async Blob { - // test_key_1 costs ~10B cycles; key_1 costs ~26B cycles - let response = await (with cycles = 10_000_000_000) managementCanister.vetkd_derive_key({ - input; - context = callerContext(caller); - transport_public_key = transportPublicKey; - key_id = keyId(); - }); - response.encrypted_key - }; -}; -``` - - - - -```rust -use ic_cdk::update; - -const DOMAIN_SEPARATOR: &[u8] = b"my_app_v1"; - -/// Encodes domain separator + caller principal so each caller's keys are isolated. -fn caller_context(caller: candid::Principal) -> Vec { - [DOMAIN_SEPARATOR.len() as u8] - .into_iter() - .chain(DOMAIN_SEPARATOR.iter().copied()) - .chain(caller.as_slice().iter().copied()) - .collect() -} - -fn key_id() -> ic_cdk::management_canister::VetKDKeyId { - ic_cdk::management_canister::VetKDKeyId { - curve: ic_cdk::management_canister::VetKDCurve::Bls12_381_G2, - name: "test_key_1".to_string(), // Use "key_1" for production - } -} - -#[update] -async fn get_public_key() -> Vec { - let caller = ic_cdk::caller(); - let request = ic_cdk::management_canister::VetKDPublicKeyArgs { - canister_id: None, - context: caller_context(caller), - key_id: key_id(), - }; - let reply = ic_cdk::management_canister::vetkd_public_key(&request) - .await - .expect("vetkd_public_key call failed"); - reply.public_key -} - -#[update] -async fn get_encrypted_vetkey(input: Vec, transport_public_key: Vec) -> Vec { - let caller = ic_cdk::caller(); // capture before await - // test_key_1 costs ~10B cycles; key_1 costs ~26B cycles - let request = ic_cdk::management_canister::VetKDDeriveKeyArgs { - input, - context: caller_context(caller), - transport_public_key, - key_id: key_id(), - }; - let reply = ic_cdk::management_canister::vetkd_derive_key(&request) - .await - .expect("vetkd_derive_key call failed"); - reply.encrypted_key -} - -ic_cdk::export_candid!(); -``` - - - - -**Key decisions:** - -- **Context**: encodes the domain separator (`my_app_v1`) plus the caller's principal. This makes every caller's keys cryptographically separate; a key derived for one principal cannot be decrypted by another. Both `getPublicKey` and `getEncryptedVetKey` must use the same context so that `decryptAndVerify` succeeds on the frontend. -- **Input**: an additional identifier within the caller's key space (a document ID, a room ID, or an empty vector for a single per-user key). Different inputs yield different keys; the same input always yields the same key. -- **Caller capture before `await`**: always read `caller` before any `await` in an update call. - -## Step 2: Generate a transport key pair on the frontend - -The transport key pair is ephemeral. Generate it fresh for each session or each key request. - -```typescript -import { TransportSecretKey } from "@dfinity/vetkeys"; - -const transportSecretKey = TransportSecretKey.random(); -const transportPublicKey = transportSecretKey.publicKeyBytes(); -``` - -Pass `transportPublicKey` to the canister when requesting a derived key. - -## Step 3: Retrieve and decrypt the vetKey - -```typescript -import { - TransportSecretKey, - DerivedPublicKey, - EncryptedVetKey, -} from "@dfinity/vetkeys"; - -// An additional identifier within the caller's key space. -// Use an empty vector for a single per-user key, or a document/room ID for multiple. -const input = new Uint8Array(0); - -const [encryptedKeyBytes, verificationKeyBytes] = await Promise.all([ - backendActor.get_encrypted_vetkey(input, transportPublicKey), - backendActor.get_public_key(), -]); - -const verificationKey = DerivedPublicKey.deserialize( - new Uint8Array(verificationKeyBytes), -); -const encryptedVetKey = EncryptedVetKey.deserialize( - new Uint8Array(encryptedKeyBytes), -); - -// Verify and decrypt: throws if the key is malformed or was tampered with -const vetKey = encryptedVetKey.decryptAndVerify( - transportSecretKey, - verificationKey, - input, -); -``` - -## Step 4: Derive a symmetric key and encrypt data - -The raw vetKey is not used directly as an AES key. Use `toDerivedKeyMaterial()` to derive a symmetric key from it. - -```typescript -// Derive a 256-bit AES-GCM key -const aesKeyMaterial = vetKey.toDerivedKeyMaterial(); -const aesKey = await crypto.subtle.importKey( - "raw", - aesKeyMaterial.data.slice(0, 32), - { name: "AES-GCM" }, - false, - ["encrypt", "decrypt"], -); - -// Encrypt -const iv = crypto.getRandomValues(new Uint8Array(12)); -const ciphertext = await crypto.subtle.encrypt( - { name: "AES-GCM", iv }, - aesKey, - new TextEncoder().encode("secret message"), -); - -// Store ciphertext (and iv) in the canister; never store the key - -// Decrypt -const plaintext = await crypto.subtle.decrypt( - { name: "AES-GCM", iv }, - aesKey, - ciphertext, -); -``` - -Store only the ciphertext and IV in the canister; the raw key exists only in the client's memory for the duration of the session. - -## Using EncryptedMaps for encrypted key-value storage - -`EncryptedMaps` is a higher-level abstraction that combines `KeyManager` (access-controlled vetKey derivation) with encrypted storage. It manages key derivation, access control, and client-side encryption transparently. Each named map is secured with a single vetKey; all key-value pairs in the map share the same access permissions. - - - - -```typescript -import { EncryptedMaps } from "@dfinity/vetkeys/encrypted_maps"; - -// encryptedMapsClientInstance connects to your backend canister -const encryptedMaps = new EncryptedMaps(encryptedMapsClientInstance); - -const mapOwner = Principal.fromText("aaaaa-aa"); -const mapName = "passwords"; -const mapKey = "email_account"; - -// Store an encrypted value (encryption is automatic) -const value = new TextEncoder().encode("my_secure_password"); -await encryptedMaps.setValue(mapOwner, mapName, mapKey, value); - -// Retrieve and decrypt a stored value -const stored = await encryptedMaps.getValue(mapOwner, mapName, mapKey); - -// Grant another user read-write access to the map -const user = Principal.fromText("bbbbbb-bb"); -await encryptedMaps.setUserRights(mapOwner, mapName, user, { ReadWrite: null }); -``` - - - - -The backend `EncryptedMaps` component stores only ciphertext; all plaintext stays on the frontend. See the [password manager example](https://github.com/dfinity/examples/tree/master/rust/vetkeys/password_manager) (Motoko + Rust) for a full implementation, or the [password manager with metadata](https://github.com/dfinity/examples/tree/master/rust/vetkeys/password_manager_with_metadata) variant that adds unencrypted metadata alongside encrypted values. - -For the Rust backend, `EncryptedMaps` lives in `ic_vetkeys::encrypted_maps`; for TypeScript, import from `@dfinity/vetkeys/encrypted_maps`. - -## Identity-based encryption (IBE) - -IBE lets anyone encrypt a message to a principal using only the canister's public key. The recipient authenticates to the canister, obtains their corresponding vetKey, and decrypts. No prior key exchange is needed and the sender does not need the recipient to be online. - -**Encrypt (sender, no canister call needed):** - -```typescript -import { - IbeCiphertext, - IbeIdentity, - IbeSeed, - DerivedPublicKey, -} from "@dfinity/vetkeys"; - -// Derive the canister's IBE public key (fetch once, cache) -const publicKeyBytes = await backendActor.get_public_key(); -const ibePublicKey = DerivedPublicKey.deserialize(new Uint8Array(publicKeyBytes)); - -// Encrypt to the recipient's principal -const recipientIdentity = IbeIdentity.fromBytes(recipientPrincipalBytes); -const seed = IbeSeed.random(); -const plaintext = new TextEncoder().encode("secret message"); - -const ciphertext = IbeCiphertext.encrypt( - ibePublicKey, - recipientIdentity, - plaintext, - seed, -); -const serialized = ciphertext.serialize(); // store in the canister or transmit -``` - -**Decrypt (recipient, after obtaining vetKey):** - -```typescript -import { IbeCiphertext } from "@dfinity/vetkeys"; - -// Obtain the vetKey for the recipient's principal (steps 2-3 above) -const vetKey = /* ... decryptAndVerify as shown in Step 3 ... */; - -const deserialized = IbeCiphertext.deserialize(serialized); -const decrypted = deserialized.decrypt(vetKey); -// decrypted is Uint8Array of the plaintext -``` - -See the [basic IBE example](https://github.com/dfinity/examples/tree/master/rust/vetkeys/basic_ibe) (Motoko + Rust) for a complete backend and frontend implementation. For IBE with a time-based release condition (timelock encryption), see the [secret-bid auction example](https://github.com/dfinity/examples/tree/master/rust/vetkeys/basic_timelock_ibe). - -## Testing locally - -Start the local network and deploy: - -```bash -icp network start -d -icp deploy backend -``` - -The local network automatically provisions both `test_key_1` and `key_1`. Verify that your canister returns a public key: - -```bash -icp canister call backend get_public_key '()' -# Returns: (blob "...") -- 48+ bytes of BLS public key data -``` - -For `vetkd_derive_key` testing, use the [chain-key testing canister](https://github.com/dfinity/chainkey-testing-canister) (`vrqyr-saaaa-aaaan-qzn4q-cai`) on mainnet as a lower-cost alternative during development. It provides a fake vetKD implementation with no threshold. Use key name `insecure_test_key_1`. Never use it with real data or in production. - -## Common mistakes - -- **Reusing transport keys across sessions.** Each session must generate a fresh transport key pair. -- **Using the raw vetKey as an AES key.** Always call `toDerivedKeyMaterial()` first; do not pass the raw bytes to `importKey`. -- **Putting secret data in the `input` field.** The `input` is sent to the management canister in plaintext. Use it as an identifier (principal, document ID), not for the secret data itself. -- **Mismatched `context` between `getPublicKey` and `getEncryptedVetKey`.** Both endpoints must derive context from the same inputs (domain separator + caller principal). If they differ, `decryptAndVerify` will fail silently. -- **Not attaching enough cycles to `vetkd_derive_key`.** `test_key_1` costs approximately 10 billion cycles; `key_1` costs approximately 26 billion cycles. - -## Next steps - -- [VetKeys concept](../../concepts/vetkeys.md): how the vetKD protocol works and what use cases it enables -- [Data integrity](data-integrity.md): certified variables and response verification -- [Internet Identity](../authentication/internet-identity.md): authenticate users before granting access to vetKeys -- [vetkeys examples](https://github.com/dfinity/examples/tree/master/rust/vetkeys): password manager, encrypted notes, IBE messaging, BLS signing, and secret-bid auction -- [ic-vetkeys library](https://github.com/dfinity/vetkeys): Rust crate and TypeScript package source - -{/* Upstream: informed by dfinity/portal docs/building-apps/network-features/vetkeys/ (introduction.mdx, api.mdx, encrypted-onchain-storage.mdx, identity-based-encryption.mdx, dkms.mdx, timelock-encryption.mdx); dfinity/icskills vetkd; dfinity/examples rust/vetkeys/password_manager, rust/vetkeys/password_manager_with_metadata, rust/vetkeys/basic_ibe, rust/vetkeys/basic_timelock_ibe, rust/vetkeys/encrypted_notes_dapp_vetkd */} From c415dba1c967c605d3f770f04e6e346bf500f01d Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 21:51:18 +0200 Subject: [PATCH 04/12] docs(canister-calls): replace retry diagram PNG with PlantUML --- docs/guides/canister-calls/idempotency.md | 21 ++++++++++++++++++++- public/img/docs/retry_idempotency.png | Bin 55032 -> 0 bytes 2 files changed, 20 insertions(+), 1 deletion(-) delete mode 100644 public/img/docs/retry_idempotency.png diff --git a/docs/guides/canister-calls/idempotency.md b/docs/guides/canister-calls/idempotency.md index b46fb51..19ef45e 100644 --- a/docs/guides/canister-calls/idempotency.md +++ b/docs/guides/canister-calls/idempotency.md @@ -17,7 +17,26 @@ A canister endpoint is idempotent if executing it multiple times is equivalent t Given an idempotent endpoint, you can implement retries from an external application by retrying the call until you observe a certified response, either a replied or rejected status; see the illustration below. If such a response is ever observed, it's sure that the transaction has been executed at least once, which, thanks to idempotency, has the same result as executing it exactly once. However, the application may not be willing to wait for a response indefinitely, and a timeout could be implemented. Upon timeout, an error should be displayed to the user instructing them to wait until the latest message that has been sent has expired (as defined by the request's `ingress_expiry`) and then manually check the status of the transaction. Ideally, timeouts should be rare and not occur during normal operation. -![Retrying an idempotent call](/img/docs/retry_idempotency.png) +```plantuml +actor User +participant "Web Browser" as Browser +participant Agent +participant "Boundary Node" as BN +participant "IC Node" as IC + +User -> Browser: Start transaction + +loop until certified response or timeout + Browser -> Agent: idempotent call + Agent -> BN: call & subsequent read_state calls + BN -> IC + IC --> BN + BN --> Agent: certified response or error + Agent --> Browser: certified response or error +end + +Browser --> User: certified response\nor timeout error message +``` The situation is similar for bounded-wait inter-canister calls. Given an idempotent endpoint, the calling canister can keep retrying until a response other than `SYS_UNKNOWN` is observed or give up after a timeout if waiting indefinitely is not an option. diff --git a/public/img/docs/retry_idempotency.png b/public/img/docs/retry_idempotency.png deleted file mode 100644 index cd251ac853955634f13fc087edd69d73c5cbe9d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55032 zcmeFZ2T)W^*Di`V0Ad0}B#0m)lJmd-iixb`j6;S21{iYGR|ORXMS^5hq9n-~R8&O5 zNF2!^5{I1g+_k~)``@?DsXFyn-T&0By5Cq9uy^m?-K$rx@T{lj@hz1b)Q3+Wrl6pp zmcMybje=sI5(UNXiG%y$in!cn7W}C3)X;HO!?>|JIN>a;ZOvJoJsiwg&E2gnC@9=J z?QY#ZQq6jBbNQs??%jIgL3W0@j1wzzujPA|jt{K<;0#$?UsMX-RUcDqt5%@J|3245 zh^^mB$fBQtbLH;w@i!^H6e|AbUzpGRr9oJ29{d!B7t*r7?sOGnO!+6 zZ9oR*;@3bX#wY%f)W7>;coS*9pma-_oa6*4D0V%xmXo_BFDLh}DZxZyd|se$R?D99 z?@N9nqj6?}_1E*)rB?K`#|&&}pWHgzdN>pH%3$q)^68M1^fx{h^!F1?+CF~K)jpto z`RK+jM^^_m=NHGT6c6*5ln)jMELScMO-ah8Mnqb;e5Oz?YS3Wgwa863A#!^jeIpSl z5UKk$hMi+TvU2KJ|GHtQwMsd@(z%qM)BH8V%I35B=VO{JT*lwrGn{&VWrU??c&KiD zx}{>QJB-T2u)lxAar5ZwgeS+pzdx2_$ob{Ulg2^mt+PbF7m8=K4?R6YnJRmKV(lZj z-wl_!@%ur<$tQba`GV#Jb~{h*eik+8%_6BXxc8v+9&OJ$kK~Xbf=2FH#y?x~_->WMX#Fk_REJ}l*qf)4D= zoiVKLcDD9-lslSjyI&Oij$GzrW8H4zj778QDBoh0!#SC=3iAr{^7CACw{{g|lRnHU zn6mDbF$ z2%Cxt@<@man($yu%uOsrF#!}~q9FYRn8b#up^zjpq#wcg%KtgPF+0);W%nF1c;YHqeYPUvgrmZ=rS-qIY* z#~*$D*LCavjV@SNm|F;0V1#+ZB}6cw3xc9NCKv$;9w7@8egRVpQ8Nn>(SP=i$5}YL zVVuk_TY?-xR-m8TvSPikvxHp#Z0=@dj;s$zjE7%>=YN$L-=7)tAy(sGQY5v;UA$PhUb6q^Jg*s2j2i)|K}zDE&2XOT>lZ*e@g=Yt;YY+ zuK$SZza@eHR^$I@*Z*tcI{aS=mAO5HL2i&Pak05QfK+S0iQg7O6Ne-}km%qh5dz*%1T+JVtSRMa9TjMcU(C@5GdQ4PaMQ)tCw< zb;0@i`aDbNc>{JB)&K84hM`SNT7p7CEw#xAS_2>6%<}Sb-UvQ_*NA7!fyNorg_8Dm zlh&j@q7mckZ{K=jZC0bBPSY|oH*pjxtE<0ljFrTqr`popA`G>irH|am8y~mXY-gdS zwp?t}e%3xXI$C-7(4nOMtOS`Y$NSP-?&N9@4-fqd)X2-U^|b90zlGV|b@&ip=~LB` z){dR(_R93*WPhsdy40+a)6J=>g{=v?qNy=)q;trP=55l_f$QtbRd2hy_q7jZWN;`b z#eDeqNNc%FJ!R|N)@GHLF7B_^wzmBlGKVR(e+Cf>u*M|k#dFBZDB=z(?Rl{0-qmZ@ zxFuFE_xP?GM)0um>*HL$YQ2sy>A^cC@7;9roZ{PiUE5dY(6H%es#ZSN)zhaNZ3uD4 znb4j;>28%0Z=B$?7!8t~N_q$&5Paw#f5^W-P9kY&57= zx7x@zYip%}_L`B;`Fq!vf4n}2y0%J3mo?+bd;WZ3%O#ntr;0954UQ-)KhsL^6{yqJ zbh$}M-)L1Jeer@$)?a@Oc`kKON0jsKMqZU5d6vIouZ_*S0;eH$y&Mw~2X@GH&a435 z&Rc8g63)9aC8gh#sgt9*=fKz8yn);^=OU_aurL*m5)PgWzkco7x&iqirRCA6?cN-Y zfjAs+w8PR81R1h!I@(J?)1bV~)6T~yc4>m^w!-({cjG%+TT|b^f3H~v2RIU#**Q@hL{djVxM;pDooX@{2P9X?f}*44jActK^2uE} z7DYV0^!oKchV<(1M@D`=ffu=#b+h$L=D3DNrtMvrJeM{ixKZ*itD7?BM+ic;$XqE{ zQ;)qWyzTD(z~x0>(X^Ye?a!OBl5Xwo$LB_ys-^d|+&|FQQZ`N3WyA;)AE?GP?V(XB z+Q;|hi{Fo?w*fA{za41D-w5(f&`cjz3^}j#DiK!X-MjJUu9KY>73b0D8^%6usYK(x zH~5jp@oz6^GCq9v&Tehy=pzb>3dgjiXWO^FILDHjoUEs&NLh%yU~@TT-L40bNU$_2 zK`vlvd~>-kU$6X2O>lc=QEyhX{pq%>@Yhpy7$V_(V&^6+YwjngXIfX|l*z-FoauTdRhC*9D2yT@N2T_ O9>6kH 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 z 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 z%0D#z)@%Ob&0IjcrO^w@*ts0WF`~AT`NFQ*9nYc6?rb*FIehxo5C5sZzewb}f*{+} RP8H~la6@y0^7F2D{tG&~4aNWf From 62f3a3060d3daea0d3b433f86cc669fe2c81e672 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 21:58:05 +0200 Subject: [PATCH 05/12] docs(security): merge resources page into security overview --- docs/guides/security/decentralization.md | 2 +- docs/guides/security/overview.md | 34 ++++++++++++++++++++- docs/guides/security/resources.md | 39 ------------------------ 3 files changed, 34 insertions(+), 41 deletions(-) delete mode 100644 docs/guides/security/resources.md diff --git a/docs/guides/security/decentralization.md b/docs/guides/security/decentralization.md index 47bf921..6b99ae5 100644 --- a/docs/guides/security/decentralization.md +++ b/docs/guides/security/decentralization.md @@ -71,6 +71,6 @@ Note that also loading other assets such as [CSS](https://xsleaks.dev/docs/attac - Make sure all the content delivered to the browser is served and certified by the canister using asset certification. This holds in particular for any JavaScript, but also for fonts, CSS, etc. -- Use a [content security policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to prevent scripts and other content from other origins from being loaded at all. See also [define security headers, including a content security policy (CSP)](./resources.md#web-security). +- Use a [content security policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to prevent scripts and other content from other origins from being loaded at all. See also [define security headers, including a content security policy (CSP)](./overview.md#web-security). diff --git a/docs/guides/security/overview.md b/docs/guides/security/overview.md index d9ffb2e..74df11e 100644 --- a/docs/guides/security/overview.md +++ b/docs/guides/security/overview.md @@ -19,4 +19,36 @@ The target audience for these documents is any developer working on ICP canister The collection of best practices may grow over time. While it is useful to improve the security of dapps on ICP, such a list will never be complete and will never cover all potential security concerns. For example, there will always be attack vectors very specific to a dapp's use cases that cannot be covered by general best practices. Thus, following the best practices can complement, but not replace, security reviews. Especially for security-critical dapps, it is recommended to perform security reviews or audits. Furthermore, please note that the best practices are currently not ordered according to risk or priority. - +## Further reading + +Below are resources covering security best practices for technologies commonly used in ICP dapps. These are equally important as the ICP-specific guidelines and should be studied carefully. + +### General +* [How to audit an Internet Computer canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister) by Joachim Breitner +* [OWASP application security verification standard](https://owasp.org/www-project-application-security-verification-standard/) +* [OWASP top ten](https://owasp.org/www-project-top-ten/) + +### Rust +* [Secure Rust guidelines](https://anssi-fr.github.io/rust-guide/01_introduction.html), in particular [unsafe code](https://anssi-fr.github.io/rust-guide/04_language.html#unsafe-code), [overflows](https://anssi-fr.github.io/rust-guide/04_language.html#integer-overflows) and [Cargo-audit](https://anssi-fr.github.io/rust-guide/03_libraries.html#cargo-audit) + * For overflowing operations, consider using `saturated` or `checked` variants, such as `saturated_add`, `saturated_sub`, `checked_add`, `checked_sub`. See the [Rust docs](https://doc.rust-lang.org/std/primitive.u32.html#method.saturating_add) for `u32`. + +### Crypto +* [OWASP cryptographic failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) points out issues related to cryptography, or the lack thereof. +* [OWASP application security verification standard](https://owasp.org/www-project-application-security-verification-standard/) (see Section V6) +* **Use the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API).** Storing key material in the browser storage (such as [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) or [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)) is considered unsafe because these keys can be accessed by JavaScript code, e.g. in an XSS attack. To protect the private key from direct access, use Web Crypto's [generateKey](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey) with `extractable=false`. + +### Web security {#web-security} +* Resources for setting security headers: + * [securityheaders.com](https://securityheaders.com/) + * [Permissions policy generator](https://www.permissionspolicy.com/) + * [Content security policy evaluator](https://csp-evaluator.withgoogle.com/) and [strict CSP](https://csp.withgoogle.com/docs/strict-csp.html) + * [OWASP secure headers project](https://owasp.org/www-project-secure-headers/) +* [SSL server test](https://www.ssllabs.com/ssltest/) +* Don't use features that could lead to an XSS vulnerability, such as [@html in Svelte](https://svelte.dev/docs#template-syntax-html). +* **Log out securely.** Clear all session data (especially [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)), clear [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), etc. on logout. Make sure other browser tabs showing the same origin are logged out if the logout is triggered in one tab. This may not happen automatically when the ICP JavaScript agent is used, since the ICP JavaScript agent keeps the private key in memory once initialized. + +### Testing +* In [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html): [test upgrades](https://mmapped.blog/posts/01-effective-rust-canisters.html#test-upgrades), [make code target-independent](https://mmapped.blog/posts/01-effective-rust-canisters.html#target-independent) +* Consider [PocketIC](../testing/pocket-ic.md) for canister testing + + diff --git a/docs/guides/security/resources.md b/docs/guides/security/resources.md deleted file mode 100644 index 6e299ba..0000000 --- a/docs/guides/security/resources.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "Security Resources" -description: "Important security resources and further reading for ICP canister and web app developers." -sidebar: - order: 12 ---- - -Below are resources which cover security best practices for technologies you are likely using in your dapp. These best practices are equally important as our Internet Computer specific guidelines and should be studied carefully. They can be useful to reference when developing secure dapps or executing security reviews. - -## General -* [How to audit an Internet Computer canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister) by Joachim Breitner -* [OWASP application security verification standard](https://owasp.org/www-project-application-security-verification-standard/) -* [OWASP top ten](https://owasp.org/www-project-top-ten/) - -## Rust -* [Secure Rust guidelines](https://anssi-fr.github.io/rust-guide/01_introduction.html), in particular [unsafe code](https://anssi-fr.github.io/rust-guide/04_language.html#unsafe-code), [overflows](https://anssi-fr.github.io/rust-guide/04_language.html#integer-overflows) and [Cargo-audit](https://anssi-fr.github.io/rust-guide/03_libraries.html#cargo-audit) - * For overflowing operations, consider using `saturated` or `checked` variants of these operations, such as `saturated_add`, `saturated_sub`, `checked_add`, `checked_sub`, etc. See e.g. the [Rust docs](https://doc.rust-lang.org/std/primitive.u32.html#method.saturating_add) for `u32`. - -## Crypto -* [OWASP cryptographic failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/) points out issues related to cryptography, or the lack thereof. -* [OWASP application security verification standard](https://owasp.org/www-project-application-security-verification-standard/) (see Section V6) -* **Use the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API).** Storing key material in the browser storage (such as [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) or [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)) is considered unsafe because these keys can be accessed by JavaScript code, e.g. in an XSS attack. To protect the private key from direct access, use Web Crypto's [generateKey](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey) with `extractable=false`. - -## Web security {#web-security} -* Resources for setting security headers: - * [securityheaders.com](https://securityheaders.com/) - * [Permissions policy generator](https://www.permissionspolicy.com/) - * [Content security policy evaluator](https://csp-evaluator.withgoogle.com/) and [strict CSP](https://csp.withgoogle.com/docs/strict-csp.html) - * [OWASP secure headers project](https://owasp.org/www-project-secure-headers/) -* [SSL server test](https://www.ssllabs.com/ssltest/) -* Don't use features that could lead to an XSS vulnerability, such as e.g. [@html in Svelte](https://svelte.dev/docs#template-syntax-html). -* **Log out securely.** Clear all session data (especially [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)), clear [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), etc. on logout. Make sure other browser tabs showing the same origin are logged out if the logout is triggered in one tab. This may not happen automatically when the ICP JavaScript agent is used, since the ICP JavaScript agent keeps the private key in memory once initialized. - -## Testing -These are some useful references for testing canisters which don't necessarily relate to security. However, having good test coverage is essential for developing secure applications. -* In [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html): [test upgrades](https://mmapped.blog/posts/01-effective-rust-canisters.html#test-upgrades), [make code target-independent](https://mmapped.blog/posts/01-effective-rust-canisters.html#target-independent) -* Consider [PocketIC](../testing/pocket-ic.md) for canister testing - - From 539bb847686f91eb02212d1b71fdf63e6ca43cec Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 22:06:16 +0200 Subject: [PATCH 06/12] docs(security): rename slugs to match full topic names for SEO --- docs/404.mdx | 2 +- docs/concepts/security.md | 6 +++--- docs/guides/backends/randomness.md | 2 +- docs/guides/index.md | 2 +- ...data-integrity.md => data-integrity-and-authenticity.md} | 0 docs/guides/security/dos-prevention.md | 2 +- ...ss-management.mdx => identity-and-access-management.mdx} | 0 .../{observability.md => observability-and-monitoring.md} | 0 8 files changed, 7 insertions(+), 7 deletions(-) rename docs/guides/security/{data-integrity.md => data-integrity-and-authenticity.md} (100%) rename docs/guides/security/{access-management.mdx => identity-and-access-management.mdx} (100%) rename docs/guides/security/{observability.md => observability-and-monitoring.md} (100%) diff --git a/docs/404.mdx b/docs/404.mdx index a0f159d..dfa8a52 100644 --- a/docs/404.mdx +++ b/docs/404.mdx @@ -16,7 +16,7 @@ Pick a guide that matches what you're building. - **[Frontends](/guides/frontends/asset-canister/)**: Serve assets, integrate frameworks, configure custom domains, and certify responses. - **[Authentication](/guides/authentication/internet-identity/)**: Add passwordless login and verifiable user identity with Internet Identity. - **[Chain Fusion](/guides/chain-fusion/bitcoin/)**: Connect canisters to Bitcoin, Ethereum, and Solana. -- **[Security](/guides/security/access-management/)**: Access control, encryption, DoS prevention, and safe upgrade patterns. +- **[Security](/guides/security/identity-and-access-management/)**: Access control, DoS prevention, and safe upgrade patterns. ## Other places to look diff --git a/docs/concepts/security.md b/docs/concepts/security.md index ef23dff..b281fd1 100644 --- a/docs/concepts/security.md +++ b/docs/concepts/security.md @@ -61,7 +61,7 @@ The following threats are your responsibility to mitigate: Every update method is publicly callable. If you do not check the caller, anyone can invoke admin functions, drain funds, or corrupt state. The anonymous principal (`2vxsx-fae`) is a particularly common gap: it must be explicitly rejected in any authenticated endpoint, because otherwise it acts as a shared identity that anyone can use. -See [Access management](../guides/security/access-management.md#reject-anonymous-callers) for implementation patterns. +See [Access management](../guides/security/identity-and-access-management.md#reject-anonymous-callers) for implementation patterns. ### Reentrancy and async interleaving @@ -97,11 +97,11 @@ Users have no way to verify that a canister's running code matches its published ## What's next -- [Access management](../guides/security/access-management.md): caller checks, guards, and role-based access control +- [Access management](../guides/security/identity-and-access-management.md): caller checks, guards, and role-based access control - [Upgrade safety](../guides/security/canister-upgrades.md): safe upgrade patterns - [Inter-canister call safety](../guides/security/inter-canister-calls.md): async pitfalls and mitigations - [DoS prevention](../guides/security/dos-prevention.md): cycle drain protection -- [Data integrity](../guides/security/data-integrity.md): input validation and storage safety +- [Data integrity](../guides/security/data-integrity-and-authenticity.md): input validation and storage safety - [Response certification](../guides/frontends/certification.md): certified variables for query responses diff --git a/docs/guides/backends/randomness.md b/docs/guides/backends/randomness.md index ec94e8f..59438e6 100644 --- a/docs/guides/backends/randomness.md +++ b/docs/guides/backends/randomness.md @@ -225,7 +225,7 @@ Note: this example predates `mo:core` and uses the older `Random.Finite` API. Th - [Verifiable Randomness (concept)](../../concepts/verifiable-randomness.md): how the IC's threshold VRF works - [Management Canister](../../references/management-canister.md): `raw_rand` API reference -- [Data Integrity](../security/data-integrity.md): using randomness in a secure application design +- [Data Integrity](../security/data-integrity-and-authenticity.md): using randomness in a secure application design - [Inter-canister calls](../canister-calls/inter-canister-calls.md#reentrancy): async patterns and reentrancy diff --git a/docs/guides/index.md b/docs/guides/index.md index ff1a2f4..506a02a 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -20,7 +20,7 @@ Practical how-to guides organized by development stage. Each guide solves a spec - **[Testing](testing/strategies.md)**: Write unit tests, run integration tests with PocketIC, and set up end-to-end testing. - **[Canister Management](canister-management/lifecycle.md)**: Deploy, upgrade, fund, optimize, and back up canisters. -- **[Security](security/access-management.md)**: Implement access control, encryption, DoS prevention, and safe upgrade patterns. +- **[Security](security/identity-and-access-management.md)**: Implement access control, DoS prevention, and safe upgrade patterns. ## Advanced features diff --git a/docs/guides/security/data-integrity.md b/docs/guides/security/data-integrity-and-authenticity.md similarity index 100% rename from docs/guides/security/data-integrity.md rename to docs/guides/security/data-integrity-and-authenticity.md diff --git a/docs/guides/security/dos-prevention.md b/docs/guides/security/dos-prevention.md index 5e79c88..bc9e8e9 100644 --- a/docs/guides/security/dos-prevention.md +++ b/docs/guides/security/dos-prevention.md @@ -17,7 +17,7 @@ To protect your canisters from DoS and DDoS attacks, consider the following stra * **Bot prevention techniques**: Use methods like captchas or proof of work to ensure only legitimate users can access your canister. CAPTCHAs help verify that the user is human, while proof of work requires the user to spend computational resources to proceed, deterring automated attacks. [Internet Identity](https://github.com/dfinity/internet-identity) has a [captcha implementation](https://github.com/dfinity/internet-identity/blob/2bf92dc16371428a3dcc1115580a691842ec76df/src/internet_identity/src/main.rs#L517) that can serve as an example for implementing this in other projects. * **Monitor cycles usage**: Regularly track your canisters cycles consumption and set alerts for any sudden spikes that may indicate an attack. * **Ingress message charging**: While charging for ingress messages (external requests to the canister) is not natively supported, custom solutions could be implemented to make sure that any expensive actions have costs associated with them. -* **Filter ingress messages using inspect message**: Certain non-critical checks can be placed in the inspect message function to filter out ingress update messages before they are executed by all nodes of a subnet. Since this code only runs on a single node, the execution does not consume cycles, but it also shouldn't be relied upon for security-critical checks such as access control. However, they can efficiently reject certain ingress messages early. Read the corresponding [documentation](../../references/ic-interface-spec/canister-interface.md#system-api-inspect-message) and [security best practice](./access-management.mdx) carefully for the caveats. +* **Filter ingress messages using inspect message**: Certain non-critical checks can be placed in the inspect message function to filter out ingress update messages before they are executed by all nodes of a subnet. Since this code only runs on a single node, the execution does not consume cycles, but it also shouldn't be relied upon for security-critical checks such as access control. However, they can efficiently reject certain ingress messages early. Read the corresponding [documentation](../../references/ic-interface-spec/canister-interface.md#system-api-inspect-message) and [security best practice](./identity-and-access-management.mdx) carefully for the caveats. ## Protect against noisy neighbors diff --git a/docs/guides/security/access-management.mdx b/docs/guides/security/identity-and-access-management.mdx similarity index 100% rename from docs/guides/security/access-management.mdx rename to docs/guides/security/identity-and-access-management.mdx diff --git a/docs/guides/security/observability.md b/docs/guides/security/observability-and-monitoring.md similarity index 100% rename from docs/guides/security/observability.md rename to docs/guides/security/observability-and-monitoring.md From 71f9254a3c05f40759aeb2de635297f6b95a013b Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 22:08:05 +0200 Subject: [PATCH 07/12] docs(security): reorder sidebar by learning progression --- docs/guides/security/canister-upgrades.md | 2 +- docs/guides/security/data-integrity-and-authenticity.md | 2 +- docs/guides/security/data-storage.md | 2 +- docs/guides/security/decentralization.md | 2 +- docs/guides/security/dos-prevention.md | 2 +- docs/guides/security/formal-verification.md | 2 +- docs/guides/security/https-outcalls.md | 2 +- docs/guides/security/identity-and-access-management.mdx | 2 +- docs/guides/security/inter-canister-calls.md | 2 +- docs/guides/security/observability-and-monitoring.md | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/guides/security/canister-upgrades.md b/docs/guides/security/canister-upgrades.md index 3e14816..25c0fce 100644 --- a/docs/guides/security/canister-upgrades.md +++ b/docs/guides/security/canister-upgrades.md @@ -2,7 +2,7 @@ title: "Canister Upgrade Security" description: "Security best practices for canister upgrade hooks, panics during upgrades, and timer reinstatement." sidebar: - order: 9 + order: 8 --- ## Be careful with panics during upgrades diff --git a/docs/guides/security/data-integrity-and-authenticity.md b/docs/guides/security/data-integrity-and-authenticity.md index d954a35..6cafbd1 100644 --- a/docs/guides/security/data-integrity-and-authenticity.md +++ b/docs/guides/security/data-integrity-and-authenticity.md @@ -2,7 +2,7 @@ title: "Data Integrity and Authenticity" description: "Security best practices for certified variables, asset certification, and protecting data authenticity on ICP." sidebar: - order: 5 + order: 4 --- ## Certified variables diff --git a/docs/guides/security/data-storage.md b/docs/guides/security/data-storage.md index ae96d78..9b9f1c0 100644 --- a/docs/guides/security/data-storage.md +++ b/docs/guides/security/data-storage.md @@ -2,7 +2,7 @@ title: "Data Storage" description: "Security best practices for canister data storage, stable memory, encryption of sensitive data, and backups." sidebar: - order: 6 + order: 3 --- ## Rust: Use `thread_local!` with `Cell/RefCell` for state variables and put all your globals in one basket diff --git a/docs/guides/security/decentralization.md b/docs/guides/security/decentralization.md index 6b99ae5..882715a 100644 --- a/docs/guides/security/decentralization.md +++ b/docs/guides/security/decentralization.md @@ -2,7 +2,7 @@ title: "Decentralization" description: "Security best practices for decentralizing dapp control using SNS, governance, and reducing centralized trust." sidebar: - order: 4 + order: 10 --- ## Use a decentralized governance system like SNS to put dapps under decentralized control diff --git a/docs/guides/security/dos-prevention.md b/docs/guides/security/dos-prevention.md index bc9e8e9..be35317 100644 --- a/docs/guides/security/dos-prevention.md +++ b/docs/guides/security/dos-prevention.md @@ -2,7 +2,7 @@ title: "Denial of Service Prevention" description: "Security best practices for protecting canisters against DoS and DDoS attacks, noisy neighbors, and expensive calls." sidebar: - order: 8 + order: 7 --- ## Protect against DoS and DDoS attacks diff --git a/docs/guides/security/formal-verification.md b/docs/guides/security/formal-verification.md index 7ec216c..40e1909 100644 --- a/docs/guides/security/formal-verification.md +++ b/docs/guides/security/formal-verification.md @@ -2,7 +2,7 @@ title: "Formal Verification" description: "Applying formal verification and TLA+ model checking to find and prove the absence of security bugs in ICP canisters." sidebar: - order: 13 + order: 12 --- Formal verification is the highest form of quality assurance for software. Given a specification of what the system should do, formal verification tools check whether this specification is satisfied by a model of the system. The unique advantage of formal verification is that it can not only find bugs but also formally **prove** their absence — including the absence of security bugs. This goes beyond what testing or manual audits can achieve. diff --git a/docs/guides/security/https-outcalls.md b/docs/guides/security/https-outcalls.md index efc5a3c..da7c50c 100644 --- a/docs/guides/security/https-outcalls.md +++ b/docs/guides/security/https-outcalls.md @@ -2,7 +2,7 @@ title: "HTTPS Outcall Security" description: "Security best practices for canister HTTPS outcalls: API keys, rate limits, idempotency, response consistency, and input validation." sidebar: - order: 7 + order: 6 --- ## Do not store sensitive data such as API keys in canisters diff --git a/docs/guides/security/identity-and-access-management.mdx b/docs/guides/security/identity-and-access-management.mdx index fcd43f5..ddf4946 100644 --- a/docs/guides/security/identity-and-access-management.mdx +++ b/docs/guides/security/identity-and-access-management.mdx @@ -2,7 +2,7 @@ title: "Identity and Access Management" description: "Security best practices for authentication, anonymous principal rejection, ingress message inspection, and session management." sidebar: - order: 3 + order: 2 --- import { Tabs, TabItem } from '@astrojs/starlight/components'; diff --git a/docs/guides/security/inter-canister-calls.md b/docs/guides/security/inter-canister-calls.md index a5d5d8f..002f2c6 100644 --- a/docs/guides/security/inter-canister-calls.md +++ b/docs/guides/security/inter-canister-calls.md @@ -2,7 +2,7 @@ title: "Inter-Canister Call Security" description: "Security best practices for handling traps in callbacks, message ordering, rejected calls, and untrustworthy canisters." sidebar: - order: 2 + order: 5 --- To understand the issues around async inter-canister calls, one needs to understand the [properties of message execution on ICP](../../references/message-execution-properties.md). Understanding these properties is a prerequisite for understanding the security issues discussed below. diff --git a/docs/guides/security/observability-and-monitoring.md b/docs/guides/security/observability-and-monitoring.md index f7a70a4..8de1272 100644 --- a/docs/guides/security/observability-and-monitoring.md +++ b/docs/guides/security/observability-and-monitoring.md @@ -2,7 +2,7 @@ title: "Observability and Monitoring" description: "Security best practices for monitoring canister cycles, logs, and health indicators." sidebar: - order: 10 + order: 9 --- ## Monitor your canister From 39452aef44cfaf09b04c357ffbaa9a7399987388 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 22:09:14 +0200 Subject: [PATCH 08/12] docs(security): rename misc.md to miscellaneous.md --- docs/guides/security/{misc.md => miscellaneous.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/guides/security/{misc.md => miscellaneous.md} (100%) diff --git a/docs/guides/security/misc.md b/docs/guides/security/miscellaneous.md similarity index 100% rename from docs/guides/security/misc.md rename to docs/guides/security/miscellaneous.md From 0d1164e2169fd5b905bff33e2c1c303bf41f6019 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 22:16:12 +0200 Subject: [PATCH 09/12] fix(security): upstream comment format, em-dashes, broken misc link --- docs/guides/canister-calls/idempotency.md | 2 +- docs/guides/security/canister-upgrades.md | 2 +- docs/guides/security/data-integrity-and-authenticity.md | 2 +- docs/guides/security/data-storage.md | 2 +- docs/guides/security/decentralization.md | 2 +- docs/guides/security/dos-prevention.md | 2 +- docs/guides/security/formal-verification.md | 6 +++--- docs/guides/security/https-outcalls.md | 6 +++--- docs/guides/security/inter-canister-calls.md | 2 +- docs/guides/security/miscellaneous.md | 2 +- docs/guides/security/observability-and-monitoring.md | 2 +- docs/guides/security/overview.md | 2 +- docs/references/message-execution-properties.md | 6 +++--- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/guides/canister-calls/idempotency.md b/docs/guides/canister-calls/idempotency.md index 19ef45e..baff003 100644 --- a/docs/guides/canister-calls/idempotency.md +++ b/docs/guides/canister-calls/idempotency.md @@ -118,4 +118,4 @@ Another approach applicable to ledgers (such as ICRC-1 or ICP) is to perform tra [^2]: More precisely, the ledger also allows for a small time drift of `created_at_time` into the future, which has to be taken into account when clearing the deduplication window. - + diff --git a/docs/guides/security/canister-upgrades.md b/docs/guides/security/canister-upgrades.md index 25c0fce..0af030d 100644 --- a/docs/guides/security/canister-upgrades.md +++ b/docs/guides/security/canister-upgrades.md @@ -49,4 +49,4 @@ Using the Rust CDK, the recurring timer is also lost on upgrade as explained in - See the Rust documentation on [set_timer_interval](https://docs.rs/ic-cdk/0.6.9/ic_cdk/timer/fn.set_timer_interval.html). - + diff --git a/docs/guides/security/data-integrity-and-authenticity.md b/docs/guides/security/data-integrity-and-authenticity.md index 6cafbd1..1433e65 100644 --- a/docs/guides/security/data-integrity-and-authenticity.md +++ b/docs/guides/security/data-integrity-and-authenticity.md @@ -626,4 +626,4 @@ If an app is served through `raw.icp0.io` in addition to `icp0.io`, an adversary - Check in the canister's `http_request` method if the request came through raw. If so, return an error and do not serve any assets. - + diff --git a/docs/guides/security/data-storage.md b/docs/guides/security/data-storage.md index 9b9f1c0..960491f 100644 --- a/docs/guides/security/data-storage.md +++ b/docs/guides/security/data-storage.md @@ -91,4 +91,4 @@ A canister could be rendered unusable and impossible to upgrade. For example, du - See the "Backup and recovery" section in [how to audit an Internet Computer canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister). - + diff --git a/docs/guides/security/decentralization.md b/docs/guides/security/decentralization.md index 882715a..14bcdf5 100644 --- a/docs/guides/security/decentralization.md +++ b/docs/guides/security/decentralization.md @@ -73,4 +73,4 @@ Note that also loading other assets such as [CSS](https://xsleaks.dev/docs/attac - Use a [content security policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to prevent scripts and other content from other origins from being loaded at all. See also [define security headers, including a content security policy (CSP)](./overview.md#web-security). - + diff --git a/docs/guides/security/dos-prevention.md b/docs/guides/security/dos-prevention.md index be35317..5cc8a93 100644 --- a/docs/guides/security/dos-prevention.md +++ b/docs/guides/security/dos-prevention.md @@ -59,4 +59,4 @@ An attacker will target expensive calls to drain the cycles balance or available - Be aware of attacks targeting high cycles-consuming calls. - See the "Cycle balance drain attacks section" in [How to audit an ICP canister](https://www.joachim-breitner.de/blog/788-How_to_audit_an_Internet_Computer_canister). - + diff --git a/docs/guides/security/formal-verification.md b/docs/guides/security/formal-verification.md index 40e1909..7cf2f42 100644 --- a/docs/guides/security/formal-verification.md +++ b/docs/guides/security/formal-verification.md @@ -5,7 +5,7 @@ sidebar: order: 12 --- -Formal verification is the highest form of quality assurance for software. Given a specification of what the system should do, formal verification tools check whether this specification is satisfied by a model of the system. The unique advantage of formal verification is that it can not only find bugs but also formally **prove** their absence — including the absence of security bugs. This goes beyond what testing or manual audits can achieve. +Formal verification is the highest form of quality assurance for software. Given a specification of what the system should do, formal verification tools check whether this specification is satisfied by a model of the system. The unique advantage of formal verification is that it can not only find bugs but also formally **prove** their absence, including the absence of security bugs. This goes beyond what testing or manual audits can achieve. The proof is always relative to the model and the specification. Any simplifications and assumptions in the model, or omissions in the specification, may hide bugs and attacks. On the other hand, verification can require a lot of effort, and model simplifications can make it significantly easier. @@ -17,7 +17,7 @@ These bugs are particularly difficult to find, as they can involve unexpected in ## TLA+ -The Temporal Logic of Actions (TLA+) is a language for specifying and verifying complex systems. TLA+ comes with a set of tools for lightweight formal verification in the form of so-called model checking. Through model checking, it exhaustively (within bounds, such as the aforementioned 2 concurrent calls bound) explores all possible concurrent interactions of a model of the code — exactly the domain that is difficult to test — and finds bugs. +The Temporal Logic of Actions (TLA+) is a language for specifying and verifying complex systems. TLA+ comes with a set of tools for lightweight formal verification in the form of so-called model checking. Through model checking, it exhaustively (within bounds, such as the aforementioned 2 concurrent calls bound) explores all possible concurrent interactions of a model of the code (exactly the domain that is difficult to test) — and finds bugs. Importantly, after building the model of the code, model checking runs with virtually no further human input, making it highly cost-effective. To illustrate with some made-up numbers: if the industry standard practices (such as testing and security reviews) eliminate 80% of the bugs, and "heavyweight" formal verification eliminates 99.99%, with TLA+ you can eliminate 90% with a fraction of the effort of the heavyweight verification. @@ -31,4 +31,4 @@ We have used TLA+ to create the following models that can be interesting for dap To find out more on why and how you can apply TLA+ to your canisters and dapps, including an in-depth guide to modeling canisters, refer to our series of blog posts ([1](https://medium.com/dfinity/eliminating-smart-contract-bugs-with-tla-e986aeb6da24), [2](https://medium.com/dfinity/weeding-out-the-bugs-with-tla-models-3606045bf24e), [3](https://mynosefroze.com/blog/2023-08-09-tla_for_canisters)). You can also look at the [DFINITY-produced TLA+ models](https://github.com/dfinity/formal-models) for examples and techniques. - + diff --git a/docs/guides/security/https-outcalls.md b/docs/guides/security/https-outcalls.md index da7c50c..22e244b 100644 --- a/docs/guides/security/https-outcalls.md +++ b/docs/guides/security/https-outcalls.md @@ -20,7 +20,7 @@ By default, the data stored inside your canister is unencrypted. Therefore, if y Make sure you don't store sensitive data inside your canister. -See also: [data confidentiality on ICP](./misc.md#data-confidentiality-on-icp). +See also: [data confidentiality on ICP](./miscellaneous.md#data-confidentiality-on-icp). ## Ensure your canisters have a sufficiently large quota with the HTTP server @@ -76,7 +76,7 @@ The pricing of HTTPS outcalls is determined by the size of the HTTP request and When using HTTPS outcalls, be mindful of the HTTP request and response sizes. Ensure that the size of the request issued and the size of the HTTP response coming from the server are reasonable. -When making an HTTPS outcall, it is possible — and highly recommended — to define the `max_response_bytes` parameter, which allows you to set the maximum allowed response size. If this parameter is not defined, it defaults to the hard response size limit of the HTTPS outcalls feature, which is 2MiB. The cycle cost of the response is always charged based on the `max_response_bytes` or 2MB if not set. +When making an HTTPS outcall, it is possible (and highly recommended) to define the `max_response_bytes` parameter, which allows you to set the maximum allowed response size. If this parameter is not defined, it defaults to the hard response size limit of the HTTPS outcalls feature, which is 2MiB. The cycle cost of the response is always charged based on the `max_response_bytes` or 2MB if not set. Finally, be aware that users may incur cycles costs for HTTPS outcalls in case these calls can be triggered by user actions. @@ -94,4 +94,4 @@ Perform input validation when using user-submitted data in the HTTPS outcalls. See the [OWASP Input Validation Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html) for more information. - + diff --git a/docs/guides/security/inter-canister-calls.md b/docs/guides/security/inter-canister-calls.md index 002f2c6..5863fce 100644 --- a/docs/guides/security/inter-canister-calls.md +++ b/docs/guides/security/inter-canister-calls.md @@ -339,4 +339,4 @@ Loops in the call graph (e.g., canister A calling B, B calling C, C calling A) m - For more information, see [current limitations of the Internet Computer](https://wiki.internetcomputer.org/wiki/Current_limitations_of_the_Internet_Computer), section "Loops in call graphs." - + diff --git a/docs/guides/security/miscellaneous.md b/docs/guides/security/miscellaneous.md index cfd0d4a..792e737 100644 --- a/docs/guides/security/miscellaneous.md +++ b/docs/guides/security/miscellaneous.md @@ -277,4 +277,4 @@ Floats in Rust may behave unexpectedly. There can be undesirable loss of precisi Use [`rust_decimal::Decimal`](https://docs.rs/rust_decimal/latest/rust_decimal/) or [`num_rational::Ratio`](https://docs.rs/num-rational/latest/num_rational/). Decimal uses a fixed-point representation with base 10 denominators, and Ratio represents rational numbers. Both implement `checked_div` to handle division by zero, which is not available for floats. Numbers in common use, like 0.1 and 0.2, can be represented more intuitively with Decimal and can be represented exactly with Ratio. Rounding oddities like `0.1 + 0.2 != 0.3`, which happen with floats in Rust, do not arise with Decimal (see https://0.30000000000000004.com/ ). With Ratio, the desired precision can be made explicit. With either Decimal or Ratio, although one still has to manage precision, the above makes arithmetic easier to reason about. - + diff --git a/docs/guides/security/observability-and-monitoring.md b/docs/guides/security/observability-and-monitoring.md index 8de1272..79477d5 100644 --- a/docs/guides/security/observability-and-monitoring.md +++ b/docs/guides/security/observability-and-monitoring.md @@ -19,4 +19,4 @@ Without monitoring, it can be hard to detect attacks or vulnerabilities that are - See [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html) for general patterns on canister observability. - + diff --git a/docs/guides/security/overview.md b/docs/guides/security/overview.md index 74df11e..1fbf3d3 100644 --- a/docs/guides/security/overview.md +++ b/docs/guides/security/overview.md @@ -51,4 +51,4 @@ Below are resources covering security best practices for technologies commonly u * In [effective Rust canisters](https://mmapped.blog/posts/01-effective-rust-canisters.html): [test upgrades](https://mmapped.blog/posts/01-effective-rust-canisters.html#test-upgrades), [make code target-independent](https://mmapped.blog/posts/01-effective-rust-canisters.html#target-independent) * Consider [PocketIC](../testing/pocket-ic.md) for canister testing - + diff --git a/docs/references/message-execution-properties.md b/docs/references/message-execution-properties.md index d22ad10..6aecbf8 100644 --- a/docs/references/message-execution-properties.md +++ b/docs/references/message-execution-properties.md @@ -5,7 +5,7 @@ description: "The 11 properties of message execution on ICP, covering atomicity, ## Asynchronous messaging model -ICP relies on an asynchronous messaging model. Compared to synchronous messaging like on Ethereum, this provides performance advantages because multiple calls can be executed concurrently — and also in parallel, when multiple canisters are involved. However, asynchronous message execution can also lead to sometimes unexpected or unintuitive behavior. Therefore, it is important to understand the properties of message execution. Potential security issues that arise in this model, such as reentrancy bugs, are discussed in the [security best practices on inter-canister calls](../guides/security/inter-canister-calls.md). +ICP relies on an asynchronous messaging model. Compared to synchronous messaging like on Ethereum, this provides performance advantages because multiple calls can be executed concurrently, and also in parallel, when multiple canisters are involved. However, asynchronous message execution can also lead to sometimes unexpected or unintuitive behavior. Therefore, it is important to understand the properties of message execution. Potential security issues that arise in this model, such as reentrancy bugs, are discussed in the [security best practices on inter-canister calls](../guides/security/inter-canister-calls.md). The [community conversation on security best practices](https://www.youtube.com/watch?v=PneRzDmf_Xw&list=PLuhDt1vhGcrez-f3I0_hvbwGZHZzkZ7Ng&index=2&t=4s) also discusses the messaging properties. @@ -19,7 +19,7 @@ A **message execution** is a set of consecutive instructions that a subnet execu - **Property 1**: Only a single message execution is run at a time per canister. Message execution within a single canister is atomic and sequential, and never parallel. -Note that parallel message execution over multiple canisters is possible — this property talks about just a single canister. +Note that parallel message execution over multiple canisters is possible; this property talks about just a single canister. - **Property 2**: Each downstream call that a canister makes, query or update, triggers a message. When using `await` on the response from an inter-canister call, the code after the `await` (the callback, highlighted in blue) is executed as a separate message execution. @@ -75,4 +75,4 @@ This property only gives a guarantee on when the request messages are executed, For more details, refer to the [IC Interface Specification abstract behavior](./ic-interface-spec/abstract-behavior.md) which defines message execution in more detail. - + From f903e56c3c112106d9d81553bd2b3c145644eae9 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 22:30:12 +0200 Subject: [PATCH 10/12] fix(security): remove em-dash in formal-verification.md --- docs/guides/security/formal-verification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/security/formal-verification.md b/docs/guides/security/formal-verification.md index 7cf2f42..502fb99 100644 --- a/docs/guides/security/formal-verification.md +++ b/docs/guides/security/formal-verification.md @@ -17,7 +17,7 @@ These bugs are particularly difficult to find, as they can involve unexpected in ## TLA+ -The Temporal Logic of Actions (TLA+) is a language for specifying and verifying complex systems. TLA+ comes with a set of tools for lightweight formal verification in the form of so-called model checking. Through model checking, it exhaustively (within bounds, such as the aforementioned 2 concurrent calls bound) explores all possible concurrent interactions of a model of the code (exactly the domain that is difficult to test) — and finds bugs. +The Temporal Logic of Actions (TLA+) is a language for specifying and verifying complex systems. TLA+ comes with a set of tools for lightweight formal verification in the form of so-called model checking. Through model checking, it exhaustively (within bounds, such as the aforementioned 2 concurrent calls bound) explores all possible concurrent interactions of a model of the code (exactly the domain that is difficult to test) and finds bugs. Importantly, after building the model of the code, model checking runs with virtually no further human input, making it highly cost-effective. To illustrate with some made-up numbers: if the industry standard practices (such as testing and security reviews) eliminate 80% of the bugs, and "heavyweight" formal verification eliminates 99.99%, with TLA+ you can eliminate 90% with a fraction of the effort of the heavyweight verification. From 6420bf91810d1baaa9648cd9a7d86603b05278ce Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 22:39:37 +0200 Subject: [PATCH 11/12] fix(security): fix Upstream comment format in identity-and-access-management.mdx --- docs/guides/security/identity-and-access-management.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/security/identity-and-access-management.mdx b/docs/guides/security/identity-and-access-management.mdx index ddf4946..a8a7f2b 100644 --- a/docs/guides/security/identity-and-access-management.mdx +++ b/docs/guides/security/identity-and-access-management.mdx @@ -247,4 +247,4 @@ For more information, view an [example implementation in the form of a Unity app * Returning the delegation chain [using a URI fragment](https://github.com/dfinity/examples/blob/main/native-apps/unity_ii_deeplink/ii_integration_dapp/src/greet_frontend/src/index.js#L73). * The example is currently being improved whereby the delegation chain will also be verified in the mobile app before using it. -{/* Upstream: dfinity/portal — building-apps/security/iam.mdx */} +{/* Upstream: sync from dfinity/portal building-apps/security/iam.mdx */} From 51de28a1117b0320cd03a6a00d4a2db9b59fe642 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 5 May 2026 22:44:38 +0200 Subject: [PATCH 12/12] chore: add product-security as codeowner for security docs --- .github/CODEOWNERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dbd77ef..7e493ac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,3 +3,9 @@ # DX team members are also configured as bypass actors in the branch ruleset # and can merge their own PRs without a separate review. * @dfinity/dx + +# Security — product-security team must approve changes to security best practices +docs/guides/security/ @dfinity/product-security @dfinity/dx +docs/concepts/security.md @dfinity/product-security @dfinity/dx +docs/references/message-execution-properties.md @dfinity/product-security @dfinity/dx +docs/guides/canister-calls/idempotency.md @dfinity/product-security @dfinity/dx