oktsec started as an HTTP proxy with 85 detection rules. A deterministic 10-stage pipeline that intercepted messages between agents and tool calls going through the MCP gateway. It worked until the gaps became obvious.
This post covers what changed between v0.8.1 and v0.10.0 and why each feature exists.
Hooks: client-side interception
The gateway intercepts tool calls going to MCP servers. But tools that the client executes locally (Bash, Read, Write, Edit) never go through an MCP server. They run directly on the machine. The pipeline didn't see them.
Clients like Claude Code have a command hook system that runs before every tool call. oktsec connects to that point. The hook command (oktsec hook) forwards the event to the gateway's /hooks/event endpoint. The gateway runs the full pipeline and responds with allow or block. If block, the client doesn't execute the tool.
PreToolUse(Bash, "rm -rf /")
The hook handler captures everything the gateway needs to make a decision:
Claude Code's PreToolUse/PostToolUse events are auto-normalized to the canonical format. This works with any MCP client that supports pre-execution callbacks. No wrapping, no stdin/stdout interception. The client calls a binary, the binary talks HTTP to the gateway, the gateway decides.
Before hooks, oktsec covered what went through MCP servers. After hooks, it covers everything the agent executes.
LLM verdict escalation
Detection rules are stateless. Each event is evaluated in isolation. An agent that does a Read, then a WebSearch, then a Bash combining data from both passes three clean checks. No single rule sees the sequence.
The LLM layer runs asynchronously after the pipeline. It receives events that triggered findings, intent mismatches, or escalation patterns. It correlates events across time for the same agent.
Async. Never in the hot path.
The deterministic pipeline still makes every real-time decision. The LLM never blocks a request directly. It adjusts the scrutiny level for future requests based on observed patterns.
llm:
enabled: true
provider: openai
model: gpt-4o-mini
escalation:
enabled: true
risk_threshold: 80
ttl_minutes: 30
The escalation tracker exposes Prometheus metrics: escalation_bumped_verdicts, escalation_threshold_breaches. Budget controls (daily/monthly USD caps) prevent runaway costs. A fallback provider kicks in if the primary fails.
The pipeline is 100% deterministic in real time. The LLM runs after, adjusting future scrutiny. Zero latency impact on tool calls.
Delegation chains
In the gateway model, when agent-a calls a tool, the gateway knows agent-a is authorized. But if agent-a spawns agent-b, and agent-b calls tools through the gateway, the gateway only sees agent-b. It has no way to verify that agent-a authorized agent-b, or that a human authorized the entire chain.
Delegation chains are Ed25519-signed tokens that create a verifiable authorization path from human to any sub-agent.
Each link in the chain narrows the scope. agent-b can only delegate a subset of what agent-a delegated to it. Every token has a TTL. If a chain is compromised, the blast radius is bounded by time and scope.
The DelegationToken structure:
type DelegationToken struct {
TokenID string // SHA-256 of content
Delegator string // who grants
Delegate string // who receives
Scope []string // allowed recipients
AllowedTools []string // allowed tools (narrowing)
IssuedAt time.Time
ExpiresAt time.Time
ChainDepth int // 0 = human/root
MaxDepth int // max re-delegation depth
ParentTokenID string // links to parent
Signature []byte // Ed25519
}
Verification checks: signature validity, expiration, scope narrowing (child scope must be subset of parent), depth limits, and parent chain continuity.
# Root delegation: human authorizes agent-a
oktsec delegate --from human --to agent-a --scope "*" --ttl 4h
# Chained: agent-a delegates to agent-b with narrower scope
oktsec delegate --from agent-a --to agent-b \
--scope target-x --tools Read,Write \
--parent <token-id> --depth 1
Agents send the chain in the X-Oktsec-Delegation header (base64-encoded JSON). The gateway verifies the full chain before processing the tool call. The chain hash is persisted in the audit trail alongside the event.
Ephemeral keys
Fixed Ed25519 keypairs live on disk indefinitely. If compromised, an attacker can use the key until someone notices and revokes it.
Ephemeral keys are task-scoped keypairs generated in memory. They expire automatically after a configurable TTL (max 4 hours by default). No disk persistence. When the process dies, the keys die with it.
store := identity.NewEphemeralKeyStore(10, 4*time.Hour)
// Issue a key for a specific task
kp, _ := store.Issue("task-123", parentFingerprint, 1*time.Hour)
// Verify during tool calls
if ek := store.Verify(publicKey); ek != nil {
// valid, non-expired ephemeral key
}
// Revoke all keys for a task when done
store.RevokeByTask("task-123")
Compromised ephemeral key = bounded blast radius. Limited by time (TTL), scope (task), and count (MaxPerTask). The attacker's window closes automatically.
Scan profiles
Not every tool call deserves the same scrutiny. An Edit writing HTML triggers injection rules that are correct for a Bash command but false positives for a static file. Applying the same 188 rules to everything produces noise that makes real alerts invisible.
Scan profiles assign trust levels per agent. Three levels:
| Profile | Behavior |
|---|---|
| strict | All 188 rules enforced. Default. |
| content-aware | Only critical rules (TC-001, TC-003, TC-006) can block/flag. Everything else is logged but verdict is clean. |
| minimal | Only critical rules enforced. Everything else flagged. |
Content tools (Read, Write, Edit, Glob, Grep) are identified by the ContentTools map. When a content-aware agent calls Edit, only path traversal, system directory writes, and credential detection rules can produce a blocking verdict. Shell injection patterns in HTML content are logged but don't trigger alerts.
agents:
claude-code:
scan_profile: content-aware
can_message: ["*"]
Separately, built-in tool exemptions handle cases where the detected pattern IS the tool's intended behavior:
var BuiltinToolExemptions = map[string][]string{
"TC-005": {"Bash", "Write", "Edit"}, // shell patterns expected
"MCPCFG_004": {"WebFetch", "WebSearch"}, // remote URLs expected
"THIRDPARTY_001": {"WebFetch", "WebSearch"}, // runtime URLs expected
}
Auto-registered agents (discovered via hooks) default to content-aware. This means oktsec works out of the box without flooding developers with false positives on normal coding activity.
Session traces
When something goes wrong, the question is always: what exactly did the agent do?
Session traces reconstruct the full timeline of tool calls for a session from audit data. Every tool call, its verdict, the gap between calls, the security decision, and the model's reasoning if available.
Session: ses_8f2a1b3c | Agent: claude-code | Duration: 4m12s
10:05:22 Read config.yaml clean 2ms
10:05:23 Grep "API_KEY" clean 3ms
[gap: 847ms]
10:05:24 Bash curl POST ... block 15ms
+-- IAP-006: Data exfiltration via agent relay
10:05:25 Read /etc/passwd flag 4ms
+-- TC-001: Path traversal
The dashboard renders this as a vertical timeline at /dashboard/sessions/{id}. Reasoning bubbles show the model's chain-of-thought when available (captured via the hooks handler and stored in a separate reasoning_log table).
trace, _ := store.BuildSessionTrace(sessionID)
// trace.Steps: chronological tool calls
// trace.Threats: count of blocked/quarantined
// trace.Duration: total session time
Terminal UI
A Bubbletea-based TUI replaces the static startup banner when running in a terminal.
Mode changes from the dashboard (enforce/observe) sync to the TUI in real time via a live config pointer. Developers stay in the terminal, security stays visible.
v0.10.0 by the numbers
Upgrade
brew upgrade oktsec/tap/oktsec
# or
go install github.com/oktsec/oktsec/cmd/oktsec@v0.10.0
Existing configs are backward compatible. New fields (scan_profile, ephemeral keys, delegation) are optional and default to previous behavior. The audit database schema migrates automatically on first run.
What's next
Delegation chain verification in the dashboard. Visual chain explorer showing the authorization path for any event. Fleet management for teams running multiple agents across projects.
The code is open source: github.com/oktsec/oktsec
oktsec is runtime security for AI agents. 188 detection rules, delegation chains, LLM escalation, hash-chained audit trail. One Go binary, no cloud dependency. Apache 2.0.
Get started
One binary. 188 detection rules. Delegation chains. Deploy in minutes.