Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

#165

P3: Managed bot accounts and per-run credentials

closed · opened by keenan 6d ago

keenan opened this 6d ago

Why

An automation needs an account to act as: to be assigned an issue, to be mentioned, to author a comment, to open a change, and to be attributable in Git and the audit log. An account already gives all of that, so the identity half is free. What is missing is a way for a workflow to act as one without holding a long-lived key that carries more power than the job needs.

Today the only way for a job to write back is a repository secret holding the account’s SSH key. The account must be maintain, because a push to the metadata branch requires it — so the bot can also push shared branches, approve other people’s changes, and close anyone’s issue. The key cannot be revoked when the run ends, and every job in the repository that declares the secret can read it, including an approved contributor tip.

This is the managed-bot slice of the agent loop: an account that a maintainer provisions, plus a credential the instance mints per run and the run’s job can use.

Decisions

  1. Credential: a per-run keypair. The instance generates an ed25519 pair when it leases the job, stores the public half with (account, repo, run, expiry), delivers the private half the way a secret is delivered, and deletes the row when the run finishes. No CA, no OpenSSH certificates: it rides the same key lookup the SSH server already does.
  2. Permissions: the account’s role, inherited. No bot-specific CI rule, no secrets rule, no separate branch namespace. A contribute bot may push only refs/heads/contrib/<name>/, and CI on that tip is held until a maintainer allows it, exactly as for a person. A maintain bot may push shared branches, its pushes run CI immediately, and its runs receive the secrets its workflows declare.
  3. Approvals: allowed when the role allows it. A maintain bot may approve a change. It still cannot approve its own, and the approval is recorded against the bot’s name like any other.
  4. Granting: the instance admin who creates the account assigns the repository role. There is no per-bot capability store and no second permission model.

The one rule the credential adds

A run credential lets the CLI do over SSH what the account’s role may already do through the browser: add a comment, open a change request for a branch the account may push, and approve when the account is an eligible approver. That replaces “the metadata branch needs maintain” for automation without widening the account’s role. A contribute bot can comment and open a change for its own branch; it cannot touch an issue’s state, labels, or anyone else’s comment, and it cannot approve.

The gate is pre-receive, where every write already passes: the SSH server marks a session that authenticated with a run credential, and the hook authorizes the metadata paths the browser already authorizes for that role. Everything the browser does not offer an account stays refused.

Slices

  • Account kind. kind on the account (person | bot), fixed at creation; a bot has no browser sign-in (no passkey, no invite link) and no instance-admin role; a bot is marked as one in the account directory, the profile, whoami consumers and the admin list; vole admin account create --bot.
  • Run credential. Mint, deliver, expire; the auth callback resolves it; the hook authorizes by role and path; the job gets VOLE_RUN_ID and VOLE_REPO, plus a scoped key and known-hosts entry under /run/vole so git push and vole issue comment work with no per-job wiring.
  • Docs and dogfood. Accounts and access gets a bot section; the agent example in docs/workflows.md points at it; one agent workflow in this repository proves the loop (blocked on the instance running a binary that knows network: before it can declare it).

What a maintainer is accepting

  • A maintain bot’s pushed code runs with the repository’s secrets and can approve changes. That is what maintain means, and granting it is the trust decision.
  • A contribute bot’s CI is held per exact tip, so an unattended loop needs maintain or a person clicking Allow CI after each push.
  • A leaked job tree holds a key that is valid until the run ends, not a key that is valid until someone notices.
  • A bot that reads untrusted issue text and then pushes code is a prompt-injection path to whatever its role allows. The role is the blast radius; choose it deliberately.

Related

  • #162 cancels a run that is already going, which the same loop needs.
  • The CI-side slices that unblocked this: #159 (network) and #161 (job budget).

Comments (1)

keenan 6d ago

Decisions recorded, and slice 1 is in review.

  • Kind and invariants: #166 (01M6FG8Q1WNC75N7AWBPEX8GER). A bot has no browser sign-in, cannot hold instance admin, is marked in the directory and on its profile, and account create --bot creates it with the repository role the admin chooses. Existing accounts read as people after the migration.
  • Roles are the grant, as decided: no bot-specific CI rule, no secrets rule, no branch namespace. A contribute bot pushes refs/heads/contrib/<name>/ and its CI waits for a maintainer to allow the tip; a maintain bot pushes shared branches, runs CI immediately, receives the secrets its workflows declare, and can approve. The docs say so in those words, including that granting the role is the trust decision.
  • Credential: per-run keypair, as decided. Slice 2.

One consequence worth stating before slice 2 lands: with contribute, “Allow CI” is per exact tip, so an agent that pushes a fixup after every review comment needs either maintain or a person clicking Allow CI each time. That is the intended cost of the role, not a bug — but it decides which role a contribute-level agent is actually usable with.

Slice 1 changes no push rule and no permission, so it can land on its own.