← Architecture showcase

UNDER THE HOOD / SECURITY AND APPROVALS

AI investigates.
People and code decide.

People sign in with Google and act within their role. An approval is bound to the exact proposal. The service that talks to AI models holds no credential that can change GitHub or the shop; a separate service makes every approved write.

SIGN-IN AND ROLES

Who can do what?

People sign in with Google through Firebase Auth. triage-api verifies the ID token on every request. Roles live in the database. The UI hides controls by role, and the API enforces them.

viewer

The lowest role. It can look at runs. It cannot start a run, decide or administer.

operator

Can start runs and trigger faults.

The console has no fault switch yet; an operator turns faults on from the command line.

approver

Can approve action plans. Can also verify, deprecate, re-verify or clear taint on knowledge.

admin

Everything above, plus user management and the environment reset.

User management is built. The environment reset is a command-line target; its admin page is Phase 6.

Only Google sign-ins with a verified email are accepted. Email-link sign-in for guests without Google accounts is deferred.

APPROVALS

How is approval tied to the exact proposal?

An approval counts for the exact proposal it was given for. If the proposal changes, the approval no longer applies. How the gate sits in the graph is on the Agent workflow page.

Built

At the gate

  • A decision is bound to a digest of the exact proposal: the action kind, the evidence IDs, the mitigation, the patch and the base commit.
  • It is accepted only while the run is paused and the digest matches. Otherwise the endpoint returns 409 and records nothing. A second decision also gets 409, and the gate does not run again.
  • The reviewer comes from the verified sign-in token on the server. The client never supplies it.

Built

Before each live action

  • One approval covers the whole action plan: the mitigation and the pull request.
  • The approval also records the base commit, and it expires 15 minutes after the decision.
  • Just before each action, code re-checks the digest, the run, the expiry and, with live tools, the deployed commit. action-runner checks the stored approval again. A failed re-check ends the run blocked before that action; an action that already ran stays applied.

THE WRITE SERVICE

Why can't an AI investigator change production?

GitHub and shop writes happen outside the process that talks to models. The AI-facing API holds no write credential for GitHub or the shop.

One service holds the write keys

action-runner holds the GitHub App key and the mitigation client, and makes every write. Cloud Run IAM lets only triage-api and the operator call it, and it checks the caller's email per route.

A database role each

Each service has its own database role. triage-api cannot write action-runner's write_operations table, and action-runner can only read approvals and runs.

Read-only model tools

Models get four read-only tools. They propose catalog actions with bounded parameters and find-and-replace edits to allowed files; code checks both before the gate and again before writing.

Pull requests, never merged

The app opens pull requests but never merges them.

The limit: triage-api still writes approval rows, so a compromised triage-api could forge an approval. The boundary is that it holds no write credential, and action-runner checks the stored approval against the exact proposal.

AUDIT AND REDACTION

What is recorded, and what is hidden?

Every run leaves a trail. We describe the log as append-only for the application, and we claim nothing stronger.

Audit log

  • A run writes run_started, gate_raised, decision_recorded and run_finished, with the approver's email on the decision.
  • A change to a person's role is audited too.
  • A blocked action and each flagged piece of evidence are audited too.
  • A database trigger refuses UPDATE, DELETE and TRUNCATE on the audit table. The trigger, not a grant, enforces this: the API's database role holds DELETE on every triage table, the audit table included.

An administrator can still disable the trigger. Successful writes are recorded in action-runner's own table, not as audit rows. An audit view with a span timeline is planned for Phase 5.

Redaction

Deterministic patterns remove secrets and personal data. The API applies them to incident text, operator notes and decision comments before any of it reaches ADK. The run's stored events are redacted too.

Tested

Built

Four planted secret types, each of which appeared in none of the stored run data or model requests:

  • an API key
  • an email address
  • a password inside a connection string
  • an AWS key

Not scanned: the budget ledger and ADK's other tables. Only these four types were planted. A provider error containing password=... is also stored redacted; one pattern was tested.

Tool output

Built

Every fetched log line, code file and commit is redacted before it is trimmed and before any model sees it. With that in place, prompt and response content is logged to BigQuery on staging.

Prompt-injection screening of the same tool output is a separate control; see the OWASP table below.

BUDGETS

How is spend capped before a call?

Every model call reserves from the run's budget before it is sent, and that includes retries and fallbacks. If the run cannot afford the call, it is refused and the run ends budget_stopped with no model call.

Ceilings persist across a pause and resume. Cloud Billing budgets stay only as an alert-level backstop.

Each attempt's cost is estimated from a versioned price table and stored, so a run's spend is measured. On staging, each run's summed cost matched its budget ledger. It is an estimate, not the provider's invoice.

OWASP AGENTIC TOP 10

How does it map to the OWASP Agentic Top 10?

How the controls above map to the OWASP Top 10 for Agentic Applications 2026. Addressed means the main routes into this app are covered and tested; anything listed as still open is bounded by another control. Partly means a known route stays open that no control bounds, and the last column says which.

Each OWASP agentic risk, its coverage, the controls and what stays open
RiskCoverageHow Beagle addresses itStill open
ASI01 Agent Goal HijackPartlyTool output and incident text are screened for injection, and flagged items are labelled as data. A proposal that cites flagged evidence as support fails validation. The graph is fixed, and a person approves every action.
Details
  • Every tool result (log lines, code, commit messages) is screened just before it is registered as evidence. The incident text and the operator note are screened when a run is admitted.
  • A flagged item reaches every later agent with a label saying it is data, not instructions. Each flag is written to the audit log and shown at the approval gate.
  • The taint check fails a proposal that cites flagged evidence as support for a standing hypothesis, or as the change a revert undoes. The run then ends unresolved instead of reaching the gate.
  • Code nodes do the routing on a fixed graph, so no agent can change where the run goes next.
  • Two golden eval cases test the flow: a planted log line that the model ignores, and one that it cites.
The screen is five regular expressions; a paraphrase gets past it. Specialists' findings are not screened again.
ASI02 Tool Misuse and ExploitationAddressedModels get four read-only tools with validated arguments. Mitigations are catalog actions with bounded parameters. Every model attempt reserves budget first.
Details
  • The four tools read logs, code, recent changes and the knowledge base. Each specialist gets one; the follow-up agent gets all four. The synthesizer, critics, reviser and postmortem agent get none.
  • Arguments are validated before a tool runs: allowed services and corpora, bounded time ranges, and file paths under app/ or config/ with no parent-directory steps.
  • The follow-up agent's limit of three tool calls is enforced in code, not only in its prompt.
  • Mitigations come from a catalog with bounded parameters and a rollback held in code. action-runner checks them again before it writes.
  • Retries, fallbacks and repairs each reserve budget before they are sent. Tool arguments are redacted before they are stored.
The specialists' call limits are prompt instructions; the deadline and budget bound them.
ASI03 Identity and Privilege AbusePartlyOnly action-runner holds GitHub and shop write credentials, and each service has its own database role. People sign in and hold roles. Approvals are bound to the exact proposal and re-checked.
Details
  • Only action-runner's service account can read the GitHub App key and its own database credential. triage-api gets a read-only GitHub token.
  • triage-api's database role cannot write action-runner's table and has no access to the shop's data. action-runner can only read approvals and runs. A test connects as each role to check this.
  • Only triage-api and the operator may call action-runner, enforced by Cloud Run IAM and again by a per-route check of the caller's email.
  • Also open: the GitHub App can write to main, though only through an operator-only demo route that no model can reach. A compromised triage-api could forge an approval row (see the write service above).
Agents share triage-api's identity. The person who started a run can also approve it.
ASI04 Agentic Supply Chain VulnerabilitiesPartlyDependencies install from hash-locked lockfiles. No tools or MCP servers are loaded at runtime. The model registry is checked at startup, and knowledge files are admitted one by one.
Details
  • Python dependencies are hash-locked and installed with a frozen sync in CI and in the container build. The ADK version is pinned exactly. The frontend and site install from their lockfiles.
  • There are no MCP toolsets, plugin entry points or exec and eval calls. The tool set is fixed in code.
  • The model registry is checked at startup, including which models may see which class of data.
  • A knowledge file with a secret shape is quarantined and never loads. A file with an injection pattern loads but is tainted, and search leaves it out.
  • Also open: most dependencies are ranges that only the lockfile fixes.
No dependency or secret scanning in CI, and no SBOM. Actions and the base image are not pinned by digest.
ASI05 Unexpected Code Execution (RCE)PartlyModels have no code-execution tool in Beagle. They propose bounded find-and-replace edits to allowed files, and changes ship as pull requests that the app never merges.
Details
  • There is no code executor in Beagle, and no shell call a model can reach.
  • Edits touch at most three files and six places. Each search text must match exactly once in the file at the deployed commit.
  • action-runner re-checks the paths and rebuilds the change from the base commit before it writes anything.
  • The storefront repo's own CI runs on the pull request. Beagle shows the result as a badge, and that badge cannot act.
  • Mitigations are fixed calls to the storefront's admin API with values from the catalog, never shell commands.
Model-written code runs in the storefront's pull-request CI before anyone reviews it; that CI's isolation is not assessed here.
ASI06 Memory and Context PoisoningAddressedKnowledge is screened on load. Search returns only stable, untainted documents: bundle documents are reviewed in Git, and a run's postmortem is searchable only after an approver verifies it. Each run pins its index version.
Details
  • Run-made postmortems are stored as drafts with no search index, so nothing can find them until an approver verifies them. Verification refuses a tainted draft and is audited.
  • An approver can clear taint only on a run-made draft. A tainted document from the reviewed bundle is fixed in Git.
  • A run keeps the index version it first searched, so a reload during the run cannot change what it sees.
  • Each run has its own ADK session. There is no shared agent memory across runs.
  • Two routes carry text between runs on purpose: Continue passes a rejected run's reviewer comment, fenced as data, and replay reuses a run's recorded tool outputs.
The same regex screen as ASI01.
ASI07 Insecure Inter-Agent CommunicationPartlyAll agents run in one process and one fixed graph. Every call between services carries a Google ID token checked per route, and GitHub webhooks are signed.
Details
  • There are no agent-to-agent network calls, transfers or sub-agents. Every agent except the four specialists returns structured output.
  • Cloud Tasks reaches the run executor with an OIDC token from its own account. Alerts arrive as a Pub/Sub push with an OIDC token from the alert account.
  • GitHub webhooks are checked with HMAC-SHA256 and only record CI results; they never change a run.
  • action-runner checks the stored approval itself rather than trusting the caller.
Specialists hand their findings to the synthesizer as free text, with no schema and no second screen. An instruction carried in a finding could sway the proposal.
ASI08 Cascading FailuresAddressedAtomic budgets, an attempt cap, per-specialist deadlines and at most one revision round. An executor that lost its lease cannot finish or pause the run.
Details
  • A run that cannot afford its next model call ends budget-stopped, with no call made.
  • A run that keeps failing stops after five attempts, and that is audited. A slow specialist is cut off at 60 seconds (90 for the code specialist) so the others carry on.
  • The critique allows at most one revision round; if the second critic still objects, the run is unresolved. An invalid structured answer gets one repair, and a rate-limit error is retried at most twice.
  • A failed re-check ends the run blocked before that action is sent; an action that already ran stays applied.
  • A run built on an older workflow version fails closed. A repeat alert joins the active run, and the task queue is rate-limited.
Spend is capped per run, not per day; Cloud Billing budgets only alert.
ASI09 Human-Agent Trust ExploitationPartlyThe gate shows the checked proposal: its checks, cited evidence, diff, critic objections, confidence and any flags. Citations must be evidence issued to the run. Approvals expire.
Details
  • The screen shows what code checked, not the model's own summary. A made-up reference fails, because citations must be evidence issued to this run.
  • Proposals below a confidence of 0.5 do not reach the gate.
  • An unresolved run shows its open objections and failed checks instead of a confident answer.
  • Deciding needs the approver role, and the decision is bound to the proposal on screen; a changed proposal gets a 409.
Checks show a proposal is well formed and cited, not that it is right. No separation of duties.
ASI10 Rogue AgentsPartlyNo agent is long-lived or self-starting; every run is bounded. The audit log is append-only for the application. Golden evals test the controls.
Details
  • Each run is started by the console, an alert or Continue, and is bounded by its budget, the attempt cap and deadlines.
  • Triggers refuse UPDATE, DELETE and TRUNCATE on the audit log. It records run start with its versions, taint flags, the gate, the decision, blocked actions and run end. Model calls and write results are kept in their own tables.
  • Golden evals check the controls with scripted models; a paid quality tier checks model behaviour. Both run only when the owner starts them.
  • Today a person can stop an action by rejecting it at the gate, and real writes can be switched off at deploy time.
  • Also open: the model-call table has no append-only trigger, and the audit log is not tamper-proof against an administrator.
A running run cannot be cancelled. No alerts on agent behaviour.

Checked against the code on 2026-10-06. No third-party governance toolkit is used.

NEXT

Compare the other ways to build it.

The architecture alternatives page sets three Google Cloud paths side by side: ADK on Agent Runtime, ADK on Cloud Run and LangGraph on Cloud Run. Each cell says who carries a production need, with a link to its source.

Next: Architecture alternatives →