Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
Actions access** UI. Do not change package visibility, permission
inheritance, or source-repository association, and do not automate the grant
through an undocumented API.
- Record manually provisioned GitHub Actions environments in
`config/manual-settings.json` with stable repository IDs, exact deployment
branch policies and reviewer sets, and variable and secret names only. Never
record credential values. Provision them only after the owning workflow is
merged and reviewed, and do not imply that `bin/publish` applies them.
- Treat `config/codeql-advanced-setup.json` as the exhaustive exception
inventory for repositories whose component/path-aware CodeQL workflow cannot
use repository-wide default setup. Keep the advanced security configuration
Expand Down
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ administrative token required by the manual publishing workflow. Package
Actions-access grants that lack a documented public API belong in
`config/manual-settings.json`; record stable package and repository IDs, grant
only read access to consumer workflows, and preserve the package's existing
visibility and source-repository association.
visibility and source-repository association. Manually provisioned GitHub
Actions environments belong in the same inventory with a stable repository
ID, exact branch policy and reviewer set, and variable and secret names only.
Never commit their values, and provision them only after the workflow that uses
them is merged and reviewed.

Keep default-branch deletion and non-fast-forward rules non-bypassable during
normal operation. Any organization-owner exception needed for GitHub's
Expand Down
86 changes: 83 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ read-only and are skipped on later runs.
read-only workflow grants recorded in `config/manual-settings.json`. The
package's visibility, permission inheritance, and source-repository link are
not changed by these grants.
- GitHub Actions environments that depend on externally issued credentials
have name-only contracts in `config/manual-settings.json`. The inventory
records the stable repository identity, exact deployment branch policy and
reviewer set, and variable and secret names; credential values remain manual
and are never committed.
- GitHub Actions defaults to read-only, cannot approve pull requests, and may
use only Atrinik, GitHub, Codecov coverage, and explicitly allowed Docker
actions.
Expand Down Expand Up @@ -90,9 +95,10 @@ read-only and are skipped on later runs.
The GitHub REST API does not expose every organization control. The desired
values are recorded in `config/manual-settings.json` and must be confirmed in
the organization UI under **Member privileges**, **Authentication security**,
**GitHub Apps**, and each listed package's **Manage Actions access** section.
Codecov must be installed for the listed repositories so their
OIDC-authenticated coverage uploads and badges remain available.
**GitHub Apps**, each listed repository's **Environments** settings, and each
listed package's **Manage Actions access** section. Codecov must be installed
for the listed repositories so their OIDC-authenticated coverage uploads and
badges remain available.

## Usage

Expand Down Expand Up @@ -354,6 +360,80 @@ payload. For a later intentional rollback, review and merge a desired-state
change first, then run the normal plan and explicitly authorized apply flow so
the previous selection is not guessed or partially overwritten.

## Provisioning the website preview-domain environment

The public `atrinik/website` repository (repository ID `1327107093`) uses the
`cloudflare-preview-domains` environment for its reviewed preview-domain
automation. That workflow uses `pull_request_target`, for which GitHub resolves
the workflow, `GITHUB_REF`, and `GITHUB_SHA` from the base repository's default
branch rather than from the pull-request head. Restricting this environment to
the exact `main` branch therefore admits the trusted default-branch workflow
without admitting ordinary pull-request merge or head refs. This remains a
valid security boundary only while the website workflow never checks out,
downloads, or executes pull-request-controlled code or artifacts.

The environment deliberately has no required reviewers. Its custom deployment
branch policy allows only the `main` branch and no tags. The inventory records
only names: the environment variables are `CLOUDFLARE_ACCOUNT_ID` and
`CLOUDFLARE_ZONE_ID`, and the environment secret is
`CLOUDFLARE_PREVIEW_TOKEN`. The external values are never stored in this
repository, fixtures, commands, or logs, and `bin/publish` does not create,
update, or delete this environment.

Do not provision any value until the exact website workflow that references
this environment has been merged to `atrinik/website@main` and reviewed for
the `pull_request_target` trust boundary. Then an organization owner or
repository administrator must:

1. Confirm the repository identity without reading any credential value:

```sh
gh api -H 'X-GitHub-Api-Version: 2026-03-10' \
repos/atrinik/website \
--jq '{id, full_name, visibility, archived, default_branch}'
```

2. Open **atrinik/website → Settings → Environments**, create
`cloudflare-preview-domains`, select custom deployment branches and tags,
add only the `main` branch pattern, add no tag pattern, and leave required
reviewers disabled.
3. Add the two exact environment variable names and the one exact environment
secret name from `config/manual-settings.json`, entering their values only
through the protected GitHub settings UI from the owning Cloudflare account.

The identity check must report ID `1327107093`, `atrinik/website`, `public`,
`archived: false`, and default branch `main`. Verify the resulting environment
without printing variable values or attempting to retrieve the secret value:

```sh
gh api -H 'X-GitHub-Api-Version: 2026-03-10' \
repos/atrinik/website/environments/cloudflare-preview-domains \
--jq '{name, deployment_branch_policy, protection_rule_types: [.protection_rules[].type]}'
gh api -H 'X-GitHub-Api-Version: 2026-03-10' \
repos/atrinik/website/environments/cloudflare-preview-domains/deployment-branch-policies \
--jq '[.branch_policies[] | {name, type}]'
gh api -H 'X-GitHub-Api-Version: 2026-03-10' \
repos/atrinik/website/environments/cloudflare-preview-domains/variables \
--jq '[.variables[].name] | sort'
gh api -H 'X-GitHub-Api-Version: 2026-03-10' \
repos/atrinik/website/environments/cloudflare-preview-domains/secrets \
--jq '[.secrets[].name] | sort'
```

The environment must report custom branch policies enabled, protected-branch
mode disabled, no `required_reviewers` protection rule, exactly one branch
policy named `main`, exactly the two recorded variable names, and exactly the
recorded secret name. If any identity or name differs, stop and reconcile it in
a reviewed desired-state change; never copy a live credential into an issue,
pull request, log, or configuration file.

To retire the environment, first merge a reviewed website change that removes
all workflow references to it and a reviewed governance change that removes
its manual inventory entry. Then delete only
`cloudflare-preview-domains` through the website's Environment settings and
confirm that no other repository environment, variable, or secret changed. No
`bin/publish --apply` run is involved in provisioning or rollback.

## Granting Classic access to the Windows build image

The private `ghcr.io/atrinik/windows-build` image is owned by the
Expand Down
107 changes: 107 additions & 0 deletions bin/validate
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,22 @@ jq -e '. == {state: "configured", query_suite: "default"}' \
jq -e '. == {state: "not-configured"}' \
"${root}/config/codeql-default-setup-advanced.json" >/dev/null

jq -e '
(keys == [
"codecov_github_app_repositories",
"github_actions_environments",
"github_packages_actions_access",
"members_can_change_repository_visibility",
"members_can_create_teams",
"members_can_delete_or_transfer_repositories",
"members_can_request_github_apps",
"reason",
"repository_admins_can_install_github_apps",
"require_secure_two_factor_methods",
"require_two_factor_authentication"
])
' "${manual_settings_config}" >/dev/null

jq -e '
.github_packages_actions_access as $access |
($access | type == "array") and
Expand Down Expand Up @@ -238,6 +254,97 @@ jq -e '
)
' "${manual_settings_config}" >/dev/null

jq -e \
--slurpfile repositories "${repositories_config}" '
.github_actions_environments as $environments |
($environments | type == "array" and length > 0) and
(
[$environments[] | [.repository, .environment]] |
length == (unique | length)
) and
all(
$environments[];
(keys == [
"deployment_branch_policy",
"environment",
"repository",
"repository_id",
"required_reviewers",
"secret_names",
"variable_names"
]) and
(
.repository |
type == "string" and
test("^atrinik/[A-Za-z0-9_.-]+$")
) and
(
.repository as $full_name |
($full_name | split("/")[1]) as $repository |
($repositories[0].pull_request_gate | index($repository)) != null and
($repositories[0].archive | index($repository)) == null
) and
(.repository_id | type == "number" and . > 0 and floor == .) and
(
.environment |
type == "string" and
length <= 255 and
test("^[a-z0-9][a-z0-9-]*[a-z0-9]$")
) and
(.required_reviewers == []) and
(
.deployment_branch_policy |
(keys == [
"custom_branch_policies",
"patterns",
"protected_branches"
]) and
.custom_branch_policies == true and
.protected_branches == false and
(.patterns | type == "array" and length > 0) and
([.patterns[] | [.type, .name]] | length == (unique | length)) and
all(
.patterns[];
(keys == ["name", "type"]) and
.type == "branch" and
(
.name |
type == "string" and
length > 0 and
test("^[A-Za-z0-9._/-]+$")
)
)
) and
(
.variable_names |
type == "array" and
length > 0 and
length == (unique | length) and
all(.[]; type == "string" and test("^[A-Z][A-Z0-9_]*$"))
) and
(
.secret_names |
type == "array" and
length > 0 and
length == (unique | length) and
all(.[]; type == "string" and test("^[A-Z][A-Z0-9_]*$"))
) and
(
.variable_names as $variables |
all(.secret_names[]; . as $secret | ($variables | index($secret)) == null)
)
) and
all(
$environments[];
. as $environment |
all(
$environments[];
.repository != $environment.repository or
.repository_id == $environment.repository_id
)
)
' "${manual_settings_config}" >/dev/null

jq -e '
(keys == [
"issue_fields",
Expand Down
27 changes: 26 additions & 1 deletion config/manual-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,31 @@
"renderer",
"server"
],
"github_actions_environments": [
{
"deployment_branch_policy": {
"custom_branch_policies": true,
"patterns": [
{
"name": "main",
"type": "branch"
}
],
"protected_branches": false
},
"environment": "cloudflare-preview-domains",
"repository": "atrinik/website",
"repository_id": 1327107093,
"required_reviewers": [],
"secret_names": [
"CLOUDFLARE_PREVIEW_TOKEN"
],
"variable_names": [
"CLOUDFLARE_ACCOUNT_ID",
"CLOUDFLARE_ZONE_ID"
]
}
],
"github_packages_actions_access": [
{
"package_owner": "atrinik",
Expand All @@ -28,5 +53,5 @@
],
"require_two_factor_authentication": true,
"require_secure_two_factor_methods": true,
"reason": "The public GitHub API does not expose supported update parameters for every setting in this file. Confirm them, Codecov GitHub App repository access, and GitHub Packages Actions access in the corresponding organization and package settings UI."
"reason": "The public GitHub API does not expose supported update parameters for every setting in this file, and external credential values must never be stored here. Confirm these settings, Codecov GitHub App repository access, GitHub Actions environments, and GitHub Packages Actions access in the corresponding organization, repository, and package settings UI."
}
Loading