Skip to content

security: give agent sessions their own GitHub App identity before unattended merge #7711

Open

Description

@kyle-sexton

Parent

Refs #7570 (thread: Merge authority and verifier), #7709 item 10 (decided by the owner on 2026-10-17: agent sessions get their own GitHub identity before report-only flips). Research: .work/poteto-review/design/agent-identity-research.md in the owner's checkout; GitHub docs fetched 2026-10-17.

Problem

The verifier's trusted-spec check and the admission marker rule (D33) trust any issue or comment from an account on trusted-actors.json. Agent sessions run gh with the owner's login (an OAuth gho_ token), so whatever they post reads as the owner's. The marker check (ratification.mjs) accepts a comment from a listed human, not posted through an App, and never edited. An agent posting as the owner passes all three tests. A trusted account therefore proves the account, not that a person wrote or reviewed the text.

Proposal

Agent sessions act as a GitHub App of their own, separate from the lanes App, using one-hour installation tokens minted per session. Reuse the dormant melodic-ai App: it has received zero events, its key is already in the estate's GitHub key vault, and dotfiles user-env.yaml:30 lists it with no local consumer. kyle-sexton stays the only kind: human trusted actor. The App is listed only as kind: bot, and only where a trigger has to accept it.

Why an App, not a machine user:

  • Two gates decide "human" by account type: babysit's is_human (babysit_merge_policy.py:735-760) and the pr-merge labeler rule (pr-pipeline/README.md:432-435). A machine user is a User and would pass both as human. An App is a Bot and already fails both.
  • A machine user needs an org seat, because an outside collaborator cannot use a fine-grained PAT. The org's one seat is filled. Apps take no seat, and GitHub's PAT docs point organization automation to Apps.
  • GitHub's Terms allow one free machine account per person, created by that person, who is responsible for what it does.

What it closes beyond the spec check:

  • Sessions can no longer post a valid admission marker. That is intended, and the code needs no change.
  • github-iac ADR 0015 accepted risk 2: the App holds no org-admin role, so a session cannot merge with --admin.

Owner decisions

  1. App: reuse melodic-ai (recommended) or register a new one. Either way: no administration permission, installed on selected repositories with azure-iac excluded (ADR 0015 trigger 5: there it could mint lane tokens), a new key generated and the old one retired.
  2. Token isolation: without it, an agent can unset GH_TOKEN and act as the owner again, so the App changes the credit, not the reach. Recommended: unattended lanes run as a separate OS user that cannot read the owner's token. Interactive sessions get a separate GH_CONFIG_DIR, plus a guardrails rule that denies gh auth switches and unsetting GH_TOKEN. Basis: judgment, since interactive sessions have the owner present.
  3. Key handling: sign the App JWT inside the vault, the azure-iac broker pattern, so the key never reaches a host (recommended). The alternative is vault-exec, which hands the key to the host process. Basis: GitHub's App key guidance, and the existing broker pattern.
  4. Merge-lane eligibility: PRs from agent sessions stay out of unattended merge (not-lane-authored is unchanged); recommended. The lanes App remains the only unattended author.
  5. Required review for agent PRs: github-iac ADR 0007 revisit trigger 3 fires, because an owner approval becomes a real second review. Recommended: record it and defer. The verifier is the gate for lane PRs.

Agent-doable work, as draft PRs after the decisions

  • dotfiles: a token mint helper and session launcher (bash and PowerShell) that set GH_TOKEN, GH_CONFIG_DIR and the git author.
  • claude-code-plugins: the App login in babysit's self_logins default; a guardrails rule against identity switches; ratification tests proving an App-posted marker is refused.
  • architecture: a trust-link entry and an ADR; amendments to github-iac ADR 0015 and ADR 0007.
  • Post-cutover probes: a session's issue reads as the App, and its marker does not admit.
  • Not yet checked: commit signing on requires-signing repos, and the commit author format.

Done when

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 17, 2026
  2. github-actions commented on Oct 17, 2026

    @github-actions
    Contributor

    Thanks for the report. Automated triage handed this issue to a maintainer.

    Proposed work class: work-class: structural
    Basis: automated intake triage; a maintainer confirms or changes it.

    Automated triage. A maintainer reviews it.

  3. added
    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 17, 2026
  4. kyle-sexton commented on Oct 17, 2026

    @kyle-sexton
    MemberAuthor

    Decision 1 (owner, 2026-10-17): reuse melodic-ai; register no new App. The owner prefers fewer Apps. A machine user would need a second paid seat and would pass the account-type human checks. The lanes App's key must stay in the cloud broker (github-iac ADR 0015). So the existing dormant App is the only identity that separates agents from the owner without adding one. Decisions 2-5 remain open.

  5. kyle-sexton commented on Oct 17, 2026

    @kyle-sexton
    MemberAuthor

    Decisions 2-5 (owner, 2026-10-17, 'go with recommended'): (2) unattended lanes run as a separate OS user that cannot read the owner's gh token; interactive sessions get their own GH_CONFIG_DIR plus a guardrails rule denying gh auth switches and unsetting GH_TOKEN. (3) The App JWT is signed inside the vault, the azure-iac broker pattern; the private key never reaches a host. (4) PRs from agent sessions stay out of unattended merge; the lanes App remains the only unattended author. (5) ADR 0007 revisit trigger 3 is recorded and deferred; the verifier is the gate for lane PRs.

  6. kyle-sexton commented on Oct 17, 2026

    @kyle-sexton
    MemberAuthor

    Decisions revised (owner, 2026-10-17, in the Automation Lanes session, after that session's design research):

    • Decision 1 now: register a new App. melodic-ai is live (a medley workflow mints it from org secrets), so it stays medley's. The new App's key is generated into Key Vault only.
    • Decision 2 now: unattended lanes get a dedicated WSL distro with interop and automount off, not a second OS user in the same distro. With interop on, any Linux user can launch Windows processes as the owner, and /mnt/c is readable. A probe confirms that interop off blocks Windows launches before the distro is used.
    • Caller auth: one certificate service principal per host per role. The function accepts app tokens only (idtyp=app), so every user token is rejected, the owner's included; it keeps an oid allowlist plus an explicit owner deny, and the certs rotate yearly.
      The interface stands: a separate function, POST /token narrowing repositories (up to 500) and permissions, a one-hour token, no cache, fail closed. Automation Lanes owns the azure-iac function and the shared signing package. This session owns the dotfiles helper (git author = the new App's bot), the guardrails rule, babysit self_logins and the ratification tests.
  7. kyle-sexton commented on Oct 20, 2026

    @kyle-sexton
    MemberAuthor

    github-iac's share of the new agent App, carried here because https://github.com/melodic-software/github-iac/issues/691 was closed as superseded by this issue: once the App is registered, github-iac needs (1) a row in the README installed-apps table (synced with live installations in https://github.com/melodic-software/github-iac/pull/717), (2) its install scope recorded (selected repositories, excluding azure-iac, unless this issue decides otherwise), (3) a trust-links entry in melodic-software/architecture, and (4) a decision on whether its bot id joins AUTOMATION_LANES_APP_SENDER_ID (open here: should review lanes review the new App's pushes?). #691's permission list (contents: read; pull requests: write; issues: write; actions: write; variables: read) is a starting point, not contradicted by the 2026-10-17 decisions; only its per-host token delivery is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.

    Type

    Fields

    No fields configured for Task.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions