Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/workcell-citools/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func subcommands() []subcommand {
{"git-config-blocklist-parity", "ROOT_DIR", 1, 1, cmdGitConfigBlocklistParity},
{"workcell-hardening-invariants", "ROOT_DIR", 1, 1, cmdWorkcellHardeningInvariants},
{"workcell-config-safety", "ROOT_DIR", 1, 1, cmdWorkcellConfigSafety},
{"workcell-runtime-invariants", "ROOT_DIR", 1, 1, cmdWorkcellRuntimeInvariants},
}
}

Expand Down Expand Up @@ -518,3 +519,11 @@ func cmdWorkcellHardeningInvariants(args []string) error {
func cmdWorkcellConfigSafety(args []string) error {
return workcellhardening.CheckConfigSafety(args[0])
}

// cmdWorkcellRuntimeInvariants runs the ten scripts/workcell runtime/gc
// checks migrated out of scripts/verify-invariants.sh; it fails (exit 1
// via die()) with the shell's original stderr message for the first
// violated invariant.
func cmdWorkcellRuntimeInvariants(args []string) error {
return workcellhardening.CheckRuntimeInvariants(args[0])
}
137 changes: 137 additions & 0 deletions internal/workcellhardening/workcellhardening.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
// fixtures). It also re-implements the adjacent config-safety block (the
// four scripts/workcell checks between the check_file loop and
// toml_section_assignments) via CheckConfigSafety; see configSafetyChecks.
// It also re-implements the adjacent runtime/gc block (the ten
// scripts/workcell checks between the SSH-collision check and the
// start_managed_profile mount function-block group) via
// CheckRuntimeInvariants; see runtimeInvariantChecks.
//
// Each invariant pins one property of the host launcher scripts/workcell:
// that run_host_colima restores the real host HOME, that the shebang
Expand Down Expand Up @@ -60,6 +64,11 @@ const (
// the top-level bash function body named functionName, mirroring
// function_block_contains_fixed (sed-range extraction + grep -Fq).
kindFunctionBlock checkKind = iota
// kindFunctionBlockAbsent requires needle (a fixed string) NOT to
// appear inside the top-level bash function body named functionName,
// mirroring a negated `function_block_contains_fixed` under a `||`
// guard (present inside the block is a violation → exit 1).
kindFunctionBlockAbsent
// kindFirstLineRegex requires the launcher's first line to match the
// anchored regex, mirroring `head -n1 ... | grep -q '^...$'`.
kindFirstLineRegex
Expand All @@ -75,6 +84,11 @@ const (
// alternation with active metacharacters), unlike kindAbsent's
// fixed-string containment.
kindRegexAbsent
// kindRegexPresent requires the regexp pattern to match somewhere in the
// launcher, mirroring an affirmative `rg -q REGEX` whose REGEX contains an
// active metacharacter (e.g. a trailing `.` meaning any char), unlike
// kindPresent's fixed-string containment.
kindRegexPresent
)

// check is one hardening invariant: how to match, what to match, which
Expand Down Expand Up @@ -246,12 +260,133 @@ func CheckConfigSafety(rootDir string) error {
return evaluate(rootDir, configSafetyChecks)
}

// runtimeInvariantChecks lists the ten scripts/workcell runtime/gc
// invariants in the same order as the former inline block in
// scripts/verify-invariants.sh (the block between the SSH-collision check
// and the start_managed_profile mount function-block group), so a
// reviewer can diff the two one-to-one.
//
// Every `rg` pattern in this block is metacharacter-free after
// unescaping (the DOCKER_CONFIG probe escapes every `$ { } .`, and the
// `--self-*-probe` / `strict mode …` / `go_colimautil …` probes contain
// no active regex metacharacters), so each reduces to fixed-string
// containment — kindPresent for affirmative `rg -q`, kindAbsent for the
// negated DOCKER_CONFIG guard.
//
// The runtime_build_codex_arch guard was one shell `if` joining three
// function_block_contains_fixed probes with `||` under a single message:
// the first two are affirmative (musl assets must be present) and the
// third is NEGATED (a gnu asset present is a violation). It is expressed
// here as two kindFunctionBlock checks plus one kindFunctionBlockAbsent
// check sharing that message, which is behaviourally identical (any of
// the three failing yields the same stderr and exit 1).
//
// Two other guards each joined two affirmative `rg -q` probes with `||`
// under one message (the --gc runtime-image/temp cleanup pair); they are
// expressed as two ordered kindPresent checks sharing that message, which
// is behaviourally identical (either missing probe yields the same
// stderr and exit 1).
var runtimeInvariantChecks = []check{
{
kind: kindPresent,
pattern: "setup_workcell_trusted_docker_client",
message: "Expected scripts/workcell to seed a trusted Docker client state before host Docker use",
},
{
// kindAbsent: the shell's rg pattern
// `DOCKER_CONFIG="\$\{REAL_HOME\}/\.docker"` escapes every
// metacharacter, so it is fixed-string containment of the literal
// assignment (present is a violation).
kind: kindAbsent,
pattern: `DOCKER_CONFIG="${REAL_HOME}/.docker"`,
message: "scripts/workcell still pins DOCKER_CONFIG to the real host home",
},
{
kind: kindPresent,
pattern: "buildx_cmd build",
message: "Expected scripts/workcell to invoke buildx through the trusted absolute plugin path",
},
{
// kindFunctionBlock (musl aarch64 asset must be present).
kind: kindFunctionBlock,
functionName: "runtime_build_codex_arch",
pattern: "aarch64-unknown-linux-musl",
message: "Expected scripts/workcell Codex release probe to resolve musl release assets",
},
{
// kindFunctionBlock (musl x86_64 asset must be present).
kind: kindFunctionBlock,
functionName: "runtime_build_codex_arch",
pattern: "x86_64-unknown-linux-musl",
message: "Expected scripts/workcell Codex release probe to resolve musl release assets",
},
{
// kindFunctionBlockAbsent (the NEGATED sub-condition): a gnu asset
// inside the block is a violation. Shares the pair's message.
kind: kindFunctionBlockAbsent,
functionName: "runtime_build_codex_arch",
pattern: "unknown-linux-gnu",
message: "Expected scripts/workcell Codex release probe to resolve musl release assets",
},
{
kind: kindPresent,
pattern: "--self-docker-probe",
message: "Expected scripts/workcell to expose a hidden self-docker probe for invariant testing",
},
{
// kindPresent (first half of the --gc cleanup guard).
kind: kindPresent,
pattern: "prune_runtime_image_cache_dir",
message: "Expected scripts/workcell --gc to cover bounded runtime-image cache and Workcell-owned temp cleanup",
},
{
// kindPresent (second half): shares the first half's message.
kind: kindPresent,
pattern: "cleanup_workcell_temp_root",
message: "Expected scripts/workcell --gc to cover bounded runtime-image cache and Workcell-owned temp cleanup",
},
{
kind: kindPresent,
pattern: "--self-staging-probe",
message: "Expected scripts/workcell to expose a hidden staging probe for invariant testing",
},
{
// rg treats the trailing `.` as "any char", so match it as a regex for
// exact `rg -q` parity (the only active metacharacter in the pattern).
kind: kindRegexPresent,
pattern: "strict mode requires --prepare when you explicitly request --rebuild.",
message: "Expected scripts/workcell to reject explicit strict-mode image rebuild requests",
},
{
kind: kindPresent,
pattern: "go_colimautil validate-profile-config",
message: "Expected scripts/workcell to validate managed Colima config through the dedicated Go helper",
},
{
kind: kindPresent,
pattern: "go_colimautil validate-runtime-mounts",
message: "Expected scripts/workcell to validate managed Lima mounts through the dedicated Go helper",
},
}

// CheckRuntimeInvariants runs the ten scripts/workcell runtime/gc
// invariants against the repo rooted at rootDir, in the shell's original
// order. It returns nil when every invariant holds (the shell's exit 0),
// or an error whose message equals the shell's stderr for the first
// violated invariant (the shell's exit 1).
func CheckRuntimeInvariants(rootDir string) error {
return evaluate(rootDir, runtimeInvariantChecks)
}

// holds reports whether the invariant is satisfied by the launcher text.
func (c check) holds(text string) bool {
switch c.kind {
case kindFunctionBlock:
block := extractNamedFunctionBlock(text, c.functionName)
return strings.Contains(block, c.pattern)
case kindFunctionBlockAbsent:
block := extractNamedFunctionBlock(text, c.functionName)
return !strings.Contains(block, c.pattern)
case kindFirstLineRegex:
return regexp.MustCompile(c.pattern).MatchString(firstLine(text))
case kindPresent:
Expand All @@ -260,6 +395,8 @@ func (c check) holds(text string) bool {
return !strings.Contains(text, c.pattern)
case kindRegexAbsent:
return !regexp.MustCompile(c.pattern).MatchString(text)
case kindRegexPresent:
return regexp.MustCompile(c.pattern).MatchString(text)
default:
return false
}
Expand Down
178 changes: 178 additions & 0 deletions internal/workcellhardening/workcellhardening_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,184 @@ func TestCheckConfigSafety(t *testing.T) {
}
}

// runtimeHappyLauncher is a minimal scripts/workcell that satisfies all
// ten runtime/gc invariants: the trusted Docker client seed, no
// DOCKER_CONFIG pin to the real host home, the buildx_cmd invocation, a
// runtime_build_codex_arch body resolving both musl assets (and no gnu
// asset), both hidden probes, both --gc cleanup helpers, the strict-mode
// rebuild rejection, and both go_colimautil validators. Individual
// negative cases mutate one property of this baseline.
const runtimeHappyLauncher = `#!/bin/bash
set -euo pipefail

setup_workcell_trusted_docker_client() {
DOCKER_CONFIG="${TRUSTED_DOCKER_CONFIG}"
}

runtime_build_image() {
buildx_cmd build --tag workcell/runtime .
}

runtime_build_codex_arch() {
case "${server_arch}" in
arm64 | aarch64) printf 'aarch64-unknown-linux-musl\n' ;;
amd64 | x86_64) printf 'x86_64-unknown-linux-musl\n' ;;
*) return 1 ;;
esac
}

handle_flags() {
case "$1" in
--self-docker-probe) run_self_docker_probe ;;
--self-staging-probe) run_self_staging_probe ;;
esac
}

gc() {
prune_runtime_image_cache_dir
cleanup_workcell_temp_root
}

reject_rebuild() {
die "strict mode requires --prepare when you explicitly request --rebuild."
}

validate_managed_config() {
go_colimautil validate-profile-config "${config}"
go_colimautil validate-runtime-mounts "${config}"
}
`

func TestCheckRuntimeInvariants(t *testing.T) {
tests := []struct {
name string
body string
wantErr string // "" means expect success
}{
{
name: "happy path all invariants hold",
body: runtimeHappyLauncher,
},
{
// kindPresent: trusted Docker client seed removed.
name: "missing trusted Docker client seed",
body: strings.Replace(runtimeHappyLauncher, "setup_workcell_trusted_docker_client", "setup_other", 1),
wantErr: "Expected scripts/workcell to seed a trusted Docker client state before host Docker use",
},
{
// kindAbsent: pinning DOCKER_CONFIG to the real host home is a
// violation.
name: "DOCKER_CONFIG pinned to real home present",
body: runtimeHappyLauncher + "\nDOCKER_CONFIG=\"${REAL_HOME}/.docker\"\n",
wantErr: "scripts/workcell still pins DOCKER_CONFIG to the real host home",
},
{
// kindPresent: buildx_cmd build removed.
name: "missing buildx_cmd build",
body: strings.Replace(runtimeHappyLauncher, "buildx_cmd build", "buildx build", 1),
wantErr: "Expected scripts/workcell to invoke buildx through the trusted absolute plugin path",
},
{
// kindFunctionBlock: aarch64 musl asset removed from the block.
name: "missing aarch64 musl asset",
body: strings.Replace(runtimeHappyLauncher, "aarch64-unknown-linux-musl", "aarch64-unknown-linux-foo", 1),
wantErr: "Expected scripts/workcell Codex release probe to resolve musl release assets",
},
{
// kindFunctionBlock: x86_64 musl asset removed from the block.
name: "missing x86_64 musl asset",
body: strings.Replace(runtimeHappyLauncher, "x86_64-unknown-linux-musl", "x86_64-unknown-linux-foo", 1),
wantErr: "Expected scripts/workcell Codex release probe to resolve musl release assets",
},
{
// kindFunctionBlockAbsent (the NEGATED sub-condition): a gnu asset
// inside the block is a violation even though both musl assets
// remain present.
name: "gnu asset present in codex arch block",
body: strings.Replace(runtimeHappyLauncher, "*) return 1 ;;", "*) printf 'x86_64-unknown-linux-gnu\\n'; return 1 ;;", 1),
wantErr: "Expected scripts/workcell Codex release probe to resolve musl release assets",
},
{
// kindFunctionBlockAbsent scoping: a gnu asset OUTSIDE the
// runtime_build_codex_arch body must NOT trip the block-scoped
// negative check, so the invariant still holds.
name: "gnu asset only outside codex arch block",
body: runtimeHappyLauncher + "\ncomment_note() { : 'x86_64-unknown-linux-gnu'; }\n",
},
{
// kindPresent: hidden self-docker probe removed.
name: "missing self-docker probe",
body: strings.Replace(runtimeHappyLauncher, "--self-docker-probe", "--self-docker-off", 1),
wantErr: "Expected scripts/workcell to expose a hidden self-docker probe for invariant testing",
},
{
// kindPresent (first half of --gc guard): runtime-image cache prune
// helper removed.
name: "missing runtime image cache prune",
body: strings.Replace(runtimeHappyLauncher, "prune_runtime_image_cache_dir", "prune_other", 1),
wantErr: "Expected scripts/workcell --gc to cover bounded runtime-image cache and Workcell-owned temp cleanup",
},
{
// kindPresent (second half of --gc guard): temp-root cleanup helper
// removed.
name: "missing temp root cleanup",
body: strings.Replace(runtimeHappyLauncher, "cleanup_workcell_temp_root", "cleanup_other", 1),
wantErr: "Expected scripts/workcell --gc to cover bounded runtime-image cache and Workcell-owned temp cleanup",
},
{
// kindPresent: hidden self-staging probe removed.
name: "missing self-staging probe",
body: strings.Replace(runtimeHappyLauncher, "--self-staging-probe", "--self-staging-off", 1),
wantErr: "Expected scripts/workcell to expose a hidden staging probe for invariant testing",
},
{
// kindPresent: strict-mode rebuild rejection message removed.
name: "missing strict-mode rebuild rejection",
body: strings.Replace(runtimeHappyLauncher, "strict mode requires --prepare when you explicitly request --rebuild.", "strict mode message changed", 1),
wantErr: "Expected scripts/workcell to reject explicit strict-mode image rebuild requests",
},
{
// kindPresent: profile-config validator removed.
name: "missing validate-profile-config",
body: strings.Replace(runtimeHappyLauncher, "go_colimautil validate-profile-config", "go_colimautil validate-other", 1),
wantErr: "Expected scripts/workcell to validate managed Colima config through the dedicated Go helper",
},
{
// kindPresent: runtime-mounts validator removed.
name: "missing validate-runtime-mounts",
body: strings.Replace(runtimeHappyLauncher, "go_colimautil validate-runtime-mounts", "go_colimautil validate-other-mounts", 1),
wantErr: "Expected scripts/workcell to validate managed Lima mounts through the dedicated Go helper",
},
{
// A missing launcher is empty content: the negative checks pass and
// the first affirmative check (trusted Docker client seed) fires,
// mirroring `rg -q` returning non-zero on a missing file.
name: "missing launcher",
body: "",
wantErr: "Expected scripts/workcell to seed a trusted Docker client state before host Docker use",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
root := writeLauncher(t, tc.body)
err := CheckRuntimeInvariants(root)
if tc.wantErr == "" {
if err != nil {
t.Fatalf("CheckRuntimeInvariants() = %v, want nil", err)
}
return
}
if err == nil {
t.Fatalf("CheckRuntimeInvariants() = nil, want error %q", tc.wantErr)
}
if err.Error() != tc.wantErr {
t.Fatalf("CheckRuntimeInvariants() error = %q, want %q", err.Error(), tc.wantErr)
}
})
}
}

// TestExtractNamedFunctionBlock pins the sed-range extraction semantics
// the run_host_colima check depends on: the block runs from the `NAME()`
// opening line through the first line beginning with `}` (inclusive), and
Expand Down
Loading
Loading