Skip to content

fix(command-bar): attribute contributed rows to the validated app name - #9226

Merged
iamwhatever merged 2 commits into
mainfrom
fix/app-attribution-provenance
Sep 7, 2026
Merged

fix(command-bar): attribute contributed rows to the validated app name#9226
iamwhatever merged 2 commits into
mainfrom
fix/app-attribution-provenance

Conversation

@bolichen97

@bolichen97 bolichen97 commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

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:

const rawAppLabel = app.displayName || app.name

displayName is free text the app chooses, so as provenance it fails in both
directions:

  • It can claim to be the host. A manifest declaring displayName: "Kiro Crew" puts
    that string where the row states its owner. kindLabel renders
    `${appLabel} · ${kind}`, so the row reads as native — while activating it sends
    the app's prompt to an agent with tools, and autoSend fires it without a further
    keystroke.
  • It can be present and invisible. || catches only "" and undefined. The
    manifest validator refuses just a falsy value (if not self.displayName in
    apps/manifest.py), so " " — or a zero-width \u200b that survives .trim(), or
    \u034f — is "present", wins over the name, and produces the empty label that
    kindLabel renders as a bare kind. That is character-for-character what a builtin
    row shows.

The second failure mode is one the file already documented as the thing to avoid:

dropping the label to empty then erased the provenance: kindLabel renders a bare kind
when there is no label, which is character-for-character what a BUILTIN row shows, so a
contributed row whose prompt goes to a tool-enabled agent read as native.

The code simply did not prevent it.

The fix

The label now comes from name, the install identity:

const rawAppLabel = app.name

KEBAB_RE (^[a-z0-9]+(?:-[a-z0-9]+)*$) constrains it, so it can neither be blank nor
spell a host word with a space or a capital, and it is unique across installed apps in a
way displayName is not. The existing clip path already used app.name for exactly this
reason; 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.fileMenuItems in #7955 and to
contributes.panelTabs in #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.ts and CommandBarOverlay.test.tsx updated: the existing
    assertions 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.
  • New coverage: every shape of displayName — absent, friendly, host-impersonating
    ("Kiro Crew"), whitespace-only, \u200b, \u034f — resolves to pr-bulk-ops, and
    the attribution is never empty.
  • Negative control: restoring app.displayName || app.name fails 2 tests in
    contributedCommands.test.ts; the fix restores them.
  • npx vitest run on 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.mjs
harness, which runs the real built SPA against fixtures. For the BEFORE frame the
external app's displayName was set to "Kiro Crew", so the frame shows the
impersonation rather than merely a friendly name.

Before — a third-party app renders as Kiro Crew · Command on rows whose prompts go
to an agent with tools, one of them "Approve every pull request behind a link, without
reviewing":

Command bar before the fix: contributed rows attributed to "Kiro Crew"

After — the same rows carry the validated install identity, pr-bulk-ops · Command:

Command bar after the fix: contributed rows attributed to "pr-bulk-ops"

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 still
distinguishable at a glance.

Pattern harvest

Rule candidate: semgrep
Pattern: an app-supplied displayName (or any manifest-authored free-text field) used as
the PROVENANCE a user identifies a third party by -- app.displayName || app.name in an
attribution, owner label, or consent prompt. Provenance must come from the validated
install identity (name, constrained by KEBAB_RE), never from a field the app chooses.
The same defect has now been found three times in three surfaces (fileMenuItems in
#7955, panelTabs in #7975, and contributed commands here), which is what makes it a
rule rather than a one-off.

Deliberately not changed

TrustAppModal.tsx also renders app.displayName || app.name, in the title of a consent
dialog. It is left alone: that surface renders
trustAppModal.not_reviewed unconditionally — "…and the name above comes from the app
itself"
— 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 || name sites are ordinary display (headings, search fields,
uninstall confirmations) where a friendly name is correct.

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.
@bolichen97
bolichen97 requested a review from a team September 7, 2026 10:06
@bolichen97
bolichen97 requested a review from a team as a code owner September 7, 2026 10:06
@bolichen97
bolichen97 requested a review from smeyffret September 7, 2026 10:06
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5.1) — ✅ PASS

UX-level review of e7f73db7e51596b1a06c316b5782562035f6c7a2 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

This PR is narrow: it changes the command-bar contributed-row attribution source from app.displayName || app.name to app.name. The only user-visible surface change is the provenance tag text — pr-bulk-ops · Command (after, shot-01) instead of Kiro Crew · Command (before, shot-02). Both states are shown in committed screenshots, and the blind read ran.

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 pr-bulk-ops — was read correctly by the cold reader as a provenance tag, and both before/after states are shown.

[UX-REVIEWED] e7f73db

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5.1) — ✅ PASS

Design-level review of e7f73db7e51596b1a06c316b5782562035f6c7a2 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Attribution now derives from the validated name (KEBAB_RE-constrained install identity) instead of app-chosen displayName, closing an impersonation/empty-label provenance gap on untrusted app manifests — right cause, minimal shape, trivially reversible.

[DESIGN-REVIEWED] e7f73db

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5.1) — ✅ PASS

Premise-level review of e7f73db7e51596b1a06c316b5782562035f6c7a2 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Assessment

The authored change is a clean, correctly-scoped one-line security fix. The authentic.patch is polluted: ~135 of its ~140 files (work-ledger apply_acceptance_update = merged #9152, the Fargate RFC = merged 8bc9701, the slack await fix = merged 5b1dc66) are already-merged main commits appearing via a stale diff base, not this author's work. My inventory covers only the genuine change, the command-bar attribution fix, and I do not flag main's commits as riding along — that would be fabrication.

The fix (contributedCommands.ts:396, app.displayName || app.nameapp.name) is derived from the external-content-untrusted boundary: a manifest's displayName is app-authored and only falsy-validated, so it can spell "Kiro Crew" or a whitespace/zero-width string that renders as a bare kind — character-for-character a builtin row. Cause-level for this surface; the clip path already used name, so no duplication. Siblings counted and handled: fileMenuItems (#7955), panelTabs (#7975) fixed; TrustAppModal.tsx is a disclosed non-change; remaining displayName || name sites (verified) are display-only.

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 ships

Intent: stop a contributed command row from attributing itself to an app-chosen name that can impersonate the host or render blank. FIX.

  1. Contributed rows now read pr-bulk-ops · Command instead of PR Bulk Ops · Command — attribution comes from KEBAB_RE-validated name, not displayName — justified.
  2. Test/comment updates asserting the new behavior — justified (the fix's proof).

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

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed e7f73db7e51596b1a06c316b5782562035f6c7a2 — this comment is updated in place on each push.

Review details

The backend enforces the app-name contract on every admission path: AppManifest.validate() calls app_name_error() (manifest.py:1899), which requires KEBAB_RE.fullmatch(name) — and discovery skips any manifest whose validation errors (discovery.py:151-158). So app.name reaching /api/apps cannot be "Kiro Crew", " ", "", or a zero-width string; KEBAB_RE (^[a-z0-9]+(?:-[a-z0-9]+)*$) forbids spaces, capitals, blanks, and non-ASCII. The candidate's concrete input — a non-kebab top-level name — does not occur in practice (a fails), and name is a core manifest field, not the untrusted contributes/extra bucket. The comment's reliance on KEBAB_RE is correct. Candidate 1 dies under falsification.

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 e7f73db7e51596b1a06c316b5782562035f6c7a2.

False positive or not applicable? A repository writer can comment:
/ai-review override fable e7f73db7e51596b1a06c316b5782562035f6c7a2: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of e7f73db7e51596b1a06c316b5782562035f6c7a2 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] e7f73db

False positive or not applicable? A repository writer can comment:
/ai-review override gpt e7f73db7e51596b1a06c316b5782562035f6c7a2: <one-sentence reason>

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.
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 7, 2026
@iamwhatever
iamwhatever enabled auto-merge (squash) September 7, 2026 11:07

@iamwhatever iamwhatever left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

@iamwhatever
iamwhatever merged commit d576c8d into main Sep 7, 2026
66 of 72 checks passed
@iamwhatever
iamwhatever deleted the fix/app-attribution-provenance branch September 7, 2026 11:07
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 7, 2026
bolichen97 added a commit that referenced this pull request Sep 7, 2026
#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)
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.

2 participants