Fallout-build / Fallout-build/Fallout
[NeverLiteral] parameter validator — refuse committed-literal values for env-sourced fields
- Dominant language
- C#
- Stars
- 154
- Forks
- 19
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 15
Description
## Motivation
In the [ErpForFactoryGames CD bring-up](https://github.com/ChrisonSimtian/ErpForFactoryGames/pull/265), the deploy target's address (a private-range LXC IP) was committed in `deploy/erp-deploy.json` as a string literal — `"Host": "10.10.107.175"`. Not a secret (RFC1918, not externally routable), but it leaked the homelab's internal topology to a public repo. We fixed it in [ErpForFactoryGames#266](https://github.com/ChrisonSimtian/ErpForFactoryGames/pull/266) by swapping the IP for an `erp-lxc` SSH-config alias resolved at runtime.
Catch was: nothing in the framework warned us. The field is plumbed through `[Parameter]` and `RemoteOptions.Host` and `SshConnectionResolver` cleanly — but the literal value sitting in a JSON config file passed every other check. Gitleaks didn't flag it (it's not credential-shaped). GitHub Secret Scanning didn't flag it. Code review didn't catch it because the IP looked like config, not a secret.
This is a generic CD smell: **fields that should resolve from environment / secret store / operator config sometimes get pinned as literals in committed files**, and the framework silently allows it. ADR-0002's `[Secret]` story handles credential-shaped fields well; the gap is the non-credential fields where the *literal* is the leak.
## Proposal
Two attributes that share a validator:
```csharp
// "This field must resolve from environment / prompt / explicit override at run-time.
// Refuse to start if it resolves to a literal in a committed config file."
[NeverLiteral]
// Stronger form: this field must come from a [SecretSource]-shaped lookup
// (env-var named after the field, secret store, prompt). Refuses parameter
// flags AND config-file values. Implied for [Secret].
[FromEnvironment]
```
Behaviour:
- At parameter-resolution time, the validator looks at *how* the value was resolved.
- If `[NeverLiteral]` and the resolution source is "committed config file", fail with `"Field {Name} must not be a literal in a committed file — set ${ENV_VAR_NAME} instead."`
- Local override files (e.g. `*.local.json` that are gitignored) pass — committed status is the discriminator, checkable via `git check-ignore`.
The validator runs on every `./build.sh` invocation, not just `Doctor`. Lifts the "we forgot to use the env-var" failure to the very first frame instead of letting it ship.
## Why this fits Fallout (not gitleaks / secret-scanning)
- Gitleaks detects patterns *in the file*. It can't tell whether a string is "supposed to be env-sourced" — that's a schema concern, only knowable from the C# parameter declaration.
- GitHub Secret Scanning is partner-pattern-driven. Non-credential leaks (IPs, internal hostnames, identifiers) aren't covered.
- Fallout already owns the parameter schema. The cost is `O(LOC for one validator)` rather than maintaining an external regex set.
## Open questions
- **Granularity.** Field-level (this string) vs object-level (this whole `RemoteOptions` block must be env-resolved). Probably both, with object-level implemented as "every field marked `[NeverLiteral]`".
- **CI vs local enforcement.** Should the validator fail the build hard, or just warn locally and fail in CI? `[Secret]` fails hard already; `[NeverLiteral]` should probably match.
- **Backwards compat for migrations.** A repo retrofitting this on existing fields needs a transition mode (warn but don't fail) — otherwise the first `[NeverLiteral]` lands and every old PR breaks.
- **Detection of "committed file" without shelling to git.** Caller might pass a `--config ` not yet under source control. Cheapest probe: `git check-ignore --quiet ` exit code; fallback to "is the file inside the repo tree" + "is it tracked at HEAD" via libgit2.
## Related
- ADR-0002 / #167 — same concern space as `[Secret]`; this is its non-credential sibling.
- #251 (Doctor) — could implement this as a `LiteralLeakProbe` rather than a parameter-attribute, if Doctor lands first.
Contributor guide
Assessment
This issue has not been assessed yet.