fix: anchor github-url host to GITHUB_SERVER_URL for GHES support - #40
Draft
phorcys420 wants to merge 1 commit into
Draft
fix: anchor github-url host to GITHUB_SERVER_URL for GHES support#40phorcys420 wants to merge 1 commit into
phorcys420 wants to merge 1 commit into
Conversation
phorcys420
force-pushed
the
phorcys/ghes-github-url
branch
4 times, most recently
from
August 24, 2026 22:40
4c0edbd to
f24ecb5
Compare
The github-url host was hardcoded to github.com, so every issue/PR URL on GitHub Enterprise Server failed validation and the action exited before commenting. Anchor the host to the runner-provided GITHUB_SERVER_URL instead. The anchor stays runner-controlled, not user-controlled, so a workflow that templates user input into github-url still cannot redirect the action to an attacker-chosen host. - parseGithubItemURL/deriveCommentKey take an optional serverURL (default https://github.com); env is read at the action.ts edge. - Build the matcher with RegExp.escape so host metacharacters stay literal, no hand-rolled escaping. - Error message reflects the resolved server URL. Fixes #39
phorcys420
force-pushed
the
phorcys/ghes-github-url
branch
from
August 24, 2026 22:44
f24ecb5 to
90dce8b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
github-urlwas validated against a regex hardcoded togithub.com, so on GitHub Enterprise Server every issue/PR URL (https://github.example.com/owner/repo/pull/1) failed validation and the action exited before commenting.This anchors the host to the runner-provided
GITHUB_SERVER_URLinstead. The anchor stays runner-controlled, never user-controlled, so a workflow that templates user input intogithub-urlstill cannot redirect the action to an attacker-chosen host.Fixes #39.
How
parseGithubItemURL/deriveCommentKeytake an optionalserverURL(defaulthttps://github.com);GITHUB_SERVER_URLis read at theaction.tsedge, matching howGITHUB_WORKFLOWis already handled.RegExp.escape(normalizeBaseUrl(serverURL)), so host metacharacters (the dots ingithub.com) stay literal. No hand-rolled escaping, no URL parsing.parseGithubURLerror message now reflects the resolved server URL.Tests
bun test(208 pass),typecheck,lint,format:checkall clean. New coverage: dotcom still parses, GHES parses whenGITHUB_SERVER_URLmatches, host-mismatch is rejected (the security case), a dot-in-host isn't treated as a regex wildcard, andderiveCommentKeyderivesowner/repo#non GHES.Implementation plan
Fix:
github-urlhost regex hardcoded togithub.com(issue #39)Make
agents-chat-actionwork on GitHub Enterprise Server by anchoring thegithub-urlvalidation to the runner-providedGITHUB_SERVER_URLinstead ofthe literal
github.com, without weakening the security property (the hostanchor stays runner-controlled, never user-controlled).
Repo:
coder/agents-chat-action· Branch:phorcys/ghes-github-urlApproach (decisions locked)
RegExp.escape. No hand-rolled escape helper, no URL parsing. The serverorigin is escaped and interpolated into a single anchored pattern.
RegExp.escapeis Stage 4 and ships in Node 24 (the action runtime).serverURLin as an optional param (defaulthttps://github.com),reading
process.env.GITHUB_SERVER_URLat theaction.tsedge, consistentwith how
GITHUB_WORKFLOWis already read there.deriveCommentKeyis GHES-aware too, so markers resolve toowner/repo#non enterprise hosts instead of falling back to the raw URL.keeps host matching case-sensitive, exactly as the current
github.comliteral does. Scheme + host must equal the server; extra path segments and
non-server hosts are rejected.
Changes
src/comment.tsReplace the module-level
GITHUB_URL_REGEXconstant with a builder that anchorsto the escaped server origin, and thread
serverURLthrough the two consumers.parseGithubItemURL(input, serverURL = DEFAULT_GITHUB_SERVER_URL): bail onempty input, then
githubURLRegex(serverURL).exec(input); groups stayowner=1, repo=2, number=3, returned via tuple destructuring.normalizeBaseUrlstill trims a trailing slash / query / fragment offserverURLbefore it is escaped, so a strayGITHUB_SERVER_URL=https://host/does not double the slash in the pattern.
RegExp.escapeexists in the pinned Bun test runtime. It is guaranteed on Node 24 (action
runtime) but Bun's JSC support is version-dependent. If the pinned Bun lacks
it, fall back to the generic-host capture-and-compare variant (static regex
with
(https:\/\/[^/]+)origin group compared tonormalizeBaseUrl(serverURL)),which needs no escaping and no
RegExp.escape. Decide based on the test run.deriveCommentKey(...): acceptserverURLin its param object and forward itto
parseGithubItemURL.GitHub server (
GITHUB_SERVER_URL) rather thangithub.com, preserving the"user-controlled
github-urlcannot redirect to an attacker host" note.normalizeBaseUrlis already imported; reused for the server origin so atrailing slash / query / fragment on
GITHUB_SERVER_URLis tolerated.src/action.tsprocess.env.GITHUB_SERVER_URL || undefined,used in the three sites that currently hardcode dotcom:
parseGithubURL()→ pass toparseGithubItemURL, and rebuild the errormessage to reference the resolved server URL instead of the hardcoded
https://github.com/...example and the "rejects non-github.com hosts"phrasing.
commentOnIssue()→ passserverURLintoderiveCommentKey.handleFailure()→ passserverURLintoderiveCommentKey.undefinedtriggers the helper default (https://github.com), solocal/non-Actions callers and existing behavior are unchanged.
src/comment.test.tsAdd
parseGithubItemURLcoverage (currently only exercised indirectly viaderiveCommentKey):serverURLarg).https://github.acme.example/owner/repo/pull/1) parses whenserverURLmatches.serverURLis rejected (returnsundefined) — thesecurity-relevant case.
deriveCommentKeycase passing a GHESserverURLyieldsowner/repo#n(not the raw-URL fallback).
The existing
deriveCommentKey"falls back to raw URL for non-github.com host"test still passes: with the default server,
code.acme.comdoesn't match.Docs
No meaningful changes (per scope). The
README.mdgithub-urlrow and theapi_errortroubleshooting line still read fine; leaving them avoids churn onan edge case. (Can revisit if you want a one-line mention.)
Validation
In a workspace (dogfood template,
coderorg):coder/agents-chat-action,bun install.bun test— all pass, new cases included.bun run typecheck.bun run lint(Biome).bun run buildif the builtdist/is committed and expected to stay insync (verify whether the repo commits build output before touching it).
Delivery
phorcys/ghes-github-url, author57866459+phorcys420@users.noreply.github.com.fix(src): anchor github-url host to GITHUB_SERVER_URL for GHES.github-urlhost regex is hardcoded togithub.com, blocking GitHub Enterprise Server #39 with theCoder Agents disclosure and this plan in a collapsible section.
Open / to confirm during implementation
dist/is committed (affects step 5 and the diff size).parseGithubURL().🤖 Opened by Coder Agents on behalf of @phorcys420.