@@ -113,6 +113,24 @@ const createGitHubRequestHeaders = (token: string) => ({
113113 'User-Agent' : 'webstackbuilders-deploy-preview-comment-action' ,
114114} )
115115
116+ const githubApiBaseUrl = ( ( ) => {
117+ const raw = ( process . env [ 'GITHUB_API_URL' ] ?? 'https://api.github.com' ) . trim ( )
118+ const parsed = new URL ( raw )
119+ if ( parsed . protocol !== 'https:' ) {
120+ throw new Error ( `Unsupported GITHUB_API_URL protocol: ${ parsed . protocol } ` )
121+ }
122+ return raw . endsWith ( '/' ) ? raw : `${ raw } /`
123+ } ) ( )
124+
125+ const isAllowedFetchUrl = ( url : string ) : boolean => {
126+ const parsed = new URL ( url )
127+ if ( parsed . protocol !== 'https:' ) {
128+ return false
129+ }
130+ const allowedHosts = new Set ( [ new URL ( githubApiBaseUrl ) . hostname , 'api.vercel.com' ] )
131+ return allowedHosts . has ( parsed . hostname )
132+ }
133+
116134const fetchJson = async < T > (
117135 url : string ,
118136 init : RequestInit ,
@@ -121,6 +139,10 @@ const fetchJson = async <T>(
121139 throw new Error ( 'Fetch API unavailable in this runtime.' )
122140 }
123141
142+ if ( ! isAllowedFetchUrl ( url ) ) {
143+ throw new Error ( `Blocked outbound request to untrusted URL: ${ url } ` )
144+ }
145+
124146 const response = await fetch ( url , init )
125147 if ( ! response . ok ) {
126148 return { ok : false , status : response . status , data : null }
@@ -190,7 +212,10 @@ const findExistingComment = async (params: {
190212 const headers = createGitHubRequestHeaders ( params . token )
191213
192214 for ( let page = 1 ; page <= 50 ; page += 1 ) {
193- const url = `https://api.github.com/repos/${ params . owner } /${ params . repo } /issues/${ params . issueNumber } /comments?per_page=100&page=${ page } `
215+ const url = new URL (
216+ `repos/${ params . owner } /${ params . repo } /issues/${ params . issueNumber } /comments?per_page=100&page=${ page } ` ,
217+ githubApiBaseUrl ,
218+ ) . toString ( )
194219 const { ok, status, data } = await fetchJson < GitHubComment [ ] > ( url , { headers } )
195220 if ( ! ok ) {
196221 warning ( `Unable to list PR comments (status ${ status } ).` )
@@ -223,7 +248,10 @@ const upsertComment = async (params: {
223248 }
224249
225250 if ( params . existingCommentId ) {
226- const url = `https://api.github.com/repos/${ params . owner } /${ params . repo } /issues/comments/${ params . existingCommentId } `
251+ const url = new URL (
252+ `repos/${ params . owner } /${ params . repo } /issues/comments/${ params . existingCommentId } ` ,
253+ githubApiBaseUrl ,
254+ ) . toString ( )
227255 const { ok, status } = await fetchJson < unknown > ( url , {
228256 method : 'PATCH' ,
229257 headers,
@@ -235,7 +263,10 @@ const upsertComment = async (params: {
235263 return
236264 }
237265
238- const url = `https://api.github.com/repos/${ params . owner } /${ params . repo } /issues/${ params . issueNumber } /comments`
266+ const url = new URL (
267+ `repos/${ params . owner } /${ params . repo } /issues/${ params . issueNumber } /comments` ,
268+ githubApiBaseUrl ,
269+ ) . toString ( )
239270 const { ok, status } = await fetchJson < unknown > ( url , {
240271 method : 'POST' ,
241272 headers,
0 commit comments