Skip to content

docs(internet-identity): document the ii-app-metadata document - #362

Merged
aterga merged 4 commits into
mainfrom
claude/icp-skills-ii-app-metadata-gq97tw
Aug 25, 2026
Merged

docs(internet-identity): document the ii-app-metadata document#362
aterga merged 4 commits into
mainfrom
claude/icp-skills-ii-app-metadata-gq97tw

Conversation

@aterga

@aterga aterga commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What

Documents Internet Identity's app metadata document (/.well-known/ii-app-metadata) in the internet-identity skill, based on dfinity/developer-docs#347, the normative "App metadata" section of the Internet Identity specification, and the implementation on dfinity/internet-identity@main.

Any app can now publish its own name, description, and logo for the II sign-in screens — permissionlessly, superseding the curated list II ships for a handful of known apps.

Changes

skills/internet-identity/SKILL.md

  • New section "Showing your app's name, description, and logo on the sign-in screen", placed after "Serving an app at more than one origin" since the two interact:
    • the document shape, and that II reads it from the derivation origin (so alternative origins inherit it — no per-origin copy)
    • the field rules that decide whether the document is used at all: 40/120 code-point limits, at least one visible character, rejected control/bidi-override characters, balanced isolates, and one bad field invalidating the whole document
    • logo requirements: raster only (SVG rejected), same origin, ≤ 1 MiB, ≤ 4096 px/axis, re-encoded by II at ≤ 512 px — plus write the URL relative, because II may fetch the document from a sibling gateway domain of the same canister and the same-origin check runs against whichever one answered
    • the split between failures that lose the whole document and failures that cost only the logo
    • transport rules: ≤ 8 KiB, 200, no redirects, no credentials, 10 s timeout
    • CORS headers for both the document and the logo, which fail differently
  • Two new pitfalls in "Mistakes That Break Your Build": serving the document on the wrong origin or without CORS (13), and expecting a bad field to be dropped / confusing a rejected logo with a rejected document (14).
  • Description mentions the document so queries about the sign-in screen's name and logo route here.

Deviation from the upstream docs PR: the docs PR configures CORS with .ic-assets.json5. This skill targets the @dfinity/static-site recipe, whose canister does not read that file (it isn't even uploaded), so the snippet uses the _headers file — matching the adjacent alternative-origins section and the static-site skill. The extension-less path also needs the bare Content-Type: form to get application/json.

evaluations/internet-identity.json — four output evals (static-site file layout, adversarial SVG + partial-validation, which origin serves the document, adversarial absolute logo URL on a gateway domain) and one trigger query.

Evals

All four added cases run with baseline; trigger evals re-run because the description changed.

Output evals — added cases (with skill vs baseline)
━━━ App metadata document on the static-site recipe ━━━

  WITH skill: 5/5 passed
    ✅ 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
    ✅ 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

  WITHOUT skill: 1/5 passed
    ❌ Serves a JSON document at .well-known/ii-app-metadata with name, description, and/or logo fields
       → The assistant produced no files at all, only a request for WebFetch/WebSearch permission.
    ❌ Adds a _headers entry setting Access-Control-Allow-Origin: * for /.well-known/ii-app-metadata AND for the logo asset
       → No _headers file or any file content was output.
    ❌ Sets Content-Type: application/json for /.well-known/ii-app-metadata (the file has no extension)
       → No headers configuration was provided since no files were output.
    ❌ Uses a raster logo (PNG/JPEG/WebP/GIF/AVIF) on the same origin as the document
       → No logo file or reference to one was provided in the output.
    ✅ Does NOT create a .ic-assets.json5 file

━━━ Adversarial: SVG logo and partial validation of ii-app-metadata ━━━
(oracle sharpened after the Copilot round)

  WITH skill: 5/5 passed
    ✅ 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
    ✅ Distinguishes the two failure modes: an SVG logo on its own costs only the logo, 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

  WITHOUT skill: 0/5 passed
    ❌ Says no — description failure invalidates the whole document rather than being dropped alone
       → The claim is framed throughout as unverified speculation ('likely', 'best recollection') rather than a confident, correct statement of fact.
    ❌ States the description limit is 120 Unicode code points
       → No specific numeric limit is ever given anywhere in the output.
    ❌ States that image/svg+xml is not accepted and raster formats are required
       → The rejection of SVG is presented as a speculative guess and only PNG/JPEG are mentioned, not the full accepted raster set.
    ❌ Distinguishes the two failure modes
       → The output instead claims the SVG issue 'alone being sufficient to break' all branding, conflating it with the whole-document failure.
    ❌ Does not claim sign-in breaks or that the screen necessarily shows the bare origin
       → The output asserts the bare-origin fallback without mentioning the curated-entry fallback for known apps.

━━━ Which origin serves ii-app-metadata with alternative origins ━━━

  WITH skill: 3/3 passed
    ✅ Says to publish it only on the derivation origin, 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

  WITHOUT skill: 0/3 passed
    ❌ Says to publish it only on the derivation origin
       → The output instructs publishing on both origins and claims II reads from the relying party origin.
    ❌ Explains that alternative origins inherit the same metadata
       → The output instead describes two separate documents serving different purposes.
    ❌ Does NOT tell the user to publish a copy on each alternative origin
       → The output explicitly tells the user to publish the metadata file on both origins.

━━━ Adversarial: absolute logo URL on a canister gateway domain ━━━
(added for the gateway-twin gap found in review)

  WITH skill: 4/4 passed
    ✅ 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
    ✅ Explains the logo's same-origin check runs against whichever origin actually served the document
    ✅ States the consequence is losing the WHOLE document, not just the logo
    ✅ Recommends a relative URL such as /logo.png

  WITHOUT skill: 0/4 passed
    ❌ Says no, or flags it as a real risk
       → The output explicitly says 'Yes, that's fine' and never mentions gateway domain normalization or fallback risk.
    ❌ Explains the same-origin check runs against whichever origin served the document
       → The output only discusses CORS between logo and derivationOrigin; the mechanism is entirely absent.
    ❌ States the consequence is losing the WHOLE document
       → No mention of document-wide validation failure; implies at most a missing logo image.
    ❌ Recommends a relative URL such as /logo.png
       → The output never suggests a relative URL and instead endorses the absolute icp.net URL as fine.

━━━ Summary ━━━
    App metadata document on the static-site recipe:                  WITH 5/5 | WITHOUT 1/5
    Adversarial: SVG logo and partial validation of ii-app-metadata:  WITH 5/5 | WITHOUT 0/5
    Which origin serves ii-app-metadata with alternative origins:     WITH 3/3 | WITHOUT 0/3
    Adversarial: absolute logo URL on a canister gateway domain:      WITH 4/4 | WITHOUT 0/4
Trigger evals (description changed)
  Should trigger: 7/7 correct
    ✅ "Add login to my ICP frontend app"
    ✅ "How do I integrate Internet Identity?"
    ✅ "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"
    ✅ "How do I get my app's name and logo to show on the Internet Identity sign-in screen?"

  Should NOT trigger: 6/6 correct
    ✅ "Connect a wallet to my dapp"
    ✅ "How do I deploy my canister?"
    ✅ "Implement ICRC-1 token transfers"
    ✅ "Set up inter-canister calls"
    ✅ "How does stable memory work?"
    ✅ "Add a frontend to my canister"

  Trigger evals: should-trigger 7/7 | should-not-trigger 6/6

Evals 15 and 16 were re-checked after the logo-bullet edit in 05fbf01 (both still 5/5). The other pre-existing cases were not re-run — none of them cover content this PR touched.

npm run validate passes — 29 skills, no errors. The skill trips the validator's "body > 500 lines" advisory (547 lines) alongside the pre-existing token advisory; per .claude/CLAUDE.md warnings are acceptable, and the next addition to this skill should go in references/.

Review rounds

Copilot (fixed in 1bc611e) — three findings, all valid:

  • Pitfall 13 conflated the two CORS failure modes. An unreadable document means none of your metadata is used; an unreadable logo costs only the logo.
  • "The app is shown with no metadata" skipped the curated fallback. Now: none of your document is applied, then the fallback order.
  • The adversarial eval's oracle did not test the distinction the section introduces. Sharpened: 4/4 vs 2/4 → 5/5 vs 0/5.

Pitfall 14 also now splits logo across both modes: a cross-origin URL fails document validation and takes the whole document down, while an SVG, an oversized image, or one that cannot be fetched costs only the logo.

@marc0olo (fixed in 05fbf01) — two must-fixes, both verified against dfinity/internet-identity@main before applying:

  • The spec link 301'd to the docs homepage, and the validator's link check follows redirects and sees 200, so CI could not catch it. Switched to the docs.internetcomputer.org/<path> form used by every other IC-docs link under skills/, with the now-published #app-metadata anchor.
  • The gateway-twin fallback was uncovered, which made an absolute logo URL a whole-document failure under this skill's own recommended derivation origin: appMetadataOrigins tries the ic0.app / icp0.io / icp.net twins in turn, and validateLogoUrl checks same-origin against whichever answered, returning the document-level INVALID. The logo bullet now says to write the URL relative and why; pitfall 14 no longer reads as being only about third-party hosts. New adversarial eval: 4/4 with the skill, 0/4 baseline — the baseline opens with "Yes, that's fine".

Non-blocking nits from that review are answered in the thread: Content-Type: application/json on the document is hygiene rather than an II requirement (fetchCapped sends it as Accept and never inspects the response), left as-is since the text does not mis-state it; credentials: omit and the 10 s timeout are implementation-only but load-bearing when debugging a fetch.

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qzx3V9y4eYVNjqnvFrhDHU
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

Skill Validation Report

Validating skill: /home/runner/work/icskills/icskills/skills/internet-identity

Structure

  • Pass: SKILL.md found

Frontmatter

  • Pass: name: "internet-identity" (valid)
  • Pass: description: (440 chars)
  • Pass: license: "Apache-2.0"
  • Pass: compatibility: (45 chars)
  • Pass: metadata: (2 entries)

Tokens

  • Warning: SKILL.md body is 8790 tokens (spec recommends < 5000)
  • Warning: SKILL.md body is 547 lines (spec recommends < 500)

Markdown

  • Pass: no unclosed code fences found

Tokens

File Tokens
SKILL.md body 8,790
Total 8,790

Content Analysis

Metric Value
Word count 4,993
Code block ratio 0.25
Imperative ratio 0.06
Information density 0.16
Instruction specificity 0.90
Sections 16
List items 46
Code blocks 13

Contamination Analysis

Metric Value
Contamination level high
Contamination score 0.52
Primary language category config
Scope breadth 4
  • Warning: Language mismatch: javascript, systems (2 categories differ from primary)
  • Multi-interface tool detected: aws

Result: 2 warnings

Project Checks


✓ Project checks passed for 1 skills (0 warnings)

@aterga
aterga marked this pull request as ready for review August 25, 2026 12:53
@aterga
aterga requested review from a team and JoshDFN as code owners August 25, 2026 12:53
@aterga
aterga requested a balanced review from Copilot August 25, 2026 12:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Documents Internet Identity app metadata for customizing sign-in screens.

Changes:

  • Adds metadata format, validation, origin, CORS, and logo guidance.
  • Adds pitfalls and evaluation cases for common mistakes.
  • Expands skill-trigger coverage.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
skills/internet-identity/SKILL.md Documents app metadata behavior and configuration.
evaluations/internet-identity.json Adds output and trigger evaluations.
Suppressed comments (1)

skills/internet-identity/SKILL.md:62

  • This wording groups SVG rejection with document-field validation and can imply that an SVG invalidates all metadata. The MIME type is checked while fetching the resource, so per line 211 an SVG costs only the logo; it does not remove valid name/description. The “no metadata at all” statement also omits the curated fallback documented at line 227.
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.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread skills/internet-identity/SKILL.md Outdated
Comment thread evaluations/internet-identity.json Outdated
Comment thread skills/internet-identity/SKILL.md Outdated
…ures

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qzx3V9y4eYVNjqnvFrhDHU
@aterga
aterga enabled auto-merge (squash) August 25, 2026 13:25
@aterga
aterga requested a review from marc0olo August 25, 2026 13:59

@marc0olo marc0olo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed this against two sources rather than the one in the description: the normative docs/ii-spec.mdx §App metadata, and the implementation in src/frontend/src/lib/utils/appMetadata.ts / lib/stores/app-metadata.store.ts on dfinity/internet-identity@main.

Verdict: sound, and accurate to an unusual degree. Every numeric and behavioural claim checks out against the code — the 40/120 code-point limits counted pre-normalization (validateTextField measures [...value].length before the \s+ collapse), the exact forbidden-character class, hasBalancedIsolates, the all-or-nothing INVALID short-circuit, APP_LOGO_CONTENT_TYPES, MAX_APP_LOGO_SIZE/MAX_APP_LOGO_DIMENSION/APP_LOGO_RENDER_SIZE, MAX_APP_METADATA_SIZE, redirect: "error", credentials: "omit", and APP_METADATA_FETCH_TIMEOUT_MILLIS. The _headers deviation from the docs PR is the right call for this repo — static-site pitfalls 1, 6, and 12 all back it, and Access-Control-Allow-Origin: * is valid here precisely because II omits credentials. The three Copilot findings were all real and correctly fixed in 1bc611e.

Worth calling out: the code check mattered

The document-vs-logo split this PR asserts is not settled by the spec text. ii-spec.mdx puts the same-origin requirement in the same bullet as the content-type/size/dimension requirements, then says a logo that "does not meet the asset requirements above costs the application only its logo" — which reads the other way. It also claims the published JSON Schema expresses every rule except isolate balancing, while the schema's logo is just {type: string, minLength: 1}, implying same-origin isn't document validation.

The implementation settles it in this PR's favour: validateLogoUrl returns the document-level INVALID sentinel for a cross-origin URL, and INVALID short-circuits the whole document in fetchAppMetadataFrom, whereas fetchLogoObjectUrl returns undefined and only drops metadata.logo. So the skill is right and the spec prose is the weaker of the two — probably worth a separate nit upstream on dfinity/internet-identity.

Two must-fixes and one content gap

See the inline comments on lines 229 and 210. Summary:

  1. The spec link 301s to the docs homepage, not the spec — and the validator's link check follows the redirect, sees 200, and passes, so CI can't catch it.
  2. The "no anchor yet" note in the description is stale; #app-metadata is live.
  3. The gateway-twin fallback isn't covered, which makes an absolute logo URL a trap under this skill's own recommended derivation-origin pattern.

Non-blocking nits

  • Content-Type: application/json on the document is hygiene, not a requirement. fetchCapped sends application/json as an Accept header only and never inspects the response's content-type; the body is decoded and JSON.parsed regardless. Only the logo's content-type is enforced. Setting it is still right and matches the block above — but that block says "II needs both", and a reader may carry the "needs" over. Only worth touching if you're editing that paragraph anyway.
  • credentials: omit and the 10 s timeout live only in the implementation, not in the normative §App metadata. Fine to document; just the likeliest claims to drift.
  • Size advisory. The skill goes 510 → 555 lines and now trips the >500-line warning alongside the pre-existing token warning. Warnings are acceptable per .claude/CLAUDE.md and the check is green — flagging only as a signal that the next addition to this skill probably belongs in references/.

Evals

Structurally valid, 14 → 17 output evals, should_trigger 6 → 7. The adversarial case is a genuinely strong regression test after the Copilot round, and its oracle is correct against the implementation: an SVG logo passes validateLogoUrl (it's same-origin) and fails only at the content-type check in fetchLogoObjectUrl, so it really does cost only the logo — exactly what the oracle demands. Skipping a re-run of the untouched cases is correct per .claude/CLAUDE.md.

Everything other than the three items above is approve-worthy as written.

Comment thread skills/internet-identity/SKILL.md Outdated
Comment thread skills/internet-identity/SKILL.md Outdated
… logo trap

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/<path> 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qzx3V9y4eYVNjqnvFrhDHU
@aterga
aterga requested a review from marc0olo August 25, 2026 15:14

@marc0olo marc0olo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed 05fbf01. All three items addressed, and the mechanism is stated accurately. I re-checked the new prose against dfinity/internet-identity@main rather than the description:

  • Spec linkdocs.internetcomputer.org/references/internet-identity-spec/#app-metadata, verified 200 with the anchor live. Stale Notes paragraph gone.
  • Gateway twins — "normalizes onto ic0.app and tries its icp0.io and icp.net twins in turn" matches GATEWAY_DOMAINS = ["ic0.app", "icp0.io", "icp.net"] exactly, order included; scoping it to a canister gateway origin is right, since gatewayOriginTwins returns [] for custom domains and localhost; and "the same-origin check runs against whichever one answered" is right because the loop hands each candidate to fetchAppMetadataFrom, which passes that same origin into validateLogoUrl. Fixed in both places it needed to be — the logo bullet and pitfall 14.
  • New adversarial eval — 18 output evals, JSON valid, and oracle 1 hedging with "says no, or flags it as a real risk" is the right calibration, since whether it actually breaks depends on which gateway answers first. A baseline that opens with "Yes, that's fine" is exactly the failure the case exists to catch.

npm run validate on the branch: 29 skills, all passed, warnings only. Re-checking evals 15 and 16 was the right call, and I did not re-run them — the logo-bullet edit is purely additive and can only reinforce both oracles, so I'd have skipped it too.

One punctuation nit inline, non-blocking. Otherwise this is ready to merge.

Comment thread skills/internet-identity/SKILL.md Outdated
…ash 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qzx3V9y4eYVNjqnvFrhDHU
@marc0olo
marc0olo self-requested a review August 25, 2026 15:29
@aterga
aterga merged commit 4bb60dc into main Aug 25, 2026
6 checks passed
@aterga
aterga deleted the claude/icp-skills-ii-app-metadata-gq97tw branch August 25, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants