Glass Box Terrarium, explained from the inside

Last updated · Latest source on GitHub ↗

Git & GitHubLesson

Author trust

An agent that reads issues and comments will meet text that tells it what to do. Whether it may act on that text has to depend on who wrote it, and the only safe source for who is the identity GitHub records, never what the text claims.

Know author_association already? Skip to In Terrarium.

Outside the box · General

The concept

GitHub attaches an author_association to every issue, pull request and comment, set from the author's account and not from anything they typed. Its values say how the author relates to the repository: OWNER, MEMBER and COLLABORATOR have been given access, CONTRIBUTOR has had a change merged before, and NONE has no tie at all. The REST API returns it with each comment.

A person without write access contributes through a fork, and a PR from a fork runs CI with no secrets and a token that normally can't write. The pull_request_target trigger lifts that limit, so it is safe only while it never runs the fork's code (GitHub Docs: Secure use reference).

Full referenceGitHub Docs: REST API endpoints for issue comments

Looking inside

In Terrarium

Terrarium draws one line. A person with write access is ; everyone else is . scripts/lib/author-trust.ts holds the line as author_association values:

scripts/lib/author-trust.ts ↗as of
// GitHub `author_association` values on each side of ADR-0020's trust line.
export const TRUSTED_ASSOCIATIONS = new Set(['OWNER', 'MEMBER', 'COLLABORATOR'])

export const PUBLIC_ASSOCIATIONS = new Set(['NONE', 'CONTRIBUTOR', 'FIRST_TIME_CONTRIBUTOR', 'FIRST_TIMER', 'MANNEQUIN'])

ADR-0020 says what each tier may do; docs/agents/guest-contributions.md applies it to fork PRs and outside agents.

The code that acts on the line sits at the merge. scripts/github/merge-pr.ts, the only path by which an agent merges, refuses a PR whose branch lives in a fork, a PR whose author is not Trusted, and a PR from the guest pipeline. That pipeline is the one route where Terrarium's own agents build what a Public guest asked for, so its PRs look Trusted; each carries a guest-pipeline label the script checks (ADR-0023, docs/adr/0023-guest-driven-demo-pipeline.md). A person merges all of those. Dependabot's version bumps follow their own rule (Protected paths).

The CI side keeps fork PRs away from write access. .github/workflows/pr-authorassociation-label.yml labels a PR trusted when its author has write access, on the plain pull_request trigger and only for a branch in the repository itself; a fork PR gets no label, and the missing label means Public. The Gate's green-check comment skips fork PRs the same way, since their token can't write.

Why it's built this way

  • One line, all or nothing. Someone with write access can already push and merge without an agent, so screening their requests stops nothing. Treating collaborators like guests, or adding a middle tier, was dropped until a real need shows up. The cost: a collaborator is trusted exactly as much as the owner. ADR-0020 (docs/adr/0020-requester-trust-tiers.md).
  • A person merges Public PRs. Public text can steer an agent, and no machine can tell if it obeyed. The (Merge gate) checks that a PR works, not why it was written, so a clean but hostile PR would pass. Merging reaches the live server, so a person merges every Public PR. The cost: each waits for a security review. ADR-0020 (docs/adr/0020-requester-trust-tiers.md).
  • Enforced in code. Agents post as the owner, so their PRs look Trusted. A review found the merge script would merge a fork or Public-author PR, and accept a faked verdict (the comment an agent posts before it merges). Now the script refuses those and ignores non-Trusted verdicts. The cost: the script and the trust line are , so changing either waits for a person. scripts/github/merge-pr.ts.

What to watch for

  • CONTRIBUTOR is not Trusted. It means a change of theirs was merged once, not that they can push. GitHub warns that approving fork runs only for first-time contributors lets someone qualify with one harmless typo fix.
  • OWNER isn't proof of a person. Agents post as the owner, so OWNER doesn't mean a person wrote it, and the session header can be copied: merge-pr.ts counts a verdict only from a Trusted author (verdictRefusal).
  • A label is a display, not the check. The trusted label helps a reader, and a missing one means Public; the scripts that decide read author_association themselves.
  • Never pull_request_target for a fork's code. It ignores the approval setting and holds the secrets; the header comment of the label workflow says why it uses the plain pull_request trigger instead.

Try it in your project

Write down which author_association values your agent may take instructions from, and check it in code before the agent acts on an issue or merges a PR. Then search your workflows for pull_request_target and, for each hit, confirm it never checks out the PR's own code.

Where next

Walkthroughs through this Lesson

Last updated · Latest source on GitHub ↗

Something went wrong

This content couldn’t be loaded. Reloading the page usually fixes it.

Technical details
route: /t/glassbox/guide/git-github/trust-tiers