mirror of
https://github.com/anotherhadi/spilltea.git
synced 2026-05-20 17:52:33 +02:00
plugin's config is now in yaml
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
+38
-3
@@ -1,5 +1,7 @@
|
|||||||
# Plugins
|
# Plugins
|
||||||
|
|
||||||
|
> **Warning:** Plugins can execute arbitrary shell commands, read and write files via `shell_pipe`, and access all intercepted traffic. Only load plugins you trust and have reviewed. You are solely responsible for the plugins you run.
|
||||||
|
|
||||||
Spilltea supports Lua plugins that can intercept, modify, and analyze HTTP traffic.
|
Spilltea supports Lua plugins that can intercept, modify, and analyze HTTP traffic.
|
||||||
You can found some pre-built plugins [here](../../plugins/).
|
You can found some pre-built plugins [here](../../plugins/).
|
||||||
|
|
||||||
@@ -33,7 +35,7 @@ Plugin = {
|
|||||||
|
|
||||||
| Hook | When called | Sync/async | Return value |
|
| Hook | When called | Sync/async | Return value |
|
||||||
| ------------------------- | ------------------------------------- | ------------ | ----------------------------------------------- |
|
| ------------------------- | ------------------------------------- | ------------ | ----------------------------------------------- |
|
||||||
| `on_config(config_text)` | At startup and on config save | always sync | ignored |
|
| `on_config()` | At startup and on config save | always sync | ignored |
|
||||||
| `on_start()` | Once at startup, after `on_config` | configurable | `false` to self-disable the plugin, otherwise 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` (sync only) |
|
| `on_request(req)` | Every request, before auto-forward | configurable | `"drop"`, `"forward"`, or `nil` (sync only) |
|
||||||
@@ -120,6 +122,10 @@ if err then
|
|||||||
else
|
else
|
||||||
log("output: " .. out)
|
log("output: " .. out)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Return the plugin's config section as a Lua table (parsed from YAML).
|
||||||
|
-- Returns an empty table if no config is set.
|
||||||
|
local cfg = get_config()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Finding deduplication
|
### Finding deduplication
|
||||||
@@ -128,9 +134,38 @@ A finding is identified by `(plugin_name, key)`. If a finding with that pair alr
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Each plugin gets a **config textarea** on the Plugins page. The raw text is passed as-is to `on_config(config_text)`. Parse it however you like (line by line, key=value, JSON, etc.).
|
Plugin configuration is stored in a `plugins.yaml` file alongside the project database. Each plugin has an `enable` toggle and an optional `config` block (arbitrary YAML).
|
||||||
|
|
||||||
`on_config` is called once at startup (before `on_start`) and again every time the user saves the config in the UI.
|
```yaml
|
||||||
|
plugins:
|
||||||
|
my_plugin:
|
||||||
|
enable: true
|
||||||
|
config: |
|
||||||
|
some_key: some_value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
other_plugin:
|
||||||
|
enable: false
|
||||||
|
```
|
||||||
|
|
||||||
|
The config block is edited from the **Plugins** page in the TUI. Inside a plugin, call `get_config()` to retrieve the config as a Lua table.
|
||||||
|
|
||||||
|
`on_config()` is called once at startup (before `on_start`) and again every time the user saves the config in the TUI. It is the right place to read `get_config()` and populate local variables.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local items = {}
|
||||||
|
|
||||||
|
function on_config()
|
||||||
|
items = {}
|
||||||
|
local cfg = get_config()
|
||||||
|
if cfg and cfg.list then
|
||||||
|
for _, v in ipairs(cfg.list) do
|
||||||
|
table.insert(items, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
## Sync vs async
|
## Sync vs async
|
||||||
|
|
||||||
|
|||||||
+1
-6
@@ -72,12 +72,7 @@ CREATE TABLE IF NOT EXISTS replay_entries (
|
|||||||
status_code INTEGER NOT NULL,
|
status_code INTEGER NOT NULL,
|
||||||
error_msg TEXT NOT NULL
|
error_msg TEXT NOT NULL
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS plugins (
|
CREATE TABLE IF NOT EXISTS findings (
|
||||||
name TEXT PRIMARY KEY,
|
|
||||||
enabled INTEGER NOT NULL DEFAULT 1,
|
|
||||||
config_text TEXT NOT NULL DEFAULT ''
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS findings (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
plugin_name TEXT NOT NULL,
|
plugin_name TEXT NOT NULL,
|
||||||
dedup_key TEXT NOT NULL,
|
dedup_key TEXT NOT NULL,
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
type PluginState struct {
|
|
||||||
Name string
|
|
||||||
Enabled bool
|
|
||||||
ConfigText string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) SavePluginState(name string, enabled bool, configText string) error {
|
|
||||||
_, err := d.conn.Exec(
|
|
||||||
`INSERT INTO plugins (name, enabled, config_text) VALUES (?, ?, ?)
|
|
||||||
ON CONFLICT(name) DO UPDATE SET enabled = excluded.enabled, config_text = excluded.config_text`,
|
|
||||||
name, enabled, configText,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) LoadPluginStates() ([]PluginState, error) {
|
|
||||||
rows, err := d.conn.Query(`SELECT name, enabled, config_text FROM plugins`)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var out []PluginState
|
|
||||||
for rows.Next() {
|
|
||||||
var s PluginState
|
|
||||||
var enabled int
|
|
||||||
if err := rows.Scan(&s.Name, &enabled, &s.ConfigText); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.Enabled = enabled != 0
|
|
||||||
out = append(out, s)
|
|
||||||
}
|
|
||||||
return out, rows.Err()
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/anotherhadi/spilltea/internal/db"
|
"github.com/anotherhadi/spilltea/internal/db"
|
||||||
goproxy "github.com/lqqyt2423/go-mitmproxy/proxy"
|
goproxy "github.com/lqqyt2423/go-mitmproxy/proxy"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newLuaState(mgr *Manager, p *Plugin) *lua.LState {
|
func newLuaState(mgr *Manager, p *Plugin) *lua.LState {
|
||||||
@@ -175,6 +176,27 @@ func registerUtilities(L *lua.LState, mgr *Manager, p *Plugin) {
|
|||||||
return 0
|
return 0
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
L.SetGlobal("get_config", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
// p.mu is already held by the hook caller - do not lock again.
|
||||||
|
configText := p.ConfigText
|
||||||
|
if configText == "" {
|
||||||
|
L.Push(L.NewTable())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
var data interface{}
|
||||||
|
if err := yaml.Unmarshal([]byte(configText), &data); err != nil || data == nil {
|
||||||
|
L.Push(L.NewTable())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
lv := goToLuaValue(L, data)
|
||||||
|
if _, ok := lv.(*lua.LTable); !ok {
|
||||||
|
L.Push(L.NewTable())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
L.Push(lv)
|
||||||
|
return 1
|
||||||
|
}))
|
||||||
|
|
||||||
L.SetGlobal("shell_pipe", L.NewFunction(func(L *lua.LState) int {
|
L.SetGlobal("shell_pipe", L.NewFunction(func(L *lua.LState) int {
|
||||||
cmd := L.CheckString(1)
|
cmd := L.CheckString(1)
|
||||||
input := L.OptString(2, "")
|
input := L.OptString(2, "")
|
||||||
@@ -201,6 +223,35 @@ func registerUtilities(L *lua.LState, mgr *Manager, p *Plugin) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func goToLuaValue(L *lua.LState, v interface{}) lua.LValue {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
t := L.NewTable()
|
||||||
|
for k, v2 := range val {
|
||||||
|
L.SetField(t, k, goToLuaValue(L, v2))
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
case []interface{}:
|
||||||
|
t := L.NewTable()
|
||||||
|
for i, v2 := range val {
|
||||||
|
L.RawSetInt(t, i+1, goToLuaValue(L, v2))
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
case string:
|
||||||
|
return lua.LString(val)
|
||||||
|
case int:
|
||||||
|
return lua.LNumber(val)
|
||||||
|
case float64:
|
||||||
|
return lua.LNumber(val)
|
||||||
|
case bool:
|
||||||
|
if val {
|
||||||
|
return lua.LTrue
|
||||||
|
}
|
||||||
|
return lua.LFalse
|
||||||
|
}
|
||||||
|
return lua.LNil
|
||||||
|
}
|
||||||
|
|
||||||
func luaTableString(t *lua.LTable, key string) string {
|
func luaTableString(t *lua.LTable, key string) string {
|
||||||
v := t.RawGetString(key)
|
v := t.RawGetString(key)
|
||||||
if s, ok := v.(lua.LString); ok {
|
if s, ok := v.(lua.LString); ok {
|
||||||
|
|||||||
+30
-45
@@ -20,6 +20,7 @@ type Manager struct {
|
|||||||
plugins []*Plugin
|
plugins []*Plugin
|
||||||
|
|
||||||
db *db.DB
|
db *db.DB
|
||||||
|
pluginsFile *PluginsFile
|
||||||
broker *intercept.Broker
|
broker *intercept.Broker
|
||||||
|
|
||||||
Notifs chan PluginNotifMsg
|
Notifs chan PluginNotifMsg
|
||||||
@@ -43,6 +44,10 @@ func (m *Manager) SetDB(d *db.DB) {
|
|||||||
m.db = d
|
m.db = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) SetPluginsFile(pf *PluginsFile) {
|
||||||
|
m.pluginsFile = pf
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) LoadFromDir(dir string) error {
|
func (m *Manager) LoadFromDir(dir string) error {
|
||||||
entries, err := os.ReadDir(dir)
|
entries, err := os.ReadDir(dir)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -52,17 +57,6 @@ func (m *Manager) LoadFromDir(dir string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var states map[string]db.PluginState
|
|
||||||
if m.db != nil {
|
|
||||||
list, err := m.db.LoadPluginStates()
|
|
||||||
if err == nil {
|
|
||||||
states = make(map[string]db.PluginState, len(list))
|
|
||||||
for _, s := range list {
|
|
||||||
states[s.Name] = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
if e.IsDir() || !strings.HasSuffix(e.Name(), ".lua") {
|
if e.IsDir() || !strings.HasSuffix(e.Name(), ".lua") {
|
||||||
continue
|
continue
|
||||||
@@ -73,9 +67,13 @@ func (m *Manager) LoadFromDir(dir string) error {
|
|||||||
log.Printf("plugin load error %s: %v", path, err)
|
log.Printf("plugin load error %s: %v", path, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if s, ok := states[p.Name]; ok {
|
if m.pluginsFile != nil {
|
||||||
p.Enabled = s.Enabled
|
if enabled, configText, found := m.pluginsFile.get(p.Name); found {
|
||||||
p.ConfigText = s.ConfigText
|
p.Enabled = enabled
|
||||||
|
p.ConfigText = configText
|
||||||
|
} else {
|
||||||
|
m.pluginsFile.register(p.Name, p.Enabled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
m.plugins = append(m.plugins, p)
|
m.plugins = append(m.plugins, p)
|
||||||
@@ -131,12 +129,6 @@ func (m *Manager) loadPlugin(path string) (*Plugin, error) {
|
|||||||
"on_response": false,
|
"on_response": false,
|
||||||
"on_history_entry": false,
|
"on_history_entry": false,
|
||||||
}
|
}
|
||||||
// Fixed-sync hooks: always sync, not configurable.
|
|
||||||
fixedSyncHooks := map[string]struct{}{
|
|
||||||
"on_config": {},
|
|
||||||
"on_quit": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
for hookName, defaultSync := range configurableHooks {
|
for hookName, defaultSync := range configurableHooks {
|
||||||
if tbl, ok := pluginTable.RawGetString(hookName).(*lua.LTable); ok {
|
if tbl, ok := pluginTable.RawGetString(hookName).(*lua.LTable); ok {
|
||||||
p.hooks[hookName] = HookConfig{Sync: tbl.RawGetString("sync") == lua.LTrue}
|
p.hooks[hookName] = HookConfig{Sync: tbl.RawGetString("sync") == lua.LTrue}
|
||||||
@@ -146,9 +138,9 @@ func (m *Manager) loadPlugin(path string) (*Plugin, error) {
|
|||||||
p.hooks[hookName] = HookConfig{Sync: defaultSync}
|
p.hooks[hookName] = HookConfig{Sync: defaultSync}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for hookName := range fixedSyncHooks {
|
for _, fixedSync := range []string{"on_config", "on_quit"} {
|
||||||
if p.L.GetGlobal(hookName) != lua.LNil {
|
if p.L.GetGlobal(fixedSync) != lua.LNil {
|
||||||
p.hooks[hookName] = HookConfig{Sync: true}
|
p.hooks[fixedSync] = HookConfig{Sync: true}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,10 +171,9 @@ func (m *Manager) TogglePlugin(name string) {
|
|||||||
found.mu.Lock()
|
found.mu.Lock()
|
||||||
found.Enabled = !found.Enabled
|
found.Enabled = !found.Enabled
|
||||||
enabled := found.Enabled
|
enabled := found.Enabled
|
||||||
configText := found.ConfigText
|
|
||||||
found.mu.Unlock()
|
found.mu.Unlock()
|
||||||
if m.db != nil {
|
if m.pluginsFile != nil {
|
||||||
if err := m.db.SavePluginState(name, enabled, configText); err != nil {
|
if err := m.pluginsFile.setEnabled(name, enabled); err != nil {
|
||||||
log.Printf("plugin %s: save state: %v", name, err)
|
log.Printf("plugin %s: save state: %v", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,8 +187,8 @@ func (m *Manager) TogglePlugin(name string) {
|
|||||||
disableIfFalse := func(p *Plugin, ret lua.LValue) {
|
disableIfFalse := func(p *Plugin, ret lua.LValue) {
|
||||||
if ret == lua.LFalse {
|
if ret == lua.LFalse {
|
||||||
p.Enabled = false
|
p.Enabled = false
|
||||||
if m.db != nil {
|
if m.pluginsFile != nil {
|
||||||
if err := m.db.SavePluginState(p.Name, false, p.ConfigText); err != nil {
|
if err := m.pluginsFile.setEnabled(p.Name, false); err != nil {
|
||||||
log.Printf("plugin %s: save state: %v", p.Name, err)
|
log.Printf("plugin %s: save state: %v", p.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,41 +232,35 @@ func (m *Manager) SaveConfig(name, configText string) {
|
|||||||
}
|
}
|
||||||
found.mu.Lock()
|
found.mu.Lock()
|
||||||
found.ConfigText = configText
|
found.ConfigText = configText
|
||||||
enabled := found.Enabled
|
|
||||||
_, hasOnConfig := found.hooks["on_config"]
|
|
||||||
found.mu.Unlock()
|
found.mu.Unlock()
|
||||||
if m.db != nil {
|
if m.pluginsFile != nil {
|
||||||
if err := m.db.SavePluginState(name, enabled, configText); err != nil {
|
if err := m.pluginsFile.setConfig(name, configText); err != nil {
|
||||||
log.Printf("plugin %s: save state: %v", name, err)
|
log.Printf("plugin %s: save config: %v", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasOnConfig {
|
if _, ok := found.hooks["on_config"]; !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// on_config is always sync.
|
|
||||||
found.mu.Lock()
|
found.mu.Lock()
|
||||||
if _, err := callHook(found, "on_config", lua.LString(configText)); err != nil {
|
if _, err := callHook(found, "on_config"); err != nil {
|
||||||
log.Printf("plugin %s on_config (config reload): %v", name, err)
|
log.Printf("plugin %s on_config: %v", name, err)
|
||||||
}
|
}
|
||||||
found.mu.Unlock()
|
found.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) RunOnStart() {
|
func (m *Manager) RunOnStart() {
|
||||||
// on_config runs first, always sync, for every enabled plugin that has it.
|
|
||||||
for _, p := range m.GetPlugins() {
|
for _, p := range m.GetPlugins() {
|
||||||
if !p.Enabled {
|
if !p.Enabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := p.hooks["on_config"]; !ok {
|
if _, ok := p.hooks["on_config"]; ok {
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
if _, err := callHook(p, "on_config", lua.LString(p.ConfigText)); err != nil {
|
if _, err := callHook(p, "on_config"); err != nil {
|
||||||
log.Printf("plugin %s on_config: %v", p.Name, err)
|
log.Printf("plugin %s on_config: %v", p.Name, err)
|
||||||
}
|
}
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
}
|
}
|
||||||
// on_start runs after, sync or async depending on plugin config.
|
}
|
||||||
for _, p := range m.GetPlugins() {
|
for _, p := range m.GetPlugins() {
|
||||||
if !p.Enabled {
|
if !p.Enabled {
|
||||||
continue
|
continue
|
||||||
@@ -287,8 +272,8 @@ func (m *Manager) RunOnStart() {
|
|||||||
disableIfFalse := func(p *Plugin, ret lua.LValue) {
|
disableIfFalse := func(p *Plugin, ret lua.LValue) {
|
||||||
if ret == lua.LFalse {
|
if ret == lua.LFalse {
|
||||||
p.Enabled = false
|
p.Enabled = false
|
||||||
if m.db != nil {
|
if m.pluginsFile != nil {
|
||||||
if err := m.db.SavePluginState(p.Name, false, p.ConfigText); err != nil {
|
if err := m.pluginsFile.setEnabled(p.Name, false); err != nil {
|
||||||
log.Printf("plugin %s: save state: %v", p.Name, err)
|
log.Printf("plugin %s: save state: %v", p.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pluginFileEntry struct {
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Config string `yaml:"config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pluginsFileData struct {
|
||||||
|
Plugins map[string]pluginFileEntry `yaml:"plugins"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginsFile struct {
|
||||||
|
path string
|
||||||
|
data pluginsFileData
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenPluginsFile(dbPath string) (*PluginsFile, error) {
|
||||||
|
path := filepath.Join(filepath.Dir(dbPath), "plugins.yaml")
|
||||||
|
pf := &PluginsFile{
|
||||||
|
path: path,
|
||||||
|
data: pluginsFileData{Plugins: make(map[string]pluginFileEntry)},
|
||||||
|
}
|
||||||
|
raw, err := os.ReadFile(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return pf, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := yaml.Unmarshal(raw, &pf.data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pf.data.Plugins == nil {
|
||||||
|
pf.data.Plugins = make(map[string]pluginFileEntry)
|
||||||
|
}
|
||||||
|
return pf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *PluginsFile) save() error {
|
||||||
|
raw, err := yaml.Marshal(&pf.data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(pf.path, raw, 0o600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *PluginsFile) get(name string) (enabled bool, config string, found bool) {
|
||||||
|
e, ok := pf.data.Plugins[name]
|
||||||
|
return e.Enable, e.Config, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *PluginsFile) register(name string, defaultEnabled bool) {
|
||||||
|
if _, ok := pf.data.Plugins[name]; !ok {
|
||||||
|
pf.data.Plugins[name] = pluginFileEntry{Enable: defaultEnabled}
|
||||||
|
_ = pf.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *PluginsFile) setEnabled(name string, enabled bool) error {
|
||||||
|
e := pf.data.Plugins[name]
|
||||||
|
e.Enable = enabled
|
||||||
|
pf.data.Plugins[name] = e
|
||||||
|
return pf.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pf *PluginsFile) setConfig(name string, configText string) error {
|
||||||
|
e := pf.data.Plugins[name]
|
||||||
|
e.Config = configText
|
||||||
|
pf.data.Plugins[name] = e
|
||||||
|
return pf.save()
|
||||||
|
}
|
||||||
@@ -106,6 +106,12 @@ func New(broker *intercept.Broker, name, path string) Model {
|
|||||||
m.findingsPage.SetDB(d)
|
m.findingsPage.SetDB(d)
|
||||||
mgr.SetDB(d)
|
mgr.SetDB(d)
|
||||||
|
|
||||||
|
pf, err := plugins.OpenPluginsFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("plugins file: %v", err)
|
||||||
|
}
|
||||||
|
mgr.SetPluginsFile(pf)
|
||||||
|
|
||||||
pluginsDir := config.ExpandPath(cfg.App.PluginsDir)
|
pluginsDir := config.ExpandPath(cfg.App.PluginsDir)
|
||||||
if err := mgr.LoadFromDir(pluginsDir); err != nil {
|
if err := mgr.LoadFromDir(pluginsDir); err != nil {
|
||||||
log.Printf("plugins: %v", err)
|
log.Printf("plugins: %v", err)
|
||||||
|
|||||||
@@ -3,22 +3,28 @@ Plugin = {
|
|||||||
description = [[
|
description = [[
|
||||||
Inject custom headers into every intercepted request.
|
Inject custom headers into every intercepted request.
|
||||||
|
|
||||||
**Config**:
|
**Config** (YAML):
|
||||||
- one 'Header-Name: value' per line.
|
```yaml
|
||||||
|
headers:
|
||||||
|
- "X-My-Header: myvalue"
|
||||||
|
```
|
||||||
]],
|
]],
|
||||||
on_request = { sync = true },
|
on_request = { sync = true },
|
||||||
}
|
}
|
||||||
|
|
||||||
local headers = {}
|
local headers = {}
|
||||||
|
|
||||||
function on_config(config_text)
|
function on_config()
|
||||||
headers = {}
|
headers = {}
|
||||||
for line in config_text:gmatch("[^\n]+") do
|
local cfg = get_config()
|
||||||
|
if cfg and cfg.headers then
|
||||||
|
for _, line in ipairs(cfg.headers) do
|
||||||
local name, value = line:match("^([^:]+):%s*(.+)$")
|
local name, value = line:match("^([^:]+):%s*(.+)$")
|
||||||
if name and value then
|
if name and value then
|
||||||
table.insert(headers, { name = name, value = value })
|
table.insert(headers, { name = name, value = value })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_request(req)
|
function on_request(req)
|
||||||
|
|||||||
+16
-15
@@ -3,11 +3,13 @@ Plugin = {
|
|||||||
description = [[
|
description = [[
|
||||||
Checks that the proxy's outbound IP is in an allowed list on startup.
|
Checks that the proxy's outbound IP is in an allowed list on startup.
|
||||||
|
|
||||||
**Config**:
|
**Config** (YAML):
|
||||||
- one IP per line
|
```yaml
|
||||||
- prefix with `!` for a blacklist entry (blocked)
|
ips:
|
||||||
- prefix with `#` to comment it out (ignored)
|
- "1.2.3.4" # whitelist entry
|
||||||
- if no IPs are configured, the check is skipped
|
- "!5.6.7.8" # blacklist entry (blocked)
|
||||||
|
```
|
||||||
|
- If no IPs are configured, the check is skipped.
|
||||||
]],
|
]],
|
||||||
on_start = { sync = false },
|
on_start = { sync = false },
|
||||||
disable_by_default = true,
|
disable_by_default = true,
|
||||||
@@ -16,23 +18,22 @@ Checks that the proxy's outbound IP is in an allowed list on startup.
|
|||||||
local whitelist = {}
|
local whitelist = {}
|
||||||
local blacklist = {}
|
local blacklist = {}
|
||||||
|
|
||||||
function on_config(config_text)
|
function on_config()
|
||||||
whitelist = {}
|
whitelist, blacklist = {}, {}
|
||||||
blacklist = {}
|
local cfg = get_config()
|
||||||
|
if cfg and cfg.ips then
|
||||||
for line in config_text:gmatch("[^\n]+") do
|
for _, entry in ipairs(cfg.ips) do
|
||||||
local trimmed = line:match("^%s*(.-)%s*$")
|
local trimmed = entry:match("^%s*(.-)%s*$")
|
||||||
if trimmed ~= "" and trimmed:sub(1, 1) ~= "#" then
|
if trimmed ~= "" then
|
||||||
if trimmed:sub(1, 1) == "!" then
|
if trimmed:sub(1, 1) == "!" then
|
||||||
local ip = trimmed:sub(2):match("^%s*(.-)%s*$")
|
local ip = trimmed:sub(2):match("^%s*(.-)%s*$")
|
||||||
if ip ~= "" then
|
if ip ~= "" then table.insert(blacklist, ip) end
|
||||||
table.insert(blacklist, ip)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
table.insert(whitelist, trimmed)
|
table.insert(whitelist, trimmed)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_start()
|
function on_start()
|
||||||
|
|||||||
+27
-28
@@ -3,38 +3,35 @@ Plugin = {
|
|||||||
description = [[
|
description = [[
|
||||||
Auto-forward requests and exclude them from history based on patterns.
|
Auto-forward requests and exclude them from history based on patterns.
|
||||||
|
|
||||||
**Config**:
|
**Config** (YAML):
|
||||||
- `pattern` - whitelist: only intercept matching requests/responses and history entries
|
```yaml
|
||||||
- `!pattern` - blacklist: skip matching requests/responses and history entries
|
patterns:
|
||||||
- `r:pattern` - whitelist for requests/responses only (history unaffected)
|
- "pattern" # whitelist: only intercept matching requests/responses and history
|
||||||
- `r:!pattern` - blacklist for requests/responses only
|
- "!pattern" # blacklist: skip matching requests/responses and history
|
||||||
- `h:pattern` - whitelist for history entries only (requests unaffected)
|
- "r:pattern" # whitelist for requests/responses only
|
||||||
- `h:!pattern` - blacklist for history entries only
|
- "r:!pattern" # blacklist for requests/responses only
|
||||||
- lines starting with `#` are comments
|
- "h:pattern" # whitelist for history only
|
||||||
|
- "h:!pattern" # blacklist for history only
|
||||||
|
```
|
||||||
|
|
||||||
Example (ignore static assets):
|
Example (ignore static assets):
|
||||||
```
|
```yaml
|
||||||
!%.css$
|
patterns:
|
||||||
!%.js$
|
- "!%.css$"
|
||||||
!%.png$
|
- "!%.js$"
|
||||||
|
- "!%.png$"
|
||||||
```
|
```
|
||||||
|
|
||||||
Example (focus on mytarget.com, skip everything else):
|
Example (focus on mytarget.com):
|
||||||
```
|
```yaml
|
||||||
mytarget%.com/
|
patterns:
|
||||||
|
- "mytarget%.com/"
|
||||||
```
|
```
|
||||||
|
|
||||||
Example (intercept mytarget.com except its static assets):
|
Example (disable history):
|
||||||
```
|
```yaml
|
||||||
mytarget%.com/
|
patterns:
|
||||||
!%.css$
|
- "h:^$"
|
||||||
!%.js$
|
|
||||||
!%.png$
|
|
||||||
```
|
|
||||||
|
|
||||||
Example (disable history: whitelist never matches any real URL):
|
|
||||||
```
|
|
||||||
h:^$
|
|
||||||
```
|
```
|
||||||
]],
|
]],
|
||||||
priority = 100,
|
priority = 100,
|
||||||
@@ -50,11 +47,13 @@ local whitelist_req = {}
|
|||||||
local blacklist_hist = {}
|
local blacklist_hist = {}
|
||||||
local whitelist_hist = {}
|
local whitelist_hist = {}
|
||||||
|
|
||||||
function on_config(config_text)
|
function on_config()
|
||||||
blacklist, whitelist = {}, {}
|
blacklist, whitelist = {}, {}
|
||||||
blacklist_req, whitelist_req = {}, {}
|
blacklist_req, whitelist_req = {}, {}
|
||||||
blacklist_hist, whitelist_hist = {}, {}
|
blacklist_hist, whitelist_hist = {}, {}
|
||||||
for line in config_text:gmatch("[^\n]+") do
|
local cfg = get_config()
|
||||||
|
if not cfg or not cfg.patterns then return end
|
||||||
|
for _, line in ipairs(cfg.patterns) do
|
||||||
local trimmed = line:match("^%s*(.-)%s*$")
|
local trimmed = line:match("^%s*(.-)%s*$")
|
||||||
if trimmed ~= "" and trimmed:sub(1, 1) ~= "#" then
|
if trimmed ~= "" and trimmed:sub(1, 1) ~= "#" then
|
||||||
local scope = trimmed:match("^([rh]):")
|
local scope = trimmed:match("^([rh]):")
|
||||||
|
|||||||
Reference in New Issue
Block a user