fix(safety): compile baked policy to code to resist binary tampering#540
Merged
steipete merged 1 commit intoopenclaw:mainfrom May 4, 2026
Merged
Conversation
The baked profile YAML lives in the binary as a string literal. With
write access to the binary, an attacker can locate it via `strings` and
flip rule bytes in place with `sed` (e.g. "send: false" -> "send: true ")
without rebuilding, weakening the doc's claim that the policy "cannot
be changed with flags, environment variables, config files, or shell
arguments".
cmd/bake-safety-profile parses the profile YAML at build time and emits
a Go file (//go:build safety_profile) of switch statements over FNV-64a
hashes of dotted command paths. Rule strings like "gmail.send" no longer
appear in the binary; the only embedded string is the profile name
(used in error messages). Patching a blocked command back on now
requires editing compiled machine code, not flipping ASCII bytes.
Layout:
- internal/safetyprofile/ is a new build-time-only package containing
the YAML parser, the FNV-64a hash function, and the codegen IR. It is
imported by cmd/bake-safety-profile and the safety_profile-tagged
hash-agreement test, but not by cmd/gog. Stock builds therefore drop
gopkg.in/yaml.v3 entirely (~460 KB smaller binary) and the parser
surface is no longer reachable at runtime.
- internal/cmd/safety_profile.go keeps only the runtime contract:
the bakedSafetyProfile handle, enforcement, help/schema visibility,
and a small bakedSafetyHashPath helper that the generated switch
calls. loadBakedSafetyProfile keeps its (profile, error) signature
so help_printer.go and schema.go are untouched; the error is now
always nil because validation moved to build time.
- internal/cmd/safety_profile_default.go (//go:build !safety_profile)
exposes a test-only override that withBakedSafetyProfile mutates to
set up scenarios. Stock binaries leave it zeroed and the profile
reports disabled.
- cmd/bake-safety-profile/safety_profile_baked_gen.go is the generated
output (//go:build safety_profile). The header carries the profile
name, rule counts, and FNV-64a algorithm note. No rule names or
hashes appear in the comment; profile names with newline or carriage
return characters are sanitized so they cannot break out of the
comment line.
The parser also rejects three malformed shapes that would otherwise be
silent no-ops in the hashed runtime switch:
- `deny: [all]` / `deny: ["*"]`: deny-side wildcards never match a real
command path through the hashed dispatch, so they reject at parse
time with a clear error.
- `all: { gmail: false }` / `"*": { gmail: false }`: nesting under a
wildcard prefix is rejected for the same reason.
- `name:` non-string or empty: rejected before the rest of the parse so
the error points at the malformed field.
Stock build behavior is preserved. Profile YAML format, build-safe.sh,
runtime contract (allow/deny semantics, help filtering, schema
filtering), end-user error messages, and the upstream callsites in
help_printer.go and schema.go are all unchanged. The new validation
only fires when authoring a malformed profile.
TestSafetyProfileHashAgreement asserts the runtime
bakedSafetyHashPath and build-time safetyprofile.HashRule produce
identical values for the command paths the generator emits switches
over.
steipete
approved these changes
May 4, 2026
Collaborator
steipete
left a comment
There was a problem hiding this comment.
Reviewed the generated hash-switch path, parser split, runtime enforcement, help/schema filtering contract, and baked build flow.
Local verification:
go test ./cmd/bake-safety-profile ./internal/safetyprofile ./internal/cmdmake lint./build-safe.sh safety-profiles/agent-safe.yaml -o bin/gog-agent-safe-review./build-safe.sh safety-profiles/readonly.yaml -o bin/gog-readonly-review./bin/gog-agent-safe-review gmail drafts send draft-1exits 2 with the baked profile block./bin/gog-readonly-review calendar update primary ev --summary xexits 2 with the readonly baked profile blockstrings bin/gog-readonly-reviewhas no rawcalendar.update,gmail.send, orgmail.drafts.sendrule strings
No blocking findings.
20 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
internal/safetyprofile/package; stockcmd/gogno longer importsgopkg.in/yaml.v3and the parser is unreachable at runtimedeny: [all]/deny: ["*"]and nesting under a wildcard prefix at parse time (their hashes never matched real command paths); reject non-string or emptyname:(was silently accepted asunnamed)This hardens #536 against an in-place
sedpatch on the binary. Same profile YAML, samebuild-safe.sh, same runtime contract;loadBakedSafetyProfilekeeps its(profile, error)signature sohelp_printer.goandschema.goare untouched.Verification
make testmake lintmake ci./build-safe.sh safety-profiles/agent-safe.yaml -o bin/gog-agent-safe-test./build-safe.sh safety-profiles/readonly.yaml -o bin/gog-readonly-test./build-safe.sh safety-profiles/full.yaml -o bin/gog-full-test./bin/gog-agent-safe-test gmail drafts send draft-1exits 2 with baked profile blockstrings bin/gog-agent-safe-test | grep -c '^gmail.send$'→0codex exec review --base upstream/main→ no findings