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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<p align="center">
<img src="assets/logo.svg" width="180" alt="aiscan logo">
<h1 align="center">aiscan</h1>
<p align="center">Agentic Security Scanner — AI-driven reconnaissance meets deterministic scanning</p>
<p align="center">AI-driven single-binary pentest agent with a built-in multi-engine arsenal, ready to go</p>
<p align="center"><strong>Preview — APIs and features may change between releases</strong></p>
</p>

Expand Down
2 changes: 1 addition & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<p align="center">
<img src="assets/logo.svg" width="180" alt="aiscan logo">
<h1 align="center">aiscan</h1>
<p align="center">Agentic Security Scanner — AI 驱动的侦察与确定性扫描融合</p>
<p align="center">AI 驱动的面向实战的单文件渗透 agent,内置多引擎武器库开箱即用</p>
<p align="center"><strong>Preview — 本项目处于早期预览阶段,API 和功能可能随版本变更</strong></p>
</p>

Expand Down
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ CFG_IOA_SPACE=$(resolve "$OPT_IOA_SPACE" "$(yaml_val "$CONFIG_FILE" ioa space)")
CFG_VERIFY=$(resolve "$OPT_VERIFY" "$(yaml_val "$CONFIG_FILE" scan verify)")
CFG_VERIFY_TIMEOUT=$(resolve "$OPT_VERIFY_TIMEOUT" "$(yaml_val "$CONFIG_FILE" scan verify_timeout)")

CFG_TAVILY_KEYS=$(resolve "$OPT_TAVILY_KEYS" "$(yaml_val "$CONFIG_FILE" websearch tavily_keys)")
CFG_TAVILY_KEYS=$(resolve "$OPT_TAVILY_KEYS" "$(yaml_val "$CONFIG_FILE" search tavily_keys)")

# build 段仅从 aiscan.yaml 读取(不做 CLI 覆盖)
if [ -z "$OSARCH" ]; then
Expand Down
6 changes: 5 additions & 1 deletion cmd/aiscan/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ package main
import (
_ "github.com/chainreactors/aiscan/pkg/tools"
_ "github.com/chainreactors/aiscan/pkg/tools/arsenal"
_ "github.com/chainreactors/aiscan/pkg/tools/proton"
_ "github.com/chainreactors/aiscan/pkg/tools/gogo"
_ "github.com/chainreactors/aiscan/pkg/tools/ioa"
_ "github.com/chainreactors/aiscan/pkg/tools/neutron"
_ "github.com/chainreactors/aiscan/pkg/tools/proton"
_ "github.com/chainreactors/aiscan/pkg/tools/proxy"
_ "github.com/chainreactors/aiscan/pkg/tools/search"
_ "github.com/chainreactors/aiscan/pkg/tools/spray"
_ "github.com/chainreactors/aiscan/pkg/tools/zombie"
)
2 changes: 1 addition & 1 deletion core/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ llm:
}

if path == "" {
t.Fatal("expected config.yaml to be found")
t.Fatal("expected aiscan.yaml to be found")
}
if option.Provider != "found-provider" {
t.Errorf("Provider: got %q, want %q", option.Provider, "found-provider")
Expand Down
37 changes: 33 additions & 4 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Changelog

## v0.2.7 — Proton 敏感信息扫描 + /loop 循环任务 + TUI 交互增强 + 多 Provider 配置
## v0.2.7 — MITM 流量捕获 + Proton 敏感信息扫描 + /loop 循环任务 + TUI 交互增强

新增 Proton 敏感信息扫描器(SDK 引擎 + 197 条内嵌规则 + 双向管道);`/loop` 循环任务调度;TUI 交互全面增强(verbosity 切换、中断控制、文件补全、实时 token 用量);并发工具执行 OOM 防护;多 Provider 列表配置格式;FOFA key-only 认证支持。
MITM 透明流量拦截(`proxy mitm` 子命令族);Proton 敏感信息扫描器(SDK 引擎 + 197 条内嵌规则 + 双向管道);`/loop` 循环任务调度;TUI 交互全面增强(verbosity 切换、中断控制、文件补全、实时 token 用量);多 Provider 列表配置格式;FOFA key-only 认证支持。

### New Features

Expand Down Expand Up @@ -67,6 +67,36 @@ bash(command="loop */5 * * * * check scan progress")
bash(command="loop list")
```

**MITM 流量捕获**

透明 HTTP/HTTPS 流量拦截,集成 utils/mitmproxy 到 proxy 命令组。扫描引擎(gogo/spray/zombie/neutron)自动路由到本地 MITM 代理;若已有外部代理(trojan/vless/clash)则作为上游透传。

- `proxy mitm start [--addr]`:启动本地 MITM 代理,自动切换扫描引擎代理
- `proxy mitm stop`:停止 MITM 并恢复之前的代理设置
- `proxy mitm status`:查看状态和 flow 计数
- `proxy mitm flows [--host/--status/--type/--last]`:按条件查询捕获的 HTTP 流
- `proxy mitm flow <id>`:查看单个 flow 详情
- `proxy mitm clear`:清空 flow 存储
- `proxy mitm analyze [--host/--last]`:结构化输出供 AI 分析

```bash
# 启动 MITM 拦截
proxy mitm start --addr 127.0.0.1:8888

# 正常执行扫描(流量自动经过 MITM)
scan -i target

# 查看捕获的流量
proxy mitm flows --last 20
proxy mitm flow 42

# AI 分析捕获的请求
proxy mitm analyze --host target.com

# 停止并恢复
proxy mitm stop
```

### Improvements

**TUI 交互增强**
Expand Down Expand Up @@ -112,8 +142,6 @@ llm:

- **Resources 统一**:4 套独立 config map(gogo/spray/zombie/proton)合并为单一 `configs map[string]map[string][]byte` + `Config(engine, name)` 方法
- **toolargs 共享工具包**:提取 `ResolveRelativePaths` 和 `NormalizeFlags` 到 `toolargs/`,6 个工具共用(proton/neutron/scan/spray/zombie/katana)
- **Deps.GetLogger()**:消除 5 个 register.go 中重复的 logger 提取模式
- 净减 180 行代码

### Bug Fixes

Expand All @@ -127,6 +155,7 @@ llm:
### Dependencies

- **spray [v1.3.1](https://github.com/chainreactors/spray/releases/tag/v1.3.1)**:mask 表达式支持所有请求字段、`--keys` 插件内嵌 156 条 proton 模板、extract severity 分级 + 上下文捕获、修复 crawl-only 提前 drain 和 OutputCh panic
- **utils/cert**:集成 utils/cert 原子化证书原语(CA 生成、子证书签发、随机 Subject、PEM 工具函数),移除本地 replace
- bump SDK、zombie、logs、utils/pty 修复上游 data race

### Breaking Changes
Expand Down
6 changes: 2 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ require (
github.com/chainreactors/sdk v0.3.4-0.20260624031614-b16da9a87441
github.com/chainreactors/spray v1.3.2-0.20260624034433-890630649b2b
github.com/chainreactors/utils v0.0.0-20260623065725-737b33d61c6b
github.com/chainreactors/utils/mitmproxy v0.0.0
github.com/chainreactors/utils/mitmproxy v0.0.0-20260624182357-8d5cad72d8f2
github.com/chainreactors/utils/pty v0.0.0-20260624031611-9aadeae3fb0e
github.com/chainreactors/zombie v1.2.3-0.20260624041317-6bf4579de29d
github.com/charmbracelet/bubbles v1.0.0
Expand Down Expand Up @@ -88,7 +88,7 @@ require (
github.com/censys/censys-sdk-go v0.19.1 // indirect
github.com/chainreactors/files v0.0.0-20240716182835-7884ee1e77f0 // indirect
github.com/chainreactors/neutron/operators/full v0.0.0-20260615055126-a9bbe4fc3e95 // indirect
github.com/chainreactors/utils/cert v0.0.0 // indirect
github.com/chainreactors/utils/cert v0.0.0-20260624181253-2b3d0b35862f // indirect
github.com/chainreactors/words v0.0.0-20260520145736-270600e60fb4 // indirect
github.com/charlievieth/fastwalk v1.0.14 // indirect
github.com/charmbracelet/bubbletea v1.3.10 // indirect
Expand Down Expand Up @@ -308,8 +308,6 @@ require (
)

replace (
github.com/chainreactors/utils/cert => ../utils/cert
github.com/chainreactors/utils/mitmproxy => ../utils/mitmproxy
github.com/reeflective/console => github.com/chainreactors/malice-network/external/console v0.0.0-20260620124902-e62d93c864f7
github.com/reeflective/readline => github.com/chainreactors/malice-network/external/readline v0.0.0-20260620124902-e62d93c864f7
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ github.com/chainreactors/spray v1.3.2-0.20260624034433-890630649b2b/go.mod h1:uh
github.com/chainreactors/utils v0.0.0-20240716182459-e85f2b01ee16/go.mod h1:LajXuvESQwP+qCMAvlcoSXppQCjuLlBrnQpu9XQ1HtU=
github.com/chainreactors/utils v0.0.0-20260623065725-737b33d61c6b h1:JWciIo6GXn+QqejE8ER0oo/IxqaAAlSaIqsHhZtnoMM=
github.com/chainreactors/utils v0.0.0-20260623065725-737b33d61c6b/go.mod h1:LajXuvESQwP+qCMAvlcoSXppQCjuLlBrnQpu9XQ1HtU=
github.com/chainreactors/utils/cert v0.0.0-20260624181253-2b3d0b35862f h1:7KonUAvkZhpmK8RDwDBCuey0qXeoqxJ7uWjoXqzL2z4=
github.com/chainreactors/utils/cert v0.0.0-20260624181253-2b3d0b35862f/go.mod h1:xvvWMcU9Fcht6GR1cc9ceAZ3/Hl2HrkoRzpeyOzx1rQ=
github.com/chainreactors/utils/mitmproxy v0.0.0-20260624182357-8d5cad72d8f2 h1:9sZzzfeYX5gD64TEpCykenqY+fhej6g2s/lCzMDNNIg=
github.com/chainreactors/utils/mitmproxy v0.0.0-20260624182357-8d5cad72d8f2/go.mod h1:2O3/Vw66VnbzhwsHGFJ2Ge98RuSh6XzMMFGZmMmlZ9M=
github.com/chainreactors/utils/pty v0.0.0-20260624031611-9aadeae3fb0e h1:KzDf08J9nGhTjyTz1pMR7aMnkbTEeagZllFcZnZuthM=
github.com/chainreactors/utils/pty v0.0.0-20260624031611-9aadeae3fb0e/go.mod h1:RW1v+8hFMeO9+TJyQ1iIx9Ea37s+B7BaDf9fyJ0OEC4=
github.com/chainreactors/words v0.0.0-20260520145736-270600e60fb4 h1:lvnDYEkatmZFHP5i321qQXK9L4vKRfso/uUfr5tOeC8=
Expand Down
2 changes: 2 additions & 0 deletions pkg/agent/context_window.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ var modelContextWindows = []struct {
{"gpt-4.1", 1047576},
{"gpt-4o", 128000},
{"gpt-4-turbo", 128000},
{"gpt-4-1", 128000},
{"gpt-4-0", 8192},
{"gpt-4", 8192},
{"o4-mini", 200000},
{"o3", 200000},
Expand Down
6 changes: 5 additions & 1 deletion pkg/agent/evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ func (e *Evaluator) Evaluate(ctx context.Context, goal, criteria string, message
lastErr = err
e.cfg.Logger.Warnf("evaluate attempt %d failed: %s", attempt+1, err)
if attempt < e.cfg.MaxRetries-1 {
time.Sleep(time.Duration(attempt+1) * time.Second)
select {
case <-time.After(time.Duration(attempt+1) * time.Second):
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
return nil, fmt.Errorf("evaluate failed after %d attempts: %w", e.cfg.MaxRetries, lastErr)
Expand Down
11 changes: 10 additions & 1 deletion pkg/agent/loop_scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type LoopScheduler struct {
type loopState struct {
entry LoopEntry
cancel context.CancelFunc
wg sync.WaitGroup
fireCount int
lastFired time.Time
}
Expand Down Expand Up @@ -198,7 +199,9 @@ func (s *LoopScheduler) fire(ctx context.Context, state *loopState) {
}

case ModeIndependent:
state.wg.Add(1)
go func() {
defer state.wg.Done()
if _, err := entry.OnFire(ctx, entry); err != nil {
s.log.Warnf("loop=%s fire=%d failed: %s", entry.Name, count, err)
}
Expand All @@ -216,6 +219,7 @@ func (s *LoopScheduler) Remove(name string) error {
state.cancel()
delete(s.loops, name)
s.mu.Unlock()
state.wg.Wait()
s.log.Importantf("loop=%s deleted", name)
return nil
}
Expand Down Expand Up @@ -245,9 +249,14 @@ func (s *LoopScheduler) Active() int {

func (s *LoopScheduler) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
states := make([]*loopState, 0, len(s.loops))
for name, state := range s.loops {
state.cancel()
states = append(states, state)
delete(s.loops, name)
}
s.mu.Unlock()
for _, state := range states {
state.wg.Wait()
}
}
5 changes: 5 additions & 0 deletions pkg/agent/loop_tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ func (c *LoopCommand) create(ctx context.Context, args []string) error {
// tryCronPrefix attempts to parse the first 5 args as a cron expression.
// Returns the parsed expression, remaining args, and whether it succeeded.
func tryCronPrefix(args []string) (*CronExpr, []string, bool) {
if len(args) >= 2 && strings.Contains(args[0], " ") {
if cron, err := ParseCron(args[0]); err == nil {
return cron, args[1:], true
}
}
if len(args) < 6 {
return nil, nil, false
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/agent/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func (c Config) init() Config {
if c.Logger == nil {
c.Logger = telemetry.NopLogger()
}
if c.MaxRetries == 0 {
if c.MaxRetries < 0 {
c.MaxRetries = DefaultMaxRetries
}
if c.MaxResultSize <= 0 {
Expand Down
8 changes: 3 additions & 5 deletions pkg/commands/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,15 @@ type Deps struct {
Provider any
ScannerProxy string
ScanOpts []any
Logger any
Logger telemetry.Logger
NodeName string
NodeMeta map[string]any
TavilyKeys string // comma-separated Tavily API keys (build-time fallback)
}

func (d *Deps) GetLogger() telemetry.Logger {
if d != nil {
if logger, ok := d.Logger.(telemetry.Logger); ok && logger != nil {
return logger
}
if d != nil && d.Logger != nil {
return d.Logger
}
return telemetry.NopLogger()
}
Expand Down
32 changes: 13 additions & 19 deletions pkg/tools/gogo/gogo.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,26 @@ import (
)

type Command struct {
engine *gogo.Engine
logger telemetry.Logger
proxy string
workDir string
toolargs.Base
engine *gogo.Engine
}

func New(engine *gogo.Engine) *Command {
return &Command{engine: engine, logger: telemetry.NopLogger()}
c := &Command{engine: engine}
c.InitLogger(nil)
return c
}

func (c *Command) WithLogger(logger telemetry.Logger) *Command {
if logger != nil {
c.logger = logger
}
c.InitLogger(logger)
return c
}

func (c *Command) SetWorkDir(dir string) { c.workDir = dir }

func (c *Command) WithProxy(proxy string) *Command {
c.proxy = proxy
c.Proxy = proxy
return c
}

func (c *Command) SetProxy(proxy string) { c.proxy = proxy }

func (c *Command) Name() string { return "gogo" }

func (c *Command) Usage() string {
Expand All @@ -52,9 +46,9 @@ func (c *Command) Execute(ctx context.Context, args []string) (err error) {
args = c.injectProxy(args)

if toolargs.BoolFlagEnabled(args, "--debug") {
restoreDebug := telemetry.ActivateDebug(c.logger)
restoreDebug := telemetry.ActivateDebug(c.Logger)
defer restoreDebug()
c.logger.Debugf("gogo debug enabled")
c.Logger.Debugf("gogo debug enabled")
}

var buf bytes.Buffer
Expand Down Expand Up @@ -86,13 +80,13 @@ func (c *Command) TestInjectProxy(args []string) []string {
}

func (c *Command) injectProxy(args []string) []string {
if c.proxy == "" {
if c.Proxy == "" {
return args
}
if toolargs.HasFlag(args, "--proxy") {
return args
}
return append(args, "--proxy", c.proxy)
return append(args, "--proxy", c.Proxy)
}

// normalizeArgs adapts common agent-generated gogo arguments before handing
Expand Down Expand Up @@ -139,10 +133,10 @@ func (c *Command) normalizeArgs(args []string) []string {
}

func (c *Command) resolvePathArg(value string) string {
if c.workDir == "" || value == "" || filepath.IsAbs(value) || strings.HasPrefix(value, "-") {
if c.WorkDir == "" || value == "" || filepath.IsAbs(value) || strings.HasPrefix(value, "-") {
return value
}
return filepath.Join(c.workDir, value)
return filepath.Join(c.WorkDir, value)
}

func isGogoValuelessJSONFlag(arg string, args []string, index int) bool {
Expand Down
22 changes: 22 additions & 0 deletions pkg/tools/gogo/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gogo

import (
"github.com/chainreactors/aiscan/pkg/commands"
"github.com/chainreactors/aiscan/pkg/tools/scan/engine"
)

func init() {
commands.RegisterFactory(commands.Factory{
Group: "scanner",
Build: func(deps *commands.Deps, reg *commands.CommandRegistry) {
es, _ := deps.EngineSet.(*engine.Set)
if es == nil || es.Gogo == nil {
return
}
reg.Register(
New(es.Gogo).WithLogger(deps.GetLogger()).WithProxy(deps.ScannerProxy),
"scanner",
)
},
})
}
Loading
Loading