Skip to content
Merged
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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,21 +127,23 @@ Detection works both at startup (scanning `/proc`) and live (via the exec tracep

#### Custom binary detections

You can configure additional binary detections via the `detections` section in your config file. Each entry matches on the basename of `argv[0]`, the same way the built-in Claude Code and OpenClaw detections work.
You can configure additional binary detections via the `detections` section in your config file. Each entry matches on the basename of `argv[0]`, the same way the built-in Claude Code and OpenClaw detections work. Optionally, use `args_regex` to also require a regex match against the process arguments.

```yaml
detections:
binaries:
- name: exfil_agent
binary: exfil-tool
- name: custom_assistant
binary: my-ai-agent
- name: exfil_script
binary: bash
args_regex: "exfil\\.sh"
```

| Field | Description |
|---|---|
| `name` | Signature name that appears in emitted events (`signature_matched`) |
| `binary` | Basename of `argv[0]` to match (e.g. `my-agent` matches `/usr/local/bin/my-agent`) |
| `args_regex` | Optional regex matched against `argv[1:]`. If set, at least one argument must match |

Custom detections are appended to the built-in set — built-in agents are always detected regardless of config.

Expand Down
5 changes: 4 additions & 1 deletion cmd/iron-sensor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ func main() {
defer emitter.Close()

cls := classifier.New(cfg.Rules.Overrides)
sigs := agent.BuildSignatures(cfg.Detections.Binaries)
sigs, err := agent.BuildSignatures(cfg.Detections.Binaries)
if err != nil {
log.Fatalf("building detections: %v", err)
}
tracker := agent.NewTracker(emitter, cls, sigs)

log.Printf("iron-sensor %s starting (sink=%s)", events.SensorVersion, cfg.SinkType)
Expand Down
1 change: 1 addition & 0 deletions examples/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ detections:
binaries: []
# - name: my_agent # signature name in events
# binary: my-agent-bin # basename of argv[0] to match
# args_regex: "" # optional regex matched against argv[1:]

rules:
# Minimum severity to emit. 0 = alert, 1 = warn, 2 = info.
Expand Down
33 changes: 29 additions & 4 deletions internal/agent/signature.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package agent

import (
"iron-sensor/internal/config"
"fmt"
"regexp"
"strings"

"iron-sensor/internal/config"
)

// Signature defines how to identify an AI coding agent process.
Expand All @@ -25,12 +28,23 @@ func BuiltinSignatures() []Signature {
}

// BuildSignatures returns builtin signatures plus any configured binary detections.
func BuildSignatures(dets []config.BinaryDetection) []Signature {
func BuildSignatures(dets []config.BinaryDetection) ([]Signature, error) {
sigs := make([]Signature, len(builtinSignatures))
copy(sigs, builtinSignatures)
for _, d := range dets {
bin := d.Binary
name := d.Name
argsPattern := d.ArgsRegex

var argsRe *regexp.Regexp
if argsPattern != "" {
var err error
argsRe, err = regexp.Compile(argsPattern)
if err != nil {
return nil, fmt.Errorf("detection %q: invalid args_regex: %w", name, err)
}
}

sigs = append(sigs, Signature{
Name: name,
MatchExe: func(_ string) bool {
Expand All @@ -40,11 +54,22 @@ func BuildSignatures(dets []config.BinaryDetection) []Signature {
if len(argv) == 0 {
return false
}
return baseName(argv[0]) == bin
if baseName(argv[0]) != bin {
return false
}
if argsRe == nil {
return true
}
for _, a := range argv[1:] {
if argsRe.MatchString(a) {
return true
}
}
return false
},
})
}
return sigs
return sigs, nil
}

var builtinSignatures = []Signature{
Expand Down
50 changes: 47 additions & 3 deletions internal/agent/signature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ func TestBuildSignatures_CustomBinary(t *testing.T) {
dets := []config.BinaryDetection{
{Name: "exfil_agent", Binary: "exfil-tool"},
}
sigs := BuildSignatures(dets)
sigs, err := BuildSignatures(dets)
require.NoError(t, err)

// Should still match builtins.
sig, ok := MatchWith(sigs, "/usr/local/bin/claude", []string{"claude"})
Expand All @@ -91,16 +92,59 @@ func TestBuildSignatures_CustomBinary(t *testing.T) {
}

func TestBuildSignatures_NoCustom(t *testing.T) {
sigs := BuildSignatures(nil)
sigs, err := BuildSignatures(nil)
require.NoError(t, err)
require.Equal(t, len(BuiltinSignatures()), len(sigs))
}

func TestBuildSignatures_CustomNoMatchOther(t *testing.T) {
dets := []config.BinaryDetection{
{Name: "my_agent", Binary: "my-agent"},
}
sigs := BuildSignatures(dets)
sigs, err := BuildSignatures(dets)
require.NoError(t, err)

_, ok := MatchWith(sigs, "/usr/bin/bash", []string{"bash"})
require.False(t, ok)
}

func TestBuildSignatures_ArgsRegex(t *testing.T) {
dets := []config.BinaryDetection{
{Name: "exfil_script", Binary: "bash", ArgsRegex: `exfil\.sh`},
}
sigs, err := BuildSignatures(dets)
require.NoError(t, err)

// Should match bash running the exfil script.
sig, ok := MatchWith(sigs, "/usr/bin/bash", []string{"bash", "/tmp/exfil.sh"})
require.True(t, ok)
require.Equal(t, "exfil_script", sig)

// Should not match bash running something else.
_, ok = MatchWith(sigs, "/usr/bin/bash", []string{"bash", "other.sh"})
require.False(t, ok)

// Should not match bash with no args.
_, ok = MatchWith(sigs, "/usr/bin/bash", []string{"bash"})
require.False(t, ok)
}

func TestBuildSignatures_ArgsRegex_NoMatch_WrongBinary(t *testing.T) {
dets := []config.BinaryDetection{
{Name: "exfil_script", Binary: "bash", ArgsRegex: `exfil\.sh`},
}
sigs, err := BuildSignatures(dets)
require.NoError(t, err)

_, ok := MatchWith(sigs, "/usr/bin/zsh", []string{"zsh", "exfil.sh"})
require.False(t, ok)
}

func TestBuildSignatures_InvalidRegex(t *testing.T) {
dets := []config.BinaryDetection{
{Name: "bad", Binary: "bash", ArgsRegex: `[invalid`},
}
_, err := BuildSignatures(dets)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid args_regex")
}
5 changes: 3 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ type DetectionsConfig struct {
}

type BinaryDetection struct {
Name string `yaml:"name"`
Binary string `yaml:"binary"`
Name string `yaml:"name"`
Binary string `yaml:"binary"`
ArgsRegex string `yaml:"args_regex"`
}

type FileSinkConfig struct {
Expand Down
Loading