Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b97ed4e
docs: add enhancement roadmap
hsborges Aug 29, 2026
83bf2eb
fix(security): redact invalid token errors
hsborges Aug 29, 2026
d1405a2
fix(auth): reject partial credentials
hsborges Aug 29, 2026
6633888
feat(proxy): harden routing and packaging
hsborges Aug 29, 2026
e35ee73
fix(lifecycle): close router resources
hsborges Aug 29, 2026
6d7815f
fix(config): validate runtime options
hsborges Aug 29, 2026
b3a3f70
fix(rate-limit): harden refresh lifecycle
hsborges Aug 29, 2026
8cc0623
fix(proxy): make cancellation state-aware
hsborges Aug 29, 2026
7646ea5
fix(monitoring): isolate public health endpoint
hsborges Aug 29, 2026
767cbdf
fix(proxy): enforce HTTP forwarding semantics
hsborges Aug 29, 2026
6882049
fix(proxy): bound request lifetime and queueing
hsborges Aug 29, 2026
ddc740c
perf(router): use event-driven dispatch
hsborges Aug 29, 2026
ca37d3f
docs: align developer guidance
hsborges Aug 29, 2026
b480eab
chore(deps): update safe dependencies
hsborges Aug 29, 2026
d535988
chore: ignore deepwork state
hsborges Aug 29, 2026
3e2eddb
chore(deps): upgrade supertest types
hsborges Aug 29, 2026
204192f
test: migrate proxy mocks to undici
hsborges Aug 29, 2026
5166674
chore(deps): upgrade node types
hsborges Aug 29, 2026
41d820f
chore(deps): upgrade chalk
hsborges Aug 29, 2026
3cd7fa1
chore(deps): upgrade commander
hsborges Aug 29, 2026
96d5e13
chore(deps): upgrade basic auth
hsborges Aug 29, 2026
28d104e
chore(deps): upgrade p-queue
hsborges Aug 29, 2026
37c3010
chore(deps): upgrade undici
hsborges Aug 29, 2026
3aaf531
chore(deps): upgrade np
hsborges Aug 29, 2026
084e543
chore(deps): upgrade typescript
hsborges Aug 29, 2026
c378ccc
docs: remove enhancement dossier
hsborges Aug 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@
dist
node_modules
samples
yarn.lock
13 changes: 10 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ on:
paths:
- "src/**"
- "package.json"
- "yarn.lock"
- "Dockerfile"
- "biome.json"
- "tsconfig.json"
- "vitest.config.ts"
Expand All @@ -15,6 +17,8 @@ on:
paths:
- "src/**"
- "package.json"
- "yarn.lock"
- "Dockerfile"
- "biome.json"
- "tsconfig.json"
- "vitest.config.ts"
Expand All @@ -34,7 +38,8 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
cache: "yarn"
- run: yarn
- run: corepack enable
- run: yarn install --frozen-lockfile
- run: yarn lint

build:
Expand All @@ -50,8 +55,9 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
cache: "yarn"
- run: corepack enable
- name: Install dependencies
run: yarn
run: yarn install --frozen-lockfile
- name: Build source code
run: yarn build

Expand All @@ -68,7 +74,8 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
cache: "yarn"
- run: yarn
- run: corepack enable
- run: yarn install --frozen-lockfile
- run: yarn test:coverage
- name: Coveralls
uses: coverallsapp/github-action@v2
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,5 @@ typings/
.AppleDouble
.LSOverride
bin/
dist/
dist/
.slim/deepwork/
4 changes: 2 additions & 2 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
npm run lint
npm run build
yarn lint
yarn build
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ npm run format # Format code with Biome
```

### Git Hooks
Pre-commit: Runs `npm run lint` and `npm run build`
Pre-commit: Runs `yarn lint` and `yarn build`
Commit-msg: Validates commit messages using commitlint (conventional commits)

### Release
Expand Down Expand Up @@ -183,7 +183,9 @@ try {
### Configuration
- Support CLI flags and environment variables
- Use `commander` for CLI parsing with `.env()` for env var binding
- Validate required options (e.g., tokens must be 40 characters)
- Validate supported GitHub credential formats: legacy 40-character credentials, `ghp_`, `gho_`,
`ghu_`, `ghs_`, and `ghr_` credentials with 36-character suffixes, or `github_pat_` credentials
with an 82-character suffix

## Common Gotchas

Expand Down
10 changes: 5 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# ---- Base Node ----
FROM node:24 AS base
WORKDIR /app
COPY package*.json ./
COPY package.json yarn.lock ./

# ---- Dependencies ----
FROM base AS dependencies
RUN npm install --force
RUN corepack enable && yarn install --frozen-lockfile --ignore-scripts

# ---- Build ----
FROM dependencies AS build
Expand All @@ -21,8 +21,8 @@ WORKDIR /app
RUN apk add --no-cache curl tini

# Install app dependencies
COPY --from=dependencies /app/package*.json ./
RUN npm ci --omit=dev --ignore-scripts --force
COPY --from=dependencies /app/package.json /app/yarn.lock ./
RUN corepack enable && yarn install --frozen-lockfile --production=true --ignore-scripts

# Bundle app source
COPY --from=build /app/dist ./dist
Expand All @@ -33,4 +33,4 @@ EXPOSE ${PORT:-3000}
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:${PORT:-3000}/status || exit 1

ENTRYPOINT ["/sbin/tini", "--", "node", "dist/cli.js"]
ENTRYPOINT ["/sbin/tini", "--", "node", "dist/cli.js"]
106 changes: 77 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
# 🖥️ GitHub Proxy Server

[![Build + S3 sync](https://github.com/gittrends-app/github-proxy-server/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/gittrends-app/github-proxy-server/actions/workflows/build.yml)
[![CI](https://github.com/gittrends-app/github-proxy-server/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/gittrends-app/github-proxy-server/actions/workflows/ci.yml)
[![Coverage Status](https://coveralls.io/repos/github/gittrends-app/github-proxy-server/badge.svg)](https://coveralls.io/github/gittrends-app/github-proxy-server)
[![GitHub version](https://badge.fury.io/gh/gittrends-app%2Fgithub-proxy-server.svg)](https://badge.fury.io/gh/gittrends-app%2Fgithub-proxy-server)
![GitHub](https://img.shields.io/github/license/gittrends-app/github-proxy-server)

<br/>

> GitHub Proxy Server is a tool to support developers and researchers collect massive amount of data from GitHub API (REST or GraphQL) by automatically managing access tokens and client requests to avoid triggering the GitHub API abuse detection mechanisms.
> GitHub Proxy Server is a tool to support developers and researchers collecting massive amounts of data from the GitHub API (REST or GraphQL) by automatically managing access tokens and client requests to avoid triggering GitHub API abuse-detection mechanisms.

<br/>

**Why should I use it?** GitHub API has a limited number of requests per client and implements several mechanisms to detect user abuses. Thus, users must handle these restrictions in their applications. GitHub Proxy Server is a tool that abstracts these problems by means of a proxy server.
**Why should I use it?** The GitHub API has a limited number of requests per client and implements several mechanisms to detect abuse. Thus, users must handle these restrictions in their applications. GitHub Proxy Server is a tool that abstracts these problems by means of a proxy server.

**When should I use it?** This tool is intended to be used by developers and researches that need to perform massive data collection of public repositories using both REST and GraphQL APIs.
**When should I use it?** This tool is intended to be used by developers and researchers who need to perform massive data collection from public repositories using both REST and GraphQL APIs.

**When should I <ins>not</ins> use it?** If you need to deal with private information of users and repositories this tool is not for you (see [limitations section](#limitations)).

**Can I use it with other libs?** Yes, as long they allow the users setup the proxy server as base url (see [samples](samples)).
**Can I use it with other libs?** Yes, as long as they allow users to set up the proxy server as a base URL (see [samples](samples)).

**How it works?**

Expand All @@ -28,7 +28,7 @@

## Features

- Support to multiple access tokens
- Support for multiple access tokens

- Load balancing

Expand All @@ -46,10 +46,10 @@ First, you need to clone the repository:
git clone https://github.com/gittrends-app/github-proxy-server.git
```

Then, install dependencies, build files, and run the server:
Node.js >=24 and Yarn 1.22.22 are required. Then, install dependencies, build files, and run the server:

```bash
yarn install
yarn install --frozen-lockfile
yarn build
yarn start --help
```
Expand All @@ -69,7 +69,7 @@ To use this tool you need to provide at least one GitHub access token:
github-proxy-server -p 3000 -t <access_token>
```

Or provide a file with several access token (one per line):
Or provide a file with several access tokens (one per line):

```bash
github-proxy-server -p 3000 --tokens <tokens.txt>
Expand Down Expand Up @@ -101,34 +101,66 @@ Then make authenticated requests:
curl -s -u myuser:mypass http://localhost:3000/users/gittrends-app 2>&1
```

**Note:** The `/status` monitoring endpoint is excluded from authentication to allow health checks.

To more usage information, use the option `--help`.
**Note:** `/status` and `/status/` are small public health endpoints and return `{"status":"ok"}`.
Unknown `/status/*` paths return `404` and never fall through to the proxy. Detailed swagger-stats
monitoring, when enabled, is isolated under `/metrics` (`/metrics/stats` and `/metrics/metrics`)
and is protected by Basic Authentication whenever proxy authentication is configured. Monitoring
paths return `404` when disabled; similarly prefixed routes such as `/status-other` still require
authentication.

When the proxy is deployed behind a trusted public URL, set `--external-base-url` or
`GPS_EXTERNAL_BASE_URL` to an absolute `http://` or `https://` URL. Redirect and `Link` headers are
rewritten to that base; if it is omitted, upstream links are preserved unchanged and the untrusted
inbound `Host` header is never used for external URLs.

Request bodies are limited to 1 MiB by default and can be configured with
`--max-request-body-bytes` or `GPS_MAX_REQUEST_BODY_BYTES` (1–16 MiB). Each live worker contributes
50 queue slots by default; configure this with `--max-queue-depth` or `GPS_MAX_QUEUE_DEPTH`.
Queued requests expire after 30 seconds by default (`--queue-wait-timeout` or
`GPS_QUEUE_WAIT_TIMEOUT`), and the total request lifetime is limited to 120 seconds by default
(`--request-lifetime-timeout` or `GPS_REQUEST_LIFETIME_TIMEOUT`). Body overflow returns `413`, a
full queue returns `503` with `Retry-After: 1`, and queue/lifetime expiry returns `504`.

### Deployment and TLS

The server listens for plain HTTP and binds to all interfaces when started by the CLI. Do not expose
that listener directly to an untrusted network: Basic Authentication credentials and proxied traffic
are not encrypted by this process. Put the server behind a trusted HTTPS/TLS termination boundary
when credentials or traffic cross an untrusted network. A local or private-network Docker health
check may continue to use `http://localhost:3000/status`; external health checks should use the
trusted HTTPS endpoint.

For more information, use the option `--help`.

```bash
Usage: index [options]
Usage: cli [options]

Options:
-p, --port [port] Port to start the proxy server (default: 3000, env: PORT)
-t, --token [token] GitHub token to be used (default: [])
--tokens [file] File containing a list of tokens (env: GPS_TOKENS_FILE)
--request-timeout [timeout] Request timeout (ms) (default: 30000, env: GPS_REQUEST_TIMEOUT)
--min-remaining <number> Stop using token on a minimum of (default: 100, env: GPS_MIN_REMAINING)
--time-budget-multiplier [multiplier] Time budget multiplier (>= 1.0) (default: 1, env: GPS_TIME_BUDGET_MULTIPLIER)
--silent Dont show requests outputs (env: GPS_SILENT)
--no-override-authorization By default, the authorization header is overrided with a configured token
--auth-username [username] Proxy authentication username (env: GPS_AUTH_USERNAME)
--auth-password [password] Proxy authentication password (env: GPS_AUTH_PASSWORD)
--no-status-monitor Disable requests monitoring on /status
-v, --version output the current version
-h, --help display help for command
-p, --port [port] Port to start the proxy server (default: 3000, env: PORT)
-t, --token [token] GitHub token to be used (default: [])
--tokens [file] File containing a list of tokens (env: GPS_TOKENS_FILE)
--request-timeout [timeout] Request timeout (ms) (default: 30000, env: GPS_REQUEST_TIMEOUT)
--min-remaining <number> Stop using token on a minimum of (default: 100, env: GPS_MIN_REMAINING)
--max-request-body-bytes [bytes] Maximum request body size (bytes) (default: 1048576, env: GPS_MAX_REQUEST_BODY_BYTES)
--max-queue-depth [depth] Maximum queued requests per worker (default: 50, env: GPS_MAX_QUEUE_DEPTH)
--queue-wait-timeout [timeout] Maximum queue wait (ms) (default: 30000, env: GPS_QUEUE_WAIT_TIMEOUT)
--request-lifetime-timeout [timeout] Maximum request lifetime (ms) (default: 120000, env: GPS_REQUEST_LIFETIME_TIMEOUT)
--time-budget-multiplier [multiplier] Time budget multiplier (>= 1.0) (default: 1, env: GPS_TIME_BUDGET_MULTIPLIER)
--external-base-url <url> Trusted external HTTP(S) base URL (env: GPS_EXTERNAL_BASE_URL)
--silent Don't show request output (env: GPS_SILENT)
--no-override-authorization By default, the authorization header is overridden with a configured token
--auth-username [username] Proxy authentication username (env: GPS_AUTH_USERNAME)
--auth-password [password] Proxy authentication password (env: GPS_AUTH_PASSWORD)
--no-status-monitor Disable requests monitoring on /metrics
-v, --version output the current version
-h, --help display help for command
```

## Limitations

GitHub Proxy Server was primarly intended to be a tool to support massive data collection of public repositories and users. To this purpose, we use a pool of access tokens to proxy requests to GitHub servers. For each request, we select the token with the lowest queue size and with more requests available.
GitHub Proxy Server was primarily intended to support massive data collection from public repositories and users. For this purpose, we use a pool of access tokens to proxy requests to GitHub servers. Requests are routed to a per-resource FIFO queue, then an event-driven dispatcher assigns them in round-robin order among eligible workers for that resource.

Besides that, **we do not perform any verification on the clients requests, which may implies in security issues for the users who provided their tokens**.
Besides that, **we do not perform any verification on clients' requests, which may imply security issues for users who provided their tokens**.

To mitigate this problem, you can:

Expand All @@ -138,12 +170,28 @@ To mitigate this problem, you can:

## Integrations

As mentioned, this tool can be used with serveral other libraries. You can find several examples in [samples](samples) folder.
As mentioned, this tool can be used with several other libraries. You can find several examples in the [samples](samples) folder.

## Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

### Development checks

Use Yarn 1.22.22 with Node.js >=24:

```bash
yarn install --frozen-lockfile
yarn test
yarn lint
npx tsc --noEmit
yarn build
```

The `.husky/pre-commit` hook runs `yarn lint` and `yarn build` automatically. Biome intentionally
enables only the current limited `noConsole` and `noExplicitAny` checks; other recommended rules
are not enabled by this project.

## License

[MIT](https://choosealicense.com/licenses/mit/)
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down
52 changes: 26 additions & 26 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"engines": {
"node": ">=24"
},
"packageManager": "yarn@1.22.22",
"type": "module",
"repository": "git@github.com:gittrends-app/github-proxy-server.git",
"author": "Hudson Silva Borges <hudsonsilbor@gmail.com>",
Expand All @@ -23,7 +24,7 @@
"build": "shx rm -rf dist && tsup-node src/cli.ts --format esm --sourcemap --minify",
"prepare": "husky",
"release": "standard-version",
"np": "np --no-publish --yarn --contents dist"
"np": "np --no-publish --contents dist"
},
"bin": {
"github-proxy-server": "./dist/cli.js"
Expand All @@ -36,54 +37,53 @@
"architecture.png"
],
"dependencies": {
"basic-auth": "^2.0.1",
"chalk": "5.6.2",
"commander": "^14.0.2",
"basic-auth": "^3.0.0",
"chalk": "6.0.0",
"commander": "^15.0.0",
"compression": "^1.8.1",
"consola": "^3.4.2",
"dayjs": "^1.11.19",
"dotenv-override-true": "^6.2.2",
"dayjs": "^1.11.23",
"express": "^5.2.1",
"http-status-codes": "^2.3.0",
"https-proxy-agent": "^7.0.6",
"ip": "^2.0.1",
"lodash": "^4.17.21",
"p-limit": "^7.2.0",
"p-queue": "^8.0.1",
"pino": "^10.2.0",
"lodash": "^4.18.1",
"p-limit": "^7.3.1",
"p-queue": "^9.3.3",
"pino": "^10.3.1",
"pino-http": "^11.0.0",
"pino-pretty": "^13.1.3",
"prom-client": "^14.2.0",
"swagger-stats": "^0.99.7",
"table": "^6.9.0",
"undici": "^7.18.2"
"undici": "^8.10.0"
},
"devDependencies": {
"@biomejs/biome": "^2.3.11",
"@commitlint/cli": "^20.3.1",
"@commitlint/config-conventional": "^20.3.1",
"@tsconfig/node20": "^20.1.8",
"@biomejs/biome": "^2.5.11",
"@commitlint/cli": "^20.5.3",
"@commitlint/config-conventional": "^20.5.3",
"@tsconfig/node20": "^20.1.10",
"@types/async": "^3.2.25",
"@types/basic-auth": "^1.1.8",
"@types/compression": "^1.8.1",
"@types/ip": "^1.1.3",
"@types/lodash": "^4.17.23",
"@types/node": "^25.0.9",
"@types/supertest": "^6.0.3",
"@types/lodash": "^4.17.25",
"@types/node": "^26.4.0",
"@types/supertest": "^7.2.1",
"@types/swagger-stats": "^0.95.11",
"@vitest/coverage-v8": "^4.0.17",
"commitizen": "^4.3.1",
"@vitest/coverage-v8": "^4.1.11",
"commitizen": "^4.3.2",
"cz-conventional-changelog": "3.3.0",
"husky": "^9.1.7",
"nock": "^14.0.10",
"np": "^10.2.0",
"nock": "^14.0.17",
"np": "^12.0.1",
"shx": "^0.4.0",
"standard-version": "^9.5.0",
"supertest": "^7.2.2",
"tmp-promise": "^3.0.3",
"tsup": "^8.5.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"vitest": "^4.0.17"
"tsx": "^4.23.12",
"typescript": "7.0.2",
"vitest": "^4.1.11"
},
"config": {
"commitizen": {
Expand Down
Loading
Loading