From f82d76b246f0e6591685727faccf5577617c2f20 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Fri, 24 Apr 2026 16:12:24 +0200 Subject: [PATCH] Add safe URL construction rules to agent guides Add a URL and query parameter construction section to contrib/claude/go-style.md requiring net/url (url.JoinPath, url.Values, url.Parse) instead of fmt.Sprintf or string concatenation. Create contrib/claude/ts-style.md with matching TypeScript rules requiring URL and URLSearchParams instead of template literals or string concatenation. Signed-off-by: Bryan Frimin --- AGENTS.md | 3 ++- contrib/claude/go-style.md | 55 ++++++++++++++++++++++++++++++++++++++ contrib/claude/ts-style.md | 32 ++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 contrib/claude/ts-style.md diff --git a/AGENTS.md b/AGENTS.md index 2fb36fb70..003cceeaf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,8 @@ Detailed guides for specific subsystems live in `contrib/claude/`: - [`contrib/claude/make.md`](contrib/claude/make.md) — GNUmakefile targets, codegen, overridable variables - [`contrib/claude/api-surface.md`](contrib/claude/api-surface.md) — GraphQL / MCP / CLI / n8n sync rules -- [`contrib/claude/go-style.md`](contrib/claude/go-style.md) — Go project deps, style (declarations, calls, imports, errors, naming, logging) +- [`contrib/claude/go-style.md`](contrib/claude/go-style.md) — Go project deps, style (declarations, calls, imports, errors, naming, logging, safe URL construction) +- [`contrib/claude/ts-style.md`](contrib/claude/ts-style.md) — TypeScript style (safe URL construction) - [`contrib/claude/go-testing.md`](contrib/claude/go-testing.md) — Go test conventions (parallel, require vs assert, naming) - [`contrib/claude/go-service.md`](contrib/claude/go-service.md) — Go service orchestration (Run, graceful shutdown, crash propagation) - [`contrib/claude/go-worker.md`](contrib/claude/go-worker.md) — Go worker pattern (poll-based, bounded concurrency, FOR UPDATE SKIP LOCKED) diff --git a/contrib/claude/go-style.md b/contrib/claude/go-style.md index b0ed92f79..07ed771ee 100644 --- a/contrib/claude/go-style.md +++ b/contrib/claude/go-style.md @@ -175,6 +175,61 @@ type ctxKey struct{ name string } var trustCenterIDKey = &ctxKey{name: "trust_center_id"} ``` +## URL and query parameter construction + +**Never** build URLs with `fmt.Sprintf`, string concatenation, or any form of string formatting. Always use the `net/url` package to construct URLs safely. + +- Use `url.URL` struct to build full URLs (scheme, host, path, query). +- Use `url.Values` to build query parameters, then call `.Encode()`. +- Use `url.QueryEscape` or `url.PathEscape` when embedding a single value into a known-safe base. +- Use the `pkg/baseurl.URLBuilder` when constructing URLs from configured base URLs. + +```go +// Bad — fmt.Sprintf +endpoint := fmt.Sprintf("https://api.example.com/users/%s?active=%t", userID, active) + +// Bad — string concatenation +endpoint := "https://api.example.com/orgs/" + orgID + "/members" + +// Good — url.JoinPath escapes each segment and sets Path + RawPath +u, err := url.JoinPath("https://api.example.com", "users", userID) +if err != nil { + return fmt.Errorf("cannot build URL: %w", err) +} + +parsed, err := url.Parse(u) +if err != nil { + return fmt.Errorf("cannot parse URL: %w", err) +} + +q := parsed.Query() +q.Set("active", strconv.FormatBool(active)) +parsed.RawQuery = q.Encode() + +// Good — URLBuilder from pkg/baseurl +u, err := baseURL.URL("/users", userID). + Query("active", strconv.FormatBool(active)). + Build() +``` + +The same rule applies to query parameters specifically: never concatenate `"?key=" + val + "&other=" + val2`. Always use `url.Values` and assign via `RawQuery`: + +```go +// Bad +raw := baseEndpoint + "?domain=" + domain + "&limit=100" + +// Good +u, err := url.Parse(baseEndpoint) +if err != nil { + return fmt.Errorf("cannot parse endpoint: %w", err) +} + +q := u.Query() +q.Set("domain", domain) +q.Set("limit", "100") +u.RawQuery = q.Encode() +``` + ## Logging `go.gearno.de/kit/log` — named, context-aware structured logging with typed fields. **Never log PII, PHI, or other sensitive data** (e.g. emails, names, passwords, tokens, health records). Log opaque identifiers (IDs, request IDs) instead. See [`contrib/claude/logging.md`](logging.md) for the full guide (allowed/forbidden data, field helpers, wiring patterns). diff --git a/contrib/claude/ts-style.md b/contrib/claude/ts-style.md new file mode 100644 index 000000000..b8d09a172 --- /dev/null +++ b/contrib/claude/ts-style.md @@ -0,0 +1,32 @@ +# TypeScript Style + +## URL and query parameter construction + +**Never** build URLs with template literals, string concatenation, or string formatting. Always use the `URL` and `URLSearchParams` APIs. + +- Use `new URL()` to construct or parse full URLs. +- Use `URLSearchParams` to build query strings. +- Use `.pathname`, `.searchParams`, and other `URL` properties to modify parts of a URL safely. +- Use `encodeURIComponent` for dynamic path segments. + +```typescript +// Bad — template literal +const endpoint = `https://api.example.com/users/${userId}?active=${active}`; + +// Bad — string concatenation +const endpoint = "https://api.example.com/orgs/" + orgId + "/members"; + +// Bad — query params via string concat +const qs = "?domain=" + domain + "&limit=100"; + +// Good — URL object +const url = new URL("https://api.example.com"); +url.pathname = `/users/${encodeURIComponent(userId)}`; +url.searchParams.set("active", String(active)); + +// Good — URLSearchParams for query strings +const params = new URLSearchParams(); +params.set("domain", domain); +params.set("limit", "100"); +const qs = params.toString(); +```