Skip to content

Validation rules

Every value that flows from you — a CLI flag, a wizard answer, a manifest field — into a template is validated before it is rendered. The rules are tight on purpose: constraining the character class removes most of the template-injection class outright, and the escape filters are only defence in depth behind it.

Every rule normalises its input to Unicode NFC before checking, so homoglyph and combining-mark tricks fail rather than sneaking past a regex. Rejections name the field and the rule; they do not echo the value above debug level.

The rules at a glance

Rule Applies to Pattern or limit
Tool / command name generate project --name, top-level command names ^[a-z][a-z0-9-]{0,63}$, not reserved; top-level command names must also not be a Rust keyword
Description --description, --about ≤ 500 bytes after NFC; no control characters except tab; no {{ or }}
Environment prefix env-prefix fields empty, or ^[A-Z][A-Z0-9_]{0,31}$
Command path generate command, remove command each /-separated segment matches ^[a-z][a-z0-9-]{0,63}$; at most 4 segments
Flag name generate flag, remove flag, and setting names ^[a-z][a-z0-9-]{0,63}$, ≤ 64 bytes, must not start with --

Names

Lowercase ASCII, a letter first, then letters, digits or hyphens, up to 64 characters. No underscores, no uppercase, no dots, no path separators.

$ rtb generate project --name version --non-interactive
  x invalid tool name: name: `version` is reserved (framework default or scaffolder verb)

That class is chosen so that shell metacharacters, path separators, YAML/TOML/Markdown metacharacters and Unicode look-alikes are all impossible by construction rather than by a blocklist.

Reserved command and tool names

A name may not shadow a framework command or a scaffolder verb:

version, doctor, update, docs, mcp, config, credentials, telemetry, init, generate, remove, regenerate, help, and the empty string.

The check applies to a tool name and to the first segment of a command path only. kube/version is fine; a bare version is not — a nested segment becomes a path string, not a top-level command.

The framework's own crate names are not on the list. rtb generate project --name rtb succeeds and produces a tool called rtb; nothing stops you shadowing the scaffolder's own binary name.

Rust keywords

A top-level command name also becomes a module name and a struct identifier in the generated code, so a Rust keyword would not compile. All strict, reserved-for-future-use and 2018-edition keywords are refused, including async, await, dyn, try, box and macro. Nested segments are exempt for the same reason as above.

Both lists are public constants (RESERVED_NAMES, RUST_KEYWORDS), so tooling can assert against the canonical set instead of restating it.

Descriptions

At most 500 bytes measured after NFC normalisation, so a normalisation that lengthens the string can push a borderline value over. Tab is the only control character allowed. {{ and }} are refused outright — not because the current engine would reparse them, but so that a future move to an engine that does cannot turn stored data into a directive.

Command paths

Up to four /-separated segments, each matching the name pattern, with no leading or trailing /. Depth is capped to keep generated help trees navigable.

The validator accepts multi-segment paths, but generate command and remove command both reject anything containing / with "nested command paths are not yet supported" — so in practice, one segment.

Flag names — and setting names

Pass the bare name. A leading -- is rejected with a message saying so, rather than being stripped, so a script that got the calling convention wrong fails loudly instead of quietly producing ----flag.

The same validator is used for generate setting <FIELD_NAME>, which is why a setting cannot be snake_case even though the help text describes it that way — see what RTB does not do.

Where the rules live

crates/rtb-cli-bin/src/validate.rs, mirroring internal/generator/validate.go in Go Tool Base field for field. The parity is deliberate: the same concept — a tool name, an env prefix, a command path — gets the same constraint in both scaffolders, so a team using both does not have to hold two sets of rules in their head.

When you add a user-facing field, add its rule there and record it in docs/development/template-security.md.