Problem
`SeiNodeSpec.ChainID` is validated only with `MinLength=1` (seinode_types.go:14-16). The value is interpolated unquoted via `%s` into bash `-c` scripts at:
- `internal/noderesource/noderesource.go::buildSeidInitContainer` (the seid-init script)
- `internal/task/bootstrap_resources.go::bootstrapSeidInitContainer` (the bootstrap variant)
A ChainID like `foo --home /etc; rm -rf /; echo ` passes admission and lands inside `bash -c` as an injection sink.
Surfaced during cross-review of #234 by the security-specialist agent. The PR doesn't widen this surface; it touches adjacent code, which is the moment to flag.
Impact
Anyone with `create/update seinodes` RBAC can execute arbitrary commands inside the seid-init container (uid 65532, with write access to the data PVC and any projected signing-key volumes in the bootstrap variant). The blast radius is the data PVC + any projected secrets mounted into the bootstrap pod.
Today, `create seinodes` is gated to platform operators on harbor. Engineer-cell namespaces have admin via `engineer-service-account` but typically don't author SeiNode CRs directly (they go through seictl + Flux). Risk is bounded but real — RBAC misconfiguration would open the path.
Relevant experts
- security-specialist (threat model owner)
- kubernetes-specialist (CRD validation marker authorship)
Proposed approach
Add a kubebuilder validation marker to `SeiNodeSpec.ChainID`:
```go
// +kubebuilder:validation:Pattern=^[a-zA-Z0-9_.-]+$
// +kubebuilder:validation:MaxLength=50
```
Pattern mirrors cosmos-sdk's accepted chain-ID character class. Length cap is conservative; real chain IDs are <30 chars.
Regenerate CRDs via `make manifests generate`. Existing chains: audit production for any chain ID that wouldn't match (`kubectl get seinodes -A -o jsonpath=...`). None expected — current convention is lowercase-alphanumeric.
Acceptance criteria
Out of scope
- Quoting the ChainID inside the bash script (defense-in-depth, separate consideration)
- Migrating the init scripts away from `bash -c` (larger refactor)
References
Problem
`SeiNodeSpec.ChainID` is validated only with `MinLength=1` (seinode_types.go:14-16). The value is interpolated unquoted via `%s` into bash `-c` scripts at:
A ChainID like `foo --home /etc; rm -rf /; echo ` passes admission and lands inside `bash -c` as an injection sink.
Surfaced during cross-review of #234 by the security-specialist agent. The PR doesn't widen this surface; it touches adjacent code, which is the moment to flag.
Impact
Anyone with `create/update seinodes` RBAC can execute arbitrary commands inside the seid-init container (uid 65532, with write access to the data PVC and any projected signing-key volumes in the bootstrap variant). The blast radius is the data PVC + any projected secrets mounted into the bootstrap pod.
Today, `create seinodes` is gated to platform operators on harbor. Engineer-cell namespaces have admin via `engineer-service-account` but typically don't author SeiNode CRs directly (they go through seictl + Flux). Risk is bounded but real — RBAC misconfiguration would open the path.
Relevant experts
Proposed approach
Add a kubebuilder validation marker to `SeiNodeSpec.ChainID`:
```go
// +kubebuilder:validation:Pattern=^[a-zA-Z0-9_.-]+$
// +kubebuilder:validation:MaxLength=50
```
Pattern mirrors cosmos-sdk's accepted chain-ID character class. Length cap is conservative; real chain IDs are <30 chars.
Regenerate CRDs via `make manifests generate`. Existing chains: audit production for any chain ID that wouldn't match (`kubectl get seinodes -A -o jsonpath=...`). None expected — current convention is lowercase-alphanumeric.
Acceptance criteria
Out of scope
References