Skip to content

Git Security & DevSecOps

12 min read

The four previous pillars built a delivery system. Code lives in a repository, arrives through pull requests, merges under policy, and is built, tested and deployed by automation.

This pillar is about the fact that every one of those stages is also an entry point.

Start with Repository Security

Continue to GitHub Copilot & AI Engineering

Section titled “Continue to GitHub Copilot & AI Engineering”

This pillar establishes the controls: who can change what, which credentials exist, what gets scanned, and what is provably the artifact you reviewed. The next pillar puts AI inside those controls.

That ordering is deliberate. Copilot, AI code review and software-development agents change how quickly code is produced; they do not change what makes a change trustworthy. An agent’s pull request meets the same branch protection, the same required checks and the same human approval as anybody else’s — and every safe agent architecture in Pillar 6 is built on the permission model this pillar establishes.

The principle it runs on: AI should accelerate engineering without removing engineering discipline.

Continue to GitHub Copilot & AI Engineering

Security is a property of the lifecycle, not a stage in it

Section titled “Security is a property of the lifecycle, not a stage in it”

The most common way organisations get software security wrong is to treat it as a checkpoint — a scan that runs somewhere near the end, a review before release, a team that says yes or no.

That framing fails for a structural reason. By the time a build artifact exists, most of the decisions that determine whether it is trustworthy have already been made: who could write to the repository, whether the change was reviewed, what the dependency tree contains, what credentials the build had, and whether anything can prove the artifact came from that source at all.

A late checkpoint can reject an artifact. It cannot retroactively make the process that produced it trustworthy.

Where weakness enters

A vertical chain from developer identity through Git repository, commit and branch, pull request, code and dependency analysis, CI/CD, build artifact, release and deployment. Each stage is a point at which an attacker can gain a foothold.

Developer identityAccounts, keys, tokens, 2FAGit repositoryAccess, configuration, policyCommit / branchAuthorship, signing, protected refsPull requestReview, ownership, required checksCode & dependency analysisStatic analysis, advisoriesCI/CDCredentials, runners, workflow contentsBuild artifactIntegrity, provenance, SBOMReleaseTags, assets, signaturesDeploymentVerification policy at admission

Read that chain twice. The first time as a delivery pipeline — that is Pillars 1 to 4. The second time as an attack surface, where each arrow is a trust boundary somebody has to be willing to defend.

That second reading is this pillar.

It is worth being concrete, because generic security advice tends to describe threats that sound plausible rather than threats that recur.

Credentials end up in Git. Not usually through carelessness with a production key — usually through a .env file that was convenient locally, a test fixture that used a real token because the fake one did not work, or a debug commit that was never meant to be pushed. Git keeps it forever, across every clone and every fork.

Permissions accumulate. Someone gets admin to unblock a release and keeps it. A contractor’s account stays active. An automation token created for one repository can write to forty. Nobody grants excessive access deliberately; it accretes, and nothing in the platform prompts a review.

A dependency changes underneath you. You did not write the vulnerable code, you did not choose the package that introduced it, and it arrived four levels deep in a transitive tree during a routine lock file update.

The build becomes the target. Compromising source code means getting a change through review. Compromising the build means the artifact people install does not match the source anyone read — and almost nobody checks.

Nothing can be proved after the fact. An artifact sits in a registry. Which commit built it? Which workflow? On what runner? If the honest answer is “we would have to guess”, then the incident response for a suspected compromise is to rebuild and redeploy everything, because you cannot rule anything out.

Each of the four clusters below closes one of those categories.

  1. Repository Security

    Identity, authentication, authorisation, repository policy and commit authenticity.

    9 lessons120+ min

  2. Secret Security

    Keeping credentials out of repositories, and responding when one gets in.

    8 lessons105+ min

  3. Code & Dependency Security

    Finding vulnerabilities in your own code and in everything it depends on.

    8 lessons110+ min

  4. Supply Chain Security

    Protecting the path from source to build, artifact, release and deployment.

    10 lessons145+ min

Everything upstream of the code itself: who has access, how they authenticate, what the repository’s policy permits, and whether a commit’s claimed authorship means anything.

This is where the return on effort is highest, because it is mostly configuration. A repository with enforced review, restricted direct pushes, no long-lived tokens and verifiable commit authorship resists an entire class of attack that no scanner detects.

The one problem in this pillar that is genuinely solvable rather than merely manageable.

A vulnerability in your code is a defect you find and fix. A leaked credential is different: it is valid the moment it leaks, it works from anywhere, and removing it from the repository does nothing to it. This cluster covers prevention first, detection second, and — because prevention is never complete — a rehearsed incident response third.

Two distinct problems that GitHub’s tooling deliberately keeps separate.

Static analysis (code scanning, CodeQL) examines source you wrote for patterns that represent vulnerabilities. The dependency tooling (dependency graph, Dependabot, dependency review) is about code you did not write and mostly did not choose. Different signals, different failure modes, different remediation, different reasons for producing noise.

The path from a reviewed commit to a running artifact, and the question of what evidence exists at each handoff.

This is the most advanced cluster and the one whose vocabulary is most frequently misused. SBOMs, provenance, attestations, signatures and SLSA are four different things plus a framework, and conflating them produces confident statements that are not true.

The term is worth unpacking once, because it is used to mean two incompatible things.

The unhelpful version means “the security team now has a seat in the pipeline” — the same gate, moved earlier, still owned elsewhere. That reliably produces a queue: engineers wait for approval, security reviews things they lack context on, and both sides conclude the other is the obstacle.

The useful version is a claim about where a control lives. A control belongs at the point where the person who can act on it is already looking. A dependency vulnerability surfaced in the pull request that introduced it is a decision the author can make in context, in seconds. The same vulnerability surfaced in a monthly report is an unattributed line item somebody has to reconstruct the history of.

That is why so much of this pillar is about placement rather than tooling:

  • A secret blocked at git push costs one command. The same secret found in history costs a rotation, a rewrite, and a coordination exercise across everyone who cloned.
  • A dependency reviewed at the pull request is a conversation about whether to take the change. The same dependency reviewed after release is an upgrade project.
  • Provenance recorded during the build is a workflow step. Provenance reconstructed afterwards is an investigation with an uncertain answer.

Nothing in this list is about buying better detection. It is about moving the same decision to the moment it is cheap.

The second half of the useful version is ownership: a control that fires for the engineer who caused it, in their workflow, on their change, is a control they can own. That is the entire practical difference between security that scales with the team and security that scales with the security team.

Most articles in this pillar work through a control the same way, because a control described without its assumptions is just a setting.

Threat. What could actually happen, stated as an event rather than a category. Not “credential risk” but “a token committed to a public repository is used from an unfamiliar address within minutes”.

Attack surface. Where it can happen. Which branches, which surfaces, which identities, which repositories. This is the part most often skipped, and it is the part that tells you whether a control you already have covers the case.

Impact. Why it matters here, specifically. The same leaked credential is a nuisance in one system and a full compromise in another, and the difference is what it can reach.

Control. What reduces the likelihood or the blast radius, with its scope stated.

Verification. How you confirm the control is working — not that it is configured, but that it does what you believe. A branch protection rule with a bypass list that includes everyone is configured and useless.

The last one is the one to insist on. Configuration is a claim; verification is evidence. A quarterly review that reads settings pages finds fewer problems than a single attempt to do the thing the control is meant to prevent.

Repository protection is often discussed as though it were one thing — “we have branch protection on”. It is a chain, and the chain has two halves that fail differently.

Identity controls and workflow controls, in sequence

A vertical chain: identity, then authentication, then authorization, then repository rules, then pull request, then review, then automated checks, then merge. The first three concern who is acting; the last five concern what happens to a change.

IdentityA person or a machine accountAuthenticationSSH key, token, GitHub AppAuthorizationRepository role, team, org policyRepository rulesRulesets and branch protectionPull requestThe proposed changeReviewHuman judgement, CODEOWNERSAutomated checksTests, scanning, policy gatesMergeThe change becomes the default branch

The first three stages answer who is acting. The last five answer what happens to a change. Both halves are required, and organisations reliably invest in only one.

A team with excellent review culture and no access control has a chain that an attacker walks around: compromise a maintainer’s token and push directly. A team with tight access control and no review has a chain where any one of forty trusted people — or anything holding one of their credentials — can merge anything.

The Repository Security cluster builds both halves, in that order.

Five distinctions worth getting right immediately

Section titled “Five distinctions worth getting right immediately”

Most security confusion is vocabulary confusion. These five carry the pillar.

Not the same thing
Authenticationestablishes who or what is actingAn SSH key proves an identity
Authorizationdetermines what that identity may doA repository role permits a push
Detectionfinds a problem that already existsSecret scanning reads history
Preventionstops the problem occurringPush protection blocks the push
Assuranceprovides evidence about somethingAn attestation records a build

And the one that causes the most incorrect conclusions in practice:

A signature proves something about a key and an identity. It does not say anything about whether the signed content is safe.

A perfectly signed commit can contain a backdoor. A correctly attested container image can be full of known CVEs. Cryptographic verification answers “did this come from where it claims?” — a question worth answering, and a completely different question from “is this any good?”

There is no configuration in this pillar that makes a repository secure, and no page here uses that phrasing.

Every control described has a scope and a failure mode, both of which are stated. Secret scanning detects supported patterns — it will not find your bespoke internal token format unless you write a custom pattern. Code scanning finds classes of defect its queries model — it will not find a logic flaw that lets one customer read another’s data. Branch protection constrains the paths it covers — it says nothing about who holds an admin token.

Knowing where a control stops is the difference between defence in depth and a false sense of coverage. Stacking three controls that all fail the same way is not depth.

There is a real overlap with GitHub Actions & CI/CD, and it is worth being precise about the split rather than pretending it does not exist.

Pillar 4 secures the automation. How to write a workflow that cannot be injected, how to authenticate to a cloud without storing a key, how to scope GITHUB_TOKEN, how to harden a runner. It is about GitHub Actions as a system you operate.

Pillar 5 secures the lifecycle the automation sits inside. The repository the workflow reads from, the credentials it must never encounter, the dependencies it builds against, and the artifact it produces — including whether anyone downstream can verify that artifact independently.

Where the two meet, this pillar links rather than repeats. The artifact attestations page assumes you can already write the workflow step and spends its length on what the attestation actually claims and how a deployment policy should evaluate it.

“Defence in depth” is often used to mean “we bought several tools”. The useful version is narrower:

Layers provide depth only when they fail independently.

Three secret scanners that all match the same regular expressions are one layer bought three times. A local pre-commit hook, a server-side push block, a CI scan of the diff and continuous scanning of history are four layers with genuinely different failure modes — the hook can be skipped, the push block covers only supported patterns, the CI scan sees only what the PR changed, and the continuous scan is retrospective. Each catches something the others miss.

The secret scanning in CI page builds exactly that stack, and is the clearest worked example of the principle in the pillar.

The clusters are ordered by dependency, not by importance.

Repository Security first, because access control and identity are what everything else assumes. There is little point scanning for vulnerabilities in a repository forty people can force-push to.

Secret Security second, because credential exposure is the fastest-moving incident type here and the response has to be practised before it is needed.

Code & Dependency Security third. It assumes pull requests and required checks, which come from GitHub Engineering.

Software Supply Chain Security last. It assumes working CI/CD from Pillar 4, and several of its controls only make sense once the earlier three are in place — provenance for an artifact built from an unprotected branch proves the artifact came from somewhere untrustworthy.

This pillar assumes working knowledge from the earlier four. Specifically:

You do not need a security background. You need to be able to read a workflow file and reason about what a Git command does to history.

Not everyone works through a pillar in order. If you have one afternoon:

These recur often enough to be worth naming on the hub rather than leaving to individual pages.

“It is a private repository, so a secret in it is fine.” Private controls who can read the repository, which is a larger and more changeable set than people picture: every collaborator, every member of every team with access, every automation token scoped to it, every fork made while it was accessible, and every laptop that has ever cloned it. It also does not survive the repository being made public by mistake, which happens.

“We removed the commit, so the secret is gone.” Rewriting history changes which commits are reachable. It does not reach clones, forks, caches, or the credential itself — which is still valid and still works. Rotation comes first, always.

“We have alerts enabled.” Enabling a detector produces findings. Findings that nobody triages become a queue, and a queue nobody reads is indistinguishable from no detection at all — with the added cost that it feels like coverage.

“The dependency is popular, so it is safe.” Popularity means many people use it. It is weak evidence about maintenance, and no evidence at all about whether the specific version you resolved was published by the maintainer you think it was.

“The artifact is signed.” Signed by whom, verified against which identity, and checked by what, at which point? A signature nobody verifies is a file. The supply chain cluster is largely about closing that loop.

Security is not a gate at the end of the pipeline. It is a property of every handoff in it — and each handoff is defensible only if you can say what it assumes, what would break it, and how you would know.

Start with Repository Security