mirror of
https://gitea.com/gitea/tea.git
synced 2026-09-10 07:26:33 -04:00
fix(actions): validate secret name and value on create
`runSecretsCreate` previously accepted any string as a secret name and value, forwarding invalid input directly to the Gitea API. The API either rejected with a generic error or accepted inputs that don't match Gitea's documented constraints, producing confusing UX. Add `validateSecretName` enforcing the Gitea Actions naming rules: uppercase letters, numbers, and underscores only; cannot start with a number; cannot use the reserved `GITHUB_` / `GITEA_` prefixes. Add `validateSecretValue` enforcing a non-empty value with a 64KB upper bound (matches the Gitea API limit). Mirror the structure of the existing variable validation in `cmd/actions/variables/set.go` for consistency.
This commit is contained in:
parent
58931b5d17
commit
50bcc1a49d
|
|
@ -6,6 +6,8 @@ package secrets
|
|||
import (
|
||||
stdctx "context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
gitea "gitea.dev/sdk"
|
||||
|
||||
|
|
@ -50,6 +52,9 @@ func runSecretsCreate(ctx stdctx.Context, cmd *cli.Command) error {
|
|||
client := c.Login.Client()
|
||||
|
||||
secretName := cmd.Args().First()
|
||||
if err := validateSecretName(secretName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read secret value using the utility
|
||||
secretValue, err := utils.ReadValue(cmd, utils.ReadValueOptions{
|
||||
|
|
@ -62,6 +67,10 @@ func runSecretsCreate(ctx stdctx.Context, cmd *cli.Command) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := validateSecretValue(secretValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.Actions.CreateRepoSecret(ctx, c.Owner, c.Repo, secretName, gitea.CreateOrUpdateSecretOption{
|
||||
Data: secretValue,
|
||||
})
|
||||
|
|
@ -72,3 +81,41 @@ func runSecretsCreate(ctx stdctx.Context, cmd *cli.Command) error {
|
|||
fmt.Printf("Secret '%s' created successfully\n", secretName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSecretName validates that a secret name follows the required format.
|
||||
func validateSecretName(name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("secret name cannot be empty")
|
||||
}
|
||||
|
||||
// Secret names must be uppercase letters, numbers, and underscores only.
|
||||
// Cannot start with a number.
|
||||
validPattern := regexp.MustCompile(`^[A-Z_][A-Z0-9_]*$`)
|
||||
if !validPattern.MatchString(name) {
|
||||
return fmt.Errorf("secret name must contain only uppercase letters, numbers, and underscores, and cannot start with a number")
|
||||
}
|
||||
|
||||
// Reserved prefixes used by Gitea/GitHub internals.
|
||||
reservedPrefixes := []string{"GITHUB_", "GITEA_"}
|
||||
for _, prefix := range reservedPrefixes {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
return fmt.Errorf("secret name cannot start with reserved prefix: %s", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSecretValue validates that a secret value is acceptable.
|
||||
func validateSecretValue(value string) error {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return fmt.Errorf("secret value cannot be empty or whitespace only")
|
||||
}
|
||||
|
||||
// 64KB upper limit per Gitea API.
|
||||
if len(value) > 65536 {
|
||||
return fmt.Errorf("secret value cannot exceed 64KB")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,133 @@
|
|||
package secrets
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateSecretName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid name",
|
||||
input: "VALID_SECRET_NAME",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid name with numbers",
|
||||
input: "SECRET_123",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid name starting with underscore",
|
||||
input: "_SECRET",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - lowercase",
|
||||
input: "invalid_secret",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - mixed case",
|
||||
input: "Invalid_Secret",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - spaces",
|
||||
input: "INVALID SECRET",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - special chars",
|
||||
input: "INVALID-SECRET!",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - starts with number",
|
||||
input: "1SECRET",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - empty",
|
||||
input: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - reserved GITHUB_ prefix",
|
||||
input: "GITHUB_TOKEN",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - reserved GITEA_ prefix",
|
||||
input: "GITEA_SECRET",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateSecretName(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateSecretName(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSecretValue(t *testing.T) {
|
||||
largeValue := strings.Repeat("a", 65537)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid value",
|
||||
input: "secret_value",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid value with whitespace",
|
||||
input: "secret value with spaces",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - empty",
|
||||
input: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - whitespace only",
|
||||
input: " \t\n ",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid - exceeds 64KB",
|
||||
input: largeValue,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid - exactly 64KB",
|
||||
input: strings.Repeat("a", 65536),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateSecretValue(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validateSecretValue(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSecretSourceArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Reference in a new issue