Skip to content

Commit 48db9db

Browse files
committed
Release v1.3.3 hardening
- add output manifests, redaction, and truncation metadata - harden remote workspace and explain output handling - expand release gate, tests, and release documentation
1 parent 80a09d0 commit 48db9db

40 files changed

Lines changed: 3304 additions & 2302 deletions

.github/workflows/publish.yml

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -35,52 +35,7 @@ jobs:
3535
- name: Run validation
3636
run: |
3737
python -m pip install --upgrade build twine pytest
38-
python -m pytest -q
39-
python -m compileall -q bla bla_cli.py setup.py tests
40-
python bla_cli.py validate-rules --strict-metadata
41-
python bla_cli.py ssh --help
42-
43-
- name: Build package
44-
run: python -m build
45-
46-
- name: Check distribution metadata
47-
run: |
48-
python -m twine check dist/*
49-
python - <<'PY'
50-
from pathlib import Path
51-
import tarfile
52-
import zipfile
53-
from bla.__version__ import __version__
54-
55-
dist = Path("dist")
56-
wheels = sorted(dist.glob("*.whl"))
57-
sdists = sorted(dist.glob("*.tar.gz"))
58-
if len(wheels) != 1 or len(sdists) != 1:
59-
raise SystemExit("Expected exactly one wheel and one source distribution")
60-
61-
with zipfile.ZipFile(wheels[0]) as archive:
62-
names = archive.namelist()
63-
if "bla/rules/web_attacks.yaml" not in names:
64-
raise SystemExit("Wheel is missing bla/rules/web_attacks.yaml")
65-
if "bla/remote/ssh_workspace.py" not in names:
66-
raise SystemExit("Wheel is missing bla/remote/ssh_workspace.py")
67-
68-
with tarfile.open(sdists[0]) as archive:
69-
names = archive.getnames()
70-
if not any(name.endswith("/bla/rules/web_attacks.yaml") for name in names):
71-
raise SystemExit("Source distribution is missing bla/rules/web_attacks.yaml")
72-
if not any(name.endswith("/bla/remote/ssh_workspace.py") for name in names):
73-
raise SystemExit("Source distribution is missing bla/remote/ssh_workspace.py")
74-
required = [
75-
f"/docs/releases/v{__version__}.md",
76-
"/sample_logs/auth.log",
77-
"/sample_logs/windows_rdp_sample.xml",
78-
"/tests/fixtures/p0/hvv_chain.jsonl",
79-
]
80-
for suffix in required:
81-
if not any(name.endswith(suffix) for name in names):
82-
raise SystemExit(f"Source distribution is missing {suffix}")
83-
PY
38+
python scripts/release_check.py --build
8439
8540
- name: Smoke test built wheel
8641
run: |

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ include README.md LICENSE pyproject.toml setup.py
22
recursive-include bla/rules *.yaml
33
recursive-include docs *.md *.png *.json
44
recursive-include sample_logs *.log *.xml
5+
recursive-include scripts *.py
56
recursive-include tests *.py *.json *.jsonl

README.md

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,24 @@ BLA 的结果分成两类:给人看的应急判断,和给系统继续处理
4444
- **提取证据**:输出 IP、域名、URL、Hash、账户、进程、命令和可疑路径,方便封禁、狩猎和工单流转。
4545
- **交给系统**:同时生成 JSON、CSV 和 SARIF,便于二次分析、Excel 排查、CI 门禁和 Code Scanning。
4646

47-
默认 `--out report/` 会落地 `index.html``report.json``events.csv``iocs.txt``report.sarif`,人能看,脚本也能继续处理。
47+
默认 `--out report/` 会落地 `index.html``report.json``events.csv``iocs.txt``report.sarif``manifest.json`,人能看,脚本也能继续处理。
4848

49-
## 最新版本:v1.3.2
49+
## 最新版本:v1.3.3
5050

51-
v1.3.2 是一次可信度与发布卫生更新,重点不是堆新功能,而是把解析、检测、终端输出、远程工作台和 README/发布流程一起收紧
51+
v1.3.3 是一次产品打磨、性能和发布质量更新,重点是让报告更适合集成,让 P0 结构化日志解析更快,并补齐交付前可重复验证能力
5252

53-
| 方向 | v1.3.2 重点 |
53+
| 方向 | v1.3.3 重点 |
5454
| --- | --- |
55-
| 解析可信度 | P0 JSON object / JSONL 都能识别,解析失败会被统计;VPN 未知认证状态不再误判为登录成功 |
56-
| 检测可信度 | 暴力破解、密码喷洒和“爆破后成功登录”按时间窗口判断,避免跨天/月误聚合 |
57-
| RDP 专项 | `--rdp` 只保留 `LogonType=10` 且带远程来源 IP 的 Windows 4624/4625 登录事件 |
58-
| 输出安全 | 终端报告会清理日志中的控制序列,HTML/CSV/JSON/SARIF 继续保持离线输出 |
59-
| 发布卫生 | README、release checklist、打包清单和发布 workflow 同步校验版本、构建产物和安装烟测 |
55+
| 性能优化 | P0 日志优先使用 `log_type/source_type` 快速分流,减少每行大范围正则判断 |
56+
| 输出可读性 | JSON 报告增加顶层 `summary`,把风险、事件、告警、案件和级别分布放到固定入口 |
57+
| 交付追溯 | 标准报告目录新增 `manifest.json`,记录输入哈希、输出哈希、版本、限制和解析异常 |
58+
| 输出安全 | 分享型报告会清理终端控制序列并遮蔽常见 token、cookie、password 等敏感字段 |
59+
| 统计效率 | 解析统计不再为起止时间排序全量时间戳,降低大日志内存和 CPU 开销 |
60+
| 检测热路径 | 缓存 RFC1918 IP 判断,复用预编译模式,减少凭据检测里的重复字符串拼接 |
61+
| 规则维护 | `validate-rules` 增加自定义正则回溯风险提示,降低扩展规则拖慢分析的概率 |
62+
| 发布校验 | 发布脚本覆盖样例分析、报告包产物、版本面、规则校验、Remote Workspace help 和 benchmark |
6063

61-
更多变更见 [v1.3.2 发布说明](https://github.com/Hackerchen716/blueteam-log-analyzer/blob/main/docs/releases/v1.3.2.md),历史版本见 [docs/releases](https://github.com/Hackerchen716/blueteam-log-analyzer/tree/main/docs/releases)
64+
更多变更见 [v1.3.3 发布说明](https://github.com/Hackerchen716/blueteam-log-analyzer/blob/main/docs/releases/v1.3.3.md),历史版本见 [docs/releases](https://github.com/Hackerchen716/blueteam-log-analyzer/tree/main/docs/releases)
6265

6366
## 核心能力
6467

@@ -230,7 +233,8 @@ bla logs/ --out incident_report/
230233
# ├── report.json
231234
# ├── events.csv
232235
# ├── iocs.txt
233-
# └── report.sarif
236+
# ├── report.sarif
237+
# └── manifest.json
234238

235239
# 生成 SARIF 报告(可上传到 GitHub Code Scanning)
236240
bla logs/ --sarif report.sarif
@@ -358,7 +362,7 @@ fi
358362

359363
关键事件时间线、ATT&CK 技术映射、应急处置建议、Top 攻击源 IP:
360364

361-
![BLA 终端报告示例](https://raw.githubusercontent.com/Hackerchen716/blueteam-log-analyzer/main/docs/screenshots/demo.png)
365+
![BLA 终端报告示例](https://raw.githubusercontent.com/Hackerchen716/blueteam-log-analyzer/main/docs/screenshots/terminal-report.png)
362366

363367
---
364368

@@ -371,7 +375,7 @@ fi
371375
| SecRepo auth.log | 86,839 | 27,075 | 624 | 100/100(严重) | SSH 暴力破解、密码喷洒、Top IP/Top User、IOC 提取 |
372376
| SecRepo Web access.log | 2,928 | 236 | 2 | 100/100(严重) | 敏感路径探测、Web 访问日志解析、`cn-hvv` 画像、IOC 提取 |
373377

374-
完整复现命令、数据来源和结果摘要见 [SecRepo 真实样本实测记录](https://github.com/Hackerchen716/blueteam-log-analyzer/blob/main/docs/secrepo-demo.md)
378+
完整复现命令、数据来源和结果摘要见 [SecRepo 公开样本验证记录](https://github.com/Hackerchen716/blueteam-log-analyzer/blob/main/docs/secrepo-sample-validation.md)
375379

376380
### SecRepo auth.log 实测总览
377381

@@ -394,7 +398,7 @@ fi
394398
```
395399
╔══════════════════════════════════════════════════════════════════════════════╗
396400
║ BlueTeam Log Analyzer (BLA) - Blue Team Incident Response ║
397-
║ Version 1.3.2 | 100% Offline | No AI ║
401+
║ Version 1.3.3 | 100% Offline | No AI ║
398402
╚══════════════════════════════════════════════════════════════════════════════╝
399403
400404
📊 分析总览
@@ -458,12 +462,12 @@ python3 -m unittest discover -s tests -v
458462
- Remote Workspace 的 `bla FILE``--rdp``journalctl:``--exit-on` 行为
459463
- 大型日志可通过 `--max-alerts` 控制终端告警展示数量
460464
- 可通过 `--syslog-year` 固定 Linux syslog 无年份时间戳
461-
- `--out` 标准报告目录可一次生成 HTML/JSON/CSV/IOC/SARIF
465+
- `--out` 标准报告目录可一次生成 HTML/JSON/CSV/IOC/SARIF/manifest
462466
- 内置 YAML Web 规则与 `--rules` 自定义规则加载
463467
- 自动识别 Linux/Web 日志时可走逐行解析路径,避免大文件一次性读入内存
464468

465469
更多可用于评估 BLA 的公开日志与靶场资源见 [测试资源推荐清单](https://github.com/Hackerchen716/blueteam-log-analyzer/blob/main/docs/testing-resources.md)
466-
SecRepo 真实样本的完整复现实测见 [SecRepo 真实样本实测记录](https://github.com/Hackerchen716/blueteam-log-analyzer/blob/main/docs/secrepo-demo.md)
470+
SecRepo 公开样本的完整复现实测见 [SecRepo 公开样本验证记录](https://github.com/Hackerchen716/blueteam-log-analyzer/blob/main/docs/secrepo-sample-validation.md)
467471

468472
---
469473

@@ -523,39 +527,48 @@ blueteam-log-analyzer/
523527
│ │ ├── registry.py # DetectorRegistry / DetectorSpec
524528
│ │ ├── enrichment.py # 统一字段富化
525529
│ │ └── correlation.py # Incident 级跨源关联
526-
│ ├── output/
530+
│ ├── output/
527531
│ │ ├── terminal.py # 终端彩色输出(ANSI,支持 Windows 10+)
528532
│ │ ├── html_report.py # HTML 报告生成(独立单文件)
529533
│ │ ├── json_report.py # JSON 报告输出
530534
│ │ ├── csv_report.py # CSV 事件导出
531535
│ │ ├── ioc_report.py # IOC 清单导出
532-
│ │ ├── sarif_report.py # SARIF 2.1.0 输出(接入 GitHub Code Scanning 等)
533-
│ │ └── bundle.py # --out 标准报告目录生成
534-
│ ├── remote/
535-
│ │ └── ssh_workspace.py# SSH 远程日志工作台
536-
│ └── utils/
537-
│ └── helpers.py # 工具函数
536+
│ │ ├── sarif_report.py # SARIF 2.1.0 输出(接入 GitHub Code Scanning 等)
537+
│ │ ├── manifest.py # 报告包交付清单与哈希记录
538+
│ │ └── bundle.py # --out 标准报告目录生成
539+
│ ├── remote/
540+
│ │ └── ssh_workspace.py# SSH 远程日志工作台
541+
│ └── utils/
542+
│ └── helpers.py # 工具函数
538543
├── docs/
539544
│ ├── assets/ # README 与发布素材
540545
│ ├── architecture.md # 可扩展内核设计说明
541546
│ ├── releases/ # 版本发布说明
542547
│ ├── screenshots/ # 界面截图
543548
│ ├── allowlist-example.json # 白名单示例
544-
│ ├── secrepo-demo.md # SecRepo 真实样本实测记录
549+
│ ├── secrepo-sample-validation.md # SecRepo 公开样本验证记录
545550
│ └── testing-resources.md# 测试资源推荐清单
546-
├── sample_logs/
547-
│ ├── auth.log # Linux SSH 暴力破解示例日志
548-
│ ├── access.log # Web 攻击示例日志(SQLi/XSS/LFI/扫描)
549-
│ ├── remote_ssh_auth.log # Remote Workspace 烟测示例
550-
│ ├── windows_rdp_sample.xml
551-
│ └── windows_4688_sample.xml
552-
├── tests/
553-
│ ├── fixtures/p0/ # P0 结构化样本和 golden incident
554-
│ └── test_regressions.py # 安全与解析回归测试
555-
├── pyproject.toml # PEP 517/621 构建配置
556-
├── setup.py # Python 包安装配置
557-
├── MANIFEST.in # sdist 文档/样本/测试清单
558-
└── README.md
551+
├── sample_logs/
552+
│ ├── auth.log # Linux SSH 暴力破解示例日志
553+
│ ├── access.log # Web 攻击示例日志(SQLi/XSS/LFI/扫描)
554+
│ ├── remote_ssh_auth.log # Remote Workspace 烟测示例
555+
│ ├── windows_rdp_sample.xml
556+
│ └── windows_4688_sample.xml
557+
├── tests/
558+
│ ├── fixtures/p0/ # P0 结构化样本和 golden incident
559+
│ ├── _support.py # 回归测试公共支撑
560+
│ ├── test_detection.py
561+
│ ├── test_outputs.py
562+
│ ├── test_p0_security.py
563+
│ ├── test_parsers.py
564+
│ ├── test_remote_workspace.py
565+
│ └── test_release_hygiene.py
566+
├── scripts/
567+
│ └── release_check.py # 本地发布质量检查
568+
├── pyproject.toml # PEP 517/621 构建配置
569+
├── setup.py # Python 包安装配置
570+
├── MANIFEST.in # sdist 文档/样本/测试清单
571+
└── README.md
559572
```
560573

561574
---

bla/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
平台: macOS / Linux / Windows
77
"""
88

9-
from .__version__ import __version__
9+
from .__version__ import __version__ as __version__
1010

1111
__author__ = "Hackerchen716"

bla/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Single source of truth for the BLA package version."""
22

3-
__version__ = "1.3.2"
3+
__version__ = "1.3.3"

bla/core/pipeline.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
from concurrent.futures import ThreadPoolExecutor, as_completed
77
from dataclasses import dataclass, field
8-
from typing import Callable, Iterable, List, Optional
8+
from typing import Any, Callable, Dict, Iterable, List, Optional
99

1010
from ..allowlist import apply_allowlist, load_allowlist
1111
from ..config import DEFAULT_THRESHOLDS, load_thresholds, load_thresholds_from_env, set_thresholds
@@ -190,6 +190,7 @@ def write_reports(
190190
parse_results: List[ParseResult],
191191
summary: AnalysisSummary,
192192
outputs: AnalysisOutputs,
193+
manifest_context: Optional[Dict[str, Any]] = None,
193194
) -> None:
194195
if outputs.html:
195196
generate_html_report(parse_results, summary, outputs.html)
@@ -202,7 +203,7 @@ def write_reports(
202203
if outputs.sarif:
203204
generate_sarif_report(parse_results, summary, outputs.sarif)
204205
if outputs.bundle_dir:
205-
generate_report_bundle(parse_results, summary, outputs.bundle_dir)
206+
generate_report_bundle(parse_results, summary, outputs.bundle_dir, manifest_context=manifest_context)
206207

207208

208209
def _configure_runtime(options: AnalysisOptions) -> None:

bla/detection/engine.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121

2222
_CONFIDENCE_DOWNGRADE = {"high": "medium", "medium": "low", "low": "low"}
2323
_WINDOWS_ACCOUNT_CHAIN_WINDOW_SECONDS = 10 * 60
24+
_CREDENTIAL_TOOL_RE = re.compile(r'mimikatz|lsadump|sekurlsa|kerberos::ptt|privilege::debug|credential.?dump', re.I)
25+
_LSASS_RE = re.compile(r'lsass', re.I)
26+
_CREDENTIAL_DETAIL_KEYS = (
27+
"command", "cmd", "commandline", "process", "processname", "image",
28+
"alert", "threat", "message", "rule", "signature", "file", "path",
29+
)
2430

2531

2632
def _adjust_for_private_ip(ip: str, confidence: str, evidence: List[str]) -> str:
@@ -31,7 +37,7 @@ def _adjust_for_private_ip(ip: str, confidence: str, evidence: List[str]) -> str
3137
"""
3238
if not ip or not is_private_ip(ip):
3339
if ip:
34-
evidence.append(f"来源类型: 公网")
40+
evidence.append("来源类型: 公网")
3541
return confidence
3642
evidence.append(f"来源类型: 内网/私有 IP({ip})")
3743
return _CONFIDENCE_DOWNGRADE.get(confidence, confidence)
@@ -396,9 +402,7 @@ def detect_defense_evasion(events: List[LogEvent]) -> List[DetectionAlert]:
396402

397403
def detect_credential_access(events: List[LogEvent]) -> List[DetectionAlert]:
398404
alerts = []
399-
mimi = [e for e in events if
400-
re.search(r'mimikatz|lsadump|sekurlsa|kerberos::ptt|privilege::debug|credential.?dump',
401-
e.message + e.raw_line + " ".join(str(v) for v in e.details.values()), re.I)]
405+
mimi = [e for e in events if _has_credential_dump_indicator(e)]
402406
if mimi:
403407
alerts.append(DetectionAlert(
404408
id="a"+gen_id("ca"), rule_id="CRED-001", rule_name="Mimikatz / 凭据转储工具",
@@ -410,7 +414,7 @@ def detect_credential_access(events: List[LogEvent]) -> List[DetectionAlert]:
410414
timestamp=max(e.timestamp for e in mimi), confidence="high",
411415
))
412416
lsass = [e for e in events if "lsass-dump" in e.tags or
413-
(re.search(r'lsass', e.message + e.raw_line, re.I) and "sysmon" in e.tags)]
417+
(_LSASS_RE.search(e.message + e.raw_line) and "sysmon" in e.tags)]
414418
if lsass:
415419
alerts.append(DetectionAlert(
416420
id="a"+gen_id("ls"), rule_id="CRED-002", rule_name="LSASS 进程访问",
@@ -424,6 +428,17 @@ def detect_credential_access(events: List[LogEvent]) -> List[DetectionAlert]:
424428
return alerts
425429

426430

431+
def _has_credential_dump_indicator(event: LogEvent) -> bool:
432+
if "lsass-dump" in event.tags or "credential-access" in event.tags:
433+
return True
434+
text = event.message + " " + event.raw_line
435+
if _CREDENTIAL_TOOL_RE.search(text):
436+
return True
437+
details = event.details
438+
detail_text = " ".join(str(details.get(key, "")) for key in _CREDENTIAL_DETAIL_KEYS)
439+
return bool(detail_text and _CREDENTIAL_TOOL_RE.search(detail_text))
440+
441+
427442
def detect_suspicious_execution(events: List[LogEvent]) -> List[DetectionAlert]:
428443
alerts = []
429444
critical_ps = [e for e in events if e.category == "PowerShell" and e.level == ThreatLevel.CRITICAL]

0 commit comments

Comments
 (0)