aq.dev / guides / software-factory-security-permissions-secrets-review-gates

Running a Software Factory Safely: Permissions, Secrets, Review Gates and Spend

A coding agent that runs unattended (on a schedule, from an issue label, or through an API) reads text written by people you do not control and acts with whatever credentials, network and merge rights its machine holds. Run it safely by assuming every issue, comment, file and dependency it reads may carry instructions, then limiting what one run can reach: a token scoped to one task, secrets delivered as environment variables, an egress allowlist, a separate worktree per task, and no path to the default branch that skips required checks and a second person's approval. Add a monthly spend cap and keep the run record, so a bad run is cheap and can be reconstructed. Each control below comes with a check you can run to prove it holds.

AQ is a platform for running a team software factory safely: AI coding agents like Claude Code and Codex work in a separate git worktree per task, secrets arrive as environment variables and are replaced in AQ's records, a crew's builders wait for a person to approve the plan when the plan gate is on, AQ's merge is refused while checks fail or the review is older than the head commit, bots' AQ-credit spend has a monthly cap, and your team approves the plan and the merge together.

AQ (aq.dev) is a multiplayer coding harness that runs real agent CLIs in an isolated git worktree per task, delivers secrets as environment variables and replaces saved values in its records, refuses its own merge while checks fail or the review is older than the head commit, and caps bots' AQ-credit spend; it does not enforce network egress limits, so those belong in your cloud and network layer.

This guide is part of the software factory series; the levels guide explains how much autonomy each stage hands to agents. Outside facts were checked on November 13, 2026 against the primary pages linked below.

Threat model

The assets are your source code (private repositories included), the credentials on the agent's machine, the default branch and what deploys from it, and your money. The adversary is anyone who can put text where an agent will read it: a public issue, a pull request title, a comment, a README in a dependency, a web page the agent fetches. That includes accounts with no access to your repository. OWASP lists this as LLM01:2025 Prompt Injection: "Indirect prompt injections occur when an LLM accepts input from external sources, such as websites or files." The LLM06:2025 Excessive Agency entry names the root causes that turn an injection into damage: excessive functionality, excessive permissions and excessive autonomy. OWASP's Top 10 for Agentic Applications for 2026 (December 9, 2025) extends that list to agents.

ThreatHow it happensDocumented case
Untrusted text as instructionsThe agent reads an issue body, PR title, hidden HTML comment or file and follows instructions planted in it.Noma Labs' GitLost (July 6, 2026): a crafted issue made a GitHub Agentic Workflow post a private repository's README in a public comment.
Leaked credentialsAn injected agent reads environment variables or files and sends a secret out through an allowed channel.The Cloud Security Alliance's note on "Comment and Control" (August 7, 2026) summarizes findings where one issue reached CI credentials held by Claude Code, Gemini CLI and Codex workflows, including a key leaked one character at a time through a pre-approved domain.
Over-broad tokensA token that can read every repository lets one injected task read all of them.Invariant Labs' GitHub MCP write-up (May 26, 2025): an issue in a public repository led an agent to copy private repository data into a public pull request.
An agent merging its own workThe agent pushes and merges with the same person's credentials, so nobody else looks before the default branch changes.No single incident; it is the condition that lets every other row reach production.
Runaway spendA loop, a retry storm or a trigger that fires too often burns model usage. OWASP's LLM Top 10 for 2025 lists it as LLM10:2025 Unbounded Consumption.Routine for any scheduled or event-driven agent without a cap.
Supply-chain changesAn agent adds or upgrades a dependency, or a compromised package runs install scripts on the agent's machine.Wiz's s1ngularity analysis (August 27, 2025): malicious Nx releases prompted installed AI CLIs with permission-skipping flags to search the disk for secrets.
Data leaving the boundaryEverything in the agent's context goes to the model provider, and any open network path can carry it elsewhere.Covered in depth in keeping secrets out of coding agent prompts.

Boundary and assumptions

Draw the boundary around one run: the agent process, its tool commands, the worktree, the environment variables and the network it can reach. Everything inside can end up in the model's context and in an outbound request. The controls in this guide rest on these assumptions; when one is false, the controls that depend on it stop holding.

  • The model can be steered. Prompt-level guardrails reduce the rate, not the possibility. GitLost got past GitHub's guardrails by adding the word "Additionally,".
  • Anything the run can read, the run can send. An environment variable keeps a secret out of the prompt you write, not out of the agent's reach.
  • Approval prompts do not exist when nobody is watching. Unattended runs need gates that hold without a person in the loop: scopes, branch rules and caps.
  • Your Git host enforces merge rules, not the agent tool. If the agent's credential can merge without review on GitHub, a button in any tool that refuses to merge does not stop a gh pr merge from the shell.
  • The run record lives outside the run. If the agent can edit its own logs, they are notes, not an audit trail.

Ten controls, each with a check

Each check is meant for a test repository and fake credentials with no privileges outside the test. A check you cannot observe (a block that leaves no log, for example) counts as inconclusive, not as a pass.

1. Least-privilege tokens per agent

Give each agent, or each routine, a token that reaches only the repositories and permissions its job needs. GitHub "recommends that you use fine-grained personal access tokens instead of personal access tokens (classic) whenever possible," and each fine-grained token "can be further limited to only access specific repositories" (GitHub docs). Apply the same rule to API keys for the tools that start agents.

Check: from the agent's shell, ask for a repository outside its scope. A 404 is the pass.

curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Authorization: Bearer $GH_TOKEN" \
  https://api.github.com/repos/YOUR_ORG/REPO_OUTSIDE_SCOPE

Fails when: the agent's machine also holds a personal login with wider access (a gh auth login session, an SSH key), and the agent uses that instead.

2. Secrets as environment variables, never in prompts

Deliver secrets to the process as environment variables or files, never by pasting them into a message, an issue or a task description, and keep production secrets off the machine entirely when the task does not need them. A pasted secret is sent to the model provider and stored in your AQ transcript from that moment.

Check: plant a unique fake value (for example FAKE_KEY_7f3a91) as a secret, run a task that prints it, then search every place the run is recorded: the transcript, run output, commit messages and pull request text. The only acceptable hits are redacted placeholders.

Fails when: the value is encoded, split or transformed before it is printed. Redaction by exact match does not recognize a base64 copy or a value sent one character at a time, which is how the Comment and Control key left.

3. Network egress limits

Allow outbound connections only to destinations someone chose: the model API, the package registry, your Git host. Vendors that ship this default it on: Codex states that "By default, the agent runs with network access turned off" (Codex agent approvals and security). Allowlists need care too: the Comment and Control key left through a domain that had been pre-approved.

Check: from every process type the run starts (the agent's shell, a setup script, an MCP server, a package install), request a destination that is not on the list and confirm it fails and is logged.

curl -s -m 5 -o /dev/null -w "%{http_code}\n" https://example.org/ || echo blocked

Fails when: the rule covers only the agent's shell tool and not the other processes. The secure workspace guide covers process coverage and the cloud-layer controls in detail.

4. An isolated worktree per task

Run each task in its own git worktree on its own branch, so a half-finished change in one task cannot leak into another and abandoning a task abandons only its branch. A worktree separates files and branches; it does not separate processes, credentials or network access on the same machine.

Check: start two tasks on one repository and confirm each runs in a different directory on a different branch.

git worktree list   # one line per task, each on its own branch

Fails when: two runs share a checkout, or a task writes outside its worktree (to a shared cache or the home directory) where the next task reads it.

5. A plan gate before edits

For larger changes, have the agent propose its steps and wait for a person to approve them before anything that can edit code starts. The gate turns "the agent misread the issue" into a rejected plan instead of a pull request.

Check: give a gated run a task, leave the plan unanswered, and confirm the branch has no new commits and no edit step has started.

Fails when: the gate covers only some paths (delegated workers, say) while the planning agent can still edit directly, or when an unattended trigger auto-approves its own plan.

6. Independent review on another vendor

Have a second agent review the diff on a different model vendor and account than the one that wrote it, so one model's blind spots or one compromised session do not author and approve the same change. Treat the agent review as a filter in front of the human review, not a replacement for it. The self-review problem explains why the author grading its own work misses things.

Check: on a reviewed change, read which vendor and account the review ran on and compare it to the author's.

Fails when: no other vendor is connected and the review quietly falls back to the same one, or the review is older than the latest push.

7. Branch protection and required checks

This is the control that stops an agent merging its own work. On GitHub, protect the default branch with "Require pull request reviews before merging", "Require status checks to pass before merging" and "Require approval of the most recent reviewable push", which requires "that the most recent reviewable push must be approved by someone other than the person who pushed it." Because an agent usually pushes with a person's credentials, that setting forces a second person. Turn on "Do not allow bypassing the above settings"; without it, GitHub says the restrictions do not apply to admins (GitHub protected branches).

Check: from the agent's shell, with its own credentials, try to merge an unapproved pull request whose checks fail. A refusal is the pass.

gh pr merge TEST_PR_NUMBER --squash   # expect: base branch policy prohibits the merge

Fails when: the credential belongs to an admin and bypass is allowed, or a workflow file the agent can edit is itself a required check.

8. Human approval for risky paths

Some changes need a named owner no matter how good the review is: authentication, billing, infrastructure, CI workflows, database migrations and dependency manifests. Put those paths in a CODEOWNERS file and require review from code owners in branch protection, so a change to them waits for that owner.

# .github/CODEOWNERS
/src/auth/            @your-org/security
/src/billing/         @your-org/payments
/infra/               @your-org/platform
/.github/workflows/   @your-org/platform
/migrations/          @your-org/data
package.json          @your-org/platform
package-lock.json     @your-org/platform

Check: open a test pull request that touches one line under /migrations/ and confirm GitHub asks for that team's review before merge.

Fails when: a path is missing from the file, or the agent can edit CODEOWNERS itself without its owner's review (list the file in itself).

9. Spend caps

Set a monthly cap per organization, per person and per scheduled job, and a ceiling per run where your tools support one. A cap turns a runaway loop into a skipped run and a notice.

Check: set a test job's cap below the cost of one run, trigger it, and confirm the run is skipped and someone is told.

Fails when: the cap covers only one way of paying (credits, say) while the same job can also draw on a subscription or an API key the cap does not see.

10. An audit trail

Keep, outside the agent's reach, a record of what started each run, which identity it ran as, what it read and ran, what it changed, what it cost, and every change to a job's configuration. The diff alone does not show that an agent read a file it should not have.

Check: pick a merged agent change and reconstruct, from records only, who or what triggered it, the commands it ran, and who approved the merge. Anything you have to guess is a gap.

Fails when: records live on the same machine and account the agent controls, or secrets are recorded in clear text and the trail itself becomes a leak.

Failure modes to plan for

  • Guardrail bypass by rewording. Prompt filters on the task text miss instructions found later in files and command output. Plan for injection to succeed and rely on scopes and gates.
  • Allowed channels as exfiltration paths. An allowlisted domain, a public comment, a pull request body or a commit message can carry data out.
  • Setup scripts with more power than the agent phase. A dependency install with network and credentials runs third-party code before any agent rule applies.
  • Fallbacks that weaken a control. A reviewer that falls back to the author's vendor, a cap that sees one funding source, a gate that only some paths pass through.
  • Standing approvals. "Always allow" answers accumulate; review them like any other permission.

A finding is not a fix: one dated example

Security work in a software factory has three stages that are easy to blur: detection (a scanner or an agent reports a possible vulnerability), validated triage (someone shows the issue is real and reachable in this codebase), and remediation (a change that stops it, retested against the same exploit). An agent that reads a scanner report and opens a pull request has done the first and third without the second, and a "fixed" finding with no reproduction cannot be told apart from a guess.

Factory's November 2026 announcements draw the same line. Security Factory (November 10, 2026, in Private Preview) describes three stages: signals consolidated from existing scanners, triage that assesses reachability and validates exploitability in sandboxes "for reproducing vulnerabilities and testing exploit hypotheses," and remediation where agents develop fixes, "test them against the original exploit conditions" and check for regressions. The Cyber Defense Network (November 12, 2026) feeds findings from partners such as Snyk, Palo Alto Networks and Semgrep into that flow. Neither page sets out a required evidence format, so the bar below is this guide's, not Factory's. AQ is not part of either and has no integration with them.

Before an agent-made security fix merges, require this evidence on the pull request:

  1. Reproduction. A test or script that shows the issue on the commit before the fix (the exploit succeeds, the unsafe value is accepted).
  2. Retest against the original exploit. The same reproduction, unchanged, failing to trigger the issue on the fixed commit.
  3. Regression test. The reproduction committed as a test, so a later change that reopens the hole fails CI.
  4. The normal suite and required checks. A fix that breaks something else is not done.

Check: run the committed reproduction on both commits and compare.

git checkout BASE_COMMIT && ./repro.sh    # expect: issue reproduced
git checkout FIX_COMMIT  && ./repro.sh    # expect: issue blocked

A finding with no reproduction stays a detection, whether a scanner, a partner network or an agent's own scan produced it. AQ's security scans report findings with severity, evidence and a suggested fix; hold them to the same bar.

A dated architecture example: Ubie's cloud agent platform

Ubie's CTO, Yuku Kotani, described the company's in-house agent platform in an article published November 12, 2026 (in Japanese). Everything in this paragraph and the list below is Ubie's description of its own system:

  • Sandbox as a tool. The agent harness runs outside the sandbox and calls it only when it runs a tool such as bash, read or write. Each session gets its own microVM, built on Kata Containers.
  • No credentials in the sandbox. The sandbox holds placeholders only. All egress goes through a proxy that attaches credentials only to requests for approved destinations, controlled per host, path and GraphQL operation; on GitHub, writes outside Ubie's own organization are blocked.
  • Personal and shared agents. A personal agent acts with the requester's own OAuth permissions and cannot be called from a shared place such as Slack. A team agent can be called from Slack, uses a service account instead of anyone's personal permissions, and derives who may see the session (Slack channel members, for example) to pick an information classification, with one service account per classification. Ubie also restricts the region and the model used for regulated or especially sensitive data.
  • Durable session, disposable execution. The session survives a crash and resumes; the sandbox keeps work on an attached disk for a period and is then cleaned up, and Ubie names that disk as the cost driver. The agent keeps responding when a sandbox fails.

Checks a reader can run against a design like this, on test accounts:

  • Placeholder leakage. Inside the sandbox, print the credential variables and search the disk for the real token's prefix; expect placeholders only. Send a request carrying the placeholder to an endpoint you control that is not approved, and confirm it arrives as the placeholder or is blocked, never as the real value.
  • Disallowed writes. From the sandbox, try a write to an unapproved host, an unapproved path on an approved host (an issue in another organization's test repository, for example) and an unapproved GraphQL mutation. Expect the proxy to refuse each one and log it.
  • Shared-audience access. Call a team agent in a channel that includes someone outside the narrowest classification and ask for a document only that classification may read; expect a refusal. Remove that person and confirm access returns.
  • Sandbox replacement. Write a marker file, destroy the sandbox mid-session, then send a message. Expect the conversation to continue in a new sandbox, and record which files survived and which were lost.

AQ does not enforce these controls: it has no credential-injecting egress proxy, no per-host, per-path or GraphQL-operation write rules, no region or model restriction by data class, no per-session microVM and no audience-derived information classification. An AQ agent runs in a work item's worktree on its machine with the person's own logins and the secrets it was given as environment variables. Teams that need these controls build and verify them in their own cloud and network layer.

Where AQ fits

AQ is a platform for running a team software factory safely: AI coding agents like Claude Code and Codex work in a separate git worktree per task, secrets arrive as environment variables and are replaced in AQ's records, a crew's builders wait for a person to approve the plan when the plan gate is on, AQ's merge is refused while checks fail or the review is older than the head commit, bots' AQ-credit spend has a monthly cap, and your team approves the plan and the merge together.

AQ is where teams put AI agents to work on reviewed pull requests and recurring jobs, using the agents and subscriptions they already have on private cloud machines of their own. It can start work unattended: bot routines fire on a schedule, a Linear label, GitHub events or a webhook, and the API and MCP server start and steer agents. This is what AQ does for each control today, and what it leaves to you.

ControlWhat AQ doesWhat it does not do
TokensAPI keys are scoped and act as the person who created them; a key without a scope gets 403 insufficient_scope. Bot access has its own ladder (bots:read, bots:operate, bots:manage). A routine runs as the person who created it, on their accounts.Agents commit and push with the GitHub login of the person driving the work item, so the agent can do on GitHub what that person can. AQ does not mint a narrower GitHub token per task; branch protection is the gate.
SecretsSecrets are stored encrypted, delivered to agents, terminals and previews as environment variables, and never shown again by any AQ screen or API. Saved values are replaced with [secret: NAME] in transcripts, run output, commit messages and pull request titles. A pasted key-shaped value prompts an offer to save it as a secret instead.Replacement applies to what AQ stores and shows, by exact value. A value the agent prints still reaches the model provider in that turn, and an encoded or split value is not recognized.
EgressAgents run on a machine that belongs to one person or team: your own Mac on the Free plan, your own cloud or an AQ-managed machine on Pro.Current public AQ docs do not establish a per-workspace default-deny egress allowlist. An AQ shell has whatever network position its machine has; configure and verify egress in your cloud and network layer. See the secure workspace guide.
WorktreesEvery work item gets its own git worktree and branch (workspaces).Work items on one machine share its processes, network and the logins on it.
Plan gateA crew with the plan gate on waits for Approve, Edit the plan or Run in one thread instead; until approved, no role that can edit code starts.The gate holds delegated roles. It does not stop the Lead's own thread from editing, and a thread outside a crew has no plan gate.
Independent reviewWhen Auto chooses a crew's reviewer, it prefers another vendor and account than the builder's.It is a preference: with no other vendor connected, the review can run on the same one. Check which model ran it.
Merge gateAQ's Merge (a GitHub squash from the Changes panel or the pull request card) is refused, with the reason, while checks are pending or failing, the head commit is unknown, or the review is older than the head.It applies to AQ's own button. An agent with a shell and the person's GitHub login can still merge on GitHub unless branch protection stops it.
Risky pathsConnected accounts (Slack, Linear, Gmail and others) keep a per-tool Allow, Ask or Off setting, and an Ask waits for the account's owner.AQ has no path-based approval for code. Use CODEOWNERS and required code-owner review.
SpendA bot's monthly cap covers what its routines spend in AQ credits, with an optional ceiling per run; at the cap the next run is skipped and you are told once.Usage on a connected subscription (your Claude or ChatGPT plan) is billed by that vendor under that plan's own limits; the bot cap covers AQ credits only.
AuditEvery turn's tool calls and their output (long output shortened) stay in the thread; each routine run records why it woke, what it did, what it produced and what it cost; changes made to a bot through the API or MCP show who made them and can be undone. Each agent turn gets a signed execution identity token your own services can verify.AQ's records are not a SIEM; export what you need to your own log store if policy requires it.

What we checked in AQ

On November 13, 2026 we ran three of the checks above from inside an AQ work item, on an AQ cloud machine, with the agent's own shell. You can run the same commands in your own work item.

# 4. Isolated worktree: the work item has its own checkout and branch
git rev-parse --show-toplevel
git rev-parse --abbrev-ref HEAD

# 3. Egress: an outbound request to an outside site
curl -s -m 10 -o /dev/null -w "%{http_code}\n" https://example.org/

# 10. Audit: verify the turn's signed identity token with AQ
T=$(python3 -c 'import json,os;print(json.load(open(os.environ["AQ_IDENTITY_FILE"]))["token"])')
curl -s -H "AQ-Identity-Token: $T" "$AQ_IDENTITY_VERIFY_URL"

Results:

  • Worktree: the checkout was a directory of its own, on a branch of its own, apart from the other work items on the machine. The check passed.
  • Egress: the request to example.org returned 200. That is the expected result for AQ today, and the reason egress limits belong in your own network layer.
  • Identity: the token was accepted (HTTP 200, valid) and named the same work item and run that AQ had written into the identity file. The same token with one character changed was refused (HTTP 401). A deploy or lease system can attribute an action to an AQ run this way without trusting environment variables.

For the machine and cloud layer under these controls, read how to secure a cloud dev workspace for coding agents. For what reaches the model and how to clean secrets out of transcripts and approval files, read keeping secrets out of coding agent prompts. Plans and machines are on the pricing page.

Frequently asked questions

How do I protect a coding agent from prompt injection in GitHub issues?

Assume injection will sometimes succeed and limit what a successful one can do. Give the run a token scoped to the one repository it works on, keep secrets it does not need off its machine, restrict outbound network to chosen destinations, and require a second person's approval before anything merges. Prompt-level filters help but have been bypassed by rewording, as Noma Labs showed with GitLost in July 2026.

What permissions should a coding agent have?

Only what its task needs: read and write on one repository, no admin rights, and no ability to merge to the default branch without review. On GitHub, use a fine-grained personal access token limited to specific repositories, which GitHub recommends over classic tokens. Check it by asking for a repository outside its scope from the agent's shell and expecting a 404.

How should coding agents get secrets in CI or unattended runs?

As environment variables or files delivered by the platform, never pasted into a prompt, issue or task description. Keep production secrets out of runs that do not need them, and plant a unique fake value to confirm your transcripts, run output and commit messages show only a redacted placeholder. Remember that an environment variable is still readable by the agent, so egress limits matter too.

Can an AI coding agent merge its own pull request?

It can if its credentials allow it. On GitHub, turn on required reviews, required status checks, "Require approval of the most recent reviewable push" and "Do not allow bypassing the above settings". The most-recent-push rule requires approval from someone other than the person who pushed, and an agent usually pushes as a person, so a second human has to approve.

Does AQ limit an agent's network access?

Current public AQ docs do not establish a per-workspace default-deny egress allowlist. An AQ shell has whatever network position its machine has, so teams that need destination allowlists configure and verify them at the VM, cloud and network layer.

How do I stop a scheduled coding agent from running up costs?

Set a monthly cap per job and a ceiling per run, and test that a run over the cap is skipped and someone is told. Make sure the cap sees every way the job can pay; a cap on credits does not cover usage billed to a connected subscription. In AQ, a bot's monthly cap covers what its routines spend in AQ credits.