Documentation
¶
Overview ¶
Package validator provides input quality checks for GTD entries and related user-facing fields. All checks use simple string operations (no complex backtracking regexes) to prevent ReDoS on adversarial input.
Index ¶
- Constants
- Variables
- func CheckCommandField(name, value string) string
- func CheckDecisionNoise(title, ctx, decision, rationale string) string
- func CheckField(name, value string) string
- func CheckHandoffNoise(intent, contextSummary string) string
- func CheckKindFields(kind, description string) []string
- func CheckTaskInput(description, kind string) []string
- func CheckVagueness(field, text, kind string) []string
- func IsValidKind(kind string) bool
- func IsValidRepoName(name string) bool
- func SanitizeTags(raw []string) ([]string, string)
- func StrictModeEnabled() bool
- func ValidateBranchName(s string) string
Constants ¶
const ( // MaxTagLen is the maximum byte length for a single tag. MaxTagLen = 100 // MaxTagCount is the maximum number of tags allowed in one call. MaxTagCount = 20 // MaxFieldLen is the maximum byte length for text fields (title, context, decision, rationale). MaxFieldLen = 5000 )
const KindGeneral = "general"
KindGeneral is the default task kind used when none is supplied; centralised here so MCP / HTTP handlers don't duplicate the string literal (goconst).
const MaxBranchNameLen = 255
MaxBranchNameLen is the maximum rune count allowed for a task's branch_name field. Not a git limit (git itself allows much longer names) — a UX/DB-hygiene cap shared by the MCP and HTTP entry points.
Variables ¶
var GitHubPRURLRe = regexp.MustCompile(`^https://github\.com/[^/]+/[^/]+/pull/\d+(/)?$`)
GitHubPRURLRe matches GitHub PR URLs of the form https://github.com/{owner}/{repo}/pull/{number}[/] SECURITY: only used for format validation — no HTTP fetch is ever made. Single canonical definition shared by handler and mcp packages to avoid drift.
var RepoNameRe = regexp.MustCompile(`^[a-zA-Z0-9_.\-]{1,100}$`)
RepoNameRe enforces a safe slug format for project repo_name values. Previously defined only inside internal/mcp/tools_gtd.go — hoisted here so MCP tools, HTTP handlers, and the store layer (PG + SQLite) share a single source of truth for the format instead of drifting independently.
Not a security boundary by itself (repo_name is never interpolated into a subprocess argv or filesystem path — see RepoSlugRe in repo_regex.go for the slug that IS used in `gh -R <slug>`), but keeps the column semantically queryable and rejects control characters / path-traversal sequences from entering the DB.
var RepoSlugRe = regexp.MustCompile(`^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$`)
RepoSlugRe validates owner/repo style GitHub slugs at the reconcile boundary where the slug flows into `gh -R <slug>` CLI invocation. Rejects whitespace, shell metas, path traversal, control chars.
SECURITY: must match before passing the slug to a subprocess command. GitHub itself permits ASCII letters, digits, hyphens, underscores, and dots in both owner and repo segments; rejecting anything else is conservative but eliminates shell-injection / path-traversal / newline-smuggling vectors.
var ValidTaskKinds = []string{KindGeneral, "fix-pr", "feature", "refactor", "research", "chore"}
ValidTaskKinds is the allowlist of accepted task kind values. The CHECK constraint in the DB migration (000044) is a secondary defence; this allowlist is the primary client-side gate.
Functions ¶
func CheckCommandField ¶
CheckCommandField rejects strings that contain ASCII control characters (< 0x20, excluding tab \t=0x09), carriage return \r, newline \n, or null byte \x00. These characters are adversarial in shell command or expected- output fields: a prompt-injected agent could store "\ngit push" to make the LLM interpret the second line as a separate shell instruction.
Returns a non-empty reason when the value is unsafe, empty string otherwise.
func CheckDecisionNoise ¶
CheckDecisionNoise validates the four required text fields of log_decision. Returns a non-empty reason when any field is noisy, empty string otherwise.
func CheckField ¶
CheckField returns a non-empty reason when value violates noise heuristics for a named text field (byte-length cap, <script> tag, markdown fence). An empty return means the value is acceptable. Shared by both the MCP and HTTP entry points for log_decision / set_session_handoff / worksession title fields — this is the single implementation; callers must not duplicate the <script> / fence detection logic.
func CheckHandoffNoise ¶
CheckHandoffNoise validates the text fields of set_session_handoff. Returns a non-empty reason when any field is noisy, empty string otherwise.
func CheckKindFields ¶
CheckKindFields verifies that description contains the per-kind required markers. Returns a slice of warning strings; empty means no issues. All checks use plain strings.Contains — no backtracking regex.
func CheckTaskInput ¶
CheckTaskInput is the single source of truth for "which quality checks run against a task's description" — CheckVagueness (field fixed at "description", matching every call site's existing warning-message text) composed with CheckKindFields. Every entry point that creates or accepts a task (HTTP CreateTask, MCP add_task, HTTP/MCP proposal-accept single + batch) MUST route through this function instead of inlining the same two calls, so a future entry point can't silently drift from what the others check.
Pure function — no I/O, no env reads. Returns a slice of warning strings; empty means no issues detected.
func CheckVagueness ¶
CheckVagueness inspects text for vague markers and patterns. field is the human-readable field name used in warning messages (e.g. "description").
kind == "chore" skips all checks — chore tasks have no required body structure.
Returns a slice of warning strings; empty means no issues detected. All checks use plain string operations (no backtracking regex) to avoid ReDoS.
func IsValidKind ¶
IsValidKind reports whether kind is a known task kind.
func IsValidRepoName ¶
IsValidRepoName reports whether name is acceptable as a project repo_name value. Empty string is valid — repo_name is optional; callers that require a non-empty value must check that separately.
func SanitizeTags ¶
SanitizeTags accepts a slice of raw tag strings (already split from a comma-separated input) and returns a cleaned slice. Rules applied per tag:
- Tags longer than MaxTagLen bytes are dropped.
- Characters outside [\w\-_./] are stripped; if the result is empty the tag is dropped.
If the input slice exceeds MaxTagCount entries, a nil slice and a rejection reason are returned so the caller can respond with -32602 (MCP) or 400 (HTTP).
func StrictModeEnabled ¶
func StrictModeEnabled() bool
StrictModeEnabled reports whether WBT_STRICT_VAGUENESS is set to a truthy value in the server environment, as understood by strconv.ParseBool ("1", "t", "T", "true", "True", "TRUE"). Never sourced from tool arguments or request bodies (user-controlled) — this is the single, server-side read of the env var; every HTTP handler and MCP tool that gates task creation/acceptance on CheckTaskInput's warnings reads the gate decision through this function instead of duplicating the os.Getenv + ParseBool pair.
func ValidateBranchName ¶
ValidateBranchName checks the invariants shared by the MCP (tools_gtd.go) and HTTP (gtd_handler.go) layers for a task's branch_name field:
- Length is counted in runes, not bytes. A 255-character CJK branch name is well within git's own limits but would trip a byte-length check because each character is 2-3 bytes in UTF-8 — counting bytes here previously caused the MCP and HTTP paths to disagree on the same input (sprint 8-7 gap E).
- ASCII control characters (< 0x20), DEL (0x7F), and Unicode category C ("Other": Cc control, Cf format like U+200B zero-width space / U+FEFF BOM, Co private-use, Cs surrogate) are rejected outright.
Returns a non-empty, user-facing error message on violation, or an empty string when s is acceptable — including the empty string itself; callers decide separately whether branch_name is required.
Types ¶
This section is empty.