How it works and what keeps it safe¶
The four workflows that run in a repository, the task that each event gives the AI, and the design that keeps an AI that reads untrusted issues from writing anything unchecked. Features has a page for each thing it does, and the security design has the threats, their countermeasures and the tests that pin them.
How it works¶
Four workflows run in the repository, and a fifth, discussion-assistant.yml, answers discussions where [discussions] answer_categories of config.toml names categories, as the starter does. Only the violet job runs an AI, and it can't write:
| Workflow | When | What |
|---|---|---|
issue-assistant.yml |
new issues, edits of their reporters, new comments, by hand | labels from the form, the first analysis, follow-ups when the reporter answers, whether a maintainer's comment waits for the reporter, whether a comment after the release reopens the issue, a comment of the reporter on an issue closed as a duplicate for the maintainers, and the slash commands of maintainers and reporters |
issue-lifecycle.yml |
every morning, merged pull requests, published releases, the end of a workflow named Release that no schedule started | reminders after 15 days, closing after 30 days, closing duplicates 3 days after the notice, or the days of [lifecycle], marking fixed issues and closing them with the release, and locking issues closed long ago when lock_closed_after_days is set |
labels.yml |
when .github/labels.toml changes on main |
creates and updates the labels; never deletes one |
findings.yml |
every morning, after each scan of CodeQL, OpenSSF Scorecard, Workflow security and OSV-Scanner, when .github/findings.toml changes |
dismisses the accepted findings of code scanning, also those of a pull request, and fails once for each other alert of main, in the first run that finds it open |
Each of the four is a short workflow with what starts it, the most permissions that its jobs get and one pin: the reusable workflow of the issue assistant at the commit of a release, which has the jobs. They run in your repository, with its events, variables, environment and firewall policy, and each job runs the issue assistant of that same commit:
Every job runs on ubuntu-24.04, the AI's on GitHub's egress-firewall runner of the same version, or on the runner that the repository variable ISSUE_ASSISTANT_AI_RUNNER names in its place, should that runner of a technical preview be gone. The events of one issue wait for each other, first in, first out, so a comment written while an analysis runs is handled after it, never dropped. A run of the lifecycle or the findings whose job has nothing to do, such as after the hourly scheduled run of a release workflow, takes no runner and never takes the place of one that waits.
Every task of the AI is one of five: triage for a new issue, follow-up when the reporter answered the assistant's questions (at most twice, and never once the maintainer joined the conversation), maintainer-reply to decide whether a maintainer's comment waits for the reporter, release-reply to decide whether the reporter of an issue that a release closed says that the problem persists, and maintainer-question to answer the question of a maintainer's /assistant ask. Which one an event of an issue gives it:
Every other event, such as a comment of someone else, gives it none, and on an issue with the label assistant-muted, only a maintainer's command does, or the reporter's /assistant again when the box of the form muted it, which a new issue with the box ticked gets from the start. A run by hand, under Actions → Issue assistant → Run workflow, gives it the task chosen there, triage by default, with an instruction if you like; it answers again where the task already has its comment only when a maintainer ticks override. On the issue of the digest, neither starts anything. The AI is off while the repository variable ISSUE_ASSISTANT_AI is off, and pauses while the engine's secret is missing.
Security¶
Built against PromptPwnd and Comment and Control, the attacks that make an AI in GitHub Actions run commands, leak secrets or write what an issue tells it to. The assistant follows every mitigation that the research on them recommends for a workflow like it, each pinned by a test; the security design maps them one by one. The issue examples of the Claude Code action, by contrast, give the AI a token that writes to issues.
Issues come from anyone, so the design assumes that an issue tries to steer the AI. Red is what anyone can write; the numbers are the trust boundaries of the security design:
- The AI only reads. It runs in the job Analyze with the AI engine (
analyze), with a read-only token, on GitHub's egress-firewall runner with an allow list in.github/egress-firewall.yaml. Each engine gets only tools that read, as Engines shows: Claude Code runs with--restrictedand the toolsRead,GrepandGlobalone, Codex runs its commands in a sandbox that can neither write nor reach the network, and the Gemini CLI and the Copilot CLI get their tools that read files of the checkout and no token of GitHub. The issue, the other issues and the discussions reach it as files that the prompt calls data, not instructions. - Issue text can't pass for someone else or hide. The AI learns who wrote what from headings that only the assistant writes; each text stands below them in a code fence that nothing in the text can close, so a line such as
## Comment 4 by @owner (maintainer)stays part of that text. Characters that GitHub shows as nothing, such as Unicode tags and zero-width spaces, which can spell instructions that no maintainer sees, are removed from every text the AI reads and from its answer. And since every analysis reads the excerpts of many other issues, only an original that a maintainer opened or triaged can close a duplicate on its own, so no issue can steer the AI into closing others. - Nothing is posted unchecked. The AI's answer is JSON with a fixed schema. The job Check and post the analysis (
apply), which runs no AI and never sees its secret, checks it against the schema, the repository's labels and the issues that exist, refuses anything that looks like a token or key, but not the links and paths that answers are full of, turns mentions, e-mail addresses and references to other repositories into code, but for the issues that exist in a public related repository, drops images, HTML and links to other sites, and links files only when git tracks them. What counts is the whole comment as GitHub shows it, so the code of the AI stays code in a list and behind an issue number too, and no table cuts it in two; the security design shows how, and how property tests and fuzzing judge whole comments by a model of GitHub's rendering of their own. It writes only to the issue or the discussion of the event, and only theapplyjob of the discussions may write to discussions. - Its words run nothing outside a comment either. The AI's words also reach the log of the run, its summary and, as the reason for a label or a suggested duplicate, the timeline of the issue. There they are cleaned like a comment and put on one line, every error and notice of the assistant is escaped, and the summary turns workflow commands off while it prints, so that no answer can run one, such as
::add-mask::. A possible security report gets a fixed reason on the timeline instead of the AI's words, and neither label names nor the model ofconfig.tomlcan add arguments to the command line of the engine. - No issue can stall it. Every analysis reads the texts of every open issue and of many closed ones, so the assistant reads each text in time that grows only with its length, and no more of it than GitHub keeps. The AI gets at most 20,000 characters of each text of its issue. Tests and fuzzing check that no crafted text takes longer than half a second.
- No stranger can run up the bill. The runs of the AI that people who don't maintain the repository start have limits: by default 20 analyses, follow-ups and replies after a release a day, 3 analyses of one reporter's new issues, and 2 comments of a reporter after each release. Every new issue counts, also while the first analyses still run, so a flood of issues at once starts no more, and a reporter who deletes their comments gets no more replies.
- The secret stays in one place: the environment
issue-assistant, which only the default branch may use. - No code of a pull request runs. The lifecycle workflow starts on
pull_request_targetonly to learn which issues a merged pull request fixes. It checks out the default branch, runs no AI and skips Dependabot's pull requests. - The jobs that write reach only GitHub. Every job without the AI starts with Harden-Runner in block mode: the network allows
api.github.comandgithub.comalone, besides the runner's own hosts and, for the findings, GitHub's results service of their cache, and no step can use sudo or containers to lift the block. A compromised action or tool in a job with a write token can't send it anywhere else. The Action test of this repository runs the action behind the same block. The block holds in a public repository, and in a private one only with a subscription or trial of StepSecurity, the maker of Harden-Runner; without one, the job runs unblocked. When Harden-Runner has no agent that blocks, because StepSecurity's service is unavailable or GitHub's outage kept it from downloading the agent, each job stops before any step reaches a host, and can run again. StepSecurity receives the repository, the run and the job of each of these jobs, and the hosts that its steps connect to. - Findings stay private. Code scanning alerts are only shown to maintainers, but the logs of a public repository's runs are public. So the Findings workflow names an open alert by its number and link only, never its rule, file or message, and nothing becomes an issue.
- Every repository uses exactly the templates. The templates are the four workflows, the workflow of the discussions and the firewall policy in
templates/, which this repository uses for its own issues and findings too. Each workflow calls the jobs of a release, the reusable workflows in.github/workflows/reusable-*.ymlof this repository, at a commit that no repository can change, and gives them no more permissions than they need.checkfails when a repository's workflow differs from its template in anything but the commit hashes of its pins, and the tests of this repository pin the rules of the templates and of the jobs.
What apply makes of an answer that an issue steered:
The security design lists the threats, their countermeasures and the tests that pin them. Report vulnerabilities privately, as described in SECURITY.md.

