Skip to content

审计系统实战

审计系统记录 HTML 处理过程中的安全事件,帮助你监控和排查潜在风险。

快速启用

最简配置,3 行代码启用审计:

go
cfg := html.DefaultConfig()
cfg.Audit = html.DefaultAuditConfig()
cfg.Audit.Enabled = true

p, _ := html.New(cfg)
defer p.Close()

启用后,安全事件会输出到标准错误,格式为 [AUDIT] JSON

审计事件类型

事件常量级别触发条件
阻止标签blocked_tagwarning危险标签被移除(如 <script>
阻止属性blocked_attrwarning危险属性被移除(如 onclick
阻止 URLblocked_urlwarning危险 URL 被拦截
输入违规input_violationcritical输入超出大小限制
深度违规depth_violationwarningDOM 嵌套超过限制
处理超时timeoutwarning单次处理超时
编码问题encoding_issueinfo编码检测失败
路径穿越path_traversalcritical文件路径含 ..,或 AllowedBaseDir 模式下经 OS 句柄解析后路径越界(防 symlink/junction)

审计级别

text
info < warning < critical
  • info:信息性事件(编码问题),无需告警
  • warning:需要关注的异常(超时、深度违规)
  • critical:安全威胁(输入违规、路径穿越)

内置 Sink 类型

LoggerAuditSink(默认)

输出到标准错误,带 [AUDIT] 前缀:

go
// 默认输出到 stderr
sink := html.NewLoggerAuditSink()

// 输出到自定义 Writer
sink := html.NewLoggerAuditSinkWithWriter(os.Stdout)

WriterAuditSink

将 JSON Lines 写入 io.Writer,适合文件持久化:

go
file, _ := os.Create("audit.jsonl")
defer file.Close()

sink := html.NewWriterAuditSink(file)

输出格式(每行一条 JSON):

json
{"timestamp":"2026-04-30T10:00:00Z","event_type":"blocked_tag","level":"warning","message":"Blocked dangerous HTML tag: script","tag":"script"}

ChannelAuditSink

将事件非阻塞地推入带缓冲的 channel,由消费方独立 goroutine 异步处理,适合集成外部系统:

go
sink := html.NewChannelAuditSink(100)

// 消费审计事件
go func() {
    for entry := range sink.Channel() {
        sendToSIEM(entry)
    }
}()

// 检查丢失的事件(channel 满时自动丢弃)
fmt.Printf("丢弃:%d\n", sink.DroppedCount())

MultiSink

扇出到多个 Sink:

go
sink := html.NewMultiSink(
    html.NewWriterAuditSink(file),
    html.NewLoggerAuditSink(),
)

FilteredSink

按条件过滤事件:

go
// 只记录 critical 级别事件
sink := html.NewFilteredSink(
    fileSink,
    func(e html.AuditEntry) bool {
        return e.Level == html.AuditLevelCritical
    },
)

LevelFilteredSink

按最低级别过滤:

go
// 只记录 warning 及以上
sink := html.NewLevelFilteredSink(fileSink, html.AuditLevelWarning)

构建审计管道

场景一:文件持久化

go
func newAuditPipeline() html.AuditSink {
    file, _ := os.OpenFile("audit.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    return html.NewWriterAuditSink(file)
}

cfg := html.DefaultConfig()
cfg.Audit = html.DefaultAuditConfig()
cfg.Audit.Enabled = true
cfg.Audit.Sink = newAuditPipeline()

场景二:分级路由

critical 事件发送到告警系统,其他事件写入文件:

go
func newTieredPipeline() html.AuditSink {
    file, _ := os.Create("audit.jsonl")

    return html.NewMultiSink(
        // 所有事件写入文件
        html.NewWriterAuditSink(file),
        // critical 事件额外发送告警
        html.NewFilteredSink(
            html.NewChannelAuditSink(50),
            func(e html.AuditEntry) bool {
                return e.Level == html.AuditLevelCritical
            },
        ),
    )
}

场景三:高安全模式

使用 HighSecurityConfigHighSecurityAuditConfig

go
cfg := html.HighSecurityConfig()
cfg.Audit = html.HighSecurityAuditConfig()
cfg.Audit.Sink = html.NewMultiSink(
    html.NewWriterAuditSink(file),
    html.NewLoggerAuditSink(),
)

p, _ := html.New(cfg)

高安全模式的审计特点:

  • 自动启用审计
  • 记录所有事件类型
  • 包含原始值(IncludeRawValues = true),便于取证分析
  • 原始值最大长度 500 字符

查询审计日志

通过 Processor 方法查询已收集的事件:

go
p, _ := html.New(cfg)
defer p.Close()

// 处理内容
p.Extract(data)

// 获取审计日志
entries := p.GetAuditLog()
for _, entry := range entries {
    fmt.Printf("[%s] %s: %s\n", entry.Level, entry.EventType, entry.Message)
}

// 清除日志
p.ClearAuditLog()

内存中的日志仅限 Processor 实例

GetAuditLog() 返回的是 Processor 内存中收集的事件。如果要持久化,请配置 Sink。

自定义 Sink

实现 AuditSink 接口,将审计事件发送到任意目标:

go
type slackSink struct {
    webhook string
}

func (s *slackSink) Write(entry html.AuditEntry) {
    if entry.Level != html.AuditLevelCritical {
        return // 只发送 critical
    }
    msg := fmt.Sprintf("[AUDIT] %s: %s", entry.EventType, entry.Message)
    http.Post(s.webhook, "text/plain", strings.NewReader(msg))
}

func (s *slackSink) Close() error {
    return nil
}

下一步