For an IP-allowlisted API, configure the proxy in the client making that API request, then compare the source IP observed by the API with the allowed exit. A GitHub runner’s default address and your proxied request’s address are separate observations. Test the exact request before running the workload.
Reviewed and locally tested: October 12, 2026. Version and route observations below describe the stated test environment. A working local route does not establish a provider’s public exit, country, anonymity, speed, or uptime.
Choose the egress boundary first
If every connection from a job needs a stable source, compare runner-level options with a selected-request proxy. GitHub documents static IP options for larger runners and recommends larger runners or self-hosted runners for allowlisting rather than treating the broad standard hosted-runner ranges as a narrow rule. Consult GitHub-hosted runners and larger runner networking before choosing an architecture.
This example checks one curl request. It does not route actions, package downloads, checkout, telemetry or every process in the job. A fixed proxy exit must also be suitable for the destination’s policy and API authentication requirements.
Define a trusted observation contract
Use an HTTPS endpoint you control, such as https://api.example/diagnostics/egress, returning {"observed_ip":"YOUR_OBSERVED_SOURCE_IP"}. The server must derive that field from a trustworthy source, not reflect a client-supplied IP header. The example endpoint is a placeholder, not a service you can call. If the API needs an authentication token, add its approved destination authentication separately and test it; this minimal check does not implement that token flow.
Add three repository values
| Name | Storage | Meaning |
|---|---|---|
| CI_PROXY_URL | Actions secret | HTTP proxy endpoint, including encoded credentials if required |
| API_EGRESS_CHECK_URL | Actions variable | Your trusted HTTPS observation endpoint |
| EXPECTED_EGRESS_IP | Actions variable | Exact proxy source IP the API should observe |
Keep the endpoint free of embedded API credentials and query-string secrets. The workflow prints only a sanitized reason and success flag, not the source IP, proxy URL or response body. Secret masking remains a secondary defense; avoid dumping environments or enabling command tracing. Follow GitHub Actions secrets guidance.
Use a manually dispatched route check
Save this workflow as .github/workflows/api-egress-check.yml in your own repository. It has no third-party action or checkout dependency. The Python process sets lowercase proxy variables only for its curl child, removes inherited proxy variables and passes an empty bypass rule. Certificate verification stays enabled; a request error, invalid observation or mismatch exits with failure.
name: Check selected API egress
'on': workflow_dispatch
permissions: {}
jobs:
verify-selected-api-route:
runs-on: ubuntu-latest
timeout-minutes: 2
env:
CI_PROXY_URL: ${{ secrets.CI_PROXY_URL }}
API_EGRESS_CHECK_URL: ${{ vars.API_EGRESS_CHECK_URL }}
EXPECTED_EGRESS_IP: ${{ vars.EXPECTED_EGRESS_IP }}
steps:
- name: Verify the API route without printing credentials
shell: bash
run: |
python3 - <<'PY'
"""Compare a trusted API's observation; never print its IP or proxy credentials."""
import ipaddress, json, os, subprocess
from urllib.parse import urlsplit
def check_route(url, proxy_url, expected_ip, curl='curl'):
endpoint=urlsplit(url);proxy=urlsplit(proxy_url)
is_local_fixture=endpoint.scheme=='http' and endpoint.hostname=='127.0.0.1'
if (endpoint.scheme!='https' and not is_local_fixture) or endpoint.username or endpoint.password or not endpoint.hostname:
return {'ok':False,'reason':'invalid_api_url'}
if proxy.scheme!='http' or not proxy.hostname or any(c in proxy_url for c in ('\n','\r')):
return {'ok':False,'reason':'invalid_proxy_url'}
try:ipaddress.ip_address(expected_ip)
except ValueError:return {'ok':False,'reason':'invalid_expected_ip'}
env={k:v for k,v in os.environ.items() if k.lower() not in ('http_proxy','https_proxy','all_proxy','no_proxy')}
env.update(http_proxy=proxy_url,https_proxy=proxy_url,NO_PROXY='')
result=subprocess.run([curl,'--silent','--show-error','--fail','--noproxy','',
'--connect-timeout','3','--max-time','8',url],
env=env,capture_output=True,text=True,timeout=12)
if result.returncode:return {'ok':False,'reason':'request_failed'}
try:observed=json.loads(result.stdout)['observed_ip']
except (ValueError,KeyError,TypeError):return {'ok':False,'reason':'invalid_observation'}
if observed!=expected_ip:return {'ok':False,'reason':'egress_mismatch'}
return {'ok':True,'reason':'trusted_api_observation_matched'}
if __name__=='__main__':
try:r=check_route(os.environ['API_EGRESS_CHECK_URL'],os.environ['CI_PROXY_URL'],os.environ['EXPECTED_EGRESS_IP'])
except (KeyError,ValueError,subprocess.SubprocessError):r={'ok':False,'reason':'configuration_or_request_failed'}
print(json.dumps(r));raise SystemExit(0 if r['ok'] else 1)
PY
The comparison is intentionally exact. Store the expected address in the same textual representation returned by your endpoint, especially for IPv6. This check assumes a small JSON response from the trusted diagnostic endpoint; it is not a general-purpose downloader or an untrusted-response parser.
What was tested, and what still needs a cloud run
On October 12, 2026, the literal workflow shell block executed inside Ubuntu 24.04 under WSL2 with Python 3.12.3 and curl 8.5.0. The YAML parsed successfully. A synthetic matching observation exited successfully; a deliberately wrong address exited with failure. Windows fixture checks also rejected a denied proxy request. Documentation-only IP fields were used to test the comparison, not to claim a real public exit.
No GitHub-hosted job ran during this test. Repository secrets, cloud network access, real API allowlisting and destination API authentication were not exercised. Your first cloud run must verify those layers, and any later workload must use the same intended proxy configuration. The workflow’s manual trigger, empty permissions and job timeout follow GitHub workflow syntax.
Classify a failed check before widening the allowlist
A request failure can be DNS, proxy authentication, CONNECT refusal, certificate validation or the destination status. An egress mismatch means the endpoint responded but its observation differed. Confirm the chosen client, bypass rules and endpoint contract before widening API access. A passing check is request-specific; it does not prove that another action or library uses the same route. Compare proxy environment behavior and curl setup.
Frequently asked questions
Can a proxy replace the API key?
No. IP allowlisting and destination authentication address different controls. Configure and verify both according to the API’s requirements.
Is a passing local shell check proof the workflow works on GitHub?
No. It verifies the script’s local request and failure behavior. The hosted runner, repository configuration, destination policy and real exit need a separate cloud execution.
Verify one client before increasing workload
Return to Proxy Setup and Developer Guides for adjacent clients. Use the proxy verification checklist to record a direct baseline and the destination’s observation. When that is repeatable, compare Mexela proxy locations against the actual destination and location requirements.
Download the developer examples
Download the developer examples ZIP for the locally tested Python clients, environment checks, CI route-check files and native PHP cURL helper. The n8n workflow is a separate illustrative reference: it was not imported or executed in n8n. Read the included README for runtime versions, setup and the limits of each check.
ZIP SHA-256: 3a43f7d1e6cbb9b2917dbcf88aaa8fe1a2733c33326187cb89c9a9119aad3e20. Keep real proxy credentials and destination secrets in your approved secret store; they are absent from this archive. Start with one approved destination and compare its observation with a direct baseline.
