From 4451bcbc139f8124b0bf04f968ea2586b17df458 Mon Sep 17 00:00:00 2001 From: Parker Brown <17183625+parkerbxyz@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:18:56 -0700 Subject: [PATCH] fix!: require `NODE_USE_ENV_PROXY` for proxy support (#342) BREAKING CHANGE: Custom proxy handling has been removed. If you use HTTP_PROXY or HTTPS_PROXY, you must now also set NODE_USE_ENV_PROXY=1 on the action step. --- .github/workflows/test.yml | 28 +++++++++- README.md | 18 +++++++ lib/request.js | 25 +++++++++ main.js | 50 ++++++++++-------- post.js | 10 +++- tests/index.js | 8 +++ ...main-proxy-requires-native-support.test.js | 14 +++++ ...post-proxy-requires-native-support.test.js | 13 +++++ tests/snapshots/index.js.md | 20 +++++++ tests/snapshots/index.js.snap | Bin 1392 -> 1538 bytes 10 files changed, 161 insertions(+), 25 deletions(-) create mode 100644 tests/main-proxy-requires-native-support.test.js create mode 100644 tests/post-proxy-requires-native-support.test.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8774c1b..2040e99 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - beta pull_request: merge_group: workflow_dispatch: @@ -33,7 +34,7 @@ jobs: name: end-to-end runs-on: ubuntu-latest # do not run from forks, as forks don’t have access to repository secrets - if: github.event_name == 'merge_group' || github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 @@ -53,3 +54,28 @@ jobs: with: route: GET /installation/repositories - run: echo '${{ steps.get-repository.outputs.data }}' + + end-to-end-proxy: + name: End-to-End with unreachable proxy + runs-on: ubuntu-latest + # do not run from forks, as forks don’t have access to repository secrets + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v4 + with: + node-version-file: package.json + cache: 'npm' + - run: npm ci + - run: npm run build + - uses: ./ # Uses the action in the root directory + continue-on-error: true + id: test + env: + NODE_USE_ENV_PROXY: "1" + https_proxy: http://127.0.0.1:9 + with: + app-id: ${{ vars.TEST_APP_ID }} + private-key: ${{ secrets.TEST_APP_PRIVATE_KEY }} + - name: Assert action failed through unreachable proxy + run: test "${{ steps.test.outcome }}" = "failure" diff --git a/README.md b/README.md index 7e13c8d..03659c2 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,24 @@ jobs: GITHUB_TOKEN: ${{ steps.create_token.outputs.token }} ``` +### Proxy support + +This action relies on Node.js native proxy support. + +If you set `HTTP_PROXY` or `HTTPS_PROXY`, also set `NODE_USE_ENV_PROXY: "1"` on the action step so Node.js honors those variables. If you need proxy bypass rules, set `NO_PROXY` alongside them. + +```yaml +- uses: actions/create-github-app-token@v3 + id: app-token + env: + HTTPS_PROXY: http://proxy.example.com:8080 + NO_PROXY: github.example.com + NODE_USE_ENV_PROXY: "1" + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} +``` + ## Inputs ### `app-id` diff --git a/lib/request.js b/lib/request.js index f35d330..5bf924a 100644 --- a/lib/request.js +++ b/lib/request.js @@ -4,6 +4,31 @@ import { request } from "@octokit/request"; // Get the GitHub API URL from the action input and remove any trailing slash const baseUrl = core.getInput("github-api-url").replace(/\/$/, ""); +const proxyEnvironmentKeys = [ + "https_proxy", + "HTTPS_PROXY", + "http_proxy", + "HTTP_PROXY", +]; + +function proxyEnvironmentConfigured() { + return proxyEnvironmentKeys.some((key) => process.env[key]); +} + +function nativeProxySupportEnabled() { + return process.env.NODE_USE_ENV_PROXY === "1"; +} + +export function ensureNativeProxySupport() { + if (!proxyEnvironmentConfigured() || nativeProxySupportEnabled()) { + return; + } + + throw new Error( + "A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.", + ); +} + // Configure the default settings for GitHub API requests export default request.defaults({ headers: { "user-agent": "actions/create-github-app-token" }, diff --git a/main.js b/main.js index 486d67e..d8ebee4 100644 --- a/main.js +++ b/main.js @@ -5,7 +5,7 @@ import { createAppAuth } from "@octokit/auth-app"; import { getPermissionsFromInputs } from "./lib/get-permissions-from-inputs.js"; import { main } from "./lib/main.js"; -import request from "./lib/request.js"; +import request, { ensureNativeProxySupport } from "./lib/request.js"; if (!process.env.GITHUB_REPOSITORY) { throw new Error("GITHUB_REPOSITORY missing, must be set to '/'"); @@ -15,31 +15,37 @@ if (!process.env.GITHUB_REPOSITORY_OWNER) { throw new Error("GITHUB_REPOSITORY_OWNER missing, must be set to ''"); } -const appId = core.getInput("app-id"); -const privateKey = core.getInput("private-key"); -const owner = core.getInput("owner"); -const repositories = core - .getInput("repositories") - .split(/[\n,]+/) - .map((s) => s.trim()) - .filter((x) => x !== ""); +async function run() { + ensureNativeProxySupport(); -const skipTokenRevoke = core.getBooleanInput("skip-token-revoke"); + const appId = core.getInput("app-id"); + const privateKey = core.getInput("private-key"); + const owner = core.getInput("owner"); + const repositories = core + .getInput("repositories") + .split(/[\n,]+/) + .map((s) => s.trim()) + .filter((x) => x !== ""); -const permissions = getPermissionsFromInputs(process.env); + const skipTokenRevoke = core.getBooleanInput("skip-token-revoke"); + + const permissions = getPermissionsFromInputs(process.env); + + return main( + appId, + privateKey, + owner, + repositories, + permissions, + core, + createAppAuth, + request, + skipTokenRevoke, + ); +} // Export promise for testing -export default main( - appId, - privateKey, - owner, - repositories, - permissions, - core, - createAppAuth, - request, - skipTokenRevoke, -).catch((error) => { +export default run().catch((error) => { /* c8 ignore next 3 */ console.error(error); core.setFailed(error.message); diff --git a/post.js b/post.js index feea045..41eb1e5 100644 --- a/post.js +++ b/post.js @@ -3,9 +3,15 @@ import * as core from "@actions/core"; import { post } from "./lib/post.js"; -import request from "./lib/request.js"; +import request, { ensureNativeProxySupport } from "./lib/request.js"; -post(core, request).catch((error) => { +async function run() { + ensureNativeProxySupport(); + + return post(core, request); +} + +run().catch((error) => { /* c8 ignore next 3 */ console.error(error); core.setFailed(error.message); diff --git a/tests/index.js b/tests/index.js index f300270..3e2d19e 100644 --- a/tests/index.js +++ b/tests/index.js @@ -21,6 +21,14 @@ for (const file of testFiles) { const env = { GITHUB_OUTPUT: undefined, GITHUB_STATE: undefined, + HTTP_PROXY: undefined, + HTTPS_PROXY: undefined, + http_proxy: undefined, + https_proxy: undefined, + NO_PROXY: undefined, + no_proxy: undefined, + NODE_OPTIONS: undefined, + NODE_USE_ENV_PROXY: undefined, }; const { stderr, stdout } = await execa("node", [`tests/${file}`], { env }); t.snapshot(stderr, "stderr"); diff --git a/tests/main-proxy-requires-native-support.test.js b/tests/main-proxy-requires-native-support.test.js new file mode 100644 index 0000000..c274764 --- /dev/null +++ b/tests/main-proxy-requires-native-support.test.js @@ -0,0 +1,14 @@ +process.env.GITHUB_REPOSITORY = "actions/create-github-app-token"; +process.env.GITHUB_REPOSITORY_OWNER = "actions"; +process.env.HTTPS_PROXY = "http://127.0.0.1:3128"; + +const originalConsoleError = console.error; +console.error = (...args) => { + originalConsoleError( + ...args.map((arg) => (arg instanceof Error ? arg.message : arg)), + ); +}; + +await import("../main.js"); +await new Promise((resolve) => setImmediate(resolve)); +process.exitCode = 0; diff --git a/tests/post-proxy-requires-native-support.test.js b/tests/post-proxy-requires-native-support.test.js new file mode 100644 index 0000000..cff0433 --- /dev/null +++ b/tests/post-proxy-requires-native-support.test.js @@ -0,0 +1,13 @@ +process.env["INPUT_GITHUB-API-URL"] = "https://api.github.com"; +process.env.HTTPS_PROXY = "http://127.0.0.1:3128"; + +const originalConsoleError = console.error; +console.error = (...args) => { + originalConsoleError( + ...args.map((arg) => (arg instanceof Error ? arg.message : arg)), + ); +}; + +await import("../post.js"); +await new Promise((resolve) => setImmediate(resolve)); +process.exitCode = 0; diff --git a/tests/snapshots/index.js.md b/tests/snapshots/index.js.md index e419536..0bd0f49 100644 --- a/tests/snapshots/index.js.md +++ b/tests/snapshots/index.js.md @@ -82,6 +82,16 @@ Generated by [AVA](https://avajs.dev). POST /app/installations/123456/access_tokens␊ {"repositories":["create-github-app-token"]}` +## main-proxy-requires-native-support.test.js + +> stderr + + 'A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.' + +> stdout + + '::error::A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.' + ## main-repo-skew.test.js > stderr @@ -333,6 +343,16 @@ Generated by [AVA](https://avajs.dev). POST /app/installations/123456/access_tokens␊ {"repositories":["create-github-app-token"],"permissions":{"issues":"write","pull_requests":"read"}}` +## post-proxy-requires-native-support.test.js + +> stderr + + 'A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.' + +> stdout + + '::error::A proxy environment variable is set, but Node.js native proxy support is not enabled. Set NODE_USE_ENV_PROXY=1 for this action step.' + ## post-revoke-token-fail-response.test.js > stderr diff --git a/tests/snapshots/index.js.snap b/tests/snapshots/index.js.snap index e66c3d55e1416e7ac7aff4b1d2b5c4e6ce80213f..8be0324c3c9d31d8582236f4c4558bf9b7dd793b 100644 GIT binary patch literal 1538 zcmV+d2L1U#RzVJ5R20vO5K<5lydgkvWg%r7byBCLU9An#jjZbf?dTuY zZknPS`y{!8WT9$GuO z_nhB3=XZX-*Y=}!%k!O{`0X)BCOx=Ms3V!rO&5X?94f&z84n^UsuF~(ekYjVG!R{1 ziXR@kE|v7_H%f1n)aTrrrT0qU(H3}ZAzcW=*#rDY9(pzUlyTFEg!KDnhskc#GHJlf zDD+P5_h9a)^pLs1+=h;XRQw@_utF$zNhO597fkvg1A$+KKm^DtxfTMIjCY9id%#KC z4~gso{`Wk8sJ>6nv&%$(%4U*2QFGLM7{?Nr+ECfHsq313Dtflv>58Vcd~F4m>euV5 zb!U0isy9|vtOe)N;-$r90w%Qv@n#`AGZ=2uaRvw|ia z5TdE)i}C{_U4O&AV@z>n+y-6y1ub{WAAEnyMf3z zcDJ?CVQM;A|wRW2~D>Ddj{vvCELc|{Lr?~7T|GN2D&P$w5I66aC!y)$wc;d zsj?wN1TLYnLL85>J~>}ucyi%LA1NmrTtWvzr4d+dj9Kdku1O^8mVK?{!KM+n<@0&Q=9Jo$-D&a~FCer9v!imZ$Y`qUtI0T31n3a*EFcCM-gjc&KS}jO zqNcGOL(g!WD4ee9RME#xod?zObsel)TE$nG)NCUeWZ+UmV$%nbSQqTHDk$G{fEJWO z7AElQK04fo=IN+EG9C5!EZpE4kC49a;fgaorwdLGlU4N0>%ViIHW(JcS-W?6WqX7Te~ScaKi>7IpNDcaz*Z-Xg}|7i;-UQwBkCawFd{=j_2h3G>srS@# zGvY#5=ZzF=4f}ziANJyrF$|dm+)4(xplK!#Xw~;<$u%BMd8Gio%HoxR zGXXeNmWnP*2m`EnqE#>GdCY!2^On}9wmqaFN9D2YT`fJzsiV0{;v3&Knfxl7h8Sau zo660BYB_`Id<-QqJ5NN91;JPy#}FsJkC6hFOVsy2%z%1loSz=J$L2(1&Xtpk8TTJO oRUY-GN8)87n)g#^a<1C;Kh9uzH^wsF9B2Ih2kQLks$MJr0OPIuO8@`> literal 1392 zcmV-$1&{hcRzV2cAp#eV`4rF+~)(D+`!8) z#NI{WyB~`P00000000B+THS8rL=5tp)YH=>wIGH); zn={|{jmMK0?Uv(NJ^uSK2qqkON~k56=jt~20a#RktuxmT1uqHUqVywY+@(J6dV>FQ zY}-`O@7|hwXHLH6-=6zm4jkG7r_F^8K`^_47mAatTAwmkw?ZzwzTRP?8@6=nGd&EP ztLHsj_%+#NVX&~RMna0-(1oB#sB4pA0KUhW@B#)LKMH_w5G8Ui04kWKVC<9I{)d9t5bAnH}nE)>>_?wo=~!qrP@`g%+?Eg3_^Ih@ZH$4_~Wj*+MXv3+gyj%{Q}6)2P-~ zzFM8sRO07O*ujMHfTCf_rgQ*01`=>JU}q!1Q=bLEH-u?cE5@o`sq2Q(tX559#WX69 z3c9Y7-NyI(jlJd`!HdFXqe;q0m-3)iR?|{GXBNuIp%)(R?2Y?lo7A_w990EFTEO{{ z>dy<$wB-D2=3{M2D(%VhiH!e9F}}|@M>6S(kW-m{u$koX{QBm0b8G+Z(QX5gZ#Q># z507?!c+l7-akOR9MU;{ZLJs9ajoE06oMSb)$7lsaVA%mkGF0hodJi4H>xSp8dE{u;&WkeRw;qX!^ z_mZ3Pk_;wtEABB+1eY;%-h<(EA^wx1>ECn3ZO%im2^B?RIh6IuVv*s+(y0)sOSWAB z9S9^Dv2@@@M8(BVus;6jQlYLW=Mcz2k|E`UslcVe<8M6+p)*mYA!-gs@`P+Mu@$z+ z#>4F>gj5F~aRL4K8l_>gb{hgrMp`3MtDsCQ&$W4p?1BhJ(SrL^h~p$u%L>3r7Yid; zmKP#yTS}=kQL#U#O5xw;y+E5X2YpAj$~AjCnoX2Et<%f2&8X=_+-Y|K(MqKv=p9*e zNk&~(Uv|c>+{c8FWu+RVc;AV5|1#Ba>6x`Q;m}hYClaSEmCF0LdvKssxUU8)*YL>7 z61}y!k%nuUK+*ex7)xf-6=ZHYzzDSEKrWG+l1PY#&CBs@OpwKl?=K|htNSZ^gi+t(0^wP0DTTAkd@Q^L;=w!o_MNxmG6m|SMJV_b1 z5T57Y9cQvn7pxu@E7#1MzeQTs;`zsSC-Xi{G{%KRlZz!6%l}*~l?#c*%eJr#Q(Ec1 z4qC}&@W#tv3gf?=&G^ujyvsoLcT;5lcm8_!;&EZ~=fXFI}Jn)!2n2wOCJb_P_dDO8IQlsN1%;XUU2V;PPiPCWQe0L$ba2Op