Skip to content

feat(cloudserver): add input validation for register and search endpoints#51

Open
Tech-Code1 wants to merge 3 commits intoGentleman-Programming:mainfrom
Tech-Code1:feat/input-validation-cloud-api
Open

feat(cloudserver): add input validation for register and search endpoints#51
Tech-Code1 wants to merge 3 commits intoGentleman-Programming:mainfrom
Tech-Code1:feat/input-validation-cloud-api

Conversation

@Tech-Code1
Copy link

Summary

  • POST /auth/register now validates username format/length, email structure, and password length before hitting the database.
  • GET /sync/search now enforces a 500-character cap on the query string and clamps limit to 1–100.
  • Validation lives in a new validators.go file — pure functions, no dependencies, easy to extend.

Motivation

Both endpoints accepted arbitrary input with no bounds checking:

  • A username of "" or 200 characters was accepted.
  • An email like notanemail passed through to Postgres, which would reject it with a cryptic constraint error.
  • A password longer than 72 characters would be silently truncated by bcrypt — the user would believe they have a strong password, but only the first 72 bytes would be hashed.
  • GET /sync/search?limit=999999 would issue an unbounded Postgres FTS query.
  • A 50,000-character q parameter would be passed directly to the FTS engine.

Changes

Endpoint What is validated Status on failure
POST /auth/register Username: 3–50 chars, [a-zA-Z0-9_-] only 400
POST /auth/register Email: must have @, domain must contain . 400
POST /auth/register Password: max 72 chars (bcrypt hard limit) 400
GET /sync/search Query: max 500 chars 400
GET /sync/search Limit: 1–100 400

The existing auth.ErrWeakPassword check (min 8 chars) in the auth layer is preserved — both validations coexist.

Test plan

  • TestValidateRegisterRequest — 13 table-driven cases covering valid input and every rejection path
  • TestValidateSearchParams — 8 cases covering bounds and edge values
  • All existing cloudserver tests pass (SKIP_DOCKER_TESTS=1)
  • Full project builds without errors (go build ./...)

🤖 Generated with Claude Code

…ectly

Introduce ErrNotFound in the cloudstore package so callers can distinguish
"resource does not exist" from database or internal errors. Previously,
GetObservation, GetChunk and GetPrompt surfaced raw sql.ErrNoRows, causing
writeStoreError to fall through to a 500 Internal Server Error even when
the resource simply did not exist.

Changes:
- Add cloudstore.ErrNotFound sentinel alongside ErrProjectSyncPaused
- Wrap sql.ErrNoRows with ErrNotFound in GetObservation, GetChunk, GetPrompt
- Update writeStoreError to resolve ErrNotFound to HTTP 404
- Simplify handlePullChunk to use writeStoreError (removes duplicate branches)
- Add errors_test.go with unit tests for writeStoreError and isDBConnectionError
Copy link
Collaborator

@Alan-TheGentleman Alan-TheGentleman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation layer is a solid idea — catching bad input before it hits Postgres is the right call, and the bcrypt 72-char guard is a nice detail.

A few things to fix before this can go in:

  1. This PR depends on #50 — the diff includes the ErrNotFound check in writeStoreError which doesn't exist on main yet. Please rebase onto #50 or wait until it's merged, then rebase onto main.

  2. Username charset is too restrictive — right now it rejects dots and unicode characters, so usernames like alan.b or señor_dev would fail. For an open source project with global users, we should at minimum allow dots (.). Consider whether unicode letters should be allowed too (Go's unicode.IsLetter already handles this, you just need to keep it in the allowed set).

  3. Password min length not validated — the comment says it "mirrors the 8-char floor in auth.ErrWeakPassword" but the function only checks the max (72). Either validate both min and max here (defense in depth), or drop the comment so it doesn't mislead. I'd prefer validating both since we're already doing input validation at this layer.

  4. Email rejects user@localhost — this is valid in local/dev environments. Not a hard blocker, but if it's easy to relax (e.g. only require a dot if the domain has more than one segment), that'd be nice. Up to you on this one.

Fix (1), (2), and (3) and this is ready.

…ints

Add lightweight validation for the two HTTP endpoints most exposed to
external input: POST /auth/register and GET /sync/search.

Registration now rejects usernames that are too short/long or contain
invalid characters, malformed email addresses, and passwords exceeding
bcrypt's 72-byte processing limit (which would silently truncate longer
passwords, making them weaker than users expect).

Search now enforces a 500-character cap on the query string and clamps
the limit parameter to 1-100, preventing unbounded Postgres FTS queries.

Validation happens before any database call, returning HTTP 400 with a
descriptive message. The auth.ErrWeakPassword check in the auth layer is
preserved and complementary.
…ation

- Allow dots in usernames so handles like `alan.b` are accepted
- Keep unicode.IsLetter so non-ASCII usernames like `señor_dev` work
- Add password minimum length check (8 chars) for defense in depth
- Relax email domain validation to accept single-segment domains like
  `user@localhost`, which are valid in local and dev environments
- Update tests to cover all new cases
@Tech-Code1 Tech-Code1 force-pushed the feat/input-validation-cloud-api branch from 9f2e874 to 177d88c Compare March 8, 2026 18:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants