From 5fc9da67a5f9a497ce9fae0a10a4ed97d8d05df7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 12:48:44 +0000 Subject: [PATCH 1/4] docs(internet-identity): document the ii-app-metadata document MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Internet Identity now lets any app publish its own name, description and logo for the sign-in screens by serving /.well-known/ii-app-metadata — permissionlessly, superseding the curated list II ships for a handful of known apps. Adds a section covering which origin serves the document (the derivation origin, so alternative origins inherit it), the field rules that decide whether it is used at all, the raster-only same-origin logo requirements, and the CORS headers both resources need. The header snippet uses the static-site recipe's _headers file rather than the legacy asset canister's .ic-assets.json5, matching the adjacent alternative-origins section. Adds two pitfalls (wrong origin / missing CORS; whole-document invalidation and SVG logos), mentions the document in the skill description, and adds three eval cases plus a trigger query. Source: dfinity/developer-docs#347, normative rules from the App metadata section of dfinity/internet-identity docs/ii-spec.mdx. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Qzx3V9y4eYVNjqnvFrhDHU --- evaluations/internet-identity.json | 33 ++++++++++++++++++++- skills/internet-identity/SKILL.md | 47 +++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/evaluations/internet-identity.json b/evaluations/internet-identity.json index c92b9015..2318ba6a 100644 --- a/evaluations/internet-identity.json +++ b/evaluations/internet-identity.json @@ -137,6 +137,36 @@ "Adds a _headers entry setting Content-Type: application/json AND Access-Control-Allow-Origin: * for /.well-known/ii-alternative-origins", "Does NOT create a .ic-assets.json5 file — that is the legacy asset canister config, which the static-site recipe does not read or upload" ] + }, + { + "name": "App metadata document on the static-site recipe", + "prompt": "IC frontend on the @dfinity/static-site recipe. I want Internet Identity's sign-in screen to show my app name and logo instead of just the URL. List ONLY the files I add under my build dir and their exact contents. No prose, no explanation.", + "expected_behaviors": [ + "Serves a JSON document at .well-known/ii-app-metadata with name, description, and/or logo fields", + "Adds a _headers entry setting Access-Control-Allow-Origin: * for /.well-known/ii-app-metadata AND for the logo asset — both are read cross-origin by II", + "Sets Content-Type: application/json for /.well-known/ii-app-metadata (the file has no extension)", + "Uses a raster logo (PNG/JPEG/WebP/GIF/AVIF) on the same origin as the document — not an SVG and not a cross-origin URL", + "Does NOT create a .ic-assets.json5 file — that is the legacy asset canister config, which the static-site recipe does not read or upload" + ] + }, + { + "name": "Adversarial: SVG logo and partial validation of ii-app-metadata", + "prompt": "My /.well-known/ii-app-metadata has \"logo\": \"/logo.svg\" and a 200-character description. II will just skip the description that's too long and still show my SVG logo and name, right? Answer briefly.", + "expected_behaviors": [ + "Says no — one invalid field invalidates the WHOLE document, so II ignores all of the metadata rather than dropping only the bad field", + "States the description limit is 120 Unicode code points, so a 200-character description fails validation", + "States that image/svg+xml is not accepted — the logo must be a raster image (image/png, image/jpeg, image/webp, image/gif, or image/avif)", + "Does NOT claim sign-in itself breaks — invalid metadata only falls back to showing the origin" + ] + }, + { + "name": "Which origin serves ii-app-metadata with alternative origins", + "prompt": "My app is served at https://shop.example.com with derivationOrigin https://.icp.net. Do I need to publish /.well-known/ii-app-metadata on both origins, and which one does Internet Identity actually read? Two or three sentences.", + "expected_behaviors": [ + "Says to publish it only on the derivation origin (https://.icp.net), because II fetches the document from the origin identities are derived for", + "Explains that the alternative origins that derivation origin lists are then presented with the same name, description, and logo — no per-origin copy to keep in sync", + "Does NOT tell the user to publish a copy on each alternative origin" + ] } ], "trigger_evals": { @@ -147,7 +177,8 @@ "I need passkey authentication for my dapp", "Set up sign-in with Internet Identity", "How does delegation work with II?", - "Add auth to my canister frontend" + "Add auth to my canister frontend", + "How do I get my app's name and logo to show on the Internet Identity sign-in screen?" ], "should_not_trigger": [ "Connect a wallet to my dapp", diff --git a/skills/internet-identity/SKILL.md b/skills/internet-identity/SKILL.md index eed661eb..dd2a44f8 100644 --- a/skills/internet-identity/SKILL.md +++ b/skills/internet-identity/SKILL.md @@ -1,6 +1,6 @@ --- name: internet-identity -description: "Integrate Internet Identity authentication. Covers passkey and OpenID sign-in flows, delegation handling, and principal-per-app isolation. Use when adding sign-in, login, auth, passkeys, or Internet Identity to a frontend or canister. Do NOT use for wallet integration or ICRC signer flows — use wallet-integration instead." +description: "Integrate Internet Identity authentication. Covers passkey and OpenID sign-in flows, delegation handling, principal-per-app isolation, and the /.well-known/ii-app-metadata document that shows your app's name, description, and logo on the sign-in screen. Use when adding sign-in, login, auth, passkeys, or Internet Identity to a frontend or canister. Do NOT use for wallet integration or ICRC signer flows — use wallet-integration instead." license: Apache-2.0 compatibility: "icp-cli >= 0.2.4, Node.js >= 22, moc >= 1.6.0" metadata: @@ -57,6 +57,10 @@ Internet Identity (II) is the Internet Computer's native authentication system. - `verified_email` is the same email as `email`, but only present when the source OpenID provider (e.g., Google) marked it as verified and II surfaced that signal through. Use `verified_email` for any access gating (admin allowlists, capability checks). Use `email` only for soft uses like contact info or mailing lists. Request both for fallback behaviour: both are returned with the same value when the source provider marked the email as verified, only `email` when it didn't. +13. **Serving `/.well-known/ii-app-metadata` on the wrong origin, or without CORS.** II reads app metadata from the origin identities are derived for — your validated `derivationOrigin` when the request sets one, the request's own origin otherwise. A document published only on the alternative origin the user visits is never fetched. Both the document *and* the logo it points at are read cross-origin, so both need `Access-Control-Allow-Origin`; without it the sign-in screen silently falls back to showing the bare origin. See "Showing your app's name, description, and logo on the sign-in screen". + +14. **Assuming a bad field in `ii-app-metadata` is just dropped, or pointing `logo` at an SVG.** One field that fails validation invalidates the **whole document** — the app is then shown with no metadata at all, not with the remaining fields. `logo` must be a raster image (`image/png`, `image/jpeg`, `image/webp`, `image/gif`, `image/avif`) on the **same origin** as the document; `image/svg+xml` is rejected outright. `name` is capped at 40 Unicode code points and `description` at 120, counted on the value as served. + ## Using II during local development **Default: use mainnet II from your local network.** Starting with `icp-cli >= 0.2.4`, the local network (pocket-ic, launched by `icp-cli-network-launcher`) is configured to trust the mainnet subnet's BLS signatures. Delegations signed by `https://id.ai` are accepted by your local replica, so both the sign-in flow *and* authenticated calls to a locally-deployed backend just work — no extra config in `icp.yaml`, no local II canister to manage, and the UI is the real one your users will see. @@ -183,6 +187,47 @@ Do **not** reach for `.ic-assets.json5` — that is the legacy asset canister's **Order matters.** Pin the derivation origin before an origin has users. Repointing an origin that has already collected sign-ins orphans every account made under it. +### Showing your app's name, description, and logo on the sign-in screen + +By default the II sign-in screens identify your app by its **origin** alone. To have II also show a name, a short tagline, and a logo, serve a JSON document at `/.well-known/ii-app-metadata`. This is permissionless — there is no list to join and no approval step — and it supersedes the curated entry II still ships for a small set of known apps. + +```json +{ + "name": "Example App", + "description": "A short tagline shown on the sign-in screen", + "logo": "/logo.png" +} +``` + +**Publish it on the derivation origin, not on every origin.** II fetches the document from the origin identities are derived for: your `derivationOrigin` once validated when the auth request sets one, and the request's own origin otherwise. Publish it once on the derivation origin and every alternative origin that origin certifies is presented with the same name, description, and logo — there is nothing to keep in sync. A copy served only on the alternative origin the user actually visits is never read. + +All three fields are optional and unknown fields are ignored, so a document stays valid as II adds fields. The rules that decide whether yours is used: + +- `name` is at most 40 characters and `description` at most 120, counted in **Unicode code points on the value as served** — before whitespace collapsing, which is display-only and never rescues an over-long value. +- Each field must contain at least one visible character. A field holding only whitespace or invisible characters is rejected, not treated as absent. +- Control characters (other than the ASCII whitespace `\t`, `\n`, `\v`, `\f`, `\r`), `U+FEFF`, and the bidirectional embeddings and overrides `U+202A`–`U+202E` are rejected: they can make rendered text read differently from what it contains. The characters mixed-direction and non-Latin names legitimately need are accepted — the marks `U+200E`, `U+200F`, `U+061C`, the isolates `U+2066`–`U+2069`, and the zero-width characters `U+200B`–`U+200D` — but isolates must be **balanced**: a field must close every isolate it opens and close none it did not open. +- **One bad field invalidates the whole document**, which is then ignored; the offending field is not dropped on its own. The app is shown with no metadata rather than half of it. II logs which field is at fault to the browser console — check the console on the sign-in screen when metadata does not appear. A document carrying no field II recognises is likewise ignored; a valid one replaces the curated fallback entry wholesale. +- `logo` must be a **raster** image URL on the **same origin** as the document (relative URLs resolve against it), served as `image/png`, `image/jpeg`, `image/webp`, `image/gif`, or `image/avif`, at most 1 MiB, and at most 4096 pixels per axis. `image/svg+xml` is rejected — serve a rasterised copy of a vector logo. II downloads the image (it is never hotlinked), redraws it at up to 512 pixels on its longest side (an animated image is flattened to its first frame), and renders that copy, so a roughly square PNG or WebP of about 512 pixels is the right thing to ship. +- Only the *shape* of `logo` — a non-empty URL on the document's own origin — is part of the validation above. Once it passes, a logo that cannot be fetched or decoded, or that breaks the content-type, size, or dimension rules, costs you the logo alone: the name and description still render. +- The document must not exceed 8 KiB, must be answered with `200`, and must not redirect (II follows redirects for neither the document nor the logo). II requests it without credentials and gives up after 10 seconds. + +**Both the document and the logo are read cross-origin, so both need CORS headers.** With `@dfinity/static-site`, extend the same `_headers` file used for alternative origins: + +``` +/.well-known/ii-app-metadata + Content-Type: application/json + Access-Control-Allow-Origin: * + +/logo.png + Access-Control-Allow-Origin: * +``` + +`.well-known/` is uploaded automatically, but this file has no extension either, so set its media type with the bare `Content-Type:` form. As above, `.ic-assets.json5` is the legacy asset canister's config and is not read by this recipe. + +Missing, unreachable, or invalid metadata never blocks sign-in: the screen falls back to the curated entry, and to your origin alone otherwise. And because the file is under the sole control of the origin serving it, publishing it verifies nothing about your app — II keeps displaying the origin alongside whatever you provide, since the origin is the part users can actually check. + +The normative rules, including a JSON Schema you can validate the document against in CI, are in the **App metadata** section of the [Internet Identity specification](https://internetcomputer.org/docs/references/internet-identity-spec). The schema expresses every rule above except isolate balancing. + ### Frontend: Requesting Identity Attributes When the backend needs more than the user's principal (e.g., a verified email), Internet Identity can return signed attributes alongside the delegation. The flow is a two-method handshake on the backend: `_internet_identity_sign_in_start` mints a nonce, and `_internet_identity_sign_in_finish` verifies the bundle. In Motoko the `mo:identity-attributes` mixin provides both methods; in Rust you implement them by hand (see "Backend: Reading Identity Attributes"). The frontend below is identical against either backend. From 1bc611e21374dff601ecbdf7842a451aa6b84a57 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 13:00:45 +0000 Subject: [PATCH 2/4] docs(internet-identity): separate document-level from logo-level failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback: the two pitfalls and one section bullet conflated a field that fails document validation with a logo asset that cannot be used. They are different failures — an unusable logo costs only the logo, while an invalid field discards the whole document — and "shown with no metadata" skipped the curated fallback II still ships for known apps. Also splits `logo` across both modes explicitly: a cross-origin URL fails document validation (whole document lost), while an SVG, an oversized image, or one that cannot be fetched costs only the logo. Sharpens the adversarial eval to test that distinction rather than assuming the bare-origin fallback: 5/5 with the skill, 0/5 baseline (was 4/4 vs 2/4). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Qzx3V9y4eYVNjqnvFrhDHU --- evaluations/internet-identity.json | 7 ++++--- skills/internet-identity/SKILL.md | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/evaluations/internet-identity.json b/evaluations/internet-identity.json index 2318ba6a..1edb8aac 100644 --- a/evaluations/internet-identity.json +++ b/evaluations/internet-identity.json @@ -151,12 +151,13 @@ }, { "name": "Adversarial: SVG logo and partial validation of ii-app-metadata", - "prompt": "My /.well-known/ii-app-metadata has \"logo\": \"/logo.svg\" and a 200-character description. II will just skip the description that's too long and still show my SVG logo and name, right? Answer briefly.", + "prompt": "My /.well-known/ii-app-metadata has \"logo\": \"/logo.svg\" and a 200-character description. II will just skip the description that's too long and still show my SVG logo and name, right? Tell me what each of the two problems actually costs me.", "expected_behaviors": [ - "Says no — one invalid field invalidates the WHOLE document, so II ignores all of the metadata rather than dropping only the bad field", + "Says no — the over-long description is a field that fails validation, and one invalid field invalidates the WHOLE document rather than being dropped on its own", "States the description limit is 120 Unicode code points, so a 200-character description fails validation", "States that image/svg+xml is not accepted — the logo must be a raster image (image/png, image/jpeg, image/webp, image/gif, or image/avif)", - "Does NOT claim sign-in itself breaks — invalid metadata only falls back to showing the origin" + "Distinguishes the two failure modes: an SVG logo on its own costs only the logo (name and description would still render), so it is the description — not the SVG — that takes the whole document down", + "Does NOT claim sign-in itself breaks, and does not assert the screen necessarily shows the bare origin — an ignored document falls back to II's curated entry for a known app, and to the origin otherwise" ] }, { diff --git a/skills/internet-identity/SKILL.md b/skills/internet-identity/SKILL.md index dd2a44f8..0cdcbf78 100644 --- a/skills/internet-identity/SKILL.md +++ b/skills/internet-identity/SKILL.md @@ -57,9 +57,9 @@ Internet Identity (II) is the Internet Computer's native authentication system. - `verified_email` is the same email as `email`, but only present when the source OpenID provider (e.g., Google) marked it as verified and II surfaced that signal through. Use `verified_email` for any access gating (admin allowlists, capability checks). Use `email` only for soft uses like contact info or mailing lists. Request both for fallback behaviour: both are returned with the same value when the source provider marked the email as verified, only `email` when it didn't. -13. **Serving `/.well-known/ii-app-metadata` on the wrong origin, or without CORS.** II reads app metadata from the origin identities are derived for — your validated `derivationOrigin` when the request sets one, the request's own origin otherwise. A document published only on the alternative origin the user visits is never fetched. Both the document *and* the logo it points at are read cross-origin, so both need `Access-Control-Allow-Origin`; without it the sign-in screen silently falls back to showing the bare origin. See "Showing your app's name, description, and logo on the sign-in screen". +13. **Serving `/.well-known/ii-app-metadata` on the wrong origin, or without CORS.** II reads app metadata from the origin identities are derived for — your validated `derivationOrigin` when the request sets one, the request's own origin otherwise. A document published only on the alternative origin the user visits is never fetched. The document *and* the logo it points at are both read cross-origin, and they fail differently: without `Access-Control-Allow-Origin` on the document none of your metadata is used (II falls back to its curated entry if it ships one for your app, and to your origin alone otherwise), while an unreadable logo costs you the logo alone — the name and description still render. See "Showing your app's name, description, and logo on the sign-in screen". -14. **Assuming a bad field in `ii-app-metadata` is just dropped, or pointing `logo` at an SVG.** One field that fails validation invalidates the **whole document** — the app is then shown with no metadata at all, not with the remaining fields. `logo` must be a raster image (`image/png`, `image/jpeg`, `image/webp`, `image/gif`, `image/avif`) on the **same origin** as the document; `image/svg+xml` is rejected outright. `name` is capped at 40 Unicode code points and `description` at 120, counted on the value as served. +14. **Assuming a bad field in `ii-app-metadata` is just dropped, or confusing a rejected logo with a rejected document.** One field that fails validation invalidates the **whole document**: none of your metadata is applied, not just the offending field (II then falls back to its curated entry if it ships one for your app, and to your origin alone otherwise). `name` is capped at 40 Unicode code points and `description` at 120, counted on the value as served. `logo` straddles the two failure modes — a URL that is not on the **same origin** as the document fails document validation and takes the whole document down with it, while an SVG (`image/svg+xml` is not accepted; serve a raster copy), an oversized image, or one that cannot be fetched or decoded costs you the logo alone. ## Using II during local development @@ -206,7 +206,7 @@ All three fields are optional and unknown fields are ignored, so a document stay - `name` is at most 40 characters and `description` at most 120, counted in **Unicode code points on the value as served** — before whitespace collapsing, which is display-only and never rescues an over-long value. - Each field must contain at least one visible character. A field holding only whitespace or invisible characters is rejected, not treated as absent. - Control characters (other than the ASCII whitespace `\t`, `\n`, `\v`, `\f`, `\r`), `U+FEFF`, and the bidirectional embeddings and overrides `U+202A`–`U+202E` are rejected: they can make rendered text read differently from what it contains. The characters mixed-direction and non-Latin names legitimately need are accepted — the marks `U+200E`, `U+200F`, `U+061C`, the isolates `U+2066`–`U+2069`, and the zero-width characters `U+200B`–`U+200D` — but isolates must be **balanced**: a field must close every isolate it opens and close none it did not open. -- **One bad field invalidates the whole document**, which is then ignored; the offending field is not dropped on its own. The app is shown with no metadata rather than half of it. II logs which field is at fault to the browser console — check the console on the sign-in screen when metadata does not appear. A document carrying no field II recognises is likewise ignored; a valid one replaces the curated fallback entry wholesale. +- **One bad field invalidates the whole document**, which is then ignored; the offending field is not dropped on its own. None of your document is applied rather than half of it; II then falls back to its curated entry if it ships one for your app, and to your origin alone otherwise. II logs which field is at fault to the browser console — check the console on the sign-in screen when metadata does not appear. A document carrying no field II recognises is likewise ignored; a valid one replaces the curated fallback entry wholesale. - `logo` must be a **raster** image URL on the **same origin** as the document (relative URLs resolve against it), served as `image/png`, `image/jpeg`, `image/webp`, `image/gif`, or `image/avif`, at most 1 MiB, and at most 4096 pixels per axis. `image/svg+xml` is rejected — serve a rasterised copy of a vector logo. II downloads the image (it is never hotlinked), redraws it at up to 512 pixels on its longest side (an animated image is flattened to its first frame), and renders that copy, so a roughly square PNG or WebP of about 512 pixels is the right thing to ship. - Only the *shape* of `logo` — a non-empty URL on the document's own origin — is part of the validation above. Once it passes, a logo that cannot be fetched or decoded, or that breaks the content-type, size, or dimension rules, costs you the logo alone: the name and description still render. - The document must not exceed 8 KiB, must be answered with `200`, and must not redirect (II follows redirects for neither the document nor the logo). II requests it without credentials and gives up after 10 seconds. From 05fbf012039255290e7cbe86c7310f9c3e1f8db4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 15:13:14 +0000 Subject: [PATCH 3/4] docs(internet-identity): fix the spec link and cover the gateway-twin logo trap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback, both verified against dfinity/internet-identity@main: The spec link 301s to the docs homepage rather than the spec, and the validator's link check follows the redirect and sees 200, so CI cannot catch it. Switched to the docs.internetcomputer.org/ form used everywhere else under skills/, with the now-published #app-metadata anchor. II normalizes a canister gateway origin onto ic0.app and tries its icp0.io and icp.net twins in turn (appMetadataOrigins), and validateLogoUrl checks same-origin against whichever twin answered — returning the document-level INVALID sentinel, not just dropping the logo. So an absolute logo URL pinned to one gateway domain loses the whole document when a sibling served it, which the section's own recommended derivation origin makes likely. The logo bullet now says to write the URL relative and why, and pitfall 14 no longer reads as being only about third-party hosts. Adds an adversarial eval for it: 4/4 with the skill, 0/4 baseline — the baseline answers "yes, that's fine". Evals 15 and 16 re-checked, both still pass. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Qzx3V9y4eYVNjqnvFrhDHU --- evaluations/internet-identity.json | 10 ++++++++++ skills/internet-identity/SKILL.md | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/evaluations/internet-identity.json b/evaluations/internet-identity.json index 1edb8aac..5cf4b06a 100644 --- a/evaluations/internet-identity.json +++ b/evaluations/internet-identity.json @@ -168,6 +168,16 @@ "Explains that the alternative origins that derivation origin lists are then presented with the same name, description, and logo — no per-origin copy to keep in sync", "Does NOT tell the user to publish a copy on each alternative origin" ] + }, + { + "name": "Adversarial: absolute logo URL on a canister gateway domain", + "prompt": "My ii-app-metadata is served from https://.icp.net, which is also my derivationOrigin, and I set \"logo\": \"https://.icp.net/logo.png\". Same canister, same domain — that's fine, right? Two or three sentences.", + "expected_behaviors": [ + "Says no, or flags it as a real risk — II normalizes a canister gateway origin onto ic0.app and falls back through the icp0.io / icp.net twins, so the document may be fetched from a sibling gateway domain of the same canister", + "Explains the logo's same-origin check runs against whichever origin actually served the document, so an absolute icp.net URL is cross-origin when ic0.app answered", + "States the consequence is losing the WHOLE document, not just the logo, because the logo URL's same-origin shape is part of document validation", + "Recommends a relative URL such as /logo.png" + ] } ], "trigger_evals": { diff --git a/skills/internet-identity/SKILL.md b/skills/internet-identity/SKILL.md index 0cdcbf78..797209e4 100644 --- a/skills/internet-identity/SKILL.md +++ b/skills/internet-identity/SKILL.md @@ -59,7 +59,7 @@ Internet Identity (II) is the Internet Computer's native authentication system. 13. **Serving `/.well-known/ii-app-metadata` on the wrong origin, or without CORS.** II reads app metadata from the origin identities are derived for — your validated `derivationOrigin` when the request sets one, the request's own origin otherwise. A document published only on the alternative origin the user visits is never fetched. The document *and* the logo it points at are both read cross-origin, and they fail differently: without `Access-Control-Allow-Origin` on the document none of your metadata is used (II falls back to its curated entry if it ships one for your app, and to your origin alone otherwise), while an unreadable logo costs you the logo alone — the name and description still render. See "Showing your app's name, description, and logo on the sign-in screen". -14. **Assuming a bad field in `ii-app-metadata` is just dropped, or confusing a rejected logo with a rejected document.** One field that fails validation invalidates the **whole document**: none of your metadata is applied, not just the offending field (II then falls back to its curated entry if it ships one for your app, and to your origin alone otherwise). `name` is capped at 40 Unicode code points and `description` at 120, counted on the value as served. `logo` straddles the two failure modes — a URL that is not on the **same origin** as the document fails document validation and takes the whole document down with it, while an SVG (`image/svg+xml` is not accepted; serve a raster copy), an oversized image, or one that cannot be fetched or decoded costs you the logo alone. +14. **Assuming a bad field in `ii-app-metadata` is just dropped, or confusing a rejected logo with a rejected document.** One field that fails validation invalidates the **whole document**: none of your metadata is applied, not just the offending field (II then falls back to its curated entry if it ships one for your app, and to your origin alone otherwise). `name` is capped at 40 Unicode code points and `description` at 120, counted on the value as served. `logo` straddles the two failure modes — a URL that is not on the **same origin** as the document fails document validation and takes the whole document down with it — and that includes your own canister on a sibling gateway domain, since II may fetch the document from any of `ic0.app`, `icp0.io`, or `icp.net` (write the URL relative), while an SVG (`image/svg+xml` is not accepted; serve a raster copy), an oversized image, or one that cannot be fetched or decoded costs you the logo alone. ## Using II during local development @@ -207,7 +207,7 @@ All three fields are optional and unknown fields are ignored, so a document stay - Each field must contain at least one visible character. A field holding only whitespace or invisible characters is rejected, not treated as absent. - Control characters (other than the ASCII whitespace `\t`, `\n`, `\v`, `\f`, `\r`), `U+FEFF`, and the bidirectional embeddings and overrides `U+202A`–`U+202E` are rejected: they can make rendered text read differently from what it contains. The characters mixed-direction and non-Latin names legitimately need are accepted — the marks `U+200E`, `U+200F`, `U+061C`, the isolates `U+2066`–`U+2069`, and the zero-width characters `U+200B`–`U+200D` — but isolates must be **balanced**: a field must close every isolate it opens and close none it did not open. - **One bad field invalidates the whole document**, which is then ignored; the offending field is not dropped on its own. None of your document is applied rather than half of it; II then falls back to its curated entry if it ships one for your app, and to your origin alone otherwise. II logs which field is at fault to the browser console — check the console on the sign-in screen when metadata does not appear. A document carrying no field II recognises is likewise ignored; a valid one replaces the curated fallback entry wholesale. -- `logo` must be a **raster** image URL on the **same origin** as the document (relative URLs resolve against it), served as `image/png`, `image/jpeg`, `image/webp`, `image/gif`, or `image/avif`, at most 1 MiB, and at most 4096 pixels per axis. `image/svg+xml` is rejected — serve a rasterised copy of a vector logo. II downloads the image (it is never hotlinked), redraws it at up to 512 pixels on its longest side (an animated image is flattened to its first frame), and renders that copy, so a roughly square PNG or WebP of about 512 pixels is the right thing to ship. +- `logo` must be a **raster** image URL on the **same origin** as the document (relative URLs resolve against it), served as `image/png`, `image/jpeg`, `image/webp`, `image/gif`, or `image/avif`, at most 1 MiB, and at most 4096 pixels per axis. `image/svg+xml` is rejected — serve a rasterised copy of a vector logo. II downloads the image (it is never hotlinked), redraws it at up to 512 pixels on its longest side (an animated image is flattened to its first frame), and renders that copy, so a roughly square PNG or WebP of about 512 pixels is the right thing to ship. **Write the URL relative** (`/logo.png`): II normalizes a canister gateway origin onto `ic0.app` and tries its `icp0.io` and `icp.net` twins in turn, so the document may be fetched from a sibling gateway domain of the same canister — and the same-origin check below runs against whichever one answered. An absolute URL pinned to one of them is cross-origin at the other two. - Only the *shape* of `logo` — a non-empty URL on the document's own origin — is part of the validation above. Once it passes, a logo that cannot be fetched or decoded, or that breaks the content-type, size, or dimension rules, costs you the logo alone: the name and description still render. - The document must not exceed 8 KiB, must be answered with `200`, and must not redirect (II follows redirects for neither the document nor the logo). II requests it without credentials and gives up after 10 seconds. @@ -226,7 +226,7 @@ All three fields are optional and unknown fields are ignored, so a document stay Missing, unreachable, or invalid metadata never blocks sign-in: the screen falls back to the curated entry, and to your origin alone otherwise. And because the file is under the sole control of the origin serving it, publishing it verifies nothing about your app — II keeps displaying the origin alongside whatever you provide, since the origin is the part users can actually check. -The normative rules, including a JSON Schema you can validate the document against in CI, are in the **App metadata** section of the [Internet Identity specification](https://internetcomputer.org/docs/references/internet-identity-spec). The schema expresses every rule above except isolate balancing. +The normative rules, including a JSON Schema you can validate the document against in CI, are in the **App metadata** section of the [Internet Identity specification](https://docs.internetcomputer.org/references/internet-identity-spec/#app-metadata). The schema expresses every rule above except isolate balancing. ### Frontend: Requesting Identity Attributes From a644abd2fd894729e0526547b7e8ec0da4926924 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 15:28:30 +0000 Subject: [PATCH 4/4] docs(internet-identity): keep pitfall 14's failure modes inside one dash pair Review nit: the gateway aside landed after the closing em-dash, so the pair no longer bracketed the whole-document failure mode and separated "while" from the mode it contrasts against. Punctuation only, no wording change. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Qzx3V9y4eYVNjqnvFrhDHU --- skills/internet-identity/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/internet-identity/SKILL.md b/skills/internet-identity/SKILL.md index 797209e4..f3cc87a2 100644 --- a/skills/internet-identity/SKILL.md +++ b/skills/internet-identity/SKILL.md @@ -59,7 +59,7 @@ Internet Identity (II) is the Internet Computer's native authentication system. 13. **Serving `/.well-known/ii-app-metadata` on the wrong origin, or without CORS.** II reads app metadata from the origin identities are derived for — your validated `derivationOrigin` when the request sets one, the request's own origin otherwise. A document published only on the alternative origin the user visits is never fetched. The document *and* the logo it points at are both read cross-origin, and they fail differently: without `Access-Control-Allow-Origin` on the document none of your metadata is used (II falls back to its curated entry if it ships one for your app, and to your origin alone otherwise), while an unreadable logo costs you the logo alone — the name and description still render. See "Showing your app's name, description, and logo on the sign-in screen". -14. **Assuming a bad field in `ii-app-metadata` is just dropped, or confusing a rejected logo with a rejected document.** One field that fails validation invalidates the **whole document**: none of your metadata is applied, not just the offending field (II then falls back to its curated entry if it ships one for your app, and to your origin alone otherwise). `name` is capped at 40 Unicode code points and `description` at 120, counted on the value as served. `logo` straddles the two failure modes — a URL that is not on the **same origin** as the document fails document validation and takes the whole document down with it — and that includes your own canister on a sibling gateway domain, since II may fetch the document from any of `ic0.app`, `icp0.io`, or `icp.net` (write the URL relative), while an SVG (`image/svg+xml` is not accepted; serve a raster copy), an oversized image, or one that cannot be fetched or decoded costs you the logo alone. +14. **Assuming a bad field in `ii-app-metadata` is just dropped, or confusing a rejected logo with a rejected document.** One field that fails validation invalidates the **whole document**: none of your metadata is applied, not just the offending field (II then falls back to its curated entry if it ships one for your app, and to your origin alone otherwise). `name` is capped at 40 Unicode code points and `description` at 120, counted on the value as served. `logo` straddles the two failure modes — a URL that is not on the **same origin** as the document fails document validation and takes the whole document down with it, and that includes your own canister on a sibling gateway domain, since II may fetch the document from any of `ic0.app`, `icp0.io`, or `icp.net` (write the URL relative) — while an SVG (`image/svg+xml` is not accepted; serve a raster copy), an oversized image, or one that cannot be fetched or decoded costs you the logo alone. ## Using II during local development