Skip to content

validate: tags on request DTOs are not enforced by anything #14

Closed

Description

@0xDeon

Found while implementing Cookit-labs/Intent-Agent#2, which aligns the agent service's proposal schema with this backend's DTOs.

Problem

CreateProposalRequest in internal/handlers/types.go carries validation tags on every field:

type CreateProposalRequest struct {
	AgentID                   string  `json:"agent_id" validate:"required"`
	StrategyType              string  `json:"strategy_type" validate:"required"`
	ProjectedSlippage         float64 `json:"projected_slippage" validate:"required,gte=0"`
	ProjectedExecutionQuality float64 `json:"projected_execution_quality" validate:"required,gte=0,lte=1"`
	ProposedAmount            string  `json:"proposed_amount" validate:"required"`
	ExecutionPath             string  `json:"execution_path" validate:"required"`
}

Nothing enforces them. There is no validator library in go.mod — no go-playground/validator or equivalent — and no code reads the tags. CreateProposal in internal/handlers/proposals.go decodes and persists:

if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
    respondError(w, http.StatusBadRequest, "invalid request body")
    return
}

The same applies to CreateIntentRequest, ValidateExecutionRequest, and SettleExecutionRequest.

Impact

A proposal with an empty agent_id, zero projected_slippage, and an empty execution_path is accepted and written today. Concretely:

  • projected_execution_quality outside 0–1 corrupts scoring in internal/scoring/engine.go, since the engine assumes a bounded input
  • zero projected_slippage makes a proposal look optimal, so a malformed or malicious bid outranks every honest one
  • an empty execution_path gives a winning proposal no venue to execute against
  • SettleExecutionRequest.ActualSlippage and ExecutionFeeUSDC feed reputation and fee accounting with no bounds check

The tags being present is arguably worse than their absence: they read as enforced, so a caller reviewing the DTO would reasonably assume the boundary is guarded.

Options

  1. Add go-playground/validator/v10 and run it in each handler after decode. Matches the tags already written; smallest diff.
  2. Validate explicitly in each handler and delete the tags. No dependency, more code, no chance of the tags drifting from reality again.
  3. Delete the tags and document that validation is the caller's job. Only honest if the callers are trusted, which they are not once the marketplace is open.

Option 1 unless the team objects to the dependency.

Note

Cookit-labs/Intent-Agent#2 added matching range validation on the Python side, so proposals from that service are bounded before they are sent. That is defence in depth, not a substitute — it only covers one caller, and a caller validating its own requests is not a boundary.

Activity

  1. 0xDeon commented on Sep 8, 2026

    @0xDeon
    ContributorAuthor

    Fixed in #27 (merged).

    Validate tags are now enforced at all seven decode sites, with every failure reported at once and fields named as the caller sent them. Slippage and quality moved from required to range checks, since required rejects the zero value and zero projected slippage is a legitimate claim.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions