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
11 changes: 0 additions & 11 deletions internal/aiagents/hook/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
"github.com/step-security/dev-machine-guard/internal/aiagents/identity"
"github.com/step-security/dev-machine-guard/internal/aiagents/ingest"
"github.com/step-security/dev-machine-guard/internal/aiagents/redact"
"github.com/step-security/dev-machine-guard/internal/aiagents/state"
"github.com/step-security/dev-machine-guard/internal/executor"
)

Expand Down Expand Up @@ -116,16 +115,6 @@ func (rt *Runtime) Run(parent context.Context, hookType event.HookEvent) error {
var ev *event.Event
defer func() { rt.emitDecidedResponse(ev, decision) }()

// Server-driven kill switch. The reconciler writes ~/.stepsecurity/
// hooks-state.json on every telemetry tick; a UI flip propagates
// to disabled here in O(1) microseconds and short-circuits the
// hot path before any enrichment, identity probe, or upload runs.
// Missing/corrupt cache returns Default()=enabled so first-run
// after install continues to work.
if cur, _ := state.Read(); !cur.Hooks.Enabled {
return nil
}

cfg, _ := ingest.Snapshot()
id := identity.Resolve(ctx, rt.Exec, cfg.CustomerID)
upload := rt.resolveUpload()
Expand Down
44 changes: 0 additions & 44 deletions internal/aiagents/hook/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ import (
"encoding/json"
"errors"
"io"
"path/filepath"
"strings"
"sync"
"testing"
"time"

cc "github.com/step-security/dev-machine-guard/internal/aiagents/adapter/claudecode"
"github.com/step-security/dev-machine-guard/internal/aiagents/event"
"github.com/step-security/dev-machine-guard/internal/aiagents/state"
"github.com/step-security/dev-machine-guard/internal/executor"
)

Expand Down Expand Up @@ -422,48 +420,6 @@ func TestRunUploadFailureFailsOpen(t *testing.T) {

// When no UploadEvent is wired (any runtime without enterprise config),
// the runtime must still complete — just with no upload attempt.
// withDisabledStateCache writes a disabled state file and restores
// the override on cleanup. Returns the path for assertions.
func withDisabledStateCache(t *testing.T) {
t.Helper()
dir := t.TempDir()
path := dir + string(filepath.Separator) + state.CacheFilename
restore := state.SetCachePathForTest(path)
t.Cleanup(restore)
s := state.Default()
s.Hooks.Enabled = false
s.Source = state.SourcePoll
if err := state.Write(s); err != nil {
t.Fatalf("seed disabled cache: %v", err)
}
}

func TestRunHonorsDisabledStateCache(t *testing.T) {
withDisabledStateCache(t)

stdin := strings.NewReader(`{
"session_id":"abc","cwd":"/tmp","tool_name":"Bash",
"tool_input":{"command":"npm install lodash","cwd":"/tmp"}
}`)
var stdout, stderr bytes.Buffer
rt, cap := newRuntime(t, stdin, &stdout, &stderr)

if err := rt.Run(context.Background(), event.HookPreToolUse); err != nil {
t.Fatalf("Run: %v", err)
}
// Allow response still emitted (fail-open contract).
if !strings.HasPrefix(strings.TrimSpace(stdout.String()), "{") {
t.Errorf("stdout should still be the allow JSON: %q", stdout.String())
}
// No upload, no enrichment, no error log lines.
if len(cap.events) != 0 {
t.Fatalf("disabled cache must short-circuit upload; got %d events", len(cap.events))
}
if len(cap.errs) != 0 {
t.Fatalf("disabled cache must not log errors; got %v", cap.errs)
}
}

func TestRunSkipsUploadWithoutSeam(t *testing.T) {
stdin := strings.NewReader(`{
"session_id":"s","cwd":"/tmp","tool_name":"Bash","tool_input":{"command":"ls"}
Expand Down
126 changes: 0 additions & 126 deletions internal/aiagents/state/cache.go

This file was deleted.

122 changes: 0 additions & 122 deletions internal/aiagents/state/cache_test.go

This file was deleted.

24 changes: 9 additions & 15 deletions internal/aiagents/state/doc.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
// Package state owns the server-driven hook enable/disable cache.
// Package state owns the server-driven hook enable/disable pull path.
//
// Flow:
//
// scheduled tick / install ──▶ Reconciler.Reconcile
// │
// ├─ Fetcher.Fetch (GET /developer-mdm-agent/features)
// ├─ cache.Write (~/.stepsecurity/hooks-state.json)
// └─ InstallFn / UninstallFn (idempotent)
// scheduled tick ──▶ Reconciler.Reconcile
// │
// ├─ Fetcher.Fetch (GET /developer-mdm-agent/features)
// └─ InstallFn / UninstallFn (idempotent)
//
// _hook hot path ──▶ cache.Read ──▶ short-circuit to allow if disabled
//
// The cache file is the single source of truth for the hot path. Both
// the polling reconciler (this package) and any future WebSocket
// transport are expected to converge on the same file, so the hot path
// never has to know which transport is active.
//
// Defaults: cache missing or unparseable ⇒ Default() (enabled). Hot path
// is fail-open by contract; a corrupt cache must not break the agent.
// The presence of the managed hook entry in the agent's settings file
// (~/.claude/settings.json, ~/.codex/config.toml) is the single source
// of truth. The hot path runs iff the entry is present; there is no
// separate on-disk enable flag for it to consult.
package state
Loading
Loading