Status: Internal architecture + API research (not a product claim or Marketplace listing)
Date: 2026-09
Primary sources: docs.github.com (Apps, Webhooks, Checks, Marketplace)
Stance: Static / symbolic review only. No active production attacks. Minimize permissions, retention, and trust surface.
Define how AXGuard presents as a GitHub App that reviews pull requests and pre-ship signals, without embedding GitHub SDK details into AXGuard Core.
Goals:
| Goal | Implication |
|---|---|
| Pre-merge security signal | Check runs + annotations + one updatable PR summary comment |
| Least privilege | Only the permissions required to read code, read/write PRs, write checks |
| Ephemeral auth | JWT → short-lived installation tokens; never store tokens permanently |
| Privacy-minimizing | Minimize source retention; no secret logging; AI only via user-configured providers |
| Deployable two ways | Hosted App (webhooks) or self-hosted / Actions-triggered adapter |
This document does not claim GitHub Marketplace approval, listing readiness, or product launch status.
┌─────────────────────────────────────────────────────────────┐
│ GitHub platform │
│ Webhooks · REST API · Checks UI · PR conversation │
└───────────────────────────┬─────────────────────────────────┘
│ HTTPS (webhooks + API)
▼
┌─────────────────────────────────────────────────────────────┐
│ AXGuard GitHub Adapter │
│ • Signature verify (X-Hub-Signature-256) │
│ • Replay guard (X-GitHub-Delivery) │
│ • JWT + installation token mint (≤1h) │
│ • Event → job queue │
│ • Checkout / sparse fetch of PR head │
│ • Map findings → check runs, annotations, PR comment │
│ • Mode switch: REVIEW | PRE-SHIP | WATCH │
└───────────────────────────┬─────────────────────────────────┘
│ internal API / CLI / library call
▼
┌─────────────────────────────────────────────────────────────┐
│ AXGuard Core │
│ scan · audit · surface · flow · verify · adversary · │
│ evidence · attack graph · report (deterministic first) │
└─────────────────────────────────────────────────────────────┘
| Layer | Owns | Must not own |
|---|---|---|
| GitHub App registration | App ID / client ID, private key, webhook secret, permission set, event subscriptions | Business logic, rule packs, Judge/Adversary |
| GitHub Adapter | Auth, webhooks, idempotency, GitHub API I/O, finding → Checks/comments mapping, mode policy | Deep analysis algorithms |
| AXGuard Core | Scanning, verification, evidence, reports | GitHub HTTP, JWT minting, webhook crypto |
- Core stays runnable as CLI (
axguard audit .) without GitHub. - Adapter can be swapped (GitHub App vs Actions job vs self-hosted webhook receiver).
- Security review of GitHub credentials is isolated to the adapter process.
GitHub Apps start with no permissions; select the minimum required.
Official guidance:
| Permission | Access | Why AXGuard needs it | Why not higher / extra |
|---|---|---|---|
| Metadata | read |
Mandatory baseline for App installation context (repo identity, basic metadata). GitHub Apps effectively always carry metadata read for installation/API discovery. | Do not request write. |
| Contents | read |
Fetch PR head / compare commits / read file blobs for analysis (HTTP Git or Contents API). Required for non-public and private repos the App is installed on. | Do not request write (no commits, no force-pushes, no branch deletion). |
| Pull requests | read + write |
Read: PR metadata, files changed, base/head SHAs, diff context. Write: create/update the single summary comment (and related PR comment surfaces documented under pull-request access). | Prefer PR comments over “Issues” unless a future feature truly needs Issues; avoid admin. |
| Checks | write |
Create/update check runs and annotations via the Checks API. Write for Checks is GitHub-App-only (OAuth apps / users cannot create check runs). | read alone cannot post CI-style results. |
Sources:
| Permission | Reason to omit |
|---|---|
Contents write |
No auto-commit / auto-fix unless a separate, opt-in product mode is designed later |
| Administration | High trust; not needed for PR review |
| Secrets / Actions secrets | Never read customer CI secrets |
| Workflows | Not required to run analysis; Actions is an alternate delivery channel |
| Security events / Dependabot / Code scanning write | Avoid overlapping GitHub-native security products unless integrating deliberately |
| Members / Organization admin | Identity expansion without need |
Any permission increase requires installers to accept the new permissions on existing installations. Keep the first listing minimal so upgrades are rare and explainable.
Configure webhooks on the App registration; subscribe only to events the chosen permissions allow.
Official guidance:
| Event | Actions to handle | Adapter behavior |
|---|---|---|
pull_request |
opened, synchronize, reopened |
Enqueue REVIEW (or PRE-SHIP if label/config says so). Use pull_request.head.sha as check run head_sha. Debounce rapid synchronize bursts. |
pull_request action types include (among others): opened, synchronize, reopened, closed, edited, ready_for_review, … — see pull_request webhook.
| Event | Use | Notes |
|---|---|---|
pull_request → closed |
Cancel queued jobs; mark WATCH state idle | Do not scan merged code unless PRE-SHIP / release mode explicitly requests post-merge |
check_run |
Handle rerequested / requested_action for “Re-run” |
Aligns with Checks UX in the CI-checks tutorial |
installation |
Create/delete tenant records; revoke local config | Fired on install/uninstall |
installation_repositories |
Add/remove repos from tenant scope | Partial installs |
push |
Optional WATCH / default-branch policy scans | High volume — prefer PR-only unless justified |
release |
Optional PRE-SHIP gate on published releases | Tag/release artifact review, not exploit |
- Respond 2xx within 10 seconds; process asynchronously via a queue (best practices).
- Payload size cap 25 MB; oversized events may not be delivered (webhook events overview).
- Prefer HTTPS with SSL verification left enabled.
- Optionally allowlist GitHub webhook source IPs from
GET /meta(hooksranges).
| Header | Purpose |
|---|---|
X-GitHub-Event |
Event name |
X-GitHub-Delivery |
Unique delivery id (idempotency / replay) |
X-GitHub-Hook-Installation-Target-ID / related |
Installation routing (when present) |
X-Hub-Signature-256 |
HMAC integrity |
App private key (PEM)
│
▼
JWT (RS256, exp ≤ 10 min) ← authenticate *as the App*
│
│ POST /app/installations/{id}/access_tokens
▼
Installation access token (≈ 1 hour) ← authenticate *as the installation*
│
▼
REST calls (Contents, PRs, Checks, …)
Official docs:
- Generating a JWT for a GitHub App
- Generating an installation access token
- Authenticating as a GitHub App installation
| Claim | Rule |
|---|---|
iat |
Issued-at; GitHub recommends skew buffer (e.g. 60s in the past) for clock drift |
exp |
No more than 10 minutes after issue |
iss |
App client ID (recommended) or application ID |
alg |
RS256 only |
| Header | Authorization: Bearer <JWT> (Bearer required for JWTs) |
- Expire after approximately one hour.
- May optionally be scoped down further with
repositories/repository_idsand a reducedpermissionsobject on mint (cannot exceed App grant). - Never store permanently. Keep in memory / short TTL cache only; mint per job or refresh near expiry.
- Prefer Octokit / official SDKs for refresh if used; still treat tokens as secrets.
GitHub has documented a staged rollout (from 2026-04-27) of a stateless installation token format (ghs_APPID_JWT-style). Apps that assume a fixed 40-character token length may break. Validate against current docs and the temporary opt-in header described in GitHub’s App token documentation / blog when testing.
| Secret | Storage | Rotation |
|---|---|---|
| App private key PEM | KMS / sealed secret; never in git | Rotate via GitHub App settings; dual-key window if possible |
| Webhook secret | Same | Rotate; dual-verify briefly if needed |
| Installation tokens | Memory only | Auto-expire ≤1h |
| Customer AI provider keys | Customer-controlled; never logged | Customer rotation |
Official: Validating webhook deliveries
- Configure a high-entropy webhook secret on the App.
- On each delivery, compute
HMAC-SHA256(secret, raw_body). - Expect header form:
sha256=<hex>. - Compare with constant-time equality (
hmac.compare_digest/crypto.timingSafeEqual). Never use==. - Verify against the raw body bytes (UTF-8); do not re-serialize JSON before verify.
- Prefer
X-Hub-Signature-256over legacyX-Hub-Signature(SHA-1).
GitHub publishes a known test vector (secret / payload / expected hex) in the validating-webhook-deliveries doc — use it in unit tests.
Official best practice: use X-GitHub-Delivery to ensure each delivery is unique per event (best practices).
Recommended adapter policy:
- After signature OK, look up
X-GitHub-Deliveryin a short-TTL idempotency store (e.g. Redis, 24–72h). - If seen → ack 2xx, skip work (safe for retries).
- If new → record delivery id, enqueue job.
- Note: manual redelivery reuses the same delivery id — treat as idempotent, not as a new scan trigger unless content/SHA policy says otherwise.
- Optionally reject deliveries whose payload
repository.pushed_at/ event timestamp is older than a configured skew (defense in depth; signature still primary).
TLS terminate → read raw body → verify X-Hub-Signature-256
→ check X-GitHub-Delivery idempotency
→ parse JSON → filter event/action
→ enqueue → return 2xx quickly
- Create with
POST /repos/{owner}/{repo}/check-runsusing installation token. - Required conceptual fields:
name,head_sha,status(queued→in_progress→completed), and on completionconclusion(success,failure,neutral,cancelled,timed_out,action_required,skipped, … per current API enum). - Only GitHub Apps can create check runs (Checks API).
Lifecycle recommendation:
- On webhook: create check run
status=in_progress(orqueuedthen update). - Run AXGuard Core.
- Update with
conclusion, markdownoutput.summary/output.text, and annotations.
- Each annotation needs at least:
path,start_line,end_line,annotation_level(notice|warning|failure),message. - Max 50 annotations per API request; append more via repeated
PATCHupdates (check runs docs). - Cap displayed findings (e.g. top N by severity/confidence) to avoid spam; link to full report artifact if needed.
- Map AXGuard severities conservatively (e.g. critical/high →
failure, medium →warning, low/info →notice), gated by confidence so UNKNOWN does not fail the check unless policy says so.
Maintain exactly one bot summary comment per PR, identified by an HTML comment marker:
<!-- AXGUARD-SECURITY-REVIEW -->Pattern:
- List issue/PR comments for the PR number.
- Find comment whose body contains the marker.
- If found →
PATCHupdate; else →POSTcreate. - Keep the marker on the first line of the body so updates stay stable across markdown edits.
Comment body outline:
- Mode (REVIEW / PRE-SHIP / WATCH)
- Verdict counts (by severity + confidence)
- Top findings with file:line
- Link to check run details
- Explicit “no active exploitation / no production attacks” disclaimer for WATCH/PRE-SHIP
Checks API behavior: pushes on fork branches may yield empty pull_requests associations for check runs created in the base repo (Checks API notes). Design for:
- Running against
pull_request.head.shafrom the webhook payload directly. - Clear UX when annotations cannot bind to fork paths the same way.
- Never requesting broader permissions “to make forks work.”
All modes are read-only analysis relative to the customer environment. No active production attacks, no live exploit attempts, no credential stuffing, no SSRF probes against customer infra.
| Mode | Trigger (typical) | Depth | GitHub outputs | Gate behavior |
|---|---|---|---|---|
| REVIEW | pull_request opened / synchronize / reopened |
PR-diff-focused + targeted Core passes | Check run + annotations + summary comment | Fail check on high-confidence critical/high per policy |
| PRE-SHIP | Label /axguard-preship, release event, or protected-branch config |
Full audit-style pipeline (threat-model → audit → triage signals) | Check run (stricter conclusions) + summary | Suitable for release blockers; still static/symbolic only |
| WATCH | Optional push to default branch, schedule, or install health | Lightweight / incremental; trend + drift | Neutral/success check or comment-only; low noise | Observability; does not page on every commit |
Shared hard rules:
- Deterministic engines first; LLM only as optional explainer/summarizer behind user-configured providers.
- Prefer UNKNOWN / needs-review over inventing evidence (AXGuard Core stance).
- Never exfiltrate full repos to third parties without explicit customer AI provider config.
| Dimension | GitHub App (webhook service) | GitHub Actions workflow |
|---|---|---|
| Trigger | Server receives webhooks | Workflow YAML on pull_request / workflow_dispatch |
| Auth | App JWT + installation token | GITHUB_TOKEN or App token via actions/create-github-app-token |
| Checks API write | Native App strength | Actions can post check runs / annotations with limitations (Actions also has annotation quotas per step) |
| UX | Install once; works across repos without copying workflows | Requires workflow file per repo (or org reusable workflow) |
| Data plane | Code may leave GitHub runners into adapter host | Code stays on GitHub-hosted / self-hosted runners if Core runs in-job |
| Ops | You run always-on HTTPS + queue | GitHub runs jobs; you maintain action + optional backend |
| Best fit | Productized “AXGuard Security” bot | Open-source / enterprise air-gap / “bring your own runner” |
Recommended product shape: App as the primary UX; ship a thin Actions wrapper that calls the same Adapter→Core path (or runs Core in-process) for customers who refuse outbound webhooks.
Complementary, not exclusive: App posts Checks; Actions can still run axguard audit and upload SARIF/HTML artifacts.
For enterprises that cannot send code or webhooks to AXGuard SaaS:
Customer VPC / GHES
├── Webhook receiver (Adapter) ← same signature + JWT logic
├── Queue + worker
├── AXGuard Core (container / binary)
└── Optional local AI endpoint (customer-configured)
Requirements:
- Public or privately routed HTTPS endpoint reachable by GitHub.com / GHES.
- Same permission set; private key and webhook secret stay in customer secret store.
- Document GHES API hostname differences (
api.vs enterprise URL) when supporting GHES. - Mirror rate-limit and delivery idempotency behavior.
Self-hosted does not reduce the need for signature verification or least privilege.
| Cost center | Who pays | Notes |
|---|---|---|
| GitHub API usage for a GitHub App | Generally included with GitHub; Apps use installation-token rate limits (base on the order of 5,000 req/hour per installation for GitHub.com, with documented scaling for large orgs — verify live via GET /rate_limit) |
API access itself is not a separate “App API fee”; abide by API Terms and rate limits |
| AI inference | Customer or AXGuard (product choice) | Separate from GitHub. Only call user-configured providers; bill accordingly |
| Hosting (Adapter + queue + storage) | Operator of the App (AXGuard SaaS or customer self-host) | TLS, compute, logs, idempotency store |
| GitHub Actions minutes | Customer (if using Actions path) | Independent of App hosting |
| Marketplace listing | Free vs paid plans are a commercial choice; paid Marketplace apps have extra publisher requirements | See §13 — no approval claimed |
Rule of thumb for messaging: “GitHub API is free with a GitHub App; AI and hosting are separate.”
| Risk | Mitigation |
|---|---|
| Forged webhooks | HMAC X-Hub-Signature-256 + constant-time compare; reject missing signature |
| Replay | Persist X-GitHub-Delivery; short TTL; idempotent handlers |
| Stolen App private key | KMS, rotation, anomaly alerts on mint volume; minimal machine access |
| Over-scoped tokens | Mint installation tokens with reduced permissions / repo subset when possible |
| Prompt injection via PR text / README | Core policy: repo text cannot rewrite safety policy; LLM never invents evidence |
| Secret leakage in logs | Redact tokens, PEMs, Authorization, webhook secrets, .env values; never log raw installation tokens |
| Untrusted fork code execution | Prefer static analysis; if any build/exec sandbox exists later, isolate hard — default is no code execution |
| SSRF via malicious repo content | No outbound fetch of user-controlled URLs during review; AI providers allowlisted |
| Secondary rate limits / abuse | Backoff; debounce synchronize; batch annotation patches |
| Confused deputy across installs | Bind every job to installation_id + repo id from verified payload |
| Supply chain of Adapter deps | Pin/lock; SBOM; no curl | bash in install docs for production |
Also follow: Best practices for creating a GitHub App.
Prepare for a future listing; do not assert that AXGuard is approved, listed, or verified.
Official references:
- Requirements for listing an app
- GitHub Marketplace Developer Agreement
- About GitHub Marketplace (path may redirect; use Marketplace docs hub if moved)
Checklist (prep only):
| Item | Notes |
|---|---|
| Privacy policy URL | Required for listings |
| Support contact / URL | Required |
| Accurate permissions description | Match runtime App settings |
| Webhook handling for Marketplace purchase events | Required if using Marketplace billing plans |
| Paid plan extras | Publisher verification; minimum installation counts (docs cite 100 GitHub App installs for paid); purchase event handling |
| ToS acceptance | Marketplace Developer Agreement |
| Security narrative | Least privilege, no permanent token storage, data retention limits |
GitHub retains discretion over what appears on Marketplace. Submission ≠ approval.
GitHub webhook (metadata + SHAs)
→ Adapter verifies signature
→ Installation token (ephemeral)
→ Fetch only needed blobs / PR diff
→ AXGuard Core (local to Adapter worker)
→ Optional: snippets to user-configured AI provider
→ Results → Checks + PR comment
→ Delete working tree / GC job artifacts per TTL
Policies:
| Topic | Policy |
|---|---|
| Source retention | Keep working copies only for job TTL (e.g. minutes–hours); prefer ephemeral disks |
| Secrets | Never log; scrub findings that are themselves secrets before commenting if policy requires redaction in public PRs |
| AI | Code/snippets leave the trust boundary only to providers the user configured; no silent third-party model calls |
| Cross-tenant | Strict installation/repo isolation; no shared caches of source across customers |
| Subprocessors | Disclose hosting + any default AI in privacy policy before Marketplace |
| Customer deletion | On installation deleted, wipe tenant config and residual artifacts |
Public PR comments are visible to anyone who can see the PR — avoid pasting full secret values into comments/annotations; point to check summary with redaction.
- Register GitHub App (dev) with minimal permissions +
pull_requestevents. - Implement signature verify + delivery idempotency + JWT/installation mint.
- Map one finding → one annotation + summary comment with
<!-- AXGUARD-SECURITY-REVIEW -->. - Wire REVIEW mode to Core
audit/scanon PR head. - Add PRE-SHIP config (label/release) and WATCH (optional).
- Add Actions composite for self-serve runners.
- Hardening: rate limits, redaction, retention TTLs.
- Marketplace prep docs/privacy — submit only when product-ready (still no approval claim).
- Default check conclusion when confidence is UNKNOWN —
neutralvssuccess? - Whether PRE-SHIP may upload HTML reports as release/check artifacts (storage + retention).
- GHES support timeline and API version pinning (
X-GitHub-Api-Version). - Whether a separate “autofix” App permission set (Contents write) is ever justified as opt-in.
- Commercial packaging: SaaS App vs self-hosted license vs Actions-only.
End of research note. Implementation belongs in a future adapters/github/ (or equivalent) — not specified here as landed code.