Skip to content

[pull] trunk from cli:trunk - #14

Open
pull[bot] wants to merge 588 commits into
Moshbbab:trunkfrom
cli:trunk
Open

[pull] trunk from cli:trunk#14
pull[bot] wants to merge 588 commits into
Moshbbab:trunkfrom
cli:trunk

Conversation

@pull

@pull pull Bot commented Jun 1, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

@pull pull Bot locked and limited conversation to collaborators Jun 1, 2026
@pull pull Bot added ⤵️ pull merge-conflict Resolve conflicts manually labels Jun 1, 2026
babakks and others added 27 commits June 10, 2026 09:32
Add ParseDiscussionOrCommentArg to the shared package for parsing
discussion numbers, URLs, comment node IDs, and comment URLs
(with #discussioncomment-NNNNN fragments).

Add ResolveCommentNodeID to the client for constructing node IDs from
repository database ID and comment database ID via msgpack encoding.

Update the comment command to accept comment URLs as positional args
for reply, edit, and delete operations. Update the view command to
accept comment URLs in the --replies flag.

Add DiscussionID field to DiscussionComment for reply flow (fetches
the parent discussion ID from a comment).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ests

Update comment acceptance test to use positional comment IDs instead of
--reply-to, and add URL-based add/edit/delete test cases. Update view
acceptance test to cover --replies with a comment URL.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
…ment argument

Drop the --replies flag from gh discussion view and instead accept a
comment URL or comment node ID (DC_*) as the positional argument. A
discussion number, #number, or discussion URL behaves as before, while a
comment reference shows that comment and its replies.

Rework GetCommentReplies to be node-centric: it resolves the parent
discussion directly from the comment node, so it no longer needs the
discussion number or a repository ownership check. The method now takes a
host string instead of a full repository.

Also add a comment mode for --web that resolves and opens the comment URL,
and add godoc to the DiscussionClient interface methods.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GetComment resolves a comment by its node ID, so it only needs the host
to select the GraphQL endpoint. Change its signature from a full
ghrepo.Interface to a host string and update all callers and tests
accordingly, mirroring GetCommentReplies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…test

Replace --replies usage with a positional comment node ID or URL to match
the redesigned gh discussion view command.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gument

In replies mode the --comments flag is meaningless, so reject the
combination with a flag error instead of silently ignoring it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
…s icon helper

Collapse the three state-specific no-results messages into a single
"no discussions found in <repo>" message, and render the answered
column with the color scheme SuccessIcon helper for consistency with
the view command.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Redesign the human-readable comment and reply output. Comments and
replies are now displayed in chronological order regardless of the
selected sort, reversing newest-first lists for display. Each header
uses a bulleted layout with an abbreviated age, distinguishes between
"commented" and "replied", and tags the newest comment or reply.
Truncation notices now indicate whether older or newer items are
hidden based on the fetch direction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make the non-interactive output of each mode print only what was asked
for. Without --comments, only the discussion metadata and body are
printed. With --comments, only the comments are printed, each as a
metadata block (author, created, url, and answer when applicable)
followed by its body. In replies mode, only the replies are printed in
the same block format, without the parent comment. Comments and replies
follow the same chronological ordering as the interactive output, and
pagination cursors are no longer emitted in non-tty mode.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The discussion URL pattern captures one or more digits, which can exceed
the int32 range. The parse error from strconv.ParseInt was previously
discarded, silently clamping the number. Return an explicit error
instead, and cover it with tests in both URL parsing functions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
…dit mode

Signed-off-by: Babak K. Shandiz <babakks@github.com>
feat: add `discussion` command set
…README note (#13631)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Babak K. Shandiz <babakks@github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Signed-off-by: Babak K. Shandiz <babakks@github.com>
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.37.0 to 0.38.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](golang/text@v0.37.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-version: 0.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Support custom CLAUDE_CONFIG_DIR in install
…/text-0.38.0

chore(deps): bump golang.org/x/text from 0.37.0 to 0.38.0
babakks and others added 30 commits July 27, 2026 12:17
…ctions-d0435be077

chore(deps): bump the codeql-actions group with 3 updates
Bumps [github.com/gabriel-vasile/mimetype](https://github.com/gabriel-vasile/mimetype) from 1.4.14 to 1.4.15.
- [Release notes](https://github.com/gabriel-vasile/mimetype/releases)
- [Commits](gabriel-vasile/mimetype@v1.4.14...v1.4.15)

---
updated-dependencies:
- dependency-name: github.com/gabriel-vasile/mimetype
  dependency-version: 1.4.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.23 to 0.0.24.
- [Commits](mattn/go-isatty@v0.0.23...v0.0.24)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-version: 0.0.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the codeql-actions group with 3 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.37.2 to 4.37.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@e064762...e4fba86)

Updates `github/codeql-action/analyze` from 4.37.2 to 4.37.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@e064762...e4fba86)

Updates `github/codeql-action/upload-sarif` from 4.37.2 to 4.37.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@e064762...e4fba86)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-actions
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.37.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [github/gh-aw-actions/setup](https://github.com/github/gh-aw-actions) from 0.83.1 to 0.83.2.
- [Release notes](https://github.com/github/gh-aw-actions/releases)
- [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md)
- [Commits](github/gh-aw-actions@8bdba80...39143c7)

---
updated-dependencies:
- dependency-name: github/gh-aw-actions/setup
  dependency-version: 0.83.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [github/gh-aw-actions/setup-cli](https://github.com/github/gh-aw-actions) from 0.83.1 to 0.83.2.
- [Release notes](https://github.com/github/gh-aw-actions/releases)
- [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md)
- [Commits](github/gh-aw-actions@8bdba80...39143c7)

---
updated-dependencies:
- dependency-name: github/gh-aw-actions/setup-cli
  dependency-version: 0.83.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](actions/checkout@v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…attn/go-isatty-0.0.24

chore(deps): bump github.com/mattn/go-isatty from 0.0.23 to 0.0.24
…ctions-6947902d1c

chore(deps): bump the codeql-actions group with 3 updates
…abriel-vasile/mimetype-1.4.15

chore(deps): bump github.com/gabriel-vasile/mimetype from 1.4.14 to 1.4.15
…h-aw-actions/setup-0.83.2

chore(deps): bump github/gh-aw-actions/setup from 0.83.1 to 0.83.2
…h-aw-actions/setup-cli-0.83.2

chore(deps): bump github/gh-aw-actions/setup-cli from 0.83.1 to 0.83.2
…checkout-7

chore(deps): bump actions/checkout from 6 to 7
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 93e6d7e7-48ed-4f1c-9ec2-a182d419e40d
Bumps [github/gh-aw-actions/setup](https://github.com/github/gh-aw-actions) from 0.83.2 to 0.83.3.
- [Release notes](https://github.com/github/gh-aw-actions/releases)
- [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md)
- [Commits](github/gh-aw-actions@39143c7...6f8e8ef)

---
updated-dependencies:
- dependency-name: github/gh-aw-actions/setup
  dependency-version: 0.83.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [github/gh-aw-actions/setup-cli](https://github.com/github/gh-aw-actions) from 0.83.2 to 0.83.3.
- [Release notes](https://github.com/github/gh-aw-actions/releases)
- [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md)
- [Commits](github/gh-aw-actions@39143c7...6f8e8ef)

---
updated-dependencies:
- dependency-name: github/gh-aw-actions/setup-cli
  dependency-version: 0.83.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
The old template asked for nothing, so a pull request could satisfy it with
an empty body. It now asks only for things a reviewer cannot get from the
diff: the problem, testing evidence, reviewer guidance, and who is
accountable for answering review.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…h-aw-actions/setup-0.83.3

chore(deps): bump github/gh-aw-actions/setup from 0.83.2 to 0.83.3
…h-aw-actions/setup-cli-0.83.3

chore(deps): bump github/gh-aw-actions/setup-cli from 0.83.2 to 0.83.3
Replace Windsurf with Devin in skill agents
Establish a pull request template for scale
…y-run

Add dependabot-triage agentic workflow
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9bf61d1c-810a-4324-874c-665275a607a9
Bumps [github/gh-aw-actions/setup-cli](https://github.com/github/gh-aw-actions) from 0.83.3 to 0.83.4.
- [Release notes](https://github.com/github/gh-aw-actions/releases)
- [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md)
- [Commits](github/gh-aw-actions@v0.83.3...e89c65e)

---
updated-dependencies:
- dependency-name: github/gh-aw-actions/setup-cli
  dependency-version: 0.83.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…h-aw-actions/setup-cli-0.83.4

chore(deps): bump github/gh-aw-actions/setup-cli from 0.83.3 to 0.83.4
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Babak K. Shandiz <babakks@github.com>
* Add terminal-safety mechanisms for untrusted content

Introduce the building blocks for keeping untrusted external content (HTTP
response bodies and the like) from reaching a terminal as live ANSI escape
sequences, while leaving the application's own styled output untouched.

- iostreams.Untrusted: a value type that wraps external content. The raw bytes
  are unexported; String sanitizes and is called automatically by fmt, so the
  default print path is safe. Raw and RawBytes are the explicit, greppable
  opt-out for non-terminal uses (disk, the API, hashing), and the type carries
  its label across JSON decoding so a decoded field stays marked.
- iostreams.ContentOut and SetContentSanitization: a sink that sanitizes raw
  external streams by default and becomes a passthrough when a command opts out,
  so the sanitization decision is made at the moment of writing.
- A CodeQL query (with help text, examples, and a per-category test suite) that
  flags HTTP response content reaching a terminal writer other than ContentOut
  without sanitization, treating ContentOut, Untrusted.String, the asciisanitizer
  wrap, and structured JSON decoding as the accepted resolutions. Wire it into
  the existing CodeQL workflow.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Migrate gist view to the terminal-safety mechanisms

GetRawGistFile now returns iostreams.Untrusted, so the two callers are forced to
declare intent. gist view carries the value to the render function and lets the
sink decide: the raw dump goes through ContentOut (which honors
--allow-escape-sequences) and the markdown path sanitizes its input with String
before rendering to Out. gist edit takes Raw, because the content is opened in an
editor and sent back to the API and must round-trip verbatim.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Migrate skills to the terminal-safety mechanisms

FetchBlob base64-decodes blob content out of the JSON response, so it now returns
iostreams.Untrusted and its callers split by intent: skills preview sanitizes for
display, while the installer (writes the file to disk) and the frontmatter parsers
take Raw to operate on the verbatim bytes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Migrate agent-task streaming logs to the terminal-safety mechanisms

The agent-task chat completion chunk's Content and ReasoningText fields arrive
over a streaming response, which is not a JSON content type, so the JSON
transport sanitizer does not run on them. Type those fields as
iostreams.Untrusted so provenance survives the per-line json.Unmarshal; printing
them later sanitizes, and presence checks use Empty. This covers a path that
flows resp.Body through json.Unmarshal to a printed field, which value dataflow
cannot track but the type expresses directly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Migrate repo read-file to the terminal-safety mechanisms

read-file already refuses to print terminal escape sequences by default (and
opts in with --allow-escape-sequences), so it guards its own bytes. Route its
two raw writes through ContentOut instead of Out, in passthrough mode, so it
uses the one sink the terminal-safety query recognizes while keeping its
refuse-by-default behavior. Passthrough is required here: sanitizing would
corrupt binary files and strip the escapes that --allow-escape-sequences
explicitly allows.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Migrate pr diff to the terminal-safety mechanisms

pr diff neutralized escape sequences only when stdout was a terminal, leaving
piped and redirected output raw. Neutralize by default in all modes instead: the
plain and name-only paths write through ContentOut, and the colored path wraps
the diff reader in the shared asciisanitizer, since colored output is always
terminal-bound. Add --allow-escape-sequences to opt back into raw bytes for the
non-colored paths, for example when piping a patch to another program. This also
replaces the command's bespoke sanitizer with the shared asciisanitizer.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Migrate api response output to the terminal-safety mechanisms

gh api copied non-JSON response bodies straight to stdout, so escape sequences in
the response reached the terminal unneutralized (JSON bodies are already cleaned
by the transport). Route the raw copy through ContentOut, which neutralizes
escape sequences on an interactive terminal, stays raw when piped so binary
payloads are not corrupted, and honors --allow-escape-sequences. Silent, verbose,
and slurp writers are left in place. The colorized JSON path now sanitizes its
input before jsoncolor adds color, keeping the color intact while removing the
raw bytes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Migrate release download stdout to the terminal-safety mechanisms

release download --output - copied asset bytes straight to stdout, so escape
sequences in an asset reached the terminal unneutralized. Route the stdout write
through ContentOut, which neutralizes escape sequences on an interactive
terminal, stays raw when piped or redirected so binary assets are not corrupted,
and honors --allow-escape-sequences. File downloads are unaffected.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Migrate codespace logs to the terminal-safety mechanisms

gh codespace logs streams a remote log file over ssh by running cat or tail -f,
and the shared ssh helper wires the command's stdout straight to os.Stdout, so
the file's bytes reach the terminal without passing through this process and
cannot be neutralized. Point the logs command's stdout at ContentOut instead: on
a terminal this sanitizes escape sequences and forces the remote output through
the process, while piped output stays raw so a follow stream is not buffered and
saved logs keep their exact bytes. The shared helper is left untouched so the
interactive ssh shell still passes control sequences through.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Build the CodeQL Go database from the shipped build

The Go analysis previously relied on autobuild, which walks the whole tree and
extracts the nested module under .github/codeql/queries' test fixtures as well as
the main module. Both declare the same module path with their own pkg/iostreams,
so the analyzer conflated the two and reported a flow through unrelated code.

Switch the Go job to build-mode: manual and build with make, the same command the
integration tests use. Extraction is then scoped to the packages the released gh
binary compiles, which excludes the separate fixtures module, so the spurious
finding is gone and the analyzed code matches what we ship. The actions job builds
nothing, so it is marked build-mode: none. The query's own test suite is unchanged
and still runs each fixture directory in isolation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Match guarded-content errors by kind in tests

Replace the binary-error boolean with a `wantErrAs` field so the table
matches typed errors with errors.As and sentinels with errors.Is, mirroring
how callers detect them. Any future typed error slots in without a new field.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 53357989-fb84-4835-9eee-258d59e755e5

* Clarify that Untrusted wraps string content

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Convert Untrusted tests to testify assertions

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Collapse guarded content cases into the table test

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Match binary terminal errors with errors.AsType

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Reuse the raw gist content when writing it out

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Cover the non-truncated gist file path

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Document the refusal guarantee on CopyGuardedContent

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Clear stale content when unmarshaling JSON null

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 53357989-fb84-4835-9eee-258d59e755e5
* feat(safeurl): add SafeURL package and CodeQL enforcement query

Introduce internal/safeurl, which builds HTTP request URLs from variable components that are percent-encoded when rendered, so user or server controlled values cannot break the path or change which resource is addressed. It provides the SafeURL interface, the MutableSafeURL and ImmutableSafeURL implementations, the JoinPath and JoinPathWithHostPrefix builders, and NewImmutableSafeURL for entrusting already-formed URLs.

Because percent-encoding leaves a component that is exactly ".." intact as a real path segment, JoinPath and JoinPathWithHostPrefix reject any such component and return an error, which callers bubble up to the command level.

Also add a CodeQL query that flags any HTTP request URL argument that is not literally the result of a safeurl.SafeURL.String call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(api): route REST paths through SafeURL

Build the hand-written REST paths in the shared API layer with safeurl so their variable components are escaped. CreateRepoTransformToV4 now takes a safeurl.SafeURL path instead of a string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(repo): build REST paths with SafeURL

Escape the variable components of the repo command REST paths. The new read-file command builds its Contents API URL with safeurl, and the repo create and edit commands pass a safeurl.SafeURL to CreateRepoTransformToV4.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(run): build REST paths with SafeURL

Escape the variable components of the run command REST paths. GetJobs no longer mutates the Run; it takes an entrusted jobs URL and a run id and returns the jobs, so its callers assign them explicitly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(release): build REST paths with SafeURL

Escape the variable components of the release command REST paths. The upload and delete-asset pipeline threads a safeurl.SafeURL through, so the asset upload URL and AssetForUpload.ExistingURL are carried as SafeURL.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(codespace): build REST paths with SafeURL

Escape the variable components of the codespace command REST paths. The NWO validation helper moves out of safeurl into the codespace package, where it is the only remaining caller.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(attestation): build bundle URLs with SafeURL

getBundle takes a safeurl.SafeURL, and the server-returned bundle URL is entrusted with NewImmutableSafeURL at the API boundary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(gist): build REST paths with SafeURL

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(workflow): build REST paths with SafeURL

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: build REST paths with SafeURL in secret, variable, ssh-key, and gpg-key commands

Escape the variable name components of these commands' REST paths and surface any path-traversal error from the builders.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(skills): build REST paths with SafeURL

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: build remaining REST paths with SafeURL

Escape the variable components of the remaining hand-written REST paths across the pr, ruleset, label, copilot, agent-task, auth, cache, extension, status, and api commands, plus the search, feature detection, and update internals, surfacing any path-traversal error from the builders.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(safeurl): cover dot-segment escaping and preservation

Add regression tests confirming a pre-encoded "%2e%2e" component is
double-encoded rather than treated as traversal, and that single "."
components are preserved verbatim instead of being collapsed. These guard
against re-introducing url.JoinPath/path.Clean and against bypassing the
traversal check via pre-encoding. Covers both JoinPath and
JoinPathWithHostPrefix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f72878e-d567-4a58-937d-f03c94c0287e

* feat(codeql): flag hand-assembled strings reaching NewImmutableSafeURL

Add a query that reports when the argument to safeurl.NewImmutableSafeURL is
tainted by fmt.Sprintf, fmt.Sprint, fmt.Sprintln or a string concatenation.
NewImmutableSafeURL renders its argument verbatim, bypassing the escaping and
traversal check that JoinPath applies, so it must only receive an already
formed, trusted URL. This is a convention guard and cannot verify the
trustedness of URLs read from struct fields or returned by API calls.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f72878e-d567-4a58-937d-f03c94c0287e

* refactor: rename pagination cursor to pageURL to avoid shadowing net/url

The pagination cursor was named url, which shadows the net/url import in
run/shared/shared.go where GetJobs and GetRun still use it. Rename it to
pageURL, which also reads more accurately, and apply the same name to the
other pagination loops so they stay consistent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f72878e-d567-4a58-937d-f03c94c0287e

* refactor(safeurl): reject owner/repo values with extra slashes

RepoPartsFromNWO used strings.Cut, which splits on the first slash and left
any remaining slashes in the name part, so "owner/repo/extra" silently
parsed as name "repo/extra". Switch to strings.Split and require exactly one
slash with a non-empty owner and name, so a value carrying extra slashes
cannot smuggle additional path segments through. Also document why this does
not reuse ghrepo.FromFullName, which accepts the broader "[HOST/]OWNER/REPO"
form.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f72878e-d567-4a58-937d-f03c94c0287e

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Kynan Ware <47394200+BagToad@users.noreply.github.com>
Copilot-Session: 2f72878e-d567-4a58-937d-f03c94c0287e
Signed-off-by: Babak K. Shandiz <babakks@github.com>
* Escape regex metacharacters in attestation SAN matching

Apply regexp.QuoteMeta to user-supplied values interpolated into regex
patterns in expandToGitHubURLRegex and validateSignerWorkflow. Without
escaping, dots in org/repo names act as regex wildcards, allowing
attestation spoofing via lookalike repositories.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review: QuoteMeta entire static string at once

Per review feedback, wrap the full formatted string in QuoteMeta
rather than individual variables, to better indicate the whole piece
is escaped and avoid confusion around trailing/leading chars.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

⤵️ pull merge-conflict Resolve conflicts manually

Projects

None yet

Development

Successfully merging this pull request may close these issues.