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.

188 detection rules
13+ dashboard pages
7 new features
4 verdicts

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.

Client (Claude Code)
PreToolUse(Bash, "rm -rf /")
oktsec hook --port 9090
POST /hooks/event
oktsec gateway — full 10-stage pipeline
188 rules identity ACL delegation chain content scan audit log
decision
BLOCK — Tool NOT executed

The hook handler captures everything the gateway needs to make a decision:

Tool call data
Tool name, input arguments, lifecycle event (pre/post execution). Enough to run the full 188-rule scan.
Agent identity
X-Oktsec-Agent and X-Oktsec-Client headers. Sub-agent type, agent ID, session ID. Full caller identification.
Delegation chain
X-Oktsec-Delegation header with the full Ed25519-signed authorization path. Verified before processing.
Model reasoning
Chain-of-thought captured and stored in the reasoning_log table. Linked to the audit entry for forensics.

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.

Event stream (per agent)
188 rules (stateless, real-time)
Each event evaluated in isolation. Microsecond latency.
findings, intent mismatches
LLM correlates events across time
Async. Never in the hot path.
risk score > threshold
Verdict escalation: clean → flag → quarantine → block
TTL expires
Auto-revert to normal verdicts
No manual intervention needed
80 default risk threshold
30m default TTL
4 escalation levels
0 real-time LLM calls

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.

Humanscope: * | tools: all
delegates
agent-ascope: * | tools: all | TTL: 4h
narrows
agent-bscope: target-x | tools: Read,Write
narrows
agent-cscope: target-x | tools: Read

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.

Ed25519 signature per hop
SHA-256 token ID
TTL per token
MaxDepth re-delegation cap

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.

In-memory only
Keys never touch disk. Process termination destroys all ephemeral keys. Zero forensic residue.
Task-scoped
Each key is bound to a specific task. RevokeByTask wipes all keys for a completed task at once.
Auto-expiring
30-second eviction loop removes expired keys. MaxTTL cap (default: 4h) prevents long-lived keys.
Bounded per task
MaxPerTask cap (default: 10) prevents key sprawl. Protects against compromised agents issuing unlimited keys.
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:

ProfileBehavior
strictAll 188 rules enforced. Default.
content-awareOnly critical rules (TC-001, TC-003, TC-006) can block/flag. Everything else is logged but verdict is clean.
minimalOnly 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).

Reasoning capture
Model chain-of-thought linked to each tool call via reasoning_log table. SHA-256 hashed. See exactly what the agent was thinking when it acted.
Compliance exports
CSV (with session header), JSON, SARIF v2.1.0. EU AI Act enforcement begins August 2026. NIST AI RMF requires this depth.
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.

Live event feed
Real-time stream from the audit Hub. Scroll, pause, resume. Expand any event to see rules triggered, content preview, and full audit metadata.
Per-agent filtering
Focus on one agent's activity. Filter the live feed without the noise from the rest of the fleet.
Status bar
Always-visible counters: events scanned, threats detected, blocked count, active agents. GitHub Dark palette with lipgloss styling.
TTY-aware
Alt-screen mode keeps your terminal clean. Falls back to static banner when piped, in CI, or Docker. Zero interference.

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

188 detection rules
15 rule categories
10 pipeline stages
13+ dashboard pages
41 posture checks
17 MCP clients
4 verdicts
3 scan profiles

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.

Stay informed

New releases, security research, and detection rule updates. No spam.

You're in. We'll keep you posted.

Be the first to know about new releases and research.