Security
The threat model, hardening measures, and ongoing obligations for the Hall of Automata.
Threat model
The system's security rests on two assumptions:
- Org admins are trusted. They have access to org secrets. If an admin is hostile, the key custody model collapses. This is an organizational assumption, not a technical one.
- Workflow source is protected. A malicious modification to
.github/workflows/could bypass the team membership check or exfiltrate secrets. Protection is enforced via branch rules and CODEOWNERS.
Threats outside this model — compromised GitHub infrastructure, Anthropic-side breaches — are not in scope here.
Protecting workflow source
The most important protection in the system. A workflow that can be modified by anyone can do anything, including printing secrets to logs.
Branch protection on main:
- Require pull request reviews before merging
- Require at least 1 approval from a code owner
- Dismiss stale reviews when new commits are pushed
- Do not allow bypass by admins (ideally)
CODEOWNERS:
# .github/CODEOWNERS
.github/workflows/ @MockaSort-Studio/admins
Any change to workflow files requires approval from the admins team. This is the primary guard against workflow tampering.
Secret hygiene
Mask the OAuth token in every workflow:
- name: Mask invoker token
run: echo "::add-mask::${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}"
This must be the first step after authorization passes, before any step that uses the token. Once masked, the value is redacted from all subsequent log output.
Never print secrets explicitly. No echo $SECRET, no debug steps that dump environment variables. If ACTIONS_STEP_DEBUG is ever enabled, disable it before running any invocation job.
Minimal workflow permissions:
permissions:
issues: write # post result comments
pull-requests: write # open PRs, post review responses
contents: write # create branches, push commits
Nothing else. The GITHUB_TOKEN should not have write access to anything it does not need.
Team membership check
The Hall App holds Members: read org permission. No separate ORG_READ_TOKEN PAT is required. Authorization runs against the App's own installation token.
Authorization is fail-closed: any API error (network, token issue, user not found) blocks the invocation. See architecture/permissions-model.md for the full flow.
Concurrency
Concurrent invocations of the same automaton on the same issue are serialized, not raced:
concurrency:
group: hamlet-${{ github.event.issue.number }}
cancel-in-progress: false
cancel-in-progress: false queues rather than cancels. Both invocations run, in order. If cancellation is preferred (last writer wins), set to true.
Invocation timeout
All workflows set a hard timeout:
timeout-minutes: 30
A stuck or runaway invocation does not run indefinitely. Adjust per automaton if tasks are expected to take longer, but always set an explicit limit.
Audit trail
Every invocation leaves two records: - Actions log: timestamp, trigger, sender, job result - Issue comment: automaton response or unauthorized notice
These are not deletable by regular org members. They provide a complete history of who invoked what and when.
What this does not protect against
- A compromised invoker account with direct org admin rights
- GitHub infrastructure compromise
- Anthropic-side data handling (governed by Anthropic's terms)
- Repos where the Hall App is not installed — the relay will not forward webhook events for those repos