Skip to content

Advertised page/perPage bounds are not enforced in list_commits, get_commit, actions_list and list_gists, and -2^63 returns a raw Go decode error #3762

Open

Description

@rafe-walker

Describe the bug

These tools advertise page with minimum: 1 (and perPage 1..100) in tools/list. At runtime they validate against a relaxed clone of the schema with the bounds removed, and they forward the value unchanged:

  • page: -1 is sent as ?page=-1.
  • page: 9007199254740992 (2^53) is sent as is. For list_commits, GitHub answers HTTP 500 and the tool returns failed to list commits: : GET …/commits?page=9007199254740992&per_page=1: 500 [], with an empty message.
  • page: 2147483647 is accepted by GitHub and returns [].

The relaxation is intentional, per the comment at pkg/github/git_gist_types.go:236-242 ("the SDK continues accepting legacy out-of-range values (e.g. page=-1, perPage=101) that the handler has always forwarded unmodified"). The result is that the advertised contract and the behaviour disagree, and very large values produce an opaque upstream 500.

Separately, -9223372036854775808 (-2^63) passes the integer range check, which is inclusive at the lower end (pkg/github/params.go:63-66; typed_read_normalizers.go:45 uses the same inSignedIntRange), and then, by code reading, fails in the typed decode with a raw encoding/json error that exposes internal type names and a fragment of the arguments:
json: cannot unmarshal number -9223372036854776000,"perPage":1... overflows into Go struct field github.ListCommitsInput.page of type int (also github.IssueReadInput.issue_number via issue_read).

Related: #3647 / PR #3649 (list_label turns perPage: 0 into 100 despite minimum: 1), the same class in a different tool. #3489 / PR #3650 fixed the upper bound (2^63) only.

Code (main 99d6d65, unchanged since v2.1.0): relaxedPaginationValidationSchema at pkg/github/git_gist_types.go:243-253, used at repositories.go:75 (get_commit), repositories.go:248 (list_commits), gists.go:49 (list_gists). actionsValidationSchema at actions_types.go:114 (actions_list). Range check at params.go:63-66.

perPage out-of-range values were not tested; the relaxation also clears the perPage min/max per the code.

Affected version

v2.1.0 (image sha256:f6056fc1…0f8a, commit 4bcdcdf) and main nightly (image sha256:48bc16d0…338f, revision 99d6d65). --read-only --toolsets=all, read-only fine-grained PAT.

Steps to reproduce the behavior

The forwarded value is visible in the request URL of the error result:

{"name":"list_commits","arguments":{"owner":"OWNER","repo":"REPO","sha":"no-such-ref","page":-1,"perPage":1}}

Result: GET …/commits?page=-1&per_page=1&sha=no-such-ref: 404.

{"name":"list_gists","arguments":{"username":"no-such-user-zz9","page":-1,"perPage":1}}

Result: GET …/users/no-such-user-zz9/gists?page=-1&per_page=1: 404.

{"name":"actions_list","arguments":{"owner":"OWNER","repo":"REPO","method":"list_workflows","page":9007199254740992,"perPage":1}}

Result: GET …/actions/workflows?page=9007199254740992&per_page=1 (403 here because the PAT has no Actions scope, but the page value is visible).

{"name":"list_commits","arguments":{"owner":"OWNER","repo":"REPO","page":9007199254740992,"perPage":1}}

Result: failed to list commits: : GET …/commits?page=9007199254740992&per_page=1: 500 [].

{"name":"list_commits","arguments":{"owner":"OWNER","repo":"REPO","page":-9223372036854775808,"perPage":1}}

Result: the raw decode error quoted above.

get_commit with page: -1 and page: 2^53 returns success with no validation error. Its error messages don't show the URL; by code it forwards page to ListOptions the same way.

Expected vs actual behavior

Expected: values outside the advertised range are clamped or rejected with a message that names the parameter. An out-of-range integer gets the same "value out of range" message as at the upper end.

Actual: the values are forwarded unmodified (opaque 500 at 2^53), and -2^63 leaks a Go decode error.

Suggested fix: if accepting legacy values must stay, normalise in the handler: page < 1 → 1, and reject page above a sane cap (for example, 2^31-1) with a clear error. Alternatively, stop advertising bounds that aren't enforced. Make the lower bound in inSignedIntRange exclusive (f > -limit), matching the upper bound, so -2^63 gets the existing range error.

Logs

list_commits page=-1 (bad ref)        -> …/commits?page=-1&per_page=1&sha=no-such-ref: 404 Not Found []
list_gists   page=-1 (bad user)       -> …/users/no-such-user-zz9/gists?page=-1&per_page=1: 404 Not Found []
actions_list page=2^53                -> …/actions/workflows?page=9007199254740992&per_page=1: 403 …
list_commits page=2^53                -> failed to list commits: : GET …/commits?page=9007199254740992&per_page=1: 500  []
list_commits page=2147483647          -> OK []
list_commits page=-2^63               -> json: cannot unmarshal number -9223372036854776000,"perPage":1... overflows into Go struct field github.ListCommitsInput.page of type int
tools/list list_commits page: {"type":"number","minimum":1}

Same on v2.1.0 and main.

Found during an independent audit of v2.1.0 on a throwaway org and private test repo. Prepared with AI assistance (Claude Code); reproduced by two independent test runs before filing.

Activity

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