From 138c61851754a7f9c52ebdbc5ca424cf062abab6 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 11 May 2026 17:15:05 +0200 Subject: [PATCH 1/2] fix(security): remove outdated refs, fix broken links in security guides - overview.md: update ANSSI Rust guide links to restructured URL paths - data-storage.md: remove abandoned ic-stable-memory library, rephrase intro; update encrypted-notes-dapp link to vetkeys example path - decentralization.md: remove LaunchTrail reference (abandoned 2022); remove basic_dao example link (path no longer exists on master) - canister-upgrades.md: update set_timer_interval links from ic-cdk to ic-cdk-timers crate where the function now lives --- docs/guides/security/canister-upgrades.md | 4 ++-- docs/guides/security/data-storage.md | 12 ++---------- docs/guides/security/decentralization.md | 4 ++-- docs/guides/security/overview.md | 2 +- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/docs/guides/security/canister-upgrades.md b/docs/guides/security/canister-upgrades.md index c490be1..207b1c6 100644 --- a/docs/guides/security/canister-upgrades.md +++ b/docs/guides/security/canister-upgrades.md @@ -39,7 +39,7 @@ Since global timers are used internally by the Motoko `Timer` mechanism, the sam 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`. -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). +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-timers/1.0.0/ic_cdk_timers/fn.set_timer_interval.html). ### Recommendation @@ -47,6 +47,6 @@ Using the Rust CDK, the recurring timer is also lost on upgrade as explained in - See the Motoko documentation on [timers](../../languages/motoko/icp-features/timers.md). -- See the Rust documentation on [set_timer_interval](https://docs.rs/ic-cdk/0.6.9/ic_cdk/timer/fn.set_timer_interval.html). +- See the Rust documentation on [set_timer_interval](https://docs.rs/ic-cdk-timers/1.0.0/ic_cdk_timers/fn.set_timer_interval.html). diff --git a/docs/guides/security/data-storage.md b/docs/guides/security/data-storage.md index e2f7fa6..9bc44e9 100644 --- a/docs/guides/security/data-storage.md +++ b/docs/guides/security/data-storage.md @@ -47,15 +47,7 @@ Canister memory is not persisted across upgrades. If data needs to be kept acros - 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. -::: +- A commonly used library for stable memory is [stable-structures](https://github.com/dfinity/stable-structures). - 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." @@ -71,7 +63,7 @@ By default, canisters provide integrity but not confidentiality. Data stored on - Consider end-to-end encrypting any private or personal data (e.g., a user's personal or private information) on canisters. -- The [encrypted notes](https://github.com/dfinity/examples/tree/master/motoko/encrypted-notes-dapp) example app illustrates how end-to-end encryption can be done. +- The [encrypted notes](https://github.com/dfinity/examples/tree/master/rust/vetkeys/encrypted_notes_dapp_vetkd) example app illustrates how end-to-end encryption can be done. ## Create backups diff --git a/docs/guides/security/decentralization.md b/docs/guides/security/decentralization.md index 307550f..672694b 100644 --- a/docs/guides/security/decentralization.md +++ b/docs/guides/security/decentralization.md @@ -27,7 +27,7 @@ In the following list, we first provide recommendations for centralized canister 2. **The app 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 an app, consider using a tool like [LaunchTrail](https://github.com/spinner-cash/launchtrail). + - 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. 3. **Full community governance**: The app is controlled by a governance framework 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 [community governance evolves](https://learn.internetcomputer.org/hc/en-us/articles/34088279488660-Tokenomics#voting-power-and-decentralization). - Ensure all components of the app are under SNS control, including the canisters serving the web frontends; see [SNS asset canisters](../governance/managing.md). @@ -41,7 +41,7 @@ An alternative to community governance (3. above) would be to create an immutabl Contrary to some other chains, immutable canisters need cycles to run, and they can receive cycles. ::: -It is also possible to implement a custom governance canister on ICP from scratch. If you decide to do this (e.g., along the lines of the [basic governance 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 governance canister is controlled by itself. +It is also possible to implement a custom governance canister on ICP from scratch. If you decide to do this, be aware that this is security critical and must be security reviewed carefully. Furthermore, users will need to verify that the governance canister is controlled by itself. ## Verify the control and trust level of canisters you depend on diff --git a/docs/guides/security/overview.md b/docs/guides/security/overview.md index 1b56806..c5024d4 100644 --- a/docs/guides/security/overview.md +++ b/docs/guides/security/overview.md @@ -29,7 +29,7 @@ Below are resources covering security best practices for technologies commonly u * [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) +* [Secure Rust guidelines](https://anssi-fr.github.io/rust-guide/introduction.html), in particular [unsafe code](https://anssi-fr.github.io/rust-guide/unsafe/generalities.html), [overflows](https://anssi-fr.github.io/rust-guide/integer.html#chapter-integer) and [Cargo-audit](https://anssi-fr.github.io/rust-guide/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 From 65b011ae3e24a4c10a4392b5742871031d73a1f6 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 11 May 2026 17:26:00 +0200 Subject: [PATCH 2/2] fix(security): migrate @dfinity/agent to @icp-sdk/core in JS example Update the client-side certificate verification example in data-integrity-and-authenticity.md to use the current @icp-sdk/core/* packages instead of the deprecated @dfinity/* packages. - Replace @dfinity/agent, @dfinity/candid, @dfinity/principal with @icp-sdk/core/agent, @icp-sdk/core/candid, @icp-sdk/core/principal - Remove isomorphic-fetch (native fetch is used) - HttpAgent: new HttpAgent() -> HttpAgent.create() - Certificate: constructor + verify() -> Certificate.create() with principal: { canisterId } (auto-verifies, throws on failure) - cert.lookup() -> cert.lookup_path() - Drop .buffer coercions: Candid already returns Uint8Array - Fix pre-existing bug: start().await -> await start() --- .../data-integrity-and-authenticity.md | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/docs/guides/security/data-integrity-and-authenticity.md b/docs/guides/security/data-integrity-and-authenticity.md index dbf3b64..6dd332f 100644 --- a/docs/guides/security/data-integrity-and-authenticity.md +++ b/docs/guides/security/data-integrity-and-authenticity.md @@ -496,11 +496,9 @@ async fn main() { **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 { Actor, HttpAgent, Certificate, Cbor, reconstruct, lookup_path } from "@icp-sdk/core/agent"; +import { IDL } from "@icp-sdk/core/candid"; +import { Principal } from "@icp-sdk/core/principal"; import assert from "node:assert/strict"; const idlFactory = ({ IDL }) => { @@ -519,13 +517,12 @@ const idlFactory = ({ IDL }) => { const canisterId = Principal.fromText("a3shf-5eaaa-aaaaa-qaafa-cai"); const host = "http://localhost:35777"; -start().await; +await start(); async function start() { - const agent = new HttpAgent({ fetch, host }); - await agent.fetchRootKey(); + const agent = await HttpAgent.create({ host }); + const rootKey = await agent.fetchRootKey(); - const rootKey = agent.rootKey.buffer; let dummyUser = { name: "test_user", age: 21 }; const actor = Actor.createActor(idlFactory, { @@ -540,21 +537,22 @@ async function start() { } async function verifyCertificate(certifiedUser, index, rootKey, canisterId) { - const certificate = certifiedUser.certificate.buffer; - const witness = certifiedUser.witness.buffer; + const certificate = certifiedUser.certificate; + const witness = certifiedUser.witness; 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(); + // Certificate.create() verifies automatically and throws if verification fails + const cert = await Certificate.create({ + certificate, + rootKey, + principal: { canisterId }, + }); 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; + const rawTime = cert.lookup_path(pathTime).value; console.log("Time skew: ", verifyTime(rawTime)); // Step 3: Check if witness root_hash matches the certified_data @@ -564,7 +562,7 @@ async function verifyCertificate(certifiedUser, index, rootKey, canisterId) { te.encode("certified_data"), ]; - const certifiedData = cert.lookup(pathData).value; + const certifiedData = cert.lookup_path(pathData).value; let witnessTree = Cbor.decode(witness); let witnessRootHash = await reconstruct(witnessTree); console.log( @@ -581,7 +579,7 @@ async function verifyCertificate(certifiedUser, index, rootKey, canisterId) { assert.deepStrictEqual(witnessData, user, "Value matches response data"); // Step 6: Return the result - return user + return user; } function verifyTime(rawTime) {