Skip to content

Role names are validated through the prototype chain: role:"toString" silently demotes a Maintainer and answers changed:true #1838

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 6d3294ad31e94b3bb35def4bde867f49
Branch: dev/1-p247-role-name-own-key
Base: f245520
At: 2026-10-30T13:43:36Z

What goes wrong

changeRole (core/src/host/project-board.js:68-90) validates the requested role with PROJECT_ROLE_RANKS[wanted] === undefined (:74), but PROJECT_ROLE_RANKS (:27) is a plain object literal, so every inherited name passes: toString, __proto__, constructor, valueOf, hasOwnProperty read a function/object instead of undefined. Nothing downstream rejects the name: wanted === 'owner' (:75) is false, and for a self-change the check PROJECT_ROLE_RANKS[wanted] > PROJECT_ROLE_RANKS[actor] (:79) is NaN > n, i.e. false. The write path then falls into else members.push(target) (:88).

Consequence for an Owner re-roling a Maintainer with role:"toString": the answer is {ok:true, roles:{maintainers:[], members:[..., target]}} — the Maintainer is silently demoted to Member and the UI reports "Role changed."

Latent until #1837 lands: on live main f245520c, project-role is missing from TASK_ENDPOINTS (core/src/host/authz.js:60-77), so a signed-in host answers bot/not-found (#1836). #1837 adds the endpoint; from that merge on, any signed-in member of the Project can call it directly and a mistyped role silently demotes someone.

Same family as #1704 (ids resolved through the prototype chain), but a different table: #1704 fixed the state tables in store.js; PROJECT_ROLE_RANKS is a separate literal and that fix does not cover it.

Reproduction

import { changeRole } from 'core/src/host/project-board.js'
const p = { id: 'project-1', name: 'P', ownerUserIds: ['owner'], roles: { maintainers: ['m-3'], members: ['m-2'] } }
changeRole(p, { actorId: 'owner', targetId: 'm-3', role: 'toString' })

Observed (probe over an unpacked copy of live f245520c, 2026-10-30): {ok:true, roles:{maintainers:[], members:['m-2','m-3']}}; __proto__ and constructor behave the same. Expected: {ok:false, reason:'unknown-role'} with roles byte-for-byte unchanged. The red-first tests below are the authoritative repro.

Expected

  • Any name that is not an own key of PROJECT_ROLE_RANKS is refused: {ok:false, reason:'unknown-role'}, and the record is untouched.
  • One-line fix: if (!Object.hasOwn(PROJECT_ROLE_RANKS, wanted)).
  • Legitimate switches keep working exactly as today: owner → member/maintainer, maintainer → member for self, and every existing refusal (owner-not-assignable, last-owner, not-owner, cannot-self-promote).

Red-first contract

On base f245520c, add three cases to the existing core/test/project-roles.test.js:

  1. changeRole(project({roles:{maintainers:['m'],members:['n']}}), {actorId:'owner', targetId:'m', role:'toString'}) → {ok:false, reason:'unknown-role'}, roles unchanged.
  2. same with role:'__proto__'.
  3. same with role:'constructor'.

All three must fail on base (they currently answer {ok:true, ...} and demote m), and the existing 5 tests must stay green, unmodified.

Scope

Verification contract

  • Commit first, then run the full npm run verify on the committed clean tree (git status --porcelain empty) with ARCH_DOCS_BASE=<live main base>; the PR body carries those real numbers only.
  • node scripts/ds-bot-patches.mjs check → the three green lines; build-client --check exit 0 as part of verify.
  • PR: Fixes #<this issue>, maintainer_can_modify: true, no placeholders, one tip.

Dedupe (2026-10-30)

Grep of live main f245520c: PROJECT_ROLE_RANKS appears only at project-board.js:27,60,74,79; no Object.hasOwn on that map. Closed-issue search: #1704 (prototype lookups on state tables, fixed 2026-10-29) is the same family but a different table and code path; #1836/#1837 cover only the missing project-role authz entry. Neither open nor closed issues cover role-name validation.

Activity

  1. Birfy commented on Oct 30, 2026

    @Birfy
    Collaborator

    Written-by: Chief

    状态补记:agent-pr。本单已有开放 PR #1842(dev/1-p247-role-name-own-key,tip 2123e2cc,正文 Fixes #1838),此前本单没有任何状态标签。CI、入队与合并由开该 PR 的那条线负责,本线不接手。

    读取时间 2026-10-30T16:35Z,来源 api.github.com。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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