secrets: reject runtime-reserved names (GITHUB_*, GITEA_*, FORGEJO_*, RUNNER_*, ACTIONS_*) that clobber the per-job token
9052a6b · gmorell · 2026-07-02 18:25
Files changed
modified
lib/git_gud/secrets/organization_secret.ex
+1
−0
@@ -26,6 +26,7 @@ defmodule GitGud.Secrets.OrganizationSecret do
| 26 | 26 | message: "must be a valid env var name (letters, digits, _, no leading digit)" |
| 27 | 27 | ) |
| 28 | 28 | |> validate_length(:name, max: 100) |
| 29 | + |> GitGud.Secrets.ReservedNames.validate_not_reserved() | |
| 29 | 30 | |> unique_constraint(:name, name: :organization_secrets_organization_id_name_index) |
| 30 | 31 | end |
| 31 | 32 | end |
modified
lib/git_gud/secrets/organization_variable.ex
+1
−0
@@ -21,6 +21,7 @@ defmodule GitGud.Secrets.OrganizationVariable do
| 21 | 21 | |> validate_required([:name]) |
| 22 | 22 | |> validate_format(:name, @name_format, message: "must be a valid env var name") |
| 23 | 23 | |> validate_length(:name, max: 100) |
| 24 | + |> GitGud.Secrets.ReservedNames.validate_not_reserved() | |
| 24 | 25 | |> validate_length(:value, max: 65_535) |
| 25 | 26 | |> unique_constraint(:name, name: :organization_variables_organization_id_name_index) |
| 26 | 27 | end |
modified
lib/git_gud/secrets/repository_secret.ex
+1
−0
@@ -26,6 +26,7 @@ defmodule GitGud.Secrets.RepositorySecret do
| 26 | 26 | message: "must be a valid env var name (letters, digits, _, no leading digit)" |
| 27 | 27 | ) |
| 28 | 28 | |> validate_length(:name, max: 100) |
| 29 | + |> GitGud.Secrets.ReservedNames.validate_not_reserved() | |
| 29 | 30 | |> unique_constraint(:name, name: :repository_secrets_repository_id_name_index) |
| 30 | 31 | end |
| 31 | 32 | end |
modified
lib/git_gud/secrets/repository_variable.ex
+1
−0
@@ -21,6 +21,7 @@ defmodule GitGud.Secrets.RepositoryVariable do
| 21 | 21 | |> validate_required([:name]) |
| 22 | 22 | |> validate_format(:name, @name_format, message: "must be a valid env var name") |
| 23 | 23 | |> validate_length(:name, max: 100) |
| 24 | + |> GitGud.Secrets.ReservedNames.validate_not_reserved() | |
| 24 | 25 | |> validate_length(:value, max: 65_535) |
| 25 | 26 | |> unique_constraint(:name, name: :repository_variables_repository_id_name_index) |
| 26 | 27 | end |
added
lib/git_gud/secrets/reserved_names.ex
+33
−0
@@ -0,0 +1,33 @@
| 1 | +defmodule GitGud.Secrets.ReservedNames do | |
| 2 | + @moduledoc """ | |
| 3 | + Rejects secret/variable names the CI runtime injects itself. | |
| 4 | + | |
| 5 | + A user secret named `GITHUB_TOKEN` (say) silently overrides the | |
| 6 | + forge's per-job token in the runner's `secrets`/env, which then breaks | |
| 7 | + `actions/checkout` on private repos ("could not read Username"). Mirror | |
| 8 | + GitHub's rule (no `GITHUB_` prefix) and extend it to the Forgejo/act | |
| 9 | + runtime prefixes so these can't be shadowed. | |
| 10 | + """ | |
| 11 | + import Ecto.Changeset | |
| 12 | + | |
| 13 | + # Case-insensitive prefixes owned by the runtime (token, context, | |
| 14 | + # runner/actions env). CI_* is intentionally NOT reserved — built-in | |
| 15 | + # vars like CI_REGISTRY are meant to be user-overridable. | |
| 16 | + @reserved_prefixes ~w(GITHUB_ GITEA_ FORGEJO_ RUNNER_ ACTIONS_) | |
| 17 | + | |
| 18 | + @doc """ | |
| 19 | + Add a validation error if the changeset's `field` (default `:name`) | |
| 20 | + starts with a runtime-reserved prefix. | |
| 21 | + """ | |
| 22 | + def validate_not_reserved(changeset, field \\ :name) do | |
| 23 | + validate_change(changeset, field, fn ^field, value -> | |
| 24 | + up = String.upcase(value || "") | |
| 25 | + | |
| 26 | + if Enum.any?(@reserved_prefixes, &String.starts_with?(up, &1)) do | |
| 27 | + [{field, "is reserved by the CI runtime (GITHUB_*, GITEA_*, FORGEJO_*, RUNNER_*, ACTIONS_*)"}] | |
| 28 | + else | |
| 29 | + [] | |
| 30 | + end | |
| 31 | + end) | |
| 32 | + end | |
| 33 | +end |
Parents: ed9a7cd