You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix GHES GraphQL path handling and end-to-end query preservation in DIFC proxy when upstream is /api/v3 (#3970)
The DIFC proxy was forwarding GraphQL requests to an invalid GHES
endpoint when the upstream API base was configured as `.../api/v3`,
causing `gh` CLI requests routed through the proxy (e.g. `/api/graphql`)
to fail with 404. This change normalizes GraphQL forwarding for GHES and
ensures GraphQL query strings are preserved end-to-end while keeping
existing REST behavior unchanged.
- **GraphQL forwarding normalization for GHES**
- Updated proxy upstream URL construction so that when `githubAPIURL`
ends with `/api/v3`, GraphQL requests are forwarded to `/api/graphql`
instead of `/api/v3/graphql`.
- GHES rewrite now applies across accepted GraphQL path forms
(`/graphql`, `/api/graphql`, `/api/v3/graphql`).
- Preserves query strings on GraphQL requests.
- **End-to-end query-string preservation**
- Updated GraphQL forwarding call sites to pass the original request
path (including raw query) rather than a hard-coded `"/graphql"` path,
so query parameters are not dropped in production DIFC flow.
- **Targeted coverage for rewritten GraphQL paths**
- Added/updated unit tests to verify GHES rewrite and query preservation
for:
- `/graphql` → `/api/graphql`
- `/graphql?foo=bar` → `/api/graphql?foo=bar`
- `/api/graphql` → `/api/graphql`
- `/api/v3/graphql?foo=bar` → `/api/graphql?foo=bar`
- Added handler tests to verify query-string preservation across GraphQL
inbound path variants.
```go
pathOnly, query, hasQuery := strings.Cut(path, "?")
if strings.HasSuffix(s.githubAPIURL, "/api/v3") && IsGraphQLPath(pathOnly) {
url = strings.TrimSuffix(s.githubAPIURL, "/api/v3") + "/api/graphql"
if hasQuery {
url += "?" + query
}
}
```
> [!WARNING]
>
>
Copy file name to clipboardExpand all lines: docs/PROXY_MODE.md
+25-7Lines changed: 25 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -89,14 +89,19 @@ The proxy reuses the same 6-phase pipeline as the MCP gateway, with Phase 3 adap
89
89
90
90
## REST Route Mapping
91
91
92
-
The proxy maps ~25 GitHub REST API URL patterns to guard tool names:
92
+
The proxy maps REST API URL patterns to guard tool names (see `internal/proxy/router.go` for the exact source of truth). Inbound paths are normalized first:
93
+
94
+
-`GH_HOST` style REST paths with `/api/v3/...` are normalized to `/...` for routing.
95
+
- Query strings are ignored for route matching and still forwarded upstream.
| ... | See `internal/proxy/router.go` for the complete regex list and precedence |
110
119
111
-
Unrecognized URLs pass through without DIFC filtering.
120
+
For **read operations** (GET and GraphQL POST), unmatched routes are denied (fail-closed) to avoid accidental unfiltered data exposure. For **write operations** (non-read methods), requests pass through unchanged.
112
121
113
122
## GraphQL Support
114
123
115
-
GraphQL queries to `/graphql` are parsed to extract the operation type and owner/repo context:
124
+
Inbound GraphQL endpoint paths accepted by the proxy:
125
+
126
+
-`/graphql` (github.com style)
127
+
-`/api/graphql` (GHES style used by `gh` when host is GHES/proxy)
-**Search queries** — mapped to `search_issues` or `search_code`
119
134
-**Viewer queries** — mapped to `get_me`
120
-
-**Unknown queries** — passed through without filtering
135
+
-**Schema introspection (`__schema`, `__type`)** — passed through (safe metadata)
136
+
-**Unknown queries** — denied (fail-closed)
121
137
122
138
Owner and repo are extracted from GraphQL variables (`$owner`, `$name`/`$repo`) or inline string arguments.
123
139
140
+
When the upstream API base is GHES-style `.../api/v3`, GraphQL forwarding is rewritten to `.../api/graphql` to match GHES routing.
141
+
124
142
## Policy Notes
125
143
126
144
-**Repo names must be lowercase** in policies (e.g., `octocat/hello-world` not `octocat/Hello-World`). The guard performs case-insensitive matching against actual GitHub data.
0 commit comments