Create a GitHub Actions job that reaches http://hello.skimasque.com:8080/
through gateway.skimasque.com:443, then prove that an unlisted destination is
denied. The hello service uses private DNS and is reachable from the managed
gateway; your runner does not need to resolve or reach it directly.
1. Prepare your GitHub repository
Use a repository you can commit to and run Actions in. This guide uses
YOUR_OWNER/YOUR_REPO, the branch main, and the workflow file
hello-skimasque.yml. Substitute your real owner and repository everywhere;
replace main in both the policy and workflow if your default branch differs.
Enable GitHub Actions in the repository's Settings → Actions → General and
allow skimasque-dev/connect if your organisation restricts third-party Actions.
Use the GitHub-hosted ubuntu-24.04 runner for this walkthrough.
No SkiMasque token or GitHub personal access token goes in repository secrets.
The job's id-token: write permission lets the Action request GitHub OIDC.
2. Create an organisation and verify the GitHub owner
Open the SkiMasque console, sign in with GitHub,
and create or select your SkiMasque organisation. Under
identity settings, verify
the GitHub user or organisation that owns YOUR_OWNER/YOUR_REPO, following the
verification flow or installing the GitHub App. Confirm the owner is verified
in the selected SkiMasque organisation. Signing in alone does not associate all
your repositories with an organisation.
Use the shared managed gateway for this test; no gateway VM, private DNS setup
on the runner, or local CLI installation is required. The managed deployment
must have its hello test endpoint enabled. In Gateways, copy the shared
gateway's audience for the selected organisation. It has the form
https://gateway.skimasque.com/o/YOUR_ORG_SLUG; replace YOUR_ORG_SLUG in the
workflow with your SkiMasque organisation slug. This slug can differ from the
GitHub owner. The bare gateway URL is not a tenant audience.
3. Publish the test policy
Open Policies in that organisation
and open the policy editor. Under Add a document, enter hello-ci.toml,
leave the target field empty for the shared gateway, and paste:
name = "hello-ci"
[match]
repository = "YOUR_OWNER/YOUR_REPO"
workflow = "hello-skimasque.yml"
branch = "main"
kind = "ci"
[[rules]]
application = "skimasque-test"
transport = "tcp"
action = "allow"
destinations = ["hello.skimasque.com:8080"]
[[tests]]
application = "skimasque-test"
destination = "hello.skimasque.com:8080"
expect = "allow"
[[tests]]
application = "skimasque-test"
destination = "hello.skimasque.com:8081"
expect = "deny"
Replace YOUR_OWNER/YOUR_REPO, click Save draft, and check that validation
and both policy tests pass. Click Publish revision. Saving a draft alone
does not change gateway authorization. Under Gateways, confirm the shared
gateway is healthy and has acknowledged the published policy revision before
running the job.
New organisations may already have a skimasque-test-OWNER.toml starter policy
allowing skimasque-test to reach the hello service from that verified owner's
repositories. The policy above gives this walkthrough an explicit repository,
workflow and branch scope. If you want only that scope, remove the broader
starter document from the draft before publishing. Preserve unrelated policies;
ensure none allows hello.skimasque.com:8081, which is the denial test below.
4. Create the complete workflow
This repository includes the hello workflow
and its hello CI policy, scoped to
skimasque-dev/skimasque on main, with audience
https://gateway.skimasque.com/o/skimasque-dev. Repository CI validates the policy; publish
it in the console before running the workflow on main. The repository job runs
automatically on every push to main and also supports manual dispatch.
Create .github/workflows/hello-skimasque.yml in your repository and paste.
Replace YOUR_ORG_SLUG with the audience slug copied in step 2:
name: Hello through SkiMasque
on:
push:
branches: [main]
workflow_dispatch:
jobs:
hello:
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
id-token: write
steps:
- name: Connect to the managed gateway
id: network
uses: skimasque-dev/connect@v2
with:
mode: proxy
proxy: gateway.skimasque.com:443
audience: https://gateway.skimasque.com/o/YOUR_ORG_SLUG
application: skimasque-test
version: v0.3.3
- name: Reach the allowed hello service
shell: bash
env:
SKIMASQUE_HTTP_PROXY: ${{ steps.network.outputs.http-proxy }}
run: |
curl --fail-with-body --show-error --silent \
--connect-timeout 10 --max-time 30 \
--proxy "$SKIMASQUE_HTTP_PROXY" --noproxy '' \
http://hello.skimasque.com:8080/ | tee hello-response.txt
grep -Fx 'private endpoint ok' hello-response.txt
- name: Verify an unlisted port is denied
shell: bash
env:
SKIMASQUE_HTTP_PROXY: ${{ steps.network.outputs.http-proxy }}
run: |
status=$(curl --show-error --silent \
--connect-timeout 10 --max-time 30 \
--proxy "$SKIMASQUE_HTTP_PROXY" --noproxy '' \
--output denied-response.txt --write-out '%{http_code}' \
http://hello.skimasque.com:8081/)
test "$status" = 403 || {
echo "Expected gateway policy denial (403), got $status"
cat denied-response.txt
exit 1
}
echo 'Gateway refused the unlisted port (403).'
Commit the file to your default branch (main here). A checkout step is not
needed because the job only uses the Action and curl. mode: proxy is explicit:
the Action defaults to transparent mode, which needs routes and DNS inputs.
The application name must match the policy; it is declared context, not proof
of the executable. The explicit proxy output and --noproxy '' ensure both
requests use the gateway even if the runner has inherited proxy exclusions.
The gateway resolves the hello hostname.
This example pins client v0.3.3, which provides the capabilities required by
connect@v2, and preserves HTTP 403 for gateway policy denials. Client
v0.3.2 supports startup but incorrectly maps those denials to 502. The older v0.3.1 client does not implement capabilities and
cannot run this Action. Action and client versions are independent: @v2
without an explicit version downloads the latest client release. Pin the
Action to a reviewed commit as well for reproducible production workflows.
5. Run the job and check its output
In GitHub, open Actions → Hello through SkiMasque → Run workflow, select
main (or the branch you placed in the policy), and click Run workflow.
Every push to main also starts the job automatically. Open the run and expand
the hello job's steps.
- Connect to the managed gateway must finish successfully: the Action exchanges GitHub OIDC and starts authenticated local proxy listeners.
- Reach the allowed hello service must print
private endpoint ok, followed byhost:,seen client:andpath: /. The client address is the gateway's internal address. The body assertion and curl's HTTP error check must pass. - Verify an unlisted port is denied must print
Gateway refused the unlisted port (403).A timeout, DNS failure, connection refusal or 502 is a failed test, not evidence of policy denial.
The job should be green, including the Action's post-job cleanup. HTTP port 8080 is intentional; changing the hello URL to HTTPS or omitting the port tests a different destination. The runner-to-gateway connection uses encrypted QUIC; the demo service speaks HTTP on the gateway's internal network.
6. Verify the gateway's audit decisions
Open Audit in the same organisation.
Find the entries around the workflow run time and inspect the repository,
workflow hello-skimasque.yml, branch/ref main / refs/heads/main, workload
kind ci, and application skimasque-test. Confirm an allow for
hello.skimasque.com:8080 and a deny for hello.skimasque.com:8081, with
the policy reason. Audit ingestion can take a short time; refresh if necessary.
Together, the expected response, explicit proxy path, 403 assertion and audit decisions verify connectivity and enforcement. Curling the service directly does not test gateway policy.
7. Troubleshoot a failed run
| Symptom | What to check |
|---|---|
| Workflow missing or no Run workflow button | Commit to the default branch, keep workflow_dispatch, and enable Actions. |
| OIDC or credential exchange fails | Keep job-level id-token: write, the organisation-specific audience copied from Gateways, and a verified repository owner in the selected organisation. |
| Action reports unrecognized subcommand capabilities | The downloaded client is too old for the Action. Set version: v0.3.3 or a newer compatible release. |
| Credential exchange returns unknown_org | Copy the selected organisation's audience from Gateways: https://gateway.skimasque.com/o/YOUR_ORG_SLUG. The bare gateway URL cannot identify your organisation. |
| Action cannot connect or times out | Check managed gateway health and UDP 443 reachability; QUIC requires UDP. Inspect the Action's startup diagnostics. |
| Hello request returns 403 | Check the published revision and gateway acknowledgement, actual repository/branch/workflow filename, kind = "ci", application and port. Read the audit denial reason. |
| Hello request returns 502 | Check gateway-side DNS/reachability and that the managed hello endpoint is enabled. Do not add public runner DNS overrides for this private service. |
| Denial test returns something other than 403 | Check audit and other matching policies for a broader allow. A network failure does not prove authorization was denied. |
| Policy changes have no effect | Publish the saved draft, check the selected organisation and gateway revision, then rerun the job to create new tunnels. |
8. Clean up and adapt the job
The Action's post hook stops its client and restores previous proxy variables
on normal job success or failure. Remove hello-ci.toml (and any unused starter
test document) from the console draft and publish the removal when done.
Delete the workflow file if you no longer need the test.
To reach your own service, replace the destination and port in both policy and workflow, set the application context consistently, and use a gateway that can reach that service. The shared gateway does not automatically reach your VPC. See GitHub Actions for proxy and transparent modes and deployment modes for customer gateways.
Run a local command
After publishing a policy for your developer identity:
skimasque exec --app curl -- curl --fail https://api.staging.example.com/health
skimasque exec --forward 15432:db.internal:5432 -- psql -h 127.0.0.1 -p 15432
The second command requires its own matching database rule and a gateway that can
reach the database. Local listeners close when the command exits. For a coding
agent, use exec --agent with --sandbox srt or an explicit --unsandboxed choice;
read agent sessions before granting access.
Next
- GitHub Actions: modes, prerequisites, identity and cleanup.
- Policies: workload kinds, baselines and enforcement limits.
- CLI: exec, tunnels, agent sessions and management commands.
- Gateways: deploy inside your network.
- Self-hosting: operate a licensed or compatible control plane.
- Troubleshooting: diagnose routing and policy decisions.