Skip to content
Open
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
6 changes: 6 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions coderd/csp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ package coderd

import (
"encoding/json"
"errors"
"fmt"
"net/http"

"cdr.dev/slog/v3"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/codersdk"
)

// cspReportMaxBytes bounds the size of a single CSP violation report. This
// endpoint is unauthenticated and CSRF-exempt (it's the browser's
// `report-uri` target), so it must not allow unbounded body sizes to reach
// json.Decode. Real CSP reports are small JSON objects; 64KB is generous.
const cspReportMaxBytes = 64 * 1024

type cspViolation struct {
Report map[string]interface{} `json:"csp-report"`
}
Expand All @@ -22,14 +30,23 @@ type cspViolation struct {
// @Tags General
// @Param request body cspViolation true "Violation report"
// @Success 200
// @Failure 413 {object} codersdk.Response
// @Router /api/v2/csp/reports [post]
func (api *API) logReportCSPViolations(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var v cspViolation

r.Body = http.MaxBytesReader(rw, r.Body, cspReportMaxBytes)
dec := json.NewDecoder(r.Body)
err := dec.Decode(&v)
if err != nil {
if _, ok := errors.AsType[*http.MaxBytesError](err); ok {
httpapi.Write(ctx, rw, http.StatusRequestEntityTooLarge, codersdk.Response{
Message: "Request body too large.",
Detail: fmt.Sprintf("Maximum CSP report size is %d bytes.", cspReportMaxBytes),
})
return
}
api.Logger.Warn(ctx, "CSP violation reported", slog.Error(err))
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to read body, invalid json.",
Expand Down
69 changes: 69 additions & 0 deletions coderd/csp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package coderd_test

import (
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)

func TestPostCSPViolations(t *testing.T) {
t.Parallel()

client := coderdtest.New(t, nil)

// oversizedReportBody builds a JSON body well over the 64KB limit
// enforced by coderd.cspReportMaxBytes, mirroring the Cure53 PoC of
// posting oversized bodies to force unbounded heap allocation.
oversizedReportBody := func() []byte {
padding := strings.Repeat("a", 128*1024)
return []byte(`{"csp-report":{"padding":"` + padding + `"}}`)
}

tests := []struct {
name string
body any
expectedStatus int
}{
{
name: "OK",
body: map[string]any{
"csp-report": map[string]any{
"document-uri": "https://example.com",
"violated-directive": "script-src",
},
},
expectedStatus: http.StatusOK,
},
{
name: "OversizedBody",
body: oversizedReportBody(),
expectedStatus: http.StatusRequestEntityTooLarge,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

ctx := testutil.Context(t, testutil.WaitShort)
res, err := client.Request(ctx, http.MethodPost, "/api/v2/csp/reports", tt.body)
require.NoError(t, err)
defer res.Body.Close()

if tt.expectedStatus != http.StatusOK {
apiErr := codersdk.ReadBodyAsError(res)
var sdkErr *codersdk.Error
require.ErrorAs(t, apiErr, &sdkErr)
require.Equal(t, tt.expectedStatus, sdkErr.StatusCode())
return
}
require.Equal(t, tt.expectedStatus, res.StatusCode)
})
}
}
12 changes: 9 additions & 3 deletions docs/reference/api/general.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading