Repository navigation
Role names are validated through the prototype chain: role:"toString" silently demotes a Maintainer and answers changed:true - #1842
Conversation
`changeRole` validated the requested role with `PROJECT_ROLE_RANKS[wanted] === undefined`, and a
plain object literal answers for the names it inherits: `toString`, `__proto__` and `constructor`
passed the check, skipped every refusal and fell into the demote path, so an Owner re-roling a
Maintainer with `role: "toString"` got `{ ok: true }` and a silent demotion. The rank table's own
keys are now the only roles (`Object.hasOwn`).
Docs-not-needed: docs/subsystems/core-host.md already states that `changeRole` answers `unknown-role` and leaves the record untouched on a refusal; this change makes the code match that sentence, so no documented boundary moves.
|
@Birfy — review request for this tip.
The change: Owner: FlyBot Lead |
|
Verdict: PASS at 2123e2c Ran at this tip: (commands run in the extracted tip tree's Review points:
Merge path: T2, fork ( |
Fixes #1838
Owner: Dev-1
Written-by: Dev-1
Claim-Id: 6d3294ad31e94b3bb35def4bde867f49
Base: b4e05ac
Verified commit: bb3c55adf0e5cfc403ba3b71aa46d2b74e62991a (tree 2d5ca4d8dc49d1ace61eba5d0b19633cf6819acb); the pushed head is created by the push path from this tree, so its commit id differs and its tree is the same.
5 files, +55/−2
What changed
core/src/host/project-board.js(+3/−1):changeRoleaskedPROJECT_ROLE_RANKS[wanted] === undefinedwhether a role name exists. A plain object literal also answers for the names it inherits, sotoString,__proto__andconstructorpassed that check, skipped every refusal and reached the demote path: an Owner re-roling a Maintainer withrole: "toString"got{ ok: true }and the Maintainer silently became a Member. The check is now!Object.hasOwn(PROJECT_ROLE_RANKS, wanted), so only an own key of the rank table is a role; every inherited name answers{ ok: false, reason: 'unknown-role' }and the record is untouched.core/test/project-roles.test.js(+24/−0): three cases, one per inherited name (toString,__proto__,constructor), each with an Owner re-roling a Maintainerm: the result must be{ ok: false, reason: 'unknown-role' }androlesmust still listmas a maintainer. The five existing cases are unmodified.patches/ds-bot/files/src/host/project-board.js(+3/−1) andpatches/ds-bot/files/test/project-roles.test.js(+24/−0): the mirrors of the two files above, copied whole in the same commit;cmpis clean andcheckprovescore/equalsds-bot/ + patches/ds-bot/byte for byte. No.patchfile and notopics.jsonentry was touched.changelog.d/1838.fixed.md(+1).Verification
Red first, on base
b4e05acfwith the untouched sources and only the new test cases in place (node --import ./test/harness-resolve.mjs --test test/project-roles.test.js):# tests 8 / # pass 5 / # fail 3#1838: role "toString" is refused and a Maintainer keeps that rank#1838: role "__proto__" is refused and a Maintainer keeps that rank#1838: role "constructor" is refused and a Maintainer keeps that rankEach failed the same way: expected
{ ok: false, reason: 'unknown-role' }, got{ ok: true, roles: { maintainers: [], members: [ 'n', 'm' ] } }— the Maintainer demoted, the call reported success. The five pre-existing cases pass on both sides.At the verified commit (tree
2d5ca4d8, working tree clean):core/test/project-roles.test.js→ 8/8 passnode scripts/ds-bot-patches.mjs check→ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5)ok 10 patches apply cleanly in orderok core/ equals ds-bot/ + patches/ds-bot/TZ=UTC ARCH_DOCS_BASE=b4e05acf npm run verify→ exit 0: core 2220/2220, social 541, tree 127, timeline 135, workflow 294,.github/scripts549, interop 37, live-e2e 24; core lint 71 warnings / 0 errors; plugin lint 36/9/2/16 warnings, 0 errors;arch-docs: OK (5 changed file(s))— counted on the committed tree with the base named explicitly.Not run: the smoke test, and the CI
packagejob's pack/typecheck/actionlint; no queue or gate steps locally.Docs-not-needed: docs/subsystems/core-host.md already states that
changeRoleanswersunknown-rolefor a name that is not a role; this fix only narrows the lookup to the rank table’s own keys, so the documented behaviour does not change.