GitHub Actions Integration with APort
APort verifies repository activity with the same OAP passport and policy verifier used by agent guardrails. The supported GitHub path is the aporthq/policy-verify-action Action, which calls:
POST /api/github/oidc/issueto issue or reuse a hosted GitHub OIDC-bound passport.POST /api/verify/policy/code.repository.merge.v1to evaluate the repository action.
Do not copy older raw curl workflows with broad secrets. The hosted default uses GitHub OIDC and does not require an APort API key in the workflow. For customer-owned audit trails, use a managed hosted passport ID plus an APort API key scoped through GitHub Actions secrets.
Recommended Setup
Run this from the repository root:
npx @aporthq/aport-agent-guardrails github
The initializer writes .github/workflows/aport-guard.yml, refuses to overwrite existing files unless --force is passed, and keeps the workflow free of broad APort API keys.
Manual Workflow Fallback
name: APort Repository Guard
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled, review_requested, review_request_removed]
pull_request_review:
types: [submitted, dismissed]
push:
branches:
- main
merge_group:
permissions:
id-token: write
contents: read
pull-requests: read
jobs:
aport:
name: APort / OAP code.repository.merge.v1
if: >-
github.event_name != 'pull_request_review' ||
github.event.action == 'dismissed' ||
github.event.review.state != 'commented'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: aporthq/policy-verify-action@v1
with:
mode: auto
mode: auto tries hosted verification first. If hosted setup is unavailable and fallback-mode is left at its default, the Action falls back to evidence-only report mode instead of blocking the workflow during the first adoption slice.
For untrusted fork PRs, use evidence-only mode or the future GitHub App path. Do not use pull_request_target as a shortcut around fork restrictions.
The push trigger detects direct pushes after they land. It should be paired with GitHub branch protection or rulesets when a branch must not accept direct pushes.
The job summary is designed to be shareable: it includes Porter, a deterministic APort status, structural findings, signed decision metadata when hosted verification is available, and a copyable README badge. Use the badge only after the workflow is installed and passing:
[](https://github.com/OWNER/REPO/actions/workflows/aport-guard.yml)
For enforcement, pair the badge with a required branch-protection check. A badge alone is visibility; the required check is what stops unsafe merges.
Repository Policy File
APort can read an optional .aport/policy.yaml or .aport/policy.yml from the trusted base branch or pre-push commit for Action-side report evidence.
version: oap-github-policy/1
repository:
protected_paths:
- .github/workflows/**
- package.json
- pnpm-lock.yaml
- functions/api/verify/**
- policies/**
github:
require_pinned_actions: true
For pull_request events, the Action fetches this file from the PR base SHA/ref through the GitHub API. For push events, it fetches policy from the pre-push commit when GitHub provides a usable before SHA. It does not read policy from the PR head or pushed final commit. If the PR changes .aport/policy.yaml or .aport/policy.yml, the check records OAP.GH.POLICY_HEAD_UNTRUSTED and continues with the trusted base policy.
This file currently controls report-only Action evidence such as protected paths and pinned-action warnings. Hosted policy authorization still goes through POST /api/verify/policy/code.repository.merge.v1 with the hosted OAP passport and policy pack.
Protected paths warn by default so new repositories can adopt APort without blocking every workflow, policy, package-manifest, or verifier change on day one. Treat them as review-sensitive evidence first. Repositories that deliberately want every protected-path touch to fail closed can opt in after tuning the path list:
- uses: aporthq/policy-verify-action@v1
with:
mode: hosted
block-protected-paths: true
protected-paths: ".github/workflows/**,.aport/**,functions/api/verify/**,policies/**,package.json"
Managed Hosted Audit
The default free path issues or reuses an APort-owned repository passport through GitHub OIDC. That is useful for quick adoption and aggregate verification counts. Teams that need the decisions in their own APort org should use a managed hosted passport:
- uses: aporthq/policy-verify-action@v1
with:
mode: hosted
agent-id: ${{ vars.APORT_GITHUB_AGENT_ID }}
api-key: ${{ secrets.APORT_API_KEY }}
Use GitHub repository or organization variables for APORT_GITHUB_AGENT_ID; it is an identifier, not a secret. Use GitHub Secrets for APORT_API_KEY; the Action sends it to APort as X-API-Key only on the verify call. Do not put the API key in plain workflow YAML.
The managed passport must be bound to the repository through its GitHub OIDC integration. During POST /api/verify/policy/code.repository.merge.v1, APort verifies both controls before signing and logging the decision:
- the API key has the required scope and can access the passport owner
- the GitHub OIDC token matches the repository binding on the passport
This keeps persistence and authorization tied to the customer's org while still preventing a workflow from reusing another repository's passport ID. GitHub report-only and blocking enforcement do not require a paid plan; Team and Enterprise are for org-owned audit, team controls, and rollout support.
For public repositories that accept fork PRs, use this deliberately: GitHub does not expose normal repository secrets to untrusted fork workflows. When the Action detects an external fork PR, it ignores the managed passport inputs and continues through the no-secret hosted OIDC path instead of failing on a missing secret. Managed hosted audit is best for same-repository branches, private repositories, and protected release workflows where secrets.APORT_API_KEY is available.
Modes
| Mode | Passport source | Hosted decision | Use case |
|---|---|---|---|
auto
|
Hosted OAP passport issued/reused through GitHub OIDC, or managed hosted passport when agent-id and api-key are set
|
Yes, when OIDC is available | Default setup; managed config does not silently fall back |
hosted
|
Hosted OAP passport issued/reused through GitHub OIDC, or managed hosted passport when configured | Yes | Explicit hosted mode; fails if hosted verification cannot return a valid signed decision |
local-json
|
.aport/passport.json or configured path
|
No | Privacy-sensitive local passport evaluation |
evidence-only
|
None | No | Free report-only checks and fork PRs |
Context Mapping
The Action places trusted GitHub identity in OIDC and runner-observed facts in context.evidence.
| GitHub source | APort field | Trust level |
|---|---|---|
OIDC repository
|
repository
|
Trusted |
OIDC repository_id
|
repository_id
|
Trusted, immutable |
OIDC repository_owner_id
|
repository_owner_id
|
Trusted, immutable |
OIDC workflow_ref
|
workflow_ref
|
Trusted |
OIDC job_workflow_ref
|
job_workflow_ref
|
Trusted when present |
| Runner diff |
files_changed, lines_added, lines_removed
|
Evidence only |
| Attribution heuristics |
evidence.actor_class, evidence.confidence
|
Evidence only |
Evidence can downgrade, require review, or explain risk. It must not be the sole basis for an allow decision when trusted OIDC facts deny.
Passport Policy
Hosted GitHub passports are normal OAP passports. They are active, L2, repository-scoped, and bind GitHub OIDC claims under integrations.github.oidc.
{
"capabilities": [
{ "id": "repo.pr.create" },
{ "id": "repo.merge" },
{ "id": "repo.push" }
],
"limits": {
"allowed_repos": ["aporthq/*"],
"allowed_base_branches": ["*"],
"allowed_paths": ["**"]
},
"integrations": {
"github": {
"allowed_repositories": ["aporthq/agent-passport"],
"allowed_repository_ids": ["123456"],
"allowed_repository_owner_ids": ["654321"],
"allowed_workflow_refs": [
"aporthq/agent-passport/.github/workflows/aport-guard.yml@refs/heads/main"
],
"oidc": {
"required": true,
"provider": "github_actions_oidc"
}
}
}
}
The verifier remains POST /api/verify/policy/code.repository.merge.v1; the OIDC issue endpoint only creates or reuses the passport. Free GitHub OIDC passports receive repo.pr.create, repo.merge, and repo.push so PRs, merges, and direct-push detections can all receive signed decisions. A push workflow is still a detection layer after GitHub accepts the push; pair it with GitHub branch protection or rulesets when a branch must not accept direct pushes at all.
Platform Configuration
Hosted free GitHub issuance requires a platform-owned org configured in Cloudflare:
APORT_GITHUB_FREE_OWNER_ID = "ap_org_..."
Optional issuance quotas:
APORT_GITHUB_OIDC_ISSUE_RPM = "30"
APORT_GITHUB_OIDC_ISSUE_REPO_DAILY_LIMIT = "25"
APORT_GITHUB_OIDC_ISSUE_OWNER_DAILY_LIMIT = "250"
If a private or staging APort deployment changes the expected GitHub OIDC audience, set APORT_GITHUB_OIDC_AUDIENCE on the API and pass the same value to the Action with oidc-audience.
These quotas protect hosted passport creation only. They do not change hosted verifier throughput. The current public slice uses Durable Object-backed issuance counters for concurrent quota enforcement, while verification itself stays on the existing low-latency verifier path.
Local JSON Mode
Use local JSON mode when you want to evaluate against a checked-in or generated passport without hosted decision persistence:
- uses: aporthq/policy-verify-action@v1
with:
mode: local-json
passport-path: .aport/passport.json
Local JSON mode still uses the same verifier implementation with body.passport, but hosted logging, telemetry, and replay lookup are skipped. The Action reads the passport from the trusted base or pre-push ref, not from untrusted PR-head checkout content.
Security Notes
- Hosted decisions are signed; the Action verifies the decision signature against
/.well-known/oap/jwks.jsonbefore using the result. - Action-only mode is report-only and is not runner-tamper-resistant.
- Protected branch enforcement should fail closed only after report-only results show acceptable false-deny rates.
- GitHub App mode is the future enforcement tier for server-side facts, fork PRs, and tamper-resistant check runs.
- CI should not use broad APort org API keys for normal GitHub verification.