check if trufflehog is installed on_start

Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
Hadi
2026-05-19 21:02:35 +02:00
parent 4240c4ceb9
commit 87fa9448d6
4 changed files with 82 additions and 22 deletions
+26 -5
View File
@@ -31,14 +31,14 @@ Plugin = {
### Hook reference ### Hook reference
| Hook | When called | Sync/async | Return value (sync only) | | Hook | When called | Sync/async | Return value |
| ------------------------- | ------------------------------------- | ------------ | ----------------------------------------------- | | ------------------------- | ------------------------------------- | ------------ | ----------------------------------------------- |
| `on_config(config_text)` | At startup and on config save | always sync | ignored | | `on_config(config_text)` | At startup and on config save | always sync | ignored |
| `on_start()` | Once at startup, after `on_config` | configurable | ignored | | `on_start()` | Once at startup, after `on_config` | configurable | `false` to self-disable the plugin, otherwise ignored |
| `on_quit()` | When the app exits | always sync | ignored | | `on_quit()` | When the app exits | always sync | ignored |
| `on_request(req)` | Every request, before auto-forward | configurable | `"drop"`, `"forward"`, or `nil` | | `on_request(req)` | Every request, before auto-forward | configurable | `"drop"`, `"forward"`, or `nil` (sync only) |
| `on_response(req, res)` | Every response | configurable | `"drop"`, `"forward"`, or `nil` | | `on_response(req, res)` | Every response | configurable | `"drop"`, `"forward"`, or `nil` (sync only) |
| `on_history_entry(entry)` | Sync: before DB insert / Async: after | configurable | `"skip"` (don't save), `"keep"` or `nil` (save) | | `on_history_entry(entry)` | Sync: before DB insert / Async: after | configurable | `"skip"` (don't save), `"keep"` or `nil` (save) -- sync only |
## Request and response objects ## Request and response objects
@@ -141,6 +141,27 @@ Each plugin gets a **config textarea** on the Plugins page. The raw text is pass
### Return values for sync hooks ### Return values for sync hooks
**`on_start`:**
| Return value | Effect |
| ------------ | -------------------------------------------------------------------------------------------- |
| `false` | The plugin is disabled immediately and the state is persisted (equivalent to toggling it off). |
| anything else | Ignored. |
This is useful for prerequisite checks (binary not found, config invalid, etc.) so the plugin does not silently run in a broken state:
```lua
function on_start()
local h = io.popen("command -v mytool 2>/dev/null")
local result = h and h:read("*a") or ""
if h then h:close() end
if result:match("^%s*$") then
notif("MyPlugin", "mytool not found, plugin disabled", "error")
return false
end
end
```
**`on_request` and `on_response`:** **`on_request` and `on_response`:**
| Return value | Effect | | Return value | Effect |
+4 -7
View File
@@ -292,22 +292,19 @@ func pushEntry(L *lua.LState, e db.Entry) *lua.LTable {
return t return t
} }
func callHook(p *Plugin, hookName string, args ...lua.LValue) (string, error) { func callHook(p *Plugin, hookName string, args ...lua.LValue) (lua.LValue, error) {
fn := p.L.GetGlobal(hookName) fn := p.L.GetGlobal(hookName)
if fn == lua.LNil { if fn == lua.LNil {
return "", nil return lua.LNil, nil
} }
if err := p.L.CallByParam(lua.P{ if err := p.L.CallByParam(lua.P{
Fn: fn, Fn: fn,
NRet: 1, NRet: 1,
Protect: true, Protect: true,
}, args...); err != nil { }, args...); err != nil {
return "", err return lua.LNil, err
} }
ret := p.L.Get(-1) ret := p.L.Get(-1)
p.L.Pop(1) p.L.Pop(1)
if s, ok := ret.(lua.LString); ok { return ret, nil
return string(s), nil
}
return "", nil
} }
+40 -10
View File
@@ -191,17 +191,31 @@ func (m *Manager) TogglePlugin(name string) {
if !ok { if !ok {
return return
} }
disableIfFalse := func(p *Plugin, ret lua.LValue) {
if ret == lua.LFalse {
p.Enabled = false
if m.db != nil {
_ = m.db.SavePluginState(p.Name, false, p.ConfigText)
}
}
}
if hc.Sync { if hc.Sync {
found.mu.Lock() found.mu.Lock()
if _, err := callHook(found, "on_start"); err != nil { ret, err := callHook(found, "on_start")
if err != nil {
log.Printf("plugin %s on_start: %v", found.Name, err) log.Printf("plugin %s on_start: %v", found.Name, err)
} else {
disableIfFalse(found, ret)
} }
found.mu.Unlock() found.mu.Unlock()
} else { } else {
go func() { go func() {
found.mu.Lock() found.mu.Lock()
if _, err := callHook(found, "on_start"); err != nil { ret, err := callHook(found, "on_start")
if err != nil {
log.Printf("plugin %s on_start: %v", found.Name, err) log.Printf("plugin %s on_start: %v", found.Name, err)
} else {
disableIfFalse(found, ret)
} }
found.mu.Unlock() found.mu.Unlock()
}() }()
@@ -264,17 +278,31 @@ func (m *Manager) RunOnStart() {
if !ok { if !ok {
continue continue
} }
disableIfFalse := func(p *Plugin, ret lua.LValue) {
if ret == lua.LFalse {
p.Enabled = false
if m.db != nil {
_ = m.db.SavePluginState(p.Name, false, p.ConfigText)
}
}
}
if hc.Sync { if hc.Sync {
p.mu.Lock() p.mu.Lock()
if _, err := callHook(p, "on_start"); err != nil { ret, err := callHook(p, "on_start")
if err != nil {
log.Printf("plugin %s on_start: %v", p.Name, err) log.Printf("plugin %s on_start: %v", p.Name, err)
} else {
disableIfFalse(p, ret)
} }
p.mu.Unlock() p.mu.Unlock()
} else { } else {
go func(p *Plugin) { go func(p *Plugin) {
p.mu.Lock() p.mu.Lock()
if _, err := callHook(p, "on_start"); err != nil { ret, err := callHook(p, "on_start")
if err != nil {
log.Printf("plugin %s on_start: %v", p.Name, err) log.Printf("plugin %s on_start: %v", p.Name, err)
} else {
disableIfFalse(p, ret)
} }
p.mu.Unlock() p.mu.Unlock()
}(p) }(p)
@@ -316,11 +344,13 @@ func (m *Manager) runSyncDecisionForPlugins(hookName string, argsFor func(*Plugi
log.Printf("plugin %s %s: %v", p.Name, hookName, err) log.Printf("plugin %s %s: %v", p.Name, hookName, err)
continue continue
} }
switch result { if s, ok := result.(lua.LString); ok {
case "drop": switch string(s) {
return intercept.Drop case "drop":
case "forward": return intercept.Drop
return intercept.Forward case "forward":
return intercept.Forward
}
} }
} }
return intercept.Intercept return intercept.Intercept
@@ -388,7 +418,7 @@ func (m *Manager) RunSyncOnHistoryEntry(e db.Entry) bool {
log.Printf("plugin %s on_history_entry: %v", p.Name, err) log.Printf("plugin %s on_history_entry: %v", p.Name, err)
continue continue
} }
if result == "skip" { if s, ok := result.(lua.LString); ok && string(s) == "skip" {
return false return false
} }
} }
+12
View File
@@ -8,11 +8,23 @@ Requires `trufflehog` v3+ to be installed and available in PATH.
Each finding is stored on the **Findings** page with the matched detector output. Each finding is stored on the **Findings** page with the matched detector output.
Findings are deduplicated per host+path+body content so repeated requests do not create duplicates. Findings are deduplicated per host+path+body content so repeated requests do not create duplicates.
]], ]],
on_start = { sync = false },
on_request = { sync = false }, on_request = { sync = false },
on_response = { sync = false }, on_response = { sync = false },
disable_by_default = true, disable_by_default = true,
} }
function on_start()
local handle = io.popen("command -v trufflehog 2>/dev/null")
local result = handle and handle:read("*a") or ""
if handle then handle:close() end
if not result or result:match("^%s*$") then
log("trufflehog is not installed or not in PATH")
notif("TruffleHog", "trufflehog is not installed or not in PATH, plugin disabled", "error")
return false
end
end
local function scan(label, content, host, path) local function scan(label, content, host, path)
if not content or content == "" then return end if not content or content == "" then return end
local out, err = shell_pipe("f=$(mktemp) && cat > \"$f\" && trufflehog filesystem --no-color \"$f\"; rc=$?; rm -f \"$f\"; exit $rc", content) local out, err = shell_pipe("f=$(mktemp) && cat > \"$f\" && trufflehog filesystem --no-color \"$f\"; rc=$?; rm -f \"$f\"; exit $rc", content)