docs: correct Gitea Actions guarantees
The security document claimed the CI workflow "is least privilege" because it declares `permissions: contents: read`. That is not true on the server this repository actually uses. The inventoried server is Gitea 1.24.2, and its official comparison page lists `permissions` / `jobs.<job_id>.permissions`, `concurrency`, and `jobs.<job_id>.timeout-minutes` under unsupported workflow syntax — verified against https://docs.gitea.com/1.24/usage/actions/comparison rather than assumed. On this server those keys are inert, so the branch cannot claim the in-job GITEA_TOKEN is read-only, and cannot claim YAML-enforced timeout or concurrency behavior. Job-token permission controls, repository/owner defaults, and server-side maximum-permission clamping arrive in the 1.26 feature line (https://docs.gitea.com/1.26/usage/actions/token-permissions). The document now separates what is actually true of this repository — full-SHA action pins, frozen-lockfile install, no pull_request_target, no referenced secret, `persist-credentials: false` on every checkout, and the declared `permissions` block — from what the server enforces. It also states precisely what `persist-credentials: false` proves: checkout leaves no credential in `.git/config` for a later step, which is not the same as removing or reducing the job token. The CI blockers are recorded: upgrade to a reviewed supported release, configure repository/owner defaults and a maximum permission, prove against the real server what the token cannot do, move timeout/cancellation/ teardown into the trusted provisioner and reaper, and finish the disposable-guest proof before opening the real pull request. The workflow keeps every declaration — they are already correct for a supported Gitea release and for GitHub Actions — and gains a comment saying plainly that this server ignores them, so no future reader mistakes them for enforcement. Nothing about runners, proxies, firewalls, or deployment is asserted here. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cda841f8c7
commit
a2db774b8c
@@ -1,5 +1,22 @@
|
||||
name: CI
|
||||
|
||||
# IMPORTANT — the currently inventoried server is Gitea 1.24.2, which *ignores* several keys used
|
||||
# below. Per https://docs.gitea.com/1.24/usage/actions/comparison, its unsupported workflow syntax
|
||||
# includes `permissions` / `jobs.<job_id>.permissions`, `concurrency`, and
|
||||
# `jobs.<job_id>.timeout-minutes`. They are declared here deliberately, so the workflow is already
|
||||
# correct on a supported release and on GitHub Actions, but on this server they are inert:
|
||||
#
|
||||
# * `permissions:` does NOT restrict the in-job `GITEA_TOKEN`. Job-token permission controls,
|
||||
# repository/owner defaults, and server-side maximum-permission clamping arrive in the 1.26
|
||||
# feature line (https://docs.gitea.com/1.26/usage/actions/token-permissions). Until the server
|
||||
# is upgraded and configured, the token's authority is whatever the server grants — not what
|
||||
# this file requests.
|
||||
# * `concurrency:` and `timeout-minutes:` are NOT bounded-execution guarantees here. Cancellation,
|
||||
# timeout, and teardown must be enforced by the trusted runner provisioner/reaper.
|
||||
#
|
||||
# `persist-credentials: false` below is unaffected by any of this and is enforced by the action
|
||||
# itself. See docs/development/security.md.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
@@ -140,15 +140,68 @@ Ekklesia does not ship one. Do not describe the current limiter as distributed t
|
||||
|
||||
## Continuous integration boundary
|
||||
|
||||
CI runs on self-hosted Gitea Actions runners. The workflow itself is least privilege: top-level
|
||||
`permissions: contents: read`, both actions pinned by full commit SHA, `pnpm install
|
||||
--frozen-lockfile`, no `pull_request_target`, no secrets referenced, and checkout configured with
|
||||
`persist-credentials: false` so the job token is not left in `.git/config` for a later step to read.
|
||||
CI runs on self-hosted Gitea Actions runners. The inventoried server is **Gitea 1.24.2**, and that
|
||||
version determines which parts of the workflow are real guarantees and which are not.
|
||||
|
||||
**The runner topology itself is not verified in this repository and is not documented here as
|
||||
though it were.** A pull request can edit `runs-on` and select any runner label its repository can
|
||||
reach, so a workflow file cannot be the boundary. Whether untrusted pull-request jobs execute on a
|
||||
disposable guest, and whether any runner reachable from this repository holds deployment authority
|
||||
or a host Docker socket, must be established against the actual Gitea instance and runner hosts.
|
||||
Until that evidence exists, treat pull-request CI as executing with whatever authority the runner
|
||||
host actually grants.
|
||||
### What is true of the repository today
|
||||
|
||||
These are properties of files in this repository, enforced by the action or the package manager,
|
||||
independent of server version:
|
||||
|
||||
- every action reference is pinned by a full immutable commit SHA;
|
||||
- dependencies install with `pnpm install --frozen-lockfile`;
|
||||
- there is no `pull_request_target` workflow;
|
||||
- no repository, organization, or instance secret is referenced anywhere in the workflow;
|
||||
- every checkout step sets `persist-credentials: false`;
|
||||
- the workflow declares `permissions: contents: read`.
|
||||
|
||||
### What `persist-credentials: false` actually proves
|
||||
|
||||
It proves exactly one thing: `actions/checkout` does not write its credential into `.git/config`,
|
||||
so a later step in the same job cannot read or reuse it from there.
|
||||
|
||||
It does **not** remove the short-lived `GITEA_TOKEN` from the job environment, and it does **not**
|
||||
reduce that token's server-side authority. A step that wants the token can still read it.
|
||||
|
||||
### What Gitea 1.24 ignores — so the branch cannot claim it
|
||||
|
||||
Per the official comparison for this version
|
||||
(<https://docs.gitea.com/1.24/usage/actions/comparison>), Gitea 1.24's unsupported workflow syntax
|
||||
includes `permissions` and `jobs.<job_id>.permissions`, `concurrency`, and
|
||||
`jobs.<job_id>.timeout-minutes`. On this server those declarations are inert. Therefore:
|
||||
|
||||
- **The workflow's `permissions: contents: read` does not restrict the job token.** This branch
|
||||
cannot claim the `GITEA_TOKEN` is read-only, because nothing in workflow YAML makes it so on
|
||||
1.24. Job-token permission controls, repository/owner defaults, and server-side
|
||||
maximum-permission clamping arrive in the 1.26 feature line
|
||||
(<https://docs.gitea.com/1.26/usage/actions/token-permissions>).
|
||||
- **`concurrency` and `timeout-minutes` are not bounded-execution guarantees here.** Cancellation,
|
||||
timeout, and teardown must be enforced by a trusted provisioner and reaper outside the workflow.
|
||||
|
||||
The declarations are kept deliberately: they are already correct for a supported Gitea release and
|
||||
for GitHub Actions, and keeping them means the upgrade does not require re-deriving the intent. They
|
||||
are documented as ignored, not removed.
|
||||
|
||||
### Runner topology is not verified
|
||||
|
||||
**The runner topology is not verified in this repository and is not documented here as though it
|
||||
were.** A pull request can edit `runs-on` and select any runner label its repository can reach, so a
|
||||
workflow file cannot be the boundary. Whether untrusted pull-request jobs execute on a disposable
|
||||
guest, and whether any runner reachable from this repository holds deployment authority or a host
|
||||
Docker socket, must be established against the actual Gitea instance and runner hosts. Until that
|
||||
evidence exists, treat pull-request CI as executing with whatever authority the runner host actually
|
||||
grants.
|
||||
|
||||
### CI blockers before the real pull request is opened
|
||||
|
||||
1. Upgrade Gitea to a reviewed supported release with job-token permission controls — at least the
|
||||
1.26 feature line. Select the actual release during infrastructure work, after backup and
|
||||
compatibility review; do not pin a version from this document.
|
||||
2. Configure repository and owner defaults, and the server-side maximum permission, so workflow
|
||||
code cannot request more than read-only repository access.
|
||||
3. Prove against the real server that the job's `GITEA_TOKEN` cannot push code, create or update
|
||||
releases, packages, issues, or pull requests, change Actions configuration, or reach another
|
||||
private repository.
|
||||
4. Enforce job timeout, cancellation, concurrency, and teardown in the trusted
|
||||
provisioner/reaper rather than relying on the ignored workflow keys above.
|
||||
5. Complete the disposable-guest runner isolation proof before opening the real pull request.
|
||||
|
||||
Reference in New Issue
Block a user