On a push through the gateway, gateway::refusal checks a protected
branch's linear rule. For a branch that does not exist yet, it checks
all of the branch's history (crates/knotless/src/gateway.rs, refusal).
dev.knotless.createCommit never calls refusal
(crates/knotless-serve/src/gateway.rs, commit and attempt): it checks
only held_to_queue. Its commits are built with one parent on
commit::start, which for a missing branch is the request's base
(crates/knotless/src/commit.rs, start). So a collaborator can create a
branch whose rule is linear, with merge commits in its history, by
committing to it with a base whose history has merges. A push of the
same tip would be refused with protected branch: no merge commits; squash or rebase.
This only matters for a rule set before its branch exists. On an existing
branch, createCommit builds on the tip, so it stays forward-only and adds
no merges.
How to see it
As owner, add a rule with linear history for a branch that does not
exist yet, say release.
As a collaborator, call createCommit to release with base set to a
commit that has a merge in its history, such as main on a repository
that merges.
The commit lands, and release now carries merge commits.
What you expected
createCommit should refuse the commit as the push does, for example by
running the same linear check on the update before landing the batch.
Where
Gateway on main at b191acef. Found by the docs audit. gateway.md says
createCommit commits "pass the rest", which is true only for existing
branches.
What happened
On a push through the gateway,
gateway::refusalchecks a protected branch'slinearrule. For a branch that does not exist yet, it checks all of the branch's history (crates/knotless/src/gateway.rs,refusal).dev.knotless.createCommitnever callsrefusal(crates/knotless-serve/src/gateway.rs,commitandattempt): it checks onlyheld_to_queue. Its commits are built with one parent oncommit::start, which for a missing branch is the request'sbase(crates/knotless/src/commit.rs,start). So a collaborator can create a branch whose rule is linear, with merge commits in its history, by committing to it with abasewhose history has merges. A push of the same tip would be refused withprotected branch: no merge commits; squash or rebase.This only matters for a rule set before its branch exists. On an existing branch, createCommit builds on the tip, so it stays forward-only and adds no merges.
How to see it
release.releasewithbaseset to a commit that has a merge in its history, such asmainon a repository that merges.releasenow carries merge commits.What you expected
createCommit should refuse the commit as the push does, for example by running the same linear check on the update before landing the batch.
Where
Gateway on main at b191acef. Found by the docs audit. gateway.md says createCommit commits "pass the rest", which is true only for existing branches.
🤖 Generated with Claude Code