# How to protect secrets when coding agents deploy your app

> Learn how coding agents can expose secrets, compare ways to manage access, and add safeguards to builds, deployment pipelines, and production code.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-10-27 | Topics: [AI](https://flaviocopes.com/tags/ai/) | Canonical: https://flaviocopes.com/agent-deploy-secrets/

A coding agent can build your app correctly and still publish your database password.

It might copy a `.env` file into the upload directory. It might put a private API key in browser JavaScript to make a feature work. Or it might run a debugging command that sends credentials into the conversation history.

There is another risk that is easier to miss. You can hide a production secret from the agent and still let it deploy server code that reads that secret. The agent doesn't need to know the value beforehand to expose it through a new endpoint.

Keeping secrets out of Git covers only one route. We need to control what the agent can read, which operations it can perform, and what happens between a code change and a production deployment.

I'll use a small web app as the example: a reading club with a public website, a server endpoint, and a private database credential. The same questions apply to a mobile backend, a CLI release, or a larger application.

## What counts as a secret?

A **secret** is a value that gives someone access or proves an identity, and therefore needs restricted handling.

API tokens, private keys, session cookies, database passwords, and webhook signing secrets all belong here. A database connection URL can contain a password even if its name doesn't mention credentials.

Some configuration is intentionally public. A public API hostname, an analytics site identifier, or a provider's publishable client key may belong in browser code. Check the provider's documentation before deciding.

The distinction is what the value authorizes. A key designed for browser use still needs the provider's intended restrictions. Calling a value “public” doesn't make an administrative token safe to expose.

For our reading club:

| Value | Who needs it? | Where I would put it |
| --- | --- | --- |
| Public website URL | Browser and server | Public configuration |
| Database password | Server application | Runtime secret store |
| Deployment credential | Approved publisher | Isolated deployment job |
| Test database credential | Test runner | Separate development environment |
| Webhook signing secret | Server handling that webhook | Runtime secret store |

Customer records and private documents also need protection. They are sensitive data even when they aren't credentials. A read-only database role can still disclose every row it is allowed to read.

## How an agent gets access

A coding agent usually combines a language model with tools. The tools can read files, run shell commands, open a browser, and call APIs.

There are several levels of exposure:

| Exposure | Example | What it allows |
| --- | --- | --- |
| The model sees the value | A command prints an API token | The value can enter the conversation and subsequent outputs |
| An agent-controlled process sees it | A shell inherits a database password | Commands can use or copy it without printing it first |
| The agent can use an authenticated tool | A deploy tool holds a token internally | The agent can perform whatever operations that tool permits |
| The agent can replace privileged application code | A deployment updates a server with secret bindings | New code can use those runtime credentials |

Hiding a value from the model is useful. It reduces accidental inclusion in answers and logs. We also need to limit the authority available through the other routes.

For example, a deploy tool can keep its token outside the model's context. If that tool accepts an arbitrary account, destination, and shell command, its interface still gives the agent too much control.

## Why coding agents make this problem harder

Developers have always leaked secrets. Agents add a workflow where one program can discover files, change code, run it, and publish the result in the same session.

An ordinary debugging sequence can cross several boundaries. The agent finds an authentication error, reads local configuration, prints the environment to investigate, and copies an example into the frontend. Each step can seem related to the task.

Build tools and dependencies also execute code. Even if the model follows your instructions, an install script or changed build script can read credentials made available to that process.

Then there is **prompt injection**. The agent may read instructions embedded in an issue, a repository file, a web page, or tool output. Those instructions can try to redirect its tools toward reading or sending secrets. OWASP's [prompt injection guidance](https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html) recommends enforcing permissions and validating tool parameters outside the model.

An `AGENTS.md` rule saying “never read `.env`” helps communicate your intent. A filesystem restriction that prevents the read gives you a boundary you can test. Use both, and check whether the shell tool has the same restrictions as the file-reading tool.

## Map where a secret could escape

For the reading club, I would trace a credential through the entire development and deployment flow.

| Place | Common exposure | Guard to consider |
| --- | --- | --- |
| Working directory | `.env`, database dumps, copied credentials | Separate workspace and restricted file access |
| Process environment | Inherited tokens, `env` output, debug traces | Start with a minimal environment and scoped identity |
| Agent conversation | Pasted secrets, command output, screenshots | Keep values out of prompts and tool responses |
| Git | Source files, old commits, configuration | Secret scanning and push protection |
| Build output | Copied files, HTML, JavaScript, source maps | Inspect and scan the final public artifact |
| CI | Untrusted scripts run beside deployment tokens | Separate build and publish jobs |
| Production runtime | New code reads an existing secret | Review privileged code changes and restrict runtime authority |
| Logs and storage | Error reports, caches, uploaded artifacts | Redaction, restricted access, retention controls |

A secret scanner at the Git boundary covers only one row. A token can stay out of Git and still end up in a deployed file or a CI log.

## Start by giving the agent less to read

For a normal coding task, the agent needs the repository and a development environment. It rarely needs your whole home directory or every credential in your shell.

Use an isolated workspace, container, VM, or dedicated operating-system account. Mount the project and required tools. Leave unrelated credentials outside it.

Check the less obvious connections too: SSH agents, cloud CLI profiles, credential helpers, browser profiles, Docker sockets, and access to a host filesystem. A container with powerful host mounts can inherit more authority than you intended.

Environment variables are another input. Launching an agent from your everyday shell may pass along tokens exported earlier. Give it only the variables its development task needs. Cleaning the environment reduces exposure, but it doesn't stop a process from reading credential files it can still access.

Don't assume `.gitignore` restricts reading. Git uses it to decide which untracked files to ignore. A shell command or build tool can still open those files.

The same applies to product-specific ignore files. Verify what they affect. Excluding a file from indexing doesn't necessarily prevent a command from reading it.

A useful first test is to place a harmless marker outside the permitted project directory. Ask the agent's file tool and shell tool to read it. Both should be denied if that is the boundary you intend to enforce.

## Use development credentials and synthetic data

The reading club can be built against a test database containing invented books and members.

Give the agent a separate development identity. Scope it to that database, with only the permissions required for the task. Use a separate provider project or account where that creates a clearer boundary.

A development token should not be able to list production secrets, change production permissions, or create a more powerful identity. Restrict administrative APIs as well as ordinary data access.

For an investigation that needs production information, first ask whether sanitized logs or a narrow query result will answer the question. If direct access is necessary, make it temporary, restrict the dataset, and audit the operation.

“Read-only” describes allowed changes. It doesn't make the data safe to hand to an agent. OWASP's [secrets management guidance](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html) covers access scope, rotation, and the lifecycle of these credentials.

## Choose how secrets reach trusted code

A secret manager helps avoid scattered plaintext files and gives you a place to control access and rotation.

Different tools solve different parts of the problem:

| Approach | Examples | Main boundary to protect |
| --- | --- | --- |
| Local secret delivery | 1Password CLI | The process receiving plaintext |
| Cloud secret store | AWS, Google, Azure | Identity policies and runtime code |
| Dynamic credentials | Vault | Role scope and credential lifetime |
| Runtime bindings | Cloudflare Workers | Code allowed to use the bindings |
| Restricted service | Publisher or API proxy | Allowed operations and data flows |

[AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html), [Google Secret Manager](https://cloud.google.com/secret-manager/docs/best-practices), and [Azure Key Vault](https://learn.microsoft.com/en-us/azure/key-vault/general/best-practices) document their access and operational controls. Choose the one that fits your infrastructure before adding another system to maintain.

[Vault's database secrets engine](https://developer.hashicorp.com/vault/docs/secrets/databases) can generate database credentials dynamically. A short lease limits how long a copied credential remains useful, provided the database integration and revocation behavior match your needs.

For local development, see my [1Password environment variables guide](https://flaviocopes.com/1password-environments/). For browser logins, [password managers and agent access](https://flaviocopes.com/ai-agent-passwords/) cover a different boundary.

### Injection into a process still exposes the value to that process

[1Password's `op run`](https://developer.1password.com/docs/cli/secrets-environment-variables/) resolves references and makes secrets available as environment variables in a subprocess.

That keeps the value out of a checked-in environment file. The subprocess can still read it.

If you run an unrestricted agent inside that subprocess, its commands may be able to read or transmit the same value. Output masking helps with accidental printing; it doesn't restrict what the process can do with the credential.

Treat injection as a delivery method. Decide separately whether you trust the recipient with the value.

### A restricted service can keep the credential elsewhere

Suppose the agent only needs to publish the reading club's static site. A small trusted publisher can hold the deployment token and accept an operation such as “publish this approved artifact to the reading-club site.”

The publisher should choose the account and destination from protected configuration. It should validate the artifact identity and reject arbitrary command arguments, shell fragments, and alternate upload locations.

For other operations, an API proxy can attach a credential to requests for specific hosts and paths. It must restrict the request body and response too. An allowed API call can still reveal customer data or perform an unwanted action.

This design takes more work than handing over a token. It becomes useful when you need unattended execution with a narrow, repeatable set of operations.

## Replace long-lived deployment tokens where possible

With workload identity, a CI job can prove where it is running and obtain temporary access.

[GitHub Actions supports OpenID Connect](https://docs.github.com/en/actions/concepts/security/openid-connect) for exchanging a job's identity token for credentials from a compatible provider. This avoids storing a long-lived cloud credential in the repository's CI secrets.

The trust policy is essential. Restrict the accepted issuer and audience, and the subject or other supported claims that identify the intended repository and deployment context. A policy that trusts every branch or every repository defeats much of the benefit.

Grant the GitHub Actions `id-token: write` permission only to the job that needs federation. That job still requires narrowly scoped provider permissions.

Distinguish temporary access to a secret manager from the lifetime of the secret it returns. A permanent API key copied during a five-minute job can remain valid after the job ends.

A short-lived credential can do damage during its lifetime. If untrusted code runs in a job that can request production access, OIDC has not made that job safe.

If an OIDC subject identifies an environment rather than a branch, enforce the allowed branches through the environment's protection rules too.

Some deployment targets don't support the federation path you want. Use a separate, scoped deploy token there, keep it in the protected publisher, and document how to revoke it. Don't assume a platform supports OIDC because your CI system does.

## Make the public artifact explicit

The project folder contains source files, local configuration, dependencies, and build output. Only a defined part of it belongs on the public website.

For a static Astro site, the usual output is `dist/`. Configure the publisher to upload that directory. Avoid upload commands that derive their destination directory from an agent-generated argument or default to the project root.

Astro [copies files in `public/` into the build untouched](https://docs.astro.build/en/basics/project-structure/#public). A secret placed there can become a downloadable file even if Git ignores it.

The exact artifact boundary depends on the deployment target. An SSR deployment can have public assets and a server bundle. A container image has its own filesystem and layers. Identify what becomes downloadable, what executes privately, and what each part can read.

Inspect the final package after all build and packaging steps. A clean `dist/` doesn't help if a later ZIP command adds the rest of the workspace.

Source maps deserve a deliberate decision. They can reveal source code and embedded values. They aren't automatically secrets, and some applications publish them intentionally. Check their content and audience rather than assuming the `.map` extension is harmless or always forbidden.

## Keep private values out of browser code

Astro's [environment variable documentation](https://docs.astro.build/en/guides/environment-variables/) explains that `PUBLIC_` variables can be exposed to client-side code.

For the reading club, `PUBLIC_API_URL` can point at the server. The database password belongs on the server.

If the agent proposes renaming a private token to `PUBLIC_DATABASE_TOKEN` to fix an undefined variable, stop at that change. Move the privileged operation behind an authenticated server endpoint instead.

Then validate what that endpoint allows. A server proxy that accepts arbitrary database queries or arbitrary upstream URLs gives callers too much authority even though the password remains hidden.

Server-only names don't protect a value after code copies it into HTML, a JSON response, a log, or a browser bundle. Review where values flow, as well as how they are declared.

## Scan source and deployment output separately

I would use a maintained scanner for recognizable credentials, plus project-specific artifact rules.

[Gitleaks](https://github.com/gitleaks/gitleaks) can scan Git history and files on disk. With Gitleaks installed, run these from the project root:

```bash
gitleaks git --redact=100 --no-banner .
gitleaks dir --redact=100 --no-banner dist
```

The first checks Git history. The second checks the output directory, including generated files that might never have been committed.

Gitleaks returns a nonzero status when it finds leaks by default. Configure CI to fail on a finding or scanner error. Don't turn the command into a warning with `|| true` or `continue-on-error`.

Full redaction reduces exposure through reports. Restrict access to scanner logs anyway, and verify how the scanner handles your file types, archives, and size limits. Archive traversal requires its own configuration in Gitleaks.

The policy should come from a trusted location. An agent that can change scanner rules, add ignore entries, and approve its own build can weaken the check it is supposed to pass.

GitHub's [push protection](https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection) adds a guard before supported secrets enter a repository. Review bypass permissions and exceptions. It complements artifact scanning because it cannot inspect bytes generated only during a later build.

A scanner cannot recognize every credential. A custom token format, transformed value, or split string can escape detection. Reduce secret availability before depending on detection.

## Test the artifact boundary with fake secrets

You can test rejection without using a credential that grants access anywhere.

Save the following as `artifact-demo.mjs`. It creates a temporary build directory and checks an intentionally tiny file policy. The marker is invented.

```javascript
import assert from 'node:assert/strict'
import { mkdtemp, mkdir, writeFile, readFile, readdir, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'

const root = await mkdtemp(join(tmpdir(), 'artifact-demo-'))
const output = join(root, 'dist')
const marker = 'DEMO_ONLY_DO_NOT_USE'
const allowed = new Set(['index.html', 'app.js'])

async function check() {
  const entries = await readdir(output, { withFileTypes: true })
  if (entries.length !== allowed.size) throw new Error('Unexpected artifact entries')
  for (const entry of entries) {
    if (!entry.isFile() || !allowed.has(entry.name)) {
      throw new Error('Unexpected artifact entry')
    }
    const content = await readFile(join(output, entry.name), 'utf8')
    if (content.includes(marker)) throw new Error('Fixture marker exposed')
  }
}

try {
  await mkdir(output)
  await writeFile(join(output, 'index.html'), '<h1>Reading club</h1>')
  await writeFile(join(output, 'app.js'), `console.log('${marker}')`)
  await assert.rejects(check(), /Fixture marker/)

  await writeFile(join(output, 'app.js'), "console.log('Ready')")
  await writeFile(join(output, '.env.fixture'), `DEMO_TOKEN=${marker}`)
  await assert.rejects(check(), /Unexpected artifact/)

  await rm(join(output, '.env.fixture'))
  await check()
  console.log('Both unsafe fixtures rejected; clean artifact accepted')
} finally {
  await rm(root, { recursive: true, force: true })
}
```

Run it:

```bash
node artifact-demo.mjs
```

The example rejects the marker inside `app.js`, rejects an unexpected environment file, and accepts a clean fixture. It reads only files it created and doesn't print their contents.

This is a demonstration of two checks: **which files are present** and **what those files contain**. The exact two-file allowlist is for this fixture only. It is not a production scanner for a real Astro build.

For a real project, define rules around your actual output. Reject credential filenames, private key files, unexpected archives, and symlinks unless your packaging policy explicitly supports them. Inspect nested directories. Decide how hidden files and compressed assets are handled.

Add a synthetic marker test to the real pipeline too. Put it in a harmless test input that follows the same route as configuration, build the app, and assert that the public artifact doesn't contain it. Then deliberately place it in a browser asset and confirm that the check fails.

A generic secret scanner may not recognize your marker. Give the test an explicit marker check or a dedicated scanner rule, kept with the protected policy.

## Separate building from publishing

The build job executes repository code. It installs dependencies, runs scripts, and processes files that an agent may have changed.

The publisher should have a narrower job: receive a specific artifact, verify the required checks and approval, then publish it to a fixed destination.

```mermaid
flowchart TD
  accTitle: Separate an agent's changes from production publishing
  accDescr: The agent proposes source changes. A build without production credentials creates an artifact. Checks and review bind approval to that artifact before an isolated publisher deploys it.
  A[Agent proposes source changes] --> B[Build with development access]
  B --> C[Inspect and scan final artifact]
  C --> D[Review source and artifact identity]
  D --> E[Approve this release]
  E --> F[Isolated publisher deploys artifact]
  G[Protected deployment credential] --> F
```

For the reading club's static site, I would give the build no production deployment credential and no production database password. The publisher would receive only the approved output and the credential required to update that site.

Use a fresh runner or another real isolation boundary between those jobs. Separate steps on one compromised machine can still share files or leave processes behind. A long-lived self-hosted runner needs particular care.

The publisher must not run the repository's install or build scripts after receiving the credential. It should use trusted publishing code and configuration. Some deployment commands perform a build, load plugins, or execute hooks automatically; check yours before treating it as an upload-only operation.

For private package downloads, a build may need a credential. Give it a separate read-only credential limited to the package registry it needs. That is a reason to scope build access carefully, rather than hand it the deployment identity too.

GitHub's [Actions security guidance](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions) covers untrusted code, token permissions, third-party actions, and runner security. Pin third-party actions to reviewed full commit SHAs and control updates to those references.

## Bind approval to the bytes being released

An approval should identify the source commit, artifact, destination, and relevant check results.

If the agent changes code or configuration after review, require a new review. If the pipeline rebuilds after approval, the resulting artifact may differ from the one you inspected.

Record the intended repository and build run alongside an immutable artifact identity and a cryptographic digest. Verify those against a protected release record, rather than accepting whichever artifact ID and checksum a caller supplies. The publisher should verify it and reject a mismatch. A digest proves which bytes you have; it doesn't prove those bytes are safe.

[GitHub's artifact documentation](https://docs.github.com/en/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/storing-and-sharing-data-from-a-workflow) describes digest validation during downloads. It also states that a mismatch produces a warning. If your policy requires blocking publication, add an explicit failure on mismatch rather than relying on that warning.

A signed artifact or provenance record can strengthen the link to its build identity. Keep the signing and verification policy outside the agent's control. Signing a malicious build preserves evidence of its origin without making it trustworthy.

## Protect the gate from the agent

The agent should be able to propose changes to deployment configuration. It should not be able to remove the controls that authorize those changes without independent review.

Protect workflow files, publishing scripts, scanner policies, and infrastructure permissions. Require the appropriate code owners or reviewers for those paths. Ensure the agent's repository token cannot bypass the branch rules or impersonate the reviewer.

On GitHub, [protected branch rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches) have bypass settings worth checking. An account with enough administration access can undermine a carefully written review policy.

A protected deployment environment can delay a job until required review completes. GitHub documents when [environment secrets become available after approval](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/review-deployments). Availability of protection rules depends on the plan and repository visibility, so verify it for your repository.

For larger setups, put publishing policy in a separately controlled service or reusable workflow. The important question is who can change it and what code gets to execute with the production credential.

Don't run untrusted pull-request code in a privileged context just because you need a convenient comment or status check. In particular, review uses of `pull_request_target` and privileged follow-up workflows before checking out or executing a contributor's code.

## Deployment access can become runtime secret access

This is the boundary I would pay the most attention to for a server application.

[Cloudflare Worker secrets](https://developers.cloudflare.com/workers/configuration/secrets/) hide their values in the dashboard and Wrangler after they are set. Worker code receives the values at runtime.

Therefore, someone who can deploy replacement Worker code may be able to make it read and disclose an existing binding. That is a consequence of the runtime access model, even if the deployment API doesn't offer a “read secret” operation.

The same question applies anywhere your application runs with an identity that can fetch secrets or access private data.

```mermaid
flowchart TD
  accTitle: Deployment authority can reach runtime secrets
  accDescr: An agent allowed to change production server code can deploy code that reads a runtime secret and sends it in a response. Protecting access to secret values alone does not block this route.
  A[Agent can replace server code] --> B[New code runs in production]
  B --> C[Runtime identity can read secret]
  C --> D[Code can return or transmit value]
```

For the reading club, keep the server's database role limited to the operations it needs. Review changes to authentication, response bodies, outbound requests, and logging before those changes reach production.

If the agent only needs to update static pages, give it a publishing path that cannot replace the privileged backend. Separate applications or deployment identities can make that restriction concrete.

For a backend release, independent review of executable code is part of protecting its secrets. Scanning static assets cannot catch every runtime disclosure route.

My [Cloudflare secrets and environments guide](https://flaviocopes.com/cloudflare-workers-secrets-environments/) covers configuring those bindings. Their placement solves storage and delivery; deployment policy controls which code gets to use them.

## Restrict outbound access where it helps

A process that can read a credential and make arbitrary network requests has an easy disclosure route.

For controlled agent environments, restrict outbound destinations to the services needed for the task. Apply this at the network, proxy, or tool boundary. Check DNS access and alternate network paths as well as HTTP requests.

This is useful defense in depth, but allowed destinations also need scrutiny. A Git hosting service can receive arbitrary repository content. An allowed logging endpoint can receive a credential in a log. A malicious response from your own app may be enough to leak data without a new external domain.

A credential-injecting proxy should attach secrets only for approved hosts and operations. It should also handle redirects safely so credentials don't follow a redirect to another destination.

If you allow unrestricted shell access, check whether the agent can change proxy settings or bypass the network policy. The restriction needs to be enforced outside the process being restricted.

## Don't hide leaks with masking

Log masking is useful for known values. It cannot guarantee that every encoded, shortened, or transformed version will be removed. GitHub's security guidance explicitly warns that automatic redaction is not guaranteed.

Avoid dumping the whole environment, request headers, or full configuration while debugging. Prefer a check such as “database credential is configured” that returns a status without its value.

Look beyond CI stdout. Secrets can reach test reports, crash dumps, screenshots, tracing systems, agent transcripts, preview deployments, caches, and retained build artifacts.

Restrict who can access those systems and how long they retain data. When a scanner finds a secret, report a rule identifier and location with the value redacted. Don't paste the value into an issue to prove the finding.

## Watch container builds too

If you deploy containers, deleting a secret in a later Dockerfile instruction may leave it in an earlier image layer.

Docker's [build secret documentation](https://docs.docker.com/build/building/secrets/) recommends secret mounts rather than build arguments or environment variables for build-time credentials.

A secret mount limits persistence through the build mechanism. The command using the mount can still copy the value into a file, print it, or send it over the network. Review the Dockerfile and the scripts that run while that mount is available.

Keep local secret files out of the build context using `.dockerignore`, and inspect the final image as well as repository files. Multi-stage builds help control what reaches the runtime image, but only if the files copied between stages are the ones you intended.

## A practical set of guards

For a small application, I would start here:

| Guard | A useful test |
| --- | --- |
| Dedicated agent workspace with development credentials | File and shell tools cannot read a marker outside the allowed workspace |
| Separate production identity | Development credentials cannot access production resources |
| Explicit public output directory | A fake `.env` file in the wrong location blocks publication |
| Source and artifact scanning | A known synthetic detection fixture causes a failed check |
| Isolated publisher | Repository scripts cannot run with the deployment credential |
| Independent production approval | The agent cannot approve or bypass its own release |
| Artifact identity verification | A modified artifact is rejected after approval |
| Restricted runtime authority | The application cannot access unrelated databases or secrets |

Then add controls that match what you operate. A team handling sensitive data may need network restrictions, dynamic credentials, stronger audit retention, and a separately administered release service.

Test the negative cases. Try an incorrect destination, an expired credential, a changed artifact, and an unauthorized production request. Verify that the system refuses them without revealing values in the error output.

Use fake values for disclosure tests. Test authorization with a real restricted identity against a harmless protected resource, or use the provider's policy simulator. An invalid dummy token cannot prove that the actual development identity lacks production access.

## If a secret has already leaked

Revoke or rotate the credential at its provider. Treat deleting the file as cleanup, not proof that nobody copied it.

Remove the exposed deployment and check other versions, previews, downloaded artifacts, and caches. Follow the hosting provider's cache-removal process where needed.

Review access logs for the credential and the resources it could reach. Look for use outside expected workloads, unexpected data access, and changes to permissions or identities.

Update legitimate consumers to the replacement credential. Where the leaked value can create other credentials or sessions, investigate and invalidate those too. The response depends on what that particular secret authorized.

Clean repository history, CI logs, and retained outputs where appropriate, while preserving incident evidence in a restricted location. My [guide to exposed GitHub credentials](https://flaviocopes.com/git-secrets/) covers the Git side of that recovery.

Finally, add a test at the boundary that failed. If `public/.env` shipped, make the artifact policy reject it. If a privileged job executed untrusted code, fix that execution path before restoring its access.

## How I would set this up for the reading club

I would let the agent work freely in a development environment with synthetic member data. It could edit the app, run tests, and prepare a preview using development-only resources.

Production database access would belong to the server identity. Deployment access would belong to an isolated publisher with a fixed destination. The agent could prepare the release, but an independent review would authorize the server code and the exact artifact being published.

I would first test an accidental secret copy, an artifact changed after approval, and an attempt to deploy an unapproved backend change. Those failures tell me more about the setup than a successful deployment does.

If you want to work through the underlying concepts, my free [Security Fundamentals course](https://flaviocopes.com/courses/security-fundamentals/) covers trust boundaries and incident response. The [Software Supply Chain Security course](https://flaviocopes.com/courses/software-supply-chain-security/) follows the path through dependencies, CI, artifacts, and publishing.
