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 <bryan@getprobo.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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).
|
||||
|
||||
32
contrib/claude/ts-style.md
Normal file
32
contrib/claude/ts-style.md
Normal file
@@ -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();
|
||||
```
|
||||
Reference in New Issue
Block a user