Skip to content
Phylax
Integration guides

Source Repositories

Connect and verify source code repositories across supported providers.

Verifying a repository answers a different question from verifying a package. Not “is this published artifact safe” but “did this artifact actually come from this source, and did the history get rewritten underneath me”.

1. Connect a provider

Authorize Phylax for GitHub. The app requests read access to repository contents, metadata and webhooks. It never requests write access to your code.

GitHub Installs Phylax on the repositories you choose, then verifies every pull request. Install the GitHub App

2. Add a repository

Adding a repository turns on continuous verification: Phylax re-evaluates it on every push rather than only when you ask.

Go to Repositories → Add repository, paste the clone URL, and pick the provider.

https://github.com/acme/service-api

Supports GitHub. GitLab and Bitbucket are not connected yet.

3. Verify on demand

Run a one-off verification without adding the repository:

Terminal window
phylax repo verify https://github.com/acme/service-api
verdict: allow
risk score: 14 / 100
commits: 248 checked, 248 signed
branches: protected, no force pushes in 90d
provenance: verified

4. What Phylax checks

Commits Verifies commit signatures and checks for tampering or rewrites.
Branches Ensures protected branches follow policy and detects force pushes.
Provenance Confirms artifact provenance from commits to build outputs.
Policy Evaluates the repository against your organization’s security policies.

Why a “Verified” badge is not enough

If your provider already shows a green Verified badge on signed commits, it is fair to ask what this adds. Quite a lot, and the reasons are specific.

The badge does not bind to a unique commit. Research published in 2026 showed that signature malleability across ECDSA, RSA, EdDSA and S/MIME lets an attacker with no access to the signing key produce a second commit with an identical tree, identical metadata, an identical message, a signature that still validates, and its own fresh Verified badge. GitHub does not canonicalize the signature container before verifying, so each variant mints an independent, durable Verified record keyed to that commit’s own hash.

Verification records are never revisited. If a signing key is later revoked or expires, commits verified under it keep their Verified status. The badge records what was true at verification time and is not re-evaluated.

And the badge was never a content claim. It says a known key signed something and that the key belongs to the account shown. It does not say the signer read the diff, or that the content was not altered between staging and committing.

What Phylax adds

Phylax pins the commit hash into the attestation and re-evaluates key state at verification time rather than trusting a cached badge. It also watches history: a force push that replaces a verified commit is a signal in its own right, and it is invisible if you only look at whether the current tip carries a badge.

5. Webhook sync

Add a webhook so Phylax re-verifies on push instead of on a schedule. Every delivery is signed with the secret you configured.

Request headers
{
"X-Phylax-Event": "repository.verified",
"X-Phylax-Delivery": "d3f1b2a4-7c8e-4a1b-9f6d-2e5c8a0b1d3f",
"X-Phylax-Timestamp": "1786240895",
"X-Phylax-Signature": "sha256=3b1f6a3b9c6f4c2e9f30b7d6c0f1a2d7e8f9c0b1a2d3e4f5a6b7c8d9e0f1a2"
}

Verify the signature

Do not act on a webhook you have not verified. Anyone who learns your endpoint URL can post to it otherwise.

import crypto from 'node:crypto';
export function verify(rawBody, headers, secret) {
const timestamp = headers['x-phylax-timestamp'];
const received = headers['x-phylax-signature'];
// Reject anything outside a five minute window.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(received ?? '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Three things in that code are the whole point, and each is a common way to get this wrong:

  • Sign the raw body, not parsed JSON. Any middleware that reorders keys or changes whitespace changes the bytes, and your signature check starts failing for legitimate deliveries. Capture the body before your JSON parser touches it.
  • Compare in constant time. crypto.timingSafeEqual and hmac.compare_digest exist because === leaks how much of the signature matched, which is enough to forge one byte at a time.
  • Check the timestamp. Without it a captured delivery can be replayed forever. Five minutes is the usual tolerance, which absorbs normal clock skew.

6. Troubleshooting

SymptomLikely cause
Repository never appears after connectingThe provider app was not granted access to that repository. Re-run the authorization and select it explicitly.
Webhooks never arriveThe app lacks webhook permission, or an egress rule is blocking the delivery.
Every delivery fails verificationThe secret does not match, or the body was parsed before signing. Check the raw-body path first.
Verdict differs from a colleague’sDifferent token scope. An org token applies org policy; a personal token returns the public default.
Repository is skipped entirelyA policy rule in your organization excludes it.

For anything else, re-run with diagnostics attached:

Terminal window
phylax repo verify https://github.com/acme/service-api --debug

Gate pull requests on these verdicts with GitHub, enforce them on every build with CI/CD Pipelines, or see every flag and exit code on the Phylax CLI page.

Did this page help you?