Skip to content
Open
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ debug-log/
.claude/
CLAUDE.md

# Local agent runtime / planning artifacts
.omx/
docs/superpowers/
tasks/

# OS files
.DS_Store

Expand Down
15 changes: 15 additions & 0 deletions changelog/current.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,18 @@ Record image-affecting changes to `manager/`, `worker/`, `openclaw-base/` here b

---

- feat(manager,worker): add local Codex runtime wiring so manager/workers can run as Codex sessions with host `~/.codex` auth and no API key ([71ef7a7](https://github.com/higress-group/hiclaw/commit/71ef7a7))
- fix(manager): preserve worker runtime when recreating local workers so codex workers do not fall back to openclaw (uncommitted)
- fix(manager,worker): send Matrix typing notifications while Codex runtime is handling a turn (uncommitted)
- fix(manager,worker): re-check Matrix room membership on each turn so DM rooms upgraded to groups do not stay misclassified (uncommitted)
- fix(worker): pass assigned Matrix room id into worker runtime and auto-join missing worker rooms on startup (uncommitted)
- fix(manager,worker): skip group-room router on explicit @mentions and keep the Codex app-server warm across turns to reduce reply latency (uncommitted)
- fix(manager): default the Manager to auto-follow allowed group-room conversations instead of requiring @mentions for every turn (uncommitted)
- feat(manager): make Manager proactively facilitate active project rooms with heartbeat-driven coordination updates and next-step assignment (uncommitted)
- fix(manager): bypass the lightweight Codex group-room router for Manager so project-room updates always reach the main coordination logic (uncommitted)
- fix(manager): update the live Manager allowlist config during project creation so Worker messages in project rooms trigger immediately across OpenClaw and CoPaw runtimes (uncommitted)
- fix(worker): stop syncing `.codex-home` runtime state through MinIO and ignore runtime-only changes in the 5-second worker sync loop (uncommitted)
- fix(worker): stop echoing manager-pulled `skills/` and `.mc.bin` runtime files back to MinIO after fallback/file-sync pulls (uncommitted)
- fix(worker): keep `.openclaw/cron/` synced so scheduled tasks still persist and Manager idle checks can see active cron jobs (uncommitted)
- fix(manager): treat task `result.md` as an authoritative completion signal during heartbeat/task-management flows instead of waiting only for a completion @mention (uncommitted)
- feat(manager): speed up manager-worker Matrix coordination with 120-second startup follow-up state and quiet-after-progress guidance (uncommitted)
43 changes: 41 additions & 2 deletions docker-proxy/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ type Mount struct {
type SecurityValidator struct {
AllowedRegistries []string
ContainerPrefix string
AllowedBindSource string
AllowedBindTarget string
DangerousCaps map[string]bool
}

Expand All @@ -85,6 +87,8 @@ func NewSecurityValidator() *SecurityValidator {
return &SecurityValidator{
AllowedRegistries: allowedRegistries,
ContainerPrefix: prefix,
AllowedBindSource: os.Getenv("HICLAW_HOST_CODEX_DIR"),
AllowedBindTarget: "/root/.codex-host",
DangerousCaps: map[string]bool{
"SYS_ADMIN": true,
"SYS_PTRACE": true,
Expand Down Expand Up @@ -115,9 +119,13 @@ func (v *SecurityValidator) ValidateContainerCreate(req ContainerCreateRequest,
return nil
}

// 3. No bind mounts (workers use MinIO, not host volumes)
// 3. No bind mounts, except the explicit readonly Codex auth/config mount.
if len(req.HostConfig.Binds) > 0 {
return fmt.Errorf("bind mounts are not allowed (got %d bind(s))", len(req.HostConfig.Binds))
for _, bind := range req.HostConfig.Binds {
if err := v.validateBind(bind); err != nil {
return err
}
}
}
for _, m := range req.HostConfig.Mounts {
if strings.EqualFold(m.Type, "bind") {
Expand Down Expand Up @@ -150,6 +158,37 @@ func (v *SecurityValidator) ValidateContainerCreate(req ContainerCreateRequest,
return nil
}

func (v *SecurityValidator) validateBind(bind string) error {
if v.AllowedBindSource == "" {
return fmt.Errorf("bind mounts are not allowed (got 1 bind(s))")
}

parts := strings.Split(bind, ":")
if len(parts) < 3 {
return fmt.Errorf("bind mount %q must be readonly and target %q", bind, v.AllowedBindTarget)
}

source := parts[0]
target := parts[1]
options := strings.Join(parts[2:], ":")
if source != v.AllowedBindSource || target != v.AllowedBindTarget {
return fmt.Errorf("bind mount %q is not allowed", bind)
}

readonly := false
for _, opt := range strings.Split(options, ",") {
if opt == "ro" {
readonly = true
break
}
}
if !readonly {
return fmt.Errorf("bind mount %q must be readonly", bind)
}

return nil
}

func (v *SecurityValidator) isImageAllowed(image string) bool {
// Allow all images from Higress registries (any region)
if isHigressRegistry(image) {
Expand Down
41 changes: 41 additions & 0 deletions docker-proxy/security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ func newTestValidator() *SecurityValidator {
return &SecurityValidator{
AllowedRegistries: []string{},
ContainerPrefix: "hiclaw-worker-",
AllowedBindSource: "/Users/test/.codex",
AllowedBindTarget: "/root/.codex-host",
DangerousCaps: map[string]bool{
"SYS_ADMIN": true,
"SYS_PTRACE": true,
Expand Down Expand Up @@ -224,6 +226,45 @@ func TestRejectBindMounts(t *testing.T) {
}
}

func TestAllowReadonlyCodexBindMount(t *testing.T) {
v := newTestValidator()
req := ContainerCreateRequest{
Image: "hiclaw/worker-agent:latest",
HostConfig: &HostConfig{
Binds: []string{"/Users/test/.codex:/root/.codex-host:ro"},
},
}
if err := v.ValidateContainerCreate(req, "hiclaw-worker-test"); err != nil {
t.Fatalf("expected readonly codex bind mount to pass, got: %v", err)
}
}

func TestRejectWritableCodexBindMount(t *testing.T) {
v := newTestValidator()
req := ContainerCreateRequest{
Image: "hiclaw/worker-agent:latest",
HostConfig: &HostConfig{
Binds: []string{"/Users/test/.codex:/root/.codex-host:rw"},
},
}
if err := v.ValidateContainerCreate(req, "hiclaw-worker-test"); err == nil {
t.Fatal("expected writable codex bind mount to be rejected")
}
}

func TestRejectWrongCodexBindSource(t *testing.T) {
v := newTestValidator()
req := ContainerCreateRequest{
Image: "hiclaw/worker-agent:latest",
HostConfig: &HostConfig{
Binds: []string{"/tmp/not-codex:/root/.codex-host:ro"},
},
}
if err := v.ValidateContainerCreate(req, "hiclaw-worker-test"); err == nil {
t.Fatal("expected wrong codex bind source to be rejected")
}
}

func TestRejectBindTypeMounts(t *testing.T) {
v := newTestValidator()
req := ContainerCreateRequest{
Expand Down
13 changes: 12 additions & 1 deletion hiclaw-controller/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@ FROM ${HIGRESS_REGISTRY}/higress/golang:1.23-alpine AS builder

ARG GOPROXY=https://proxy.golang.org,direct
ENV GOPROXY=${GOPROXY}
RUN apk add --no-cache gcc musl-dev
RUN set -eu; \
attempt=1; \
while true; do \
apk add --no-cache gcc musl-dev && break; \
if [ "${attempt}" -ge 5 ]; then \
exit 1; \
fi; \
echo "apk add gcc musl-dev failed (attempt ${attempt}), retrying..." >&2; \
rm -rf /var/cache/apk/*; \
sleep $((attempt * 5)); \
attempt=$((attempt + 1)); \
done

WORKDIR /build
COPY go.mod go.sum ./
Expand Down
Loading
Loading