Skip to content
Open
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Community evidence is shape-checked by GitHub Actions, but publication still req
| Version | `v2.3` |
| Audit steps | 14 |
| Risk matrix | 6D |
| pytest collected tests | 778 |
| pytest collected tests | 782 |
| CLI flags | 21 |
| Runtime profiles | `general`, `web3`, `full` |

Expand Down Expand Up @@ -328,7 +328,7 @@ API Relay Audit 也可以作为 agent skill 使用。
| 版本 | `v2.3` |
| 审计步骤 | 14 |
| 风险矩阵 | 6D |
| pytest collected tests | 778 |
| pytest collected tests | 782 |
| CLI flags | 21 |
| Runtime profiles | `general`, `web3`, `full` |

Expand Down
5 changes: 3 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ contributor, arXiv:2026-04-26, 正交威胁轴:模型替换质量欺诈 vs 我
detector, not a hosted preflight service, not a new API family, and not a
change to LOW/MEDIUM/HIGH semantics. `inconclusive` remains
`inconclusive`.
- **Final test count**: 778/778 passing (733 baseline → 764 after current
- **Final test count**: 782/782 passing (733 baseline → 764 after current
master follow-ups → 769 after release-engineering version sync coverage →
775 after query-family growth contracts → 778 after release-hardening
public verification regressions).
public verification regressions → 779 after report reproducibility metadata
→ 782 after metadata source-boundary regressions).

### v1.9 — Dual-distribution full generation (2026-06-01)
- **Standalone product promise preserved**: root `audit.py` stays committed,
Expand Down
18 changes: 15 additions & 3 deletions api_relay_audit/reporter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Markdown report generator for audit results."""

from datetime import datetime
from datetime import datetime, timezone


class Reporter:
Expand Down Expand Up @@ -77,7 +77,8 @@ def flag(self, level, msg):
self.summary.append((level, msg))
self.sections.append(f"{icon} **{msg}**\n")

def render(self, target_url="", model=""):
def render(self, target_url="", model="", tool_version="", profile="",
tool_commit=""):
"""Render the complete Markdown report.

Produces a header block (title, metadata, risk summary) followed
Expand All @@ -88,6 +89,11 @@ def render(self, target_url="", model=""):
metadata when provided.
model: The model identifier used for the audit. Shown in the
report metadata when provided.
tool_version: API Relay Audit version used for the run.
profile: Audit profile used for the run (``general``, ``web3``,
or ``full``).
tool_commit: Optional git commit for checkout-based runs. Omitted
when the standalone script is run outside a repository.

Returns:
A single Markdown string containing the full report.
Expand All @@ -100,12 +106,18 @@ def render(self, target_url="", model=""):
"""
header = (
f"# API Relay Security Audit Report\n\n"
f"**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
f"**Generated**: {datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}\n"
)
if tool_version:
header += f"**Tool Version**: `{tool_version}`\n"
if profile:
header += f"**Profile**: `{profile}`\n"
if target_url:
header += f"**Target**: `{target_url}`\n"
if model:
header += f"**Model**: `{model}`\n"
if tool_commit:
header += f"**Tool Commit**: `{tool_commit}`\n"

header += "\n## Risk Summary\n\n"
for level, msg in self.summary:
Expand Down
95 changes: 89 additions & 6 deletions audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# Regenerate after modular audit changes with:
# python3 scripts/build-standalone.py
# CI verifies this generated artifact plus key behavior regressions.
# source_sha256: 57cc4ddc3ccb3ed54e76e82dfc447c6e21322c2c03ab804625924e7ec655f245
# standalone_body_sha256: 3adf747824d6eeb9df837a1e6ae8fd6fe5aa861c57b6a422b9367e52ee0c630f
# source_sha256: 0948426703dede1934aa265e5558c50f9d993930e2c41b1b444deff9be4b36e2
# standalone_body_sha256: ec1ca8ab319f24698d522d7e5651145d9e25b4a096921d65a5bad72582de8882
# END GENERATED STANDALONE HEADER

"""
Expand Down Expand Up @@ -1624,7 +1624,7 @@ def iter_stdout():

"""Markdown report generator for audit results."""

from datetime import datetime
from datetime import datetime, timezone


class Reporter:
Expand Down Expand Up @@ -1701,7 +1701,8 @@ def flag(self, level, msg):
self.summary.append((level, msg))
self.sections.append(f"{icon} **{msg}**\n")

def render(self, target_url="", model=""):
def render(self, target_url="", model="", tool_version="", profile="",
tool_commit=""):
"""Render the complete Markdown report.

Produces a header block (title, metadata, risk summary) followed
Expand All @@ -1712,6 +1713,11 @@ def render(self, target_url="", model=""):
metadata when provided.
model: The model identifier used for the audit. Shown in the
report metadata when provided.
tool_version: API Relay Audit version used for the run.
profile: Audit profile used for the run (``general``, ``web3``,
or ``full``).
tool_commit: Optional git commit for checkout-based runs. Omitted
when the standalone script is run outside a repository.

Returns:
A single Markdown string containing the full report.
Expand All @@ -1724,12 +1730,18 @@ def render(self, target_url="", model=""):
"""
header = (
f"# API Relay Security Audit Report\n\n"
f"**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
f"**Generated**: {datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}\n"
)
if tool_version:
header += f"**Tool Version**: `{tool_version}`\n"
if profile:
header += f"**Profile**: `{profile}`\n"
if target_url:
header += f"**Target**: `{target_url}`\n"
if model:
header += f"**Model**: `{model}`\n"
if tool_commit:
header += f"**Tool Commit**: `{tool_commit}`\n"

header += "\n## Risk Summary\n\n"
for level, msg in self.summary:
Expand Down Expand Up @@ -4839,6 +4851,26 @@ def run_channel_classifier(client):



TOOL_VERSION_FALLBACK = "2.3.0"


def _api_relay_audit_checkout_root(script_path):
"""Return this project's checkout root, or ``None`` for copied scripts."""
script_path = script_path.resolve()
candidates = []
if script_path.parent.name == "scripts":
candidates.append(script_path.parent.parent)
candidates.append(script_path.parent)

for root in candidates:
if all((
(root / "VERSION").is_file(),
(root / "scripts" / "build-standalone.py").is_file(),
(root / "api_relay_audit" / "reporter.py").is_file(),
)):
return root
return None


def _format_identity_inconsistency(non_claude_matches):
"""Render Step 5's non-Claude self-ID finding without over-attribution."""
Expand Down Expand Up @@ -4866,6 +4898,51 @@ def _report_error(report, error, status=None):
report.p(format_diagnosis(_diagnosis_for_error(error, status=status)))


def _tool_version():
"""Return the packaged tool version for report metadata."""
repo_root = _api_relay_audit_checkout_root(Path(__file__).resolve())
if repo_root is not None:
candidate = repo_root / "VERSION"
try:
value = candidate.read_text(encoding="utf-8").strip()
except OSError:
value = ""
if re.fullmatch(r"\d+\.\d+\.\d+", value):
return value
return TOOL_VERSION_FALLBACK


def _tool_commit_from_checkout():
"""Return a short git commit only when this script is in this repo checkout."""
repo_root = _api_relay_audit_checkout_root(Path(__file__).resolve())
if repo_root is None:
return ""
if not (repo_root / ".git").exists():
return ""
try:
root_result = subprocess.run(
["git", "-C", str(repo_root), "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
timeout=2,
check=True,
)
git_root = Path(root_result.stdout.strip()).resolve()
if git_root != repo_root.resolve():
return ""
result = subprocess.run(
["git", "-C", str(repo_root), "rev-parse", "--short=12", "HEAD"],
capture_output=True,
text=True,
timeout=2,
check=True,
)
except Exception:
return ""
commit = result.stdout.strip()
return commit if re.fullmatch(r"[0-9a-f]{7,12}", commit) else ""


# ============================================================
# CLI
# ============================================================
Expand Down Expand Up @@ -6688,7 +6765,13 @@ def main():
"anomaly, or Web3 injection detected.")

# Output
md = report.render(target_url=client.base_url, model=args.model)
md = report.render(
target_url=client.base_url,
model=args.model,
tool_version=f"v{_tool_version()}",
profile=args.profile,
tool_commit=_tool_commit_from_checkout(),
)

if args.output:
Path(args.output).parent.mkdir(parents=True, exist_ok=True)
Expand Down
12 changes: 6 additions & 6 deletions docs/_metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@
| 单文件版版本 | `v2.3` | `audit.py` docstring |
| 步骤数 (Step N) | **14** | grep `Step N` in `scripts/audit.py` |
| 步骤数 (单文件版) | 14 | grep `Step N` in `audit.py` |
| 测试数 (pytest) | **778** | `pytest --collect-only` |
| 测试数 (static) | 754 | grep `def test_*` in tests/ |
| 测试数 (pytest) | **782** | `pytest --collect-only` |
| 测试数 (static) | 758 | grep `def test_*` in tests/ |
| CLI flag 数 | 21 | grep `add_argument("--*")` |
| profile 选项 | general, web3, full | argparse choices |
| ROADMAP 上次更新 | 2026-06-07 | `ROADMAP.md` 头部 |
| Codex review 提及次数 | 4 | grep `Codex review (cycle\|round)` 在 Shipped 节 |
| Codex review 已编号轮次(最大) | 6 | grep `Nth Codex review round` |
| Codex bug 累计(最新声称) | 18 | grep `cumulative N real bug` |
| 测试数演进 (ROADMAP) | [546, 560, 562, 586, 642, 700, 778] | grep `Final test count: N/N passing` |
| Recorded commit SHA | `eeb9a2c` | recent reachable commit; `--check` allows follow-up metrics commits |
| Recorded commit date | 2026-06-07 | recent reachable commit; `--check` allows follow-up metrics commits |
| 测试数演进 (ROADMAP) | [546, 560, 562, 586, 642, 700, 782] | grep `Final test count: N/N passing` |
| Recorded commit SHA | `68d4141` | recent reachable commit; `--check` allows follow-up metrics commits |
| Recorded commit date | 2026-06-12 | recent reachable commit; `--check` allows follow-up metrics commits |

## 一致性自检

- ✅ 版本一致:两份都是 `v2.3`。
- ✅ 步骤数一致:14。
- ℹ️ pytest (778) vs 静态 (754) 差距 >20,多出来的来自 parametrize/fixture——以 pytest 为准。
- ℹ️ pytest (782) vs 静态 (758) 差距 >20,多出来的来自 parametrize/fixture——以 pytest 为准。

## 人工 review 边界(脚本抓不到,每次发布要人工核对)

Expand Down
73 changes: 72 additions & 1 deletion scripts/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@
from api_relay_audit.tool_substitution import run_tool_substitution_test
from api_relay_audit.web3.injection_probes import run_web3_injection_probes

TOOL_VERSION_FALLBACK = "2.3.0"


def _api_relay_audit_checkout_root(script_path):
"""Return this project's checkout root, or ``None`` for copied scripts."""
script_path = script_path.resolve()
candidates = []
if script_path.parent.name == "scripts":
candidates.append(script_path.parent.parent)
candidates.append(script_path.parent)

for root in candidates:
if all((
(root / "VERSION").is_file(),
(root / "scripts" / "build-standalone.py").is_file(),
(root / "api_relay_audit" / "reporter.py").is_file(),
)):
return root
return None


def _format_identity_inconsistency(non_claude_matches):
"""Render Step 5's non-Claude self-ID finding without over-attribution."""
Expand Down Expand Up @@ -96,6 +116,51 @@ def _report_error(report, error, status=None):
report.p(format_diagnosis(_diagnosis_for_error(error, status=status)))


def _tool_version():
"""Return the packaged tool version for report metadata."""
repo_root = _api_relay_audit_checkout_root(Path(__file__).resolve())
if repo_root is not None:
candidate = repo_root / "VERSION"
try:
value = candidate.read_text(encoding="utf-8").strip()
except OSError:
value = ""
if re.fullmatch(r"\d+\.\d+\.\d+", value):
return value
return TOOL_VERSION_FALLBACK


def _tool_commit_from_checkout():
"""Return a short git commit only when this script is in this repo checkout."""
repo_root = _api_relay_audit_checkout_root(Path(__file__).resolve())
if repo_root is None:
return ""
if not (repo_root / ".git").exists():
return ""
try:
root_result = subprocess.run(
["git", "-C", str(repo_root), "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
timeout=2,
check=True,
)
git_root = Path(root_result.stdout.strip()).resolve()
if git_root != repo_root.resolve():
return ""
result = subprocess.run(
["git", "-C", str(repo_root), "rev-parse", "--short=12", "HEAD"],
capture_output=True,
text=True,
timeout=2,
check=True,
)
except Exception:
return ""
commit = result.stdout.strip()
return commit if re.fullmatch(r"[0-9a-f]{7,12}", commit) else ""


# ============================================================
# CLI
# ============================================================
Expand Down Expand Up @@ -1919,7 +1984,13 @@ def main():
"anomaly, or Web3 injection detected.")

# Output
md = report.render(target_url=client.base_url, model=args.model)
md = report.render(
target_url=client.base_url,
model=args.model,
tool_version=f"v{_tool_version()}",
profile=args.profile,
tool_commit=_tool_commit_from_checkout(),
)

if args.output:
Path(args.output).parent.mkdir(parents=True, exist_ok=True)
Expand Down
8 changes: 7 additions & 1 deletion scripts/sync-version.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,18 @@ def replace_regex(

def sync_audit_script(version: Version, path: Path) -> str:
text = read_text(path)
return replace_regex(
text = replace_regex(
text,
rf"API Relay Security Audit Tool {DISPLAY_VERSION_RE}",
f"API Relay Security Audit Tool {version.display}",
path,
)
return replace_regex(
text,
rf'^TOOL_VERSION_FALLBACK = "{FULL_VERSION_RE}"$',
f'TOOL_VERSION_FALLBACK = "{version.full}"',
path,
)


def sync_build_standalone(version: Version, path: Path) -> str:
Expand Down
Loading
Loading