fix(command-bar): attribute contributed rows to the validated app name - #9226
Conversation
A contributed command's attribution came from `app.displayName`, which is free text the app chooses, so it could not do the one job it has. An app declaring `displayName: "Kiro Crew"` put that string where the row states its owner, and the row then read as native while its prompt goes to an agent with tools. In the other direction `||` is not the emptiness test it looks like: the manifest validator refuses only a falsy `displayName`, so `" "` or a zero-width `\u200b` is "present", wins over the name, and renders the empty label that `kindLabel` shows as a bare kind -- character for character what a builtin row shows. The label now comes from `name`, the install identity. `KEBAB_RE` limits it to `[a-z0-9]` plus single hyphens, so it can neither be blank nor spell a host word with a space or a capital, and it is unique per installed app. The clip path already used `name`; the unclipped path now agrees with it.
UX Review (Fable 5.1) — ✅ PASSUX-level review of This PR is narrow: it changes the command-bar contributed-row attribution source from Reconciliation: the sole changed element is the non-clickable source tag. The blind reader read it correctly as "a tag saying where the command comes from" ("nothing if clicked"). The rows themselves (Merge/Review/Approve all PRs) and their descriptions ("behind a link") are pre-existing and untouched by this diff — the reader's "no idea what 'behind a link' means" is out of scope. No primary/clickable control is added or changed; no lens-13 in-product state transition (the before/after are two fixtures demonstrating the fix, not a persistent element the user watches transform), so no recording is needed. The kebab identifier being less "friendly" is the PR's explicit, documented tradeoff (provenance the app cannot choose) and belongs to the design lane, not comprehension — the reader understood it. UX-Verdict: PASS The one user-visible change — the source tag now reads [UX-REVIEWED] e7f73db |
Design Review (Fable 5.1) — ✅ PASSDesign-level review of Design-Verdict: PASS Attribution now derives from the validated [DESIGN-REVIEWED] e7f73db |
First Principles Review (Fable 5.1) — ✅ PASSPremise-level review of AssessmentThe authored change is a clean, correctly-scoped one-line security fix. The The fix ( First-Principles-Verdict: PASS A spoofable, app-chosen attribution is replaced with the validated install identity — the one item is the fix, cause-level, siblings counted. What this change shipsIntent: stop a contributed command row from attributing itself to an app-chosen name that can impersonate the host or render blank. FIX.
Note for the human: the patch's non-command-bar files are a diff-base artifact (merged main commits), not part of this PR. [FIRST-PRINCIPLES-REVIEWED] e7f73db |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThe backend enforces the app-name contract on every admission path: No grounded new findings: the diff strictly narrows attribution to the one field the backend validates. No findings. [OPUS-REVIEWED] e7f73db Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Captured with the repo's own scripts/capture-command-bar-contributed.mjs harness, with the external app's displayName set to "Kiro Crew" so the BEFORE frame shows the impersonation the fix removes rather than merely a friendly name.
iamwhatever
left a comment
There was a problem hiding this comment.
Tier 1 auto-approve: fix (5 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: a contributed command-bar row took its attribution from the app-chosen displayName, so a whitespace/zero-width value rendered an empty label that reads as a builtin row and a host-shaped value could claim to be core; attribution now comes from the validated kebab-case install name.
#9226) * fix(command-bar): attribute contributed rows to the validated app name A contributed command's attribution came from `app.displayName`, which is free text the app chooses, so it could not do the one job it has. An app declaring `displayName: "Kiro Crew"` put that string where the row states its owner, and the row then read as native while its prompt goes to an agent with tools. In the other direction `||` is not the emptiness test it looks like: the manifest validator refuses only a falsy `displayName`, so `" "` or a zero-width `\u200b` is "present", wins over the name, and renders the empty label that `kindLabel` shows as a bare kind -- character for character what a builtin row shows. The label now comes from `name`, the install identity. `KEBAB_RE` limits it to `[a-z0-9]` plus single hyphens, so it can neither be blank nor spell a host word with a space or a capital, and it is unique per installed app. The clip path already used `name`; the unclipped path now agrees with it. * docs(command-bar): add before/after evidence for the attribution fix Captured with the repo's own scripts/capture-command-bar-contributed.mjs harness, with the external app's displayName set to "Kiro Crew" so the BEFORE frame shows the impersonation the fix removes rather than merely a friendly name. (cherry picked from commit d576c8d)
The defect
A contributed command's attribution — the string that tells a reader which app owns
the row they are about to run — came from
app.displayName:displayNameis free text the app chooses, so as provenance it fails in bothdirections:
displayName: "Kiro Crew"putsthat string where the row states its owner.
kindLabelrenders`${appLabel} · ${kind}`, so the row reads as native — while activating it sendsthe app's
promptto an agent with tools, andautoSendfires it without a furtherkeystroke.
||catches only""andundefined. Themanifest validator refuses just a falsy value (
if not self.displayNameinapps/manifest.py), so" "— or a zero-width\u200bthat survives.trim(), or\u034f— is "present", wins over the name, and produces the empty label thatkindLabelrenders as a bare kind. That is character-for-character what a builtinrow shows.
The second failure mode is one the file already documented as the thing to avoid:
The code simply did not prevent it.
The fix
The label now comes from
name, the install identity:KEBAB_RE(^[a-z0-9]+(?:-[a-z0-9]+)*$) constrains it, so it can neither be blank norspell a host word with a space or a capital, and it is unique across installed apps in a
way
displayNameis not. The existing clip path already usedapp.namefor exactly thisreason; this makes the unclipped path agree with it instead of preferring a field nothing
validates. The length cap, the clip-without-ellipsis behaviour and the warning are all
unchanged, and no interface changes.
This is the same rule already applied to
contributes.fileMenuItemsin #7955 and tocontributes.panelTabsin #7975: provenance never comes from a field the app chooses.It was raised there as out of scope for those PRs, which is why it is here on its own.
Verification
contributedCommands.test.tsandCommandBarOverlay.test.tsxupdated: the existingassertions asserted the app-supplied name, which was the defect. The fixture keeps its
displayName: 'PR Bulk Ops'deliberately, so the tests now prove it is ignored.displayName— absent, friendly, host-impersonating(
"Kiro Crew"), whitespace-only,\u200b,\u034f— resolves topr-bulk-ops, andthe attribution is never empty.
app.displayName || app.namefails 2 tests incontributedCommands.test.ts; the fix restores them.npx vitest runon the three command-bar suites: 140 passed.tsc -b,eslint --max-warnings 0,npm run i18n:check(19 checks),npx jscpd .:all clean. No backend files touched, so the Python gates are not in scope.
Visual evidence
Captured with the repo's own
website/scripts/capture-command-bar-contributed.mjsharness, which runs the real built SPA against fixtures. For the BEFORE frame the
external app's
displayNamewas set to"Kiro Crew", so the frame shows theimpersonation rather than merely a friendly name.
Before — a third-party app renders as
Kiro Crew · Commandon rows whose prompts goto an agent with tools, one of them "Approve every pull request behind a link, without
reviewing":
After — the same rows carry the validated install identity,
pr-bulk-ops · Command:The visual delta is deliberate and is the point of the change: the attribution trades a
friendlier string for one the app cannot choose. Builtin rows are unaffected -- they
carry a category (
Setting,Channels · ...), so a contributed row is stilldistinguishable at a glance.
Pattern harvest
Rule candidate: semgrep
Pattern: an app-supplied
displayName(or any manifest-authored free-text field) used asthe PROVENANCE a user identifies a third party by --
app.displayName || app.namein anattribution, owner label, or consent prompt. Provenance must come from the validated
install identity (
name, constrained byKEBAB_RE), never from a field the app chooses.The same defect has now been found three times in three surfaces (
fileMenuItemsin#7955,
panelTabsin #7975, and contributed commands here), which is what makes it arule rather than a one-off.
Deliberately not changed
TrustAppModal.tsxalso rendersapp.displayName || app.name, in the title of a consentdialog. It is left alone: that surface renders
trustAppModal.not_reviewedunconditionally — "…and the name above comes from the appitself" — so app-supplied naming there is a disclosed, deliberate design rather than an
oversight. Changing what that dialog calls an app is a product decision, not a defect fix.
The remaining
displayName || namesites are ordinary display (headings, search fields,uninstall confirmations) where a friendly name is correct.