feat: add secret value and file path validation (#24269)

Add secret value validation to reject null bytes and values exceeding 32KB.
The 32KB limit applies uniformly to both env var and file secrets because the
value field is shared and the destination can change after creation.

Add file path validation to also reject null bytes and paths exceeding 4096
bytes.

Wire up secret value validation into both POST and PATCH handlers.
This commit is contained in:
Zach
2026-04-13 07:24:34 -06:00
committed by GitHub
parent 6ab30123bf
commit 4854f33678
5 changed files with 161 additions and 5 deletions
+14
View File
@@ -46,6 +46,13 @@ func (api *API) postUserSecret(rw http.ResponseWriter, r *http.Request) {
})
return
}
if err := codersdk.UserSecretValueValid(req.Value); err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid secret value.",
Detail: err.Error(),
})
return
}
envOpts := codersdk.UserSecretEnvValidationOptions{
AIGatewayEnabled: api.DeploymentValues.AI.BridgeConfig.Enabled.Value(),
}
@@ -212,6 +219,13 @@ func (api *API) patchUserSecret(rw http.ResponseWriter, r *http.Request) {
FilePath: "",
}
if req.Value != nil {
if err := codersdk.UserSecretValueValid(*req.Value); err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid secret value.",
Detail: err.Error(),
})
return
}
params.Value = *req.Value
}
if req.Description != nil {
+52
View File
@@ -2,6 +2,7 @@ package coderd_test
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -188,6 +189,36 @@ func TestPostUserSecret(t *testing.T) {
require.ErrorAs(t, err, &sdkErr)
assert.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
})
t.Run("NullByteInValue", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)
_, err := client.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
Name: "null-byte-secret",
Value: "before\x00after",
})
require.Error(t, err)
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr)
assert.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
assert.Contains(t, sdkErr.Message, "Invalid secret value")
})
t.Run("OversizedValue", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)
_, err := client.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
Name: "oversized-secret",
Value: strings.Repeat("a", codersdk.MaxSecretValueSize+1),
})
require.Error(t, err)
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr)
assert.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
assert.Contains(t, sdkErr.Message, "Invalid secret value")
})
}
func TestGetUserSecrets(t *testing.T) {
@@ -372,6 +403,27 @@ func TestPatchUserSecret(t *testing.T) {
require.ErrorAs(t, err, &sdkErr)
assert.Equal(t, http.StatusConflict, sdkErr.StatusCode())
})
t.Run("InvalidValue", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)
_, err := client.CreateUserSecret(ctx, codersdk.Me, codersdk.CreateUserSecretRequest{
Name: "patch-invalid-val",
Value: "good-value",
})
require.NoError(t, err)
badVal := "before\x00after"
_, err = client.UpdateUserSecret(ctx, codersdk.Me, "patch-invalid-val", codersdk.UpdateUserSecretRequest{
Value: &badVal,
})
require.Error(t, err)
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr)
assert.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
assert.Contains(t, sdkErr.Message, "Invalid secret value")
})
}
func TestDeleteUserSecret(t *testing.T) {