Skip to content

Commit 9fd200c

Browse files
authored
Fix/CVE 2026 34986 go jose go OIDC (#581)
* fix(deps): address CVE-2026-34986 and drop go-jose/v3 - Bump github.com/go-jose/go-jose/v4 to v4.1.4 (patched for CVE-2026-34986). - Upgrade github.com/coreos/go-oidc/v3 to v3.17.0 so the OIDC stack uses go-jose/v4 only; removes the indirect go-jose/v3 dependency. Made-with: Cursor * chore: update aws * chore: fix tests
1 parent cbce6ff commit 9fd200c

44 files changed

Lines changed: 2377 additions & 1859 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@
3737
"Bash(do echo:*)",
3838
"Read(//Users/lakhansamani/personal/authorizer/authorizer/internal/storage/db/**)",
3939
"Bash(done)",
40-
"Bash(grep:*)"
40+
"Bash(grep:*)",
41+
"Bash(TEST_DBS=\"sqlite\" go test -p 1 -v -count=1 ./internal/integration_tests/)",
42+
"Bash(TEST_DBS=\"sqlite\" go test -p 1 -v -count=1 ./internal/memory_store/...)",
43+
"Bash(TEST_DBS=\"sqlite\" go test -p 1 -v -count=1 ./internal/storage/)"
4144
]
4245
}
4346
}

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
output: "trivy-results.sarif"
7676
severity: "CRITICAL,HIGH"
7777
- name: Upload Trivy results
78-
uses: github/codeql-action/upload-sarif@v3
78+
uses: github/codeql-action/upload-sarif@v4
7979
if: always()
8080
with:
8181
sarif_file: "trivy-results.sarif"

CLAUDE.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ make build-app # Build login UI (web/app)
1919
make build-dashboard # Build admin UI (web/dashboard)
2020
make generate-graphql # Regenerate after schema.graphqls change
2121

22-
# Testing (TEST_DBS env var selects databases, default: postgres)
23-
make test # Docker Postgres (default)
24-
make test-sqlite # SQLite in-memory (no Docker)
25-
make test-mongodb # Docker MongoDB
22+
# Testing
23+
# Integration tests always use SQLite (no Docker needed).
24+
# Storage provider tests honour TEST_DBS (default: all 7 DBs, needs Docker).
25+
# Optional: TEST_ENABLE_REDIS=1 runs Redis memory_store unit tests (Redis on localhost:6380).
26+
make test # SQLite integration + storage (via TEST_DBS)
27+
make test-sqlite # SQLite everywhere (no Docker)
2628
make test-all-db # ALL 7 databases (postgres,sqlite,mongodb,arangodb,scylladb,dynamodb,couchbase)
2729

28-
# Single test against specific DBs
29-
go clean --testcache && TEST_DBS="sqlite,postgres" go test -p 1 -v -run TestSignup ./internal/integration_tests/
30+
# Single test
31+
go clean --testcache && TEST_DBS="sqlite" go test -p 1 -v -run TestSignup ./internal/integration_tests/
3032
```
3133

3234
## Architecture (Quick Reference)
@@ -40,6 +42,7 @@ go clean --testcache && TEST_DBS="sqlite,postgres" go test -p 1 -v -run TestSign
4042
- Token management: `internal/token/`
4143
- Tests: `internal/integration_tests/`
4244
- Frontend: `web/app/` (user UI) | `web/dashboard/` (admin UI)
45+
- Optional NULL semantics across SQL/document DBs and DynamoDB: `docs/storage-optional-null-fields.md`
4346

4447
**Pattern**: Every subsystem uses `Dependencies` struct + `New()``Provider` interface.
4548

@@ -49,7 +52,7 @@ go clean --testcache && TEST_DBS="sqlite,postgres" go test -p 1 -v -run TestSign
4952
2. **Schema changes must update ALL 13+ database providers**
5053
3. **Run `make generate-graphql`** after editing `schema.graphqls`
5154
4. **Security**: parameterized queries only, `crypto/rand` for tokens, `crypto/subtle` for comparisons, never log secrets
52-
5. **Tests**: integration tests with real DBs, table-driven subtests, testify assertions
55+
5. **Tests**: integration tests use SQLite via `getTestConfig()` (no `runForEachDB`); storage tests cover all DBs via `TEST_DBS`; testify assertions
5356
6. **NEVER commit to main** — always work on a feature branch (`feat/`, `fix/`, `security/`, `chore/`), push to the branch, and create a merge request. Main must stay deployable.
5457

5558
## AI Agent Roles

Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# syntax=docker/dockerfile:1.4
22
# Use BuildKit for cache mounts (faster CI: DOCKER_BUILDKIT=1)
3+
#
4+
# Alpine v3.23 main still ships busybox 1.37.0-r30 (e.g. CVE-2025-60876); edge/main has r31+.
5+
# Pin busybox from edge until the stable branch backports it. See alpine/aports work item #17940.
36
FROM golang:1.25-alpine3.23 AS go-builder
7+
ARG ALPINE_EDGE_MAIN=https://dl-cdn.alpinelinux.org/alpine/edge/main
8+
RUN apk add --no-cache -X "${ALPINE_EDGE_MAIN}" "busybox>=1.37.0-r31"
49
WORKDIR /authorizer
510

611
ARG TARGETPLATFORM
@@ -33,6 +38,8 @@ RUN --mount=type=cache,target=/go/pkg/mod \
3338
chmod 755 build/${GOOS}/${GOARCH}/authorizer
3439

3540
FROM alpine:3.23.3 AS node-builder
41+
ARG ALPINE_EDGE_MAIN=https://dl-cdn.alpinelinux.org/alpine/edge/main
42+
RUN apk add --no-cache -X "${ALPINE_EDGE_MAIN}" "busybox>=1.37.0-r31"
3643
WORKDIR /authorizer
3744
COPY web/app/package*.json web/app/
3845
COPY web/dashboard/package*.json web/dashboard/
@@ -47,6 +54,8 @@ COPY web/dashboard web/dashboard
4754
RUN cd web/app && npm run build && cd ../dashboard && npm run build
4855

4956
FROM alpine:3.23.3
57+
ARG ALPINE_EDGE_MAIN=https://dl-cdn.alpinelinux.org/alpine/edge/main
58+
RUN apk add --no-cache -X "${ALPINE_EDGE_MAIN}" "busybox>=1.37.0-r31"
5059

5160
ARG TARGETARCH=amd64
5261

Makefile

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ DEFAULT_VERSION=0.1.0-local
33
VERSION := $(or $(VERSION),$(DEFAULT_VERSION))
44
DOCKER_IMAGE ?= authorizerdev/authorizer:$(VERSION)
55

6+
# Full module test run. Storage provider tests honour TEST_DBS (defaults to all).
7+
# Integration tests and memory_store/db tests always use SQLite.
8+
# Redis memory_store tests run only when TEST_ENABLE_REDIS=1.
9+
GO_TEST_ALL := go test -p 1 -v ./...
10+
611
.PHONY: all bootstrap build build-app build-dashboard build-local-image build-push-image trivy-scan
712

813
all: build build-app build-dashboard
@@ -39,51 +44,50 @@ clean:
3944
dev:
4045
go run main.go --database-type=sqlite --database-url=test.db --jwt-type=HS256 --jwt-secret=test --admin-secret=admin --client-id=123456 --client-secret=secret
4146

42-
test: test-cleanup test-docker-up
43-
go clean --testcache && TEST_DBS="postgres" go test -p 1 -v ./...
44-
$(MAKE) test-cleanup
47+
test:
48+
go clean --testcache && TEST_DBS="sqlite" $(GO_TEST_ALL)
4549

4650
test-postgres: test-cleanup-postgres
4751
docker run -d --name authorizer_postgres -p 5434:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres postgres
4852
sleep 3
49-
go clean --testcache && TEST_DBS="postgres" go test -p 1 -v ./...
53+
go clean --testcache && TEST_DBS="postgres" $(GO_TEST_ALL)
5054
docker rm -vf authorizer_postgres
5155

5256
test-sqlite:
53-
go clean --testcache && TEST_DBS="sqlite" go test -p 1 -v ./...
57+
go clean --testcache && TEST_DBS="sqlite" $(GO_TEST_ALL)
5458

5559
test-mongodb: test-cleanup-mongodb
5660
docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
5761
sleep 3
58-
go clean --testcache && TEST_DBS="mongodb" go test -p 1 -v ./...
62+
go clean --testcache && TEST_DBS="mongodb" $(GO_TEST_ALL)
5963
docker rm -vf authorizer_mongodb_db
6064

6165
test-scylladb: test-cleanup-scylladb
6266
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
6367
sleep 15
64-
go clean --testcache && TEST_DBS="scylladb" go test -p 1 -v ./...
68+
go clean --testcache && TEST_DBS="scylladb" $(GO_TEST_ALL)
6569
docker rm -vf authorizer_scylla_db
6670

6771
test-arangodb: test-cleanup-arangodb
6872
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.10.3
6973
sleep 5
70-
go clean --testcache && TEST_DBS="arangodb" go test -p 1 -v ./...
74+
go clean --testcache && TEST_DBS="arangodb" $(GO_TEST_ALL)
7175
docker rm -vf authorizer_arangodb
7276

7377
test-dynamodb: test-cleanup-dynamodb
7478
docker run -d --name authorizer_dynamodb -p 8000:8000 amazon/dynamodb-local:latest
7579
sleep 3
76-
go clean --testcache && TEST_DBS="dynamodb" go test -p 1 -v ./...
80+
go clean --testcache && TEST_DBS="dynamodb" $(GO_TEST_ALL)
7781
docker rm -vf authorizer_dynamodb
7882

7983
test-couchbase: test-cleanup-couchbase
8084
docker run -d --name authorizer_couchbase -p 8091-8097:8091-8097 -p 11210:11210 -p 11207:11207 -p 18091-18095:18091-18095 -p 18096:18096 -p 18097:18097 couchbase:latest
8185
sh scripts/couchbase-test.sh
82-
go clean --testcache && TEST_DBS="couchbase" go test -p 1 -v ./...
86+
go clean --testcache && TEST_DBS="couchbase" $(GO_TEST_ALL)
8387
docker rm -vf authorizer_couchbase
8488

85-
test-all-db: test-cleanup test-docker-up
86-
go clean --testcache && TEST_DBS="postgres,sqlite,mongodb,arangodb,scylladb,dynamodb,couchbase" go test -p 1 -v ./...
89+
test-all-db: test-cleanup test-docker-up test-cleanup
90+
go clean --testcache && TEST_DBS="postgres,sqlite,mongodb,arangodb,scylladb,dynamodb,couchbase" $(GO_TEST_ALL)
8791
$(MAKE) test-cleanup
8892

8993
# Start all test database containers

cmd/root.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ var (
5555
defaultTwitterScopes = []string{"tweet.read", "users.read"}
5656
defaultRobloxScopes = []string{"openid", "profile"}
5757
// Default RPS cap per IP; raised from 10 to reduce false positives on busy UIs.
58-
defaultRateLimitRPS = float64(30)
58+
defaultRateLimitRPS = 30
5959
defaultRateLimitBurst = 20
6060
)
6161

@@ -161,7 +161,7 @@ func init() {
161161
f.BoolVar(&rootArgs.config.DisableAdminHeaderAuth, "disable-admin-header-auth", false, "Disable admin authentication via X-Authorizer-Admin-Secret header")
162162

163163
// Rate limiting flags
164-
f.Float64Var(&rootArgs.config.RateLimitRPS, "rate-limit-rps", defaultRateLimitRPS, "Maximum requests per second per IP for rate limiting")
164+
f.IntVar(&rootArgs.config.RateLimitRPS, "rate-limit-rps", defaultRateLimitRPS, "Maximum requests per second per IP for rate limiting")
165165
f.IntVar(&rootArgs.config.RateLimitBurst, "rate-limit-burst", defaultRateLimitBurst, "Maximum burst size per IP for rate limiting")
166166
f.BoolVar(&rootArgs.config.RateLimitFailClosed, "rate-limit-fail-closed", false, "On rate-limit backend errors, reject with 503 instead of allowing the request")
167167

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Optional fields and NULL semantics across storage providers
2+
3+
This document explains how **nullable** fields (especially `*int64` timestamps such as `email_verified_at`, `phone_number_verified_at`, `revoked_timestamp`) are **stored**, **updated**, and why we **do not** add broad `json`/`bson` **`omitempty`** tags to those struct fields.
4+
5+
## Goal
6+
7+
Application code uses Go **nil pointers** to mean “unset / not verified / not revoked.” Updates must **clear** a previously set value when the pointer is **nil**, matching SQL **NULL** semantics.
8+
9+
## Behaviour by provider
10+
11+
| Provider | Typical update path | Nil pointer on update |
12+
|----------|---------------------|------------------------|
13+
| **SQL (GORM)** | `Save(&user)` | Written as **SQL NULL**. |
14+
| **Cassandra** | JSON → map → `UPDATE` | Nil map values become **`= null`** in CQL. |
15+
| **MongoDB** | `UpdateOne` with `$set` and the `User` struct | Driver marshals nil pointers as **BSON Null** when the field is **not** `omitempty`, so the field is cleared in the document. |
16+
| **Couchbase** | `Upsert` full document | `encoding/json` encodes nil pointers as JSON **`null`** unless the field uses `json:",omitempty"`, in which case the key is **omitted** and old values can persist. |
17+
| **ArangoDB** | `UpdateDocument` with struct | Encoding follows JSON-style rules; nil pointers become **`null`** when not omitted by tags. |
18+
| **DynamoDB** | `UpdateItem` with **SET** from marshalled attributes | Nil pointers are **omitted from SET** (see `internal/storage/db/dynamodb/marshal.go`). Attributes are **not** removed automatically, so **explicit REMOVE** is required to clear a previously stored attribute. Implemented for users in `internal/storage/db/dynamodb/user.go` (`updateByHashKeyWithRemoves`, `userDynamoRemoveAttrsIfNil`). Reads may normalize `0` → unset via `normalizeUserOptionalPtrs`. |
19+
20+
## Why not use `omitempty` on `json` / `bson` for nullable auth fields?
21+
22+
For **document** databases, **`omitempty`** means: *if this pointer is nil, do not include this key in the encoded payload.*
23+
24+
During an **update**, omitting a key usually means **“do not change this field”**, not **“set to null.”** That reproduces the DynamoDB-class bug: the old value remains.
25+
26+
Therefore, for fields where **nil must clear** stored state, keep **`json` / `bson` tags without `omitempty`** (as in `internal/storage/schemas/user.go`) unless every call site is proven to do a **full document replace** and you have verified the driver behaviour end-to-end.
27+
28+
MongoDB’s own guidance aligns with this: `omitempty` skips marshaling empty values, which is wrong when you need to persist **null** to clear a field in `$set`.
29+
30+
## DynamoDB specifics
31+
32+
- **PutItem**: Omitting nil pointers keeps items small; optional attributes may be absent (same idea as “omitempty” on write, but implemented in custom `marshalStruct` by skipping nil pointers).
33+
- **UpdateItem**: Only **SET** attributes present in the marshalled map. Clearing requires **`REMOVE`** for the corresponding attribute names when the Go field is nil.
34+
- Do **not** rely on adding `dynamo:",omitempty"` alone to “fix” updates: the custom marshaller already skips nil pointers; the gap was **REMOVE** on update, not tag-based omission.
35+
36+
## Related code
37+
38+
- Schema: `internal/storage/schemas/user.go`
39+
- DynamoDB user update + REMOVE list: `internal/storage/db/dynamodb/user.go`
40+
- DynamoDB update helper: `internal/storage/db/dynamodb/ops.go` (`updateByHashKeyWithRemoves`)
41+
- DynamoDB marshal/unmarshal: `internal/storage/db/dynamodb/marshal.go`
42+
43+
## TEST_DBS and memory_store tests
44+
45+
- **`internal/memory_store/db`**: Runs one subtest per entry in `TEST_DBS` (URLs aligned with `internal/integration_tests/test_helper.go` — keep `test_config_test.go` in sync when adding backends).
46+
- **`internal/memory_store` (Redis / in-memory)**: Not driven by `TEST_DBS`. In-memory tests always run; **Redis** subtests run only when **`TEST_ENABLE_REDIS=1`** (or `true`) and Redis is reachable (e.g. `localhost:6380` in `provider_test.go`). See `redisMemoryStoreTestsEnabled` in `provider_test.go`.

go.mod

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@ go 1.25.5
55
require (
66
github.com/99designs/gqlgen v0.17.73
77
github.com/arangodb/go-driver v1.6.0
8-
github.com/aws/aws-sdk-go v1.47.4
9-
github.com/coreos/go-oidc/v3 v3.6.0
8+
github.com/aws/aws-sdk-go-v2 v1.41.5
9+
github.com/aws/aws-sdk-go-v2/config v1.32.14
10+
github.com/aws/aws-sdk-go-v2/credentials v1.19.14
11+
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.37
12+
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.8.37
13+
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.57.1
14+
github.com/aws/smithy-go v1.24.3
15+
github.com/coreos/go-oidc/v3 v3.17.0
1016
github.com/couchbase/gocb/v2 v2.6.4
1117
github.com/ekristen/gorm-libsql v0.0.0-20231101204708-6e113112bcc2
1218
github.com/gin-gonic/gin v1.9.1
1319
github.com/glebarez/sqlite v1.10.0
14-
github.com/go-jose/go-jose/v4 v4.1.3
20+
github.com/go-jose/go-jose/v4 v4.1.4
1521
github.com/gocql/gocql v1.6.0
1622
github.com/golang-jwt/jwt/v4 v4.5.2
1723
github.com/google/uuid v1.6.0
18-
github.com/guregu/dynamo v1.20.2
1924
github.com/pquerna/otp v1.4.0
2025
github.com/prometheus/client_golang v1.23.2
2126
github.com/redis/go-redis/v9 v9.6.3
@@ -41,10 +46,21 @@ require (
4146
github.com/agnivade/levenshtein v1.2.1 // indirect
4247
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect
4348
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect
49+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect
50+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
51+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
52+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
53+
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.32.14 // indirect
54+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
55+
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.21 // indirect
56+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
57+
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect
58+
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 // indirect
59+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect
60+
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect
4461
github.com/beorn7/perks v1.0.1 // indirect
4562
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
4663
github.com/bytedance/sonic v1.9.1 // indirect
47-
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
4864
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4965
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
5066
github.com/couchbase/gocbcore/v10 v10.2.8 // indirect
@@ -55,7 +71,6 @@ require (
5571
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
5672
github.com/gin-contrib/sse v0.1.0 // indirect
5773
github.com/glebarez/go-sqlite v1.21.2 // indirect
58-
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
5974
github.com/go-playground/locales v0.14.1 // indirect
6075
github.com/go-playground/universal-translator v0.18.1 // indirect
6176
github.com/go-playground/validator/v10 v10.14.0 // indirect
@@ -77,7 +92,6 @@ require (
7792
github.com/jackc/puddle/v2 v2.2.1 // indirect
7893
github.com/jinzhu/inflection v1.0.0 // indirect
7994
github.com/jinzhu/now v1.1.5 // indirect
80-
github.com/jmespath/go-jmespath v0.4.0 // indirect
8195
github.com/json-iterator/go v1.1.12 // indirect
8296
github.com/klauspost/compress v1.18.0 // indirect
8397
github.com/klauspost/cpuid/v2 v2.2.4 // indirect

0 commit comments

Comments
 (0)