Manav.id
Standards ยท 18 min read

Who approved this deploy? The audit log says the pipeline did.

Every change management control in a modern engineering organisation proves that a workflow ran and that a session clicked. None of them proves that a person decided. As agents write, review and merge a growing share of production changes, that gap stops being a philosophical curiosity and becomes the thing your auditor asks about.

The request lands at 4:12 on a Tuesday afternoon, in the shared channel where audit questions go to ruin an evening. The auditor has picked three production deployments from the previous quarter, at random, from the change log. For each one, she would like to see evidence that the change was reviewed and approved by an authorised person other than the author, before it reached production.

This is not a hard question. It is the most ordinary question in the entire engagement. Change management is the part of the audit everyone expects to pass, because the company has spent real money on exactly this: branch protection is on, required reviewers are configured, the production environment has a manual approval gate, and a compliance platform has been screenshotting all of it automatically since March.

So the platform engineer opens the first of the three. Pull request 8,841, a change to the billing reconciliation job. Author: one engineer. Approver: another engineer. Approved at 11:04, merged at 11:06, deployed at 11:11. She exports the page as a PDF, drops it in the evidence folder, and moves to the second.

The second one is where the afternoon stops being ordinary. Pull request 9,102 is a dependency bump. The author is a bot. The approver is also a bot, because auto merge is enabled for patch level updates that pass CI, which is a sensible policy that the platform team wrote deliberately and would defend in any room. The change went to production without a human looking at it, and the evidence page for it looks structurally identical to the first one. Same fields. Same green checks. Same PDF.

And once you have seen that, you cannot unsee the question it raises about the first one. What exactly did that first PDF prove? It proves that an account named after a colleague clicked a button in a web application. It does not prove that the colleague was at the keyboard. It does not prove that anybody read the diff. It does not even prove that the code which was reviewed is the code that reached production, because the review happened against a commit and the deployment happened against an artifact, and nothing in the evidence connects those two things.

What evidence proves that a production deployment was approved by an authorised person? A signature from the approver's enrolled device over the deployed artifact's digest and target environment, verified by the pipeline before promotion and verifiable offline by an auditor afterwards. A pull request approval, an environment gate click, and an audit log entry all prove that a workflow ran and a session clicked, which is a different and much weaker claim.

What does a pull request approval actually prove?

Let us be precise, because the answer is genuinely useful and it is not that the control is worthless.

A pull request approval is a row in a database, written by a web application, when a request arrived carrying a valid session cookie belonging to an account with permission to approve, on a repository where the branch protection settings required an approval. Everything in that sentence is real, and several parts of it are strong. The permission model is enforced. The setting was configured. The row exists and is timestamped.

Here is what the row does not contain. It does not contain any artifact that a third party can check without asking the platform. It does not contain evidence about which human was present. It does not bind the approval to the thing that shipped, only to the branch state at the moment of the click. And critically, every property that makes it trustworthy is a property of the platform's configuration, which is mutable by the same population the control is supposed to constrain.

The evidence is configuration, and the configuration is editable

Think about what the auditor is really being shown. She is shown that on the day she looked, the repository required two approvals and blocked force pushes. She infers from this that on the day of the change, the same rules applied. That inference is doing enormous work, and the systems mostly do keep a settings audit log, so the inference is not baseless. But it is an inference about administrative history rather than a property of the change itself.

The analogy that gets this across: it is the difference between a signed and witnessed contract, and a photograph of a room with a sign on the wall saying that contracts in this room must be witnessed. The photograph is evidence about the policy. The signature is evidence about the transaction. Auditors have accepted the photograph for years because, until recently, producing a plausible fake of the underlying activity took more effort than doing the work properly.

That calculation has changed, which we will come to. But first, the second structural problem, which is older and worse.

Why does separation of duties fail on accounts?

Separation of duties is the oldest control in the book, and it is a good one. The person who writes the change should not be the person who approves it, because a second pair of eyes catches errors and a second person raises the cost of deliberate wrongdoing.

In a code hosting platform, this is enforced as a rule about accounts: the account that opened the pull request may not be counted among the accounts that approved it. That is a faithful implementation of the rule as stated. It is also, in practice, enforcement against the wrong noun.

Duties are separated between humans, and platforms separate between accounts, and those two things come apart in ordinary, non malicious ways all the time. One engineer holds a personal account and a machine account for their automation. A contractor has an account at the client and an account under their own organisation. A departing employee's account persists past their last day, a problem we treat at length in the offboarding post. A shared service account is used by four people on the platform team, which is the thing every audit tells you not to do and every small team does anyway.

None of those situations requires a bad actor. Each one of them silently converts a two person control into a one person control, and the evidence looks perfect in every case, because the evidence was only ever checking account identity in the first place.

The stolen session version

Now add an adversary. An attacker who has stolen a reviewer's session, by any of the mechanisms in the session theft post, can approve pull requests. Not forge approvals, not tamper with logs. Approve them, genuinely, through the real interface, generating real audit log entries that will look correct forever.

This is the failure we call Session-Inherited Authorization in the Identity Failure Map: the login established a session, and every consequential action taken afterwards silently inherits the authority of that login, no matter how long ago it happened or who is now holding it. Change management is a textbook instance. The reviewer authenticated at nine in the morning with a hardware key, and the approval at three in the afternoon carries all the weight of that morning ceremony while requiring none of it.

What happens when an agent authors, reviews and deploys?

Here is where the ground has genuinely moved, and where a control that was adequate becomes a control that is misleading.

Coding agents now open pull requests at volume. Commentary through 2026 has described very large numbers of agent opened pull requests across public code hosting, with a substantial noise fraction, and while the specific counts vary by who is measuring and how, the direction is not in dispute by anyone who reviews code for a living. We looked at what this does to maintainers in the pull request flood post. What it does to change management is a separate and sharper problem.

Consider the four roles in a change: author, reviewer, approver, deployer. An agent can now occupy any of them. In many pipelines it occupies several at once, entirely legitimately and with the team's full knowledge. An agent opens a dependency bump, CI passes, auto merge fires, the deployment pipeline promotes the artifact through staging on a schedule, and production follows because the change passed the gates that were configured for it.

Every gate did its job. Nothing was bypassed. And there is no human decision anywhere in the sequence, which may be exactly what the team intended for patch level dependency updates, and may be a catastrophe for the change that ships next to it under the same policy.

The evidence cannot tell the two cases apart

This is the crux. It is not that agent driven deploys are bad. Automated dependency updates are a security improvement on the alternative, which is dependencies that never get updated. The problem is that the evidence trail produced by a deliberate, well governed, fully automated change is indistinguishable from the evidence trail produced by a change that a human was supposed to review and did not.

An organisation with a mature agent pipeline and an organisation whose reviewers rubber stamp everything in eleven seconds produce the same PDFs. So do both of those and an organisation where an attacker holds a reviewer session. The evidence has lost its power to discriminate, which is the only property that made it evidence.

And the auditor cannot fix this by asking better questions, because the underlying records genuinely do not contain the distinguishing information. It was never captured.

Which change management evidence can be altered, and by whom?

It is worth laying this out explicitly, because most teams have never enumerated it. The question for each evidence type is simple: who can produce or alter this artifact, and can anyone outside the platform verify it?

Evidence artifactWhat it establishesWho can produce or alter itVerifiable outside the platform?
Pull request approvalAn account with review permission clicked approve on a branch stateAnyone holding that account's session, including an agent with its tokenNo
Required reviewer settingThe rule was configured when the auditor lookedAny repository or organisation administratorNo
Environment protection ruleA gate existed for this environmentAdministrators, who can also bypass it where bypass is permittedNo
Deployment log entryA pipeline run promoted an artifactThe CI system, acting under a service principalNo
Slack approval threadA message was posted from an accountAnyone holding that session, or an integration tokenNo
Signed commitA key signed this commit objectWhoever holds the key, which for CI signing is the pipelineYes, for the commit
Build provenance attestationThis artifact was built by this pipeline from this sourceThe build systemYes, for the build
Human signed approval receiptThis enrolled person authorised this artifact digest for this environmentOnly the holder of the enrolled deviceYes

Read down the last column. Two rows are verifiable outside the platform today, and both of them are about machines. Signed commits prove a key signed a commit. Build provenance, through work like SLSA and Sigstore, proves which pipeline built which artifact from which source, and it is genuinely excellent work that has raised the floor across the industry. We wrote about its boundary in the signed publish post: provenance proves the pipeline, and a worm that steals a maintainer credential has a pipeline.

Nobody has been signing the decision.

What would a deploy approval that survives the platform look like?

The design follows directly from the diagnosis. If the weakness is that approval is an application state change, make it an artifact. If the weakness is that it binds to a session, bind it to a device. If the weakness is that it references a branch, reference the digest of the thing that will actually run.

Concretely: the approver's enrolled device produces a signature over a small, canonical object naming exactly what is being approved.

{
  "type": "deploy.approval",
  "repo": "acme/billing-service",
  "commit_sha": "9f2c1ab4e7d05b83c1f6ae2290d4471bb0e5c8a1",
  "artifact_digest": "sha256:4b2e...c9f1",
  "environment": "production",
  "change_id": "CHG-2026-0917-114",
  "approver": "did:manav:8f3a...b21c",
  "not_after": "2026-09-17T18:00:00Z",
  "nonce": "6d9f0c2a"
}

Walk the fields, because each one closes a specific hole.

artifact_digest is the one that matters most and the one that is usually missing. The approval is bound to the bytes that will run, not to a branch pointer that can move afterwards. This is what closes the gap between what was reviewed and what shipped. If the pipeline rebuilds and the digest changes, the approval no longer matches, and the promotion stops.

environment prevents an approval for staging from being replayed as an approval for production, which sounds like an exotic attack and is mostly a mundane pipeline bug.

not_after puts a clock on it. An approval that has been sitting around for three days is not an approval for tonight's deploy, and expiry says so mechanically rather than relying on a convention.

nonce prevents a single approval from being replayed to promote the same artifact twice, which matters when promotion has side effects such as running migrations.

approver is an identifier for the enrolled human, not a platform account. That is the whole point. The account is a name the platform gave to a login. The enrolled identity is a key held on a device, and the person holding the device is the person the control is actually about.

Where it sits in the pipeline

The integration is a gate before promotion, not a replacement for anything. The pipeline requests an approval, waits, and verifies the receipt against the artifact it is about to deploy.

# Deploy gate: verify a human signed this exact artifact
digest=$(crane digest "$IMAGE")
receipt=$(manav approvals fetch --change "$CHANGE_ID")

manav verify \
  --receipt "$receipt" \
  --expect-type deploy.approval \
  --expect-field artifact_digest="$digest" \
  --expect-field environment=production \
  --require-fresh 4h \
  --exclude-identity "$COMMIT_AUTHOR_ID" \
  || { echo "No valid human approval for $digest"; exit 1; }

deploy --image "$IMAGE" --env production

Two details in that snippet carry most of the value. The verification asserts the digest of the image about to be deployed, computed at deploy time, rather than trusting a field someone typed. And --exclude-identity enforces separation of duties against the enrolled human rather than the platform account, which is the fix for every one of the failure modes in the previous section. Two accounts belonging to one person now fail the check, because the identity is the key, not the login.

The verification is a signature check against a published key. It does not call our servers, it does not require the code hosting platform to be reachable, and the same receipt verifies identically in six months when an auditor runs the check herself. That is the property that makes it evidence rather than a screenshot: it survives the platform. If you migrate from one CI system to another, the receipts from the old one still verify. If a vendor goes away, the receipts still verify.

What the approver actually experiences

Almost nothing changes, which is the design goal. The reviewer reads the diff as before. When they approve, a prompt appears on their phone or their laptop showing the repository, the environment and a short digest, and they confirm with the same gesture that unlocks their device. It takes a few seconds.

The interesting design question is not the happy path but the tiering. Not every deploy warrants this, and pretending otherwise is how controls die. A sensible policy signs production and anything touching payments, authentication or customer data, and lets everything else run on existing gates. The routine case can use a lighter check on a device already trusted in that place, and the sensitive case can require a fresh liveness check. Getting this tiering right is most of the implementation work, and it is judgment, not technology.

How do you handle break glass without breaking the control?

This is where every real system leaks, so it deserves its own treatment rather than a footnote.

Production is on fire at three in the morning. The one person who understands the payment router is awake and the approval policy requires a second human. In every organisation, one of two things happens next. Either there is a documented emergency path, or someone with administrator rights turns the gate off, ships the fix, and turns it back on. The second is more common than anybody admits in an audit.

The instinct is to make the emergency path harder, and that instinct is wrong. If the emergency path is slower than turning off the control, engineers will turn off the control, correctly, because the outage is real and the control is abstract. You cannot win that argument with policy.

The correct design inverts it. Make the emergency path fast and loud rather than slow and quiet.

Fast means a single engineer can proceed alone, immediately, with one signature instead of two. No ticket, no phone tree, no waiting. Loud means that signature is over an object that says exactly what it is:

{
  "type": "deploy.approval.emergency",
  "artifact_digest": "sha256:71ac...30de",
  "environment": "production",
  "sole_approver": "did:manav:8f3a...b21c",
  "declared_reason": "payment router 5xx, incident INC-4471",
  "review_due_by": "2026-09-18T17:00:00Z"
}

Now the emergency deploy is not an absence of evidence. It is a specific, signed, non repudiable statement that a named person decided, alone, at 03:14, to ship this exact artifact to production and gave this reason. The receipt is stronger evidence than the normal path, not weaker.

The follow up is mechanical: the pipeline emits a review obligation, and the retrospective produces a second signature confirming that a colleague examined what shipped. If that second signature is missing by the due date, that is a finding with a name attached, which is the sort of thing that actually gets fixed.

Compare this to the current state, where break glass means an administrator toggled a setting and the audit log records a configuration change with no reason and no artifact binding at all. The signed emergency path is better for the engineer, who no longer has to choose between the control and the outage, and better for the auditor, who now has more information about emergencies rather than less.

How does this map to the frameworks you are audited against?

The control language across the major frameworks is consistent in what it asks for and silent on how you prove it, which is precisely the space this fills.

The Trust Services Criteria used for SOC 2 require, under the change management criterion commonly cited as CC8.1, that the entity authorises, designs, develops, configures, documents, tests, approves and implements changes to infrastructure and software. The word is approves. It does not say how approval is to be evidenced, and the customary answer has been the pull request screenshot because that was the best available artifact.

ISO/IEC 27001 addresses change management in its Annex A control on change management for information processing facilities. The EU Digital Operational Resilience Act imposes ICT change management obligations on financial entities and their critical providers. The NIST Secure Software Development Framework, published as SP 800-218, asks organisations to archive and protect each software release and to define and use criteria for software review and approval. The EU Cyber Resilience Act adds product obligations for manufacturers of products with digital elements as its provisions phase in.

None of these documents demands a cryptographic receipt, and it would be dishonest to claim they do. What they do, collectively, is describe an approval obligation whose customary evidence is now weaker than the customary evidence was five years ago, because the population of things that can click a button has expanded. A receipt is simply the strongest available way to answer a question the frameworks already ask.

What does this not fix?

Several important things, and they should be said plainly.

It does not evaluate the change. A signature proves a specific person authorised a specific artifact. It says nothing about whether the code is correct, safe or wise. An approver who signs without reading has still approved, and the receipt will faithfully record their decision to do so. This control moves accountability, not competence.

It does not stop a compromised device. If an attacker fully controls the approver's enrolled device, they can produce signatures. The control raises the cost from stealing a session cookie to compromising a specific physical device belonging to a specific person, which is a large increase but not infinity.

It does not remove the need for the existing gates. Branch protection, required reviews, environment rules and build provenance all still do their jobs. This composes with them and is worthless without them, because a signature over an artifact digest means nothing if the artifact was built from unreviewed source.

Coverage is bounded by adoption. Receipts only exist for pipelines you have instrumented. Everything else remains screenshot territory, and a realistic programme covers production and the highest sensitivity services first and accepts that the long tail takes quarters.

Approval fatigue is real and it is the main way this fails in practice. Require signatures on too many things and people will sign reflexively, at which point you have added friction and gained little. The discipline of gating narrowly is not a nice to have, it is the difference between a control and a ritual.

Native CI connectors are not shipped. The pattern above is implementable today against the signing API as a pipeline step, and the demonstrations at the signed code lab and the privileged action lab show the mechanics. First party plugins for the major CI systems are future work, not something you can install this afternoon.

What to do this week

None of this requires buying anything. The first four items are pure inspection and will tell you more about your change management posture than a quarter of compliance tooling.

  1. Pick three production deploys at random from the last quarter, exactly as an auditor would, and trace each one from approval to artifact. Specifically, check whether you can prove the reviewed commit produced the deployed artifact. Most teams discover they cannot.
  2. Count what fraction of production changes had a human decision. Separate agent authored and auto merged changes from human reviewed ones in your deployment records. Teams are routinely surprised by this number, in both directions.
  3. Enumerate who can alter the controls. List every account with permission to change branch protection, environment rules or required reviewers, and compare it to the list of people whose changes those rules constrain. The overlap is the honest measure of the control.
  4. Find your bypass rate. Query how often environment protection was overridden or emergency paths were used in the last ninety days. If the number is zero, verify that it is really zero rather than unlogged.
  5. Map accounts to humans. Identify every case where one person controls two accounts that could satisfy a separation of duties rule between them. Fix the ones you can, document the rest.
  6. Write the tiering policy before the technology. Decide which environments and which services warrant a human signature. Keep the list short enough that people take it seriously.
  7. Design the emergency path deliberately. If it is currently "an admin turns off the gate", replace it with a single signature and a declared reason. Fast and loud beats slow and quiet.
  8. Prototype the verification step on one non critical service, binding the approval to the artifact digest computed at deploy time. The engineering is a few hours; the argument about which services get it is the long part.

Frequently asked questions

Does a GitHub pull request approval satisfy SOC 2 CC8.1? In practice auditors have accepted it for years, and it remains customary evidence. What it establishes is that an account with review permission clicked approve and that the repository was configured to require one. It does not establish which human was present, nor that the reviewed commit is the artifact that reached production. Whether that gap matters is increasingly a question your auditor will ask rather than assume.

How do you approve changes that an AI agent authored? Two coherent policies exist and both are defensible. Either a human signs the promotion, in which case the agent is an author like any other, or the agent operates under an explicit scoped delegation that names which change classes it may ship without a human, with limits and an expiry. What is not defensible is the current default, where the distinction is invisible in the evidence.

What is separation of duties in CI/CD, and why does it fail? It is the rule that the author of a change may not approve it. Platforms enforce it between accounts, but duties are separated between people, and one person routinely controls more than one account through machine accounts, contractor accounts or shared credentials. Enforcing against an enrolled human identity rather than a platform login closes that gap.

Will this slow down our deploys? For the approver it is a few seconds, comparable to unlocking a phone. The real cost is not latency but attention, which is why the tiering policy matters more than the integration. Sign production and the services that would appear in a breach notification. Leave the rest on existing gates.

How is this different from signed commits or build provenance? Signed commits prove a key signed a commit object. Build provenance proves which pipeline built an artifact from which source. Both are about machines and both are valuable. A deploy approval receipt proves a specific person authorised a specific artifact for a specific environment, which is the one claim the existing artifacts do not make.

What happens if the approver loses their device? The same thing that happens when they lose their laptop: they enroll another one. The practical requirement is to enroll at least two authenticators per approver so that a broken phone never becomes a reason to disable the control, which is how controls quietly die.

Can an auditor verify these receipts without access to our systems? Yes, and that is the point. A receipt is a signature over a canonical object, checkable against a published key with no callback. You can hand an auditor a file and a verification command, and the check produces the same answer on their laptop as on yours, this quarter or in three years.

Sources

  1. AICPA, Trust Services Criteria for Security, Availability, Processing Integrity, Confidentiality, and Privacy, including the common criteria on change management. AICPA SOC suite of services
  2. ISO/IEC 27001, Information security management systems, including Annex A controls on change management. ISO/IEC 27001
  3. NIST, Secure Software Development Framework, SP 800-218, on release archiving, protection, and review and approval criteria. NIST SP 800-218
  4. Regulation (EU) 2022/2554 on digital operational resilience for the financial sector, ICT change management obligations. DORA
  5. Regulation (EU) 2024/2847 on horizontal cybersecurity requirements for products with digital elements. Cyber Resilience Act
  6. SLSA, Supply chain Levels for Software Artifacts, on build provenance. slsa.dev
  7. Sigstore, artifact signing and transparency for software supply chains. sigstore.dev
  8. GitHub documentation on deployment environments and manual approval gates. GitHub environments
  9. PCI Security Standards Council, PCI DSS requirements covering change management for system components. PCI SSC document library
  10. Manav developer documentation on per action signatures and offline receipt verification. manav.id/docs
Your change management control is not the rule you configured. It is the artifact you can hand to a stranger three years from now.