Skip to content

safeGetCanisterEnv takes the first ic_env cookie when several are present #1384

Description

@marc0olo

In packages/core/src/agent/canister-env/index.ts:

return globalThis.document.cookie
  .split(";")
  .find(cookie => cookie.trim().startsWith(`${cookieName}=`));

With more than one ic_env cookie on an origin, this resolves whichever the browser happens to list first. If that one names a canister that no longer exists, every call rejects with IC0536 and the app looks like the network is down.

Why several can be present

A cookie is keyed on (name, domain, path), partitioning adds a separate jar, and cookies ignore the port. The writers of ic_env do not agree on any of those:

writer attributes
mainnet asset canister (skills.internetcomputer.org) Partitioned; SameSite=None; Secure; Max-Age=31536000; Path=<certified>
local gateway (icp network start) SameSite=Lax; secure; Max-Age=31536000; Path=<certified>
typical Vite dev-server simulation SameSite=Lax (no Path, no Max-Age)

So they accumulate rather than replace, and Max-Age=31536000 lets a stale one persist for a year. Port-insensitivity means a dev server on localhost:5173 and a gateway on localhost:8000 share a jar.

To be precise about evidence: the attributes above are measured. The duplicate-cookie failure was diagnosed against a running local network but I have not reproduced it here, so I am not claiming to know which combination produced it — only that the resolver picks arbitrarily when it happens.

Suggestion

Anything that makes the ambiguity visible or decidable, roughly in order of preference:

  1. Return all candidates (or expose a getAllCanisterEnvs) so the caller can choose.
  2. Prefer the non-partitioned copy, or the most specific Path — some documented rule rather than list order.
  3. At minimum, console.warn when more than one ic_env is present and they disagree. That alone turns an opaque IC0536 into a one-glance diagnosis.

Related: dfinity/icp-cli#702 asks for a shorter Max-Age on the gateway-set cookie, which would reduce how long a stale copy survives but does not make the resolver deterministic.

Implementation note for whoever picks this up: cookieStore.delete silently removes nothing from the partitioned jar unless partitioned: true is passed, so any cleanup helper has to try both.

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