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.
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-apiSupports GitHub. GitLab and Bitbucket are not connected yet.
phylax repo add https://github.com/acme/service-api --provider githubAdd --policy strict to attach a named policy at creation time.
curl -X POST https://api.phyi.dev/v1/repositories \ -H "Authorization: Bearer $PHYLAX_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"url": "https://github.com/acme/service-api", "provider": "github"}'3. Verify on demand
Run a one-off verification without adding the repository:
phylax repo verify https://github.com/acme/service-apiverdict: allowrisk score: 14 / 100commits: 248 checked, 248 signedbranches: protected, no force pushes in 90dprovenance: verified4. What Phylax checks
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.
{ "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);}import hashlib, hmac, time
def verify(raw_body: bytes, headers: dict, secret: str) -> bool: timestamp = headers["x-phylax-timestamp"] received = headers.get("x-phylax-signature", "")
# Reject anything outside a five minute window. if abs(time.time() - int(timestamp)) > 300: return False
expected = "sha256=" + hmac.new( secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256, ).hexdigest()
return hmac.compare_digest(expected, received)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.timingSafeEqualandhmac.compare_digestexist 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.
Deduplicate on X-Phylax-Delivery so a retried delivery is processed once. And when rotating
the secret, accept both the old and new value for an overlap window rather than cutting over
instantly, or you will drop deliveries that were in flight.
6. Troubleshooting
| Symptom | Likely cause |
|---|---|
| Repository never appears after connecting | The provider app was not granted access to that repository. Re-run the authorization and select it explicitly. |
| Webhooks never arrive | The app lacks webhook permission, or an egress rule is blocking the delivery. |
| Every delivery fails verification | The secret does not match, or the body was parsed before signing. Check the raw-body path first. |
| Verdict differs from a colleague’s | Different token scope. An org token applies org policy; a personal token returns the public default. |
| Repository is skipped entirely | A policy rule in your organization excludes it. |
For anything else, re-run with diagnostics attached:
phylax repo verify https://github.com/acme/service-api --debugRelated guides
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.