Skip to content

Dev-server ic_env cookies collide between projects, because cookies ignore port #38

Description

@marc0olo

Needs confirmation before acting — this follows from the template plus cookie scoping rules, I have not reproduced it. Filing it because if it holds, it bites more often than the duplicate-cookie case it was found alongside (dfinity/icp-js-core#1384).

The claim

All three frontend templates set the environment via a Set-Cookie response header on the Vite dev server:

hello-world/react-frontend/vite.config.ts:52 (identically in vue-frontend and svelte-frontend):

const server = {
  headers: {
    "Set-Cookie": `ic_env=${encodeURIComponent(
      `PUBLIC_CANISTER_ID:${CANISTER_NAME}=${canisterId}&ic_root_key=${rootKey}`
    )}; SameSite=Lax;`,
  },
  ...

Cookies provide no isolation by port (RFC 6265 §1: "cookies for a given host are shared across all the ports on that host"). So a dev server on localhost:5173 and another on localhost:5174 write to the same jar. With no Domain these are host-only cookies on localhost, and with no Path the default-path for a request to / is /.

Same name, same host, same path means the second write doesn't sit alongside the first — it replaces it. Two scaffolded projects running dev servers concurrently would therefore clobber each other, and whichever started last wins for both. The first project's tab reads the second project's PUBLIC_CANISTER_ID:backend and root key, so its calls go to a canister from an unrelated project.

Why no attribute change fixes it

  • Path=/ doesn't help — both are already at /, and that's what you want anyway.
  • Partitioned doesn't help — the partition key is the top-level site, and both are localhost.
  • Port cannot be expressed in any cookie attribute at all.

The transport is the problem, not its attributes. Deployed frontends are unaffected: those are served at http://<canister-id>.localhost:8000/, distinct hosts, correctly isolated. This is specific to the dev-server simulation of the asset canister's cookie.

Suggested direction

Have the dev server inject the environment through Vite's define (or import.meta.env) instead of a cookie. That's per-server by construction, so it's port-scoped for free, and it removes an ambient mutable value from local development. The trade-off is that the dev path stops being byte-identical to the deployed path, where the cookie is genuinely the right mechanism because the asset canister has no build step to inject into. @icp-sdk/core/agent/canister-env would still be the reader in production; only the dev-server simulation changes.

If keeping the cookie is preferred, the template could at least namespace it per project (ic_env_<project>) via the existing cookieName option on getCanisterEnv, which converts a silent wrong-canister into a clean "not found".

Repro sketch (unverified)

  1. Scaffold two projects from hello-world, deploy both to a local network.
  2. npm run dev in each — Vite picks 5173 and 5174.
  3. Open both in the same browser.
  4. Check document.cookie on each; expect a single ic_env carrying the later project's canister id.
  5. Expect the first project's calls to target the wrong backend.

Worth running before deciding anything. If the collision doesn't reproduce, close this.

Context

Found while scoping dfinity/icp-js-core#1384 (getCanisterEnv taking the first of several ic_env cookies). That one is being fixed in dfinity/icp-js-core#1386, but the fix does not cover this: here there is only ever one cookie, so nothing conflicts — the single value is simply the wrong project's.

The same pattern appears in ~35 vite.config.js files in dfinity/examples and one in dfinity/icp-cli/examples/, so whatever is decided here should propagate there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions