Skip to main content

General security best practices

Security
Concept

Overview

This document provides security best practices for developing canisters and web apps served by canisters on the Internet Computer. 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 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 and how to audit an Internet Computer canister. The relevant sections are linked in the individual best practices.

Target audience

This document was initially intended for internal use at DFINITY. However, it has now been published to the community so every developer can benefit. The target audience are any developers working on canisters or web apps for the Internet Computer. This is also of interest to anyone doing reviews of such code.

Disclaimers and limitations

The collection of best practices may grow over time. While it is useful to improve security of dapps on the Internet Computer, 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 dapps 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/audits. Furthermore, please note that the best practices are currently not ordered according to risk or priority.

Certify query responses if they are relevant for security

Security concern

The responses to query calls (as opposed to update calls) are not threshold-signed by the canister/subnet. Thus, a single malicious replica or boundary node may change the data, violating its authenticity. This is especially risky if update calls depend on the response to query calls.

Recommendation

  • All security-relevant query response data that needs authenticity guarantees (this needs to be assessed for each dapp) should be certified by ICP using certified variables. Consider using existing data structures such as certified-map. The data certification must be validated in the frontend.

  • Alternatively, these calls have to be issued as update calls by the caller (e.g. in agent-js), but that impacts performance: it takes a few seconds. Note that every query can also be issued as an update by the caller.

  • Examples are asset certification in Internet Identity, NNS dapp, or the canister signature implementation in Internet Identity.

Data confidentiality on the Internet Computer

Security concern

When storing data on the Internet Computer, 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 a 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 sufficent 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.

Nonspecific to the Internet Computer

The best practices in this section are very general and not specific to the Internet Computer. This list is by no means complete and only lists a few very specific concerns that have led to issues in the past.

Don’t use third-party components with known vulnerabilities

Security concern

Using vulnerable and outdated components is a big security risk.

Recommendation

  • Regularly check your third party components against databases of known vulnerabilities:

  • This should be integrated into the build process, the build should fail if there are known vulnerabilities.

  • Don’t use forked versions of repositories that are not maintained and may not be trustworthy.

  • Avoid using third party components that are not widely used and may not have had sufficient (ideally third party) review.

  • Pin the versions of the components you are using to avoid switching to corrupt updates automatically.

Don’t implement crypto yourself

Security concern

It is easy to make mistakes when implementing cryptographic algorithms, leading to security bugs.

Recommendation

  • Use well known libraries that may be open source and have been reviewed by many people. For example, use the Web Crypto API in JavaScript or use crates such as sha256 in Rust.

Use secure cryptographic schemes

Security concern

Some cryptographic schemes have been broken (old TLS versions, MD5, SHA1, DES, …​), or they could be so new that they have not yet been appropriately researched. Using these introduces security issues.

Recommendation

If you need to use crypto, only use cryptographic schemes that have not been broken and do not have known issues. Ideally use algorithms that have been standardized by e.g. NIST or IETF.

References:

Test your code

Security concern

Having small test coverage is risky, as code changes become difficult and may violate correctness and security properties, leading to bugs. It is hard to verify correctness and security properties in reviews (and security reviews) if there are no corresponding tests.

Recommendation

Write tests for canister implementations and frontend code, especially for security relevant properties and invariants.

Avoid test and dev code in production

Security concern

It is risky to include code paths in production code that are only used for development or testing setups. If something goes wrong (and it sometimes does!), this may introduce security bugs in production.

For example, there have been issues where the public key to verify certification was fetched from an untrusted source, since this is what is done on test networks.

Recommendation

Avoid test and dev code in production code whenever possible.