Reject invalid secret names before any command builds a path (closes #33)
check / check (push) Failing after 18s
check / check (push) Failing after 18s
`secret rm ..` resolved to the vault directory and deleted the whole vault; `secret rm .` and `secret rm ""` deleted every secret. rm, mv, the version commands, encrypt and decrypt built paths from the name without checking it; import checked it only after reading the source file. vault.ValidateSecretName wraps the existing name rule and its error states the rule. Each of those commands calls it on the name as given, before building any path; a move checks both names before switching the current vault. AddSecret, GetSecretVersion and GetSecretObject use it too. The regression test copies two in-memory vaults for each rejected command and requires the exact error and an unchanged state directory. Model: opus-5-5
This commit is contained in:
@@ -29,10 +29,11 @@ var (
|
||||
// ErrNilValueBuffer indicates a nil value buffer was supplied.
|
||||
ErrNilValueBuffer = errors.New("value buffer is nil")
|
||||
|
||||
// ErrInvalidSecretName indicates a secret name that does not match
|
||||
// the allowed pattern [a-z0-9.\-_/]+. Composed as
|
||||
// "invalid secret name '<name>': must match pattern [a-z0-9.\-_/]+",
|
||||
// or as "invalid secret name: <name>" by GetSecretObject.
|
||||
// ErrInvalidSecretName indicates a secret name that breaks the naming
|
||||
// rule: only letters, digits, '.', '-', '_' and '/'; not empty; no
|
||||
// leading '.' or '/', no trailing '/', no '//', no '..' path segment.
|
||||
// Composed by ValidateSecretName as
|
||||
// "invalid secret name '<name>': <the rule>".
|
||||
ErrInvalidSecretName = errors.New("invalid secret name")
|
||||
|
||||
// ErrSecretExists indicates the secret already exists and --force
|
||||
|
||||
+26
-12
@@ -79,6 +79,7 @@ func (v *Vault) ListSecrets() ([]string, error) {
|
||||
// - No leading or trailing slashes
|
||||
// - No double slashes
|
||||
// - No names starting with dots
|
||||
// - No ".." path segments
|
||||
func isValidSecretName(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
@@ -110,6 +111,22 @@ func isValidSecretName(name string) bool {
|
||||
return matched
|
||||
}
|
||||
|
||||
// ValidateSecretName returns an error wrapping ErrInvalidSecretName when
|
||||
// name is not a valid secret name. Call it on the name exactly as the user
|
||||
// gave it, before building any path from it.
|
||||
func ValidateSecretName(name string) error {
|
||||
if !isValidSecretName(name) {
|
||||
return fmt.Errorf(
|
||||
"%w '%s': only letters, digits, '.', '-', '_' and '/' are allowed, "+
|
||||
"and a name must not be empty, start with '.' or '/', end with '/', "+
|
||||
"contain '//', or have '..' as a path segment",
|
||||
ErrInvalidSecretName, name,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSecret adds a secret to this vault
|
||||
func (v *Vault) AddSecret(name string, value *memguard.LockedBuffer, force bool) error {
|
||||
if value == nil {
|
||||
@@ -124,13 +141,11 @@ func (v *Vault) AddSecret(name string, value *memguard.LockedBuffer, force bool)
|
||||
)
|
||||
|
||||
// Validate secret name
|
||||
if !isValidSecretName(name) {
|
||||
err := ValidateSecretName(name)
|
||||
if err != nil {
|
||||
secret.Debug("Invalid secret name provided", "secret_name", name)
|
||||
|
||||
return fmt.Errorf(
|
||||
"%w '%s': must match pattern [a-z0-9.\\-_/]+",
|
||||
ErrInvalidSecretName, name,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
secret.Debug("Secret name validation passed", "secret_name", name)
|
||||
@@ -358,8 +373,9 @@ func (v *Vault) UnlockVault() (*age.X25519Identity, error) {
|
||||
|
||||
// GetSecretObject retrieves a Secret object with metadata loaded from this vault
|
||||
func (v *Vault) GetSecretObject(name string) (*secret.Secret, error) {
|
||||
if !isValidSecretName(name) {
|
||||
return nil, fmt.Errorf("%w: %s", ErrInvalidSecretName, name)
|
||||
err := ValidateSecretName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First check if the secret exists by checking for the metadata file
|
||||
@@ -628,13 +644,11 @@ func (v *Vault) updatePreviousVersion(
|
||||
// version exist, and resolves an empty version to the current one.
|
||||
func (v *Vault) resolveSecretVersion(name, version string) (string, error) {
|
||||
// Validate secret name to prevent path traversal
|
||||
if !isValidSecretName(name) {
|
||||
err := ValidateSecretName(name)
|
||||
if err != nil {
|
||||
secret.Debug("Invalid secret name provided", "secret_name", name)
|
||||
|
||||
return "", fmt.Errorf(
|
||||
"%w '%s': must match pattern [a-z0-9.\\-_/]+",
|
||||
ErrInvalidSecretName, name,
|
||||
)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Get vault directory
|
||||
|
||||
Reference in New Issue
Block a user