mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-26 06:43:15 +08:00
Completes the collapse to a single shipped composition and replaces the
zero-tolerance VirusTotal gate with a narrow, disclosed policy.
Packaging and installers
- package-release.sh loses --variant; archives are codebase-memory-mcp-<os>-<arch>
with exactly four members. install.sh/install.ps1 lose --ui/--standard.
- The extractor drops CBMUIPK pack parsing and --archive-scope; its scan-set and
association manifests (which the gate depends on) are unchanged otherwise.
- npm/PyPI/Go wrappers: the runtime "set" is one file again. The Windows lock
and race fixes from #1495/#1496 are kept; only multi-file set membership goes.
This also fixes `pip install` on Windows, which rejected the fifth archive
member against a hardcoded four-name allowlist.
- The wrappers' post-download probe moves from --verify-runtime-assets (removed)
to --version, which proves the same thing: the binary executes.
VirusTotal gate
- Exactly ONE detection is tolerated, and only when the engine is Microsoft AND
the label ends in `!ml`. Two or more engines, any non-`!ml` label, any other
vendor, any suspicious verdict and every infrastructure error still block.
- A tolerated object prints TOLERATED:, never OK:, and its counts are recorded
in vt-results.tsv exactly as a blocked one would be.
- append-vt-notes.sh mirrors the policy. It previously hard-failed on any
malicious count, so loosening only the gate would have passed the scan and
then died at note publication. The notes now DISCLOSE a tolerated detection
and link to SECURITY.md rather than claiming "0 malicious" for everything.
Rationale for the tolerance is in the gate itself: the verdict is not a property
of our bytes. It inverts across architectures and link modes, moves between
sibling artifacts of one build, and lands in different variant buckets for the
same source. The same `!ml` family hits llama.cpp, GitHub's own `gh`, Microsoft's
own Go toolchain and Anthropic's Claude installer.
The zero-tolerance contract becomes test_vt_gate_policy_contract.sh, asserting
the full matrix: 1x Microsoft !ml passes and reports TOLERATED; a Microsoft
signature label, a non-Microsoft engine, two engines, a suspicious verdict and
every malformed-response case still block. Its tripwire is narrowed to the
reverted endpoint-verification mechanism rather than the words "false positive",
so it no longer fires on a deliberate in-gate policy branch.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
286 lines
11 KiB
Bash
Executable File
286 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Idempotently publish the exact VirusTotal scope and measured result evidence
|
||
# after check-virustotal.sh has passed.
|
||
set -euo pipefail
|
||
|
||
: "${GH_TOKEN:?append-vt-notes: GH_TOKEN is required}"
|
||
: "${VERSION:?append-vt-notes: VERSION is required}"
|
||
: "${GITHUB_REPOSITORY:?append-vt-notes: GITHUB_REPOSITORY is required}"
|
||
|
||
VT_ASSOCIATIONS="${VT_ASSOCIATIONS:-binaries/associations.tsv}"
|
||
VT_RESULTS_PATH="${VT_RESULTS_PATH:-binaries/vt-results.tsv}"
|
||
WORK="$(mktemp -d "${TMPDIR:-/tmp}/cbm-vt-notes.XXXXXX")"
|
||
trap 'rm -rf "$WORK"' EXIT
|
||
|
||
gh release view "$VERSION" \
|
||
--json body --jq '.body // ""' --repo "$GITHUB_REPOSITORY" > "$WORK/current.md"
|
||
|
||
python3 - "$VT_ASSOCIATIONS" "$VT_RESULTS_PATH" "$WORK/current.md" "$WORK/updated.md" \
|
||
"$GITHUB_REPOSITORY" "$VERSION" <<'PY'
|
||
from __future__ import annotations
|
||
|
||
import csv
|
||
import pathlib
|
||
import re
|
||
import sys
|
||
import urllib.parse
|
||
from typing import Dict, List, Sequence, Tuple
|
||
|
||
|
||
START = "<!-- cbm-security-verification:start -->"
|
||
END = "<!-- cbm-security-verification:end -->"
|
||
ASSOCIATION_FIELDS = (
|
||
"association_type",
|
||
"archive",
|
||
"archive_sha256",
|
||
"variant",
|
||
"kind",
|
||
"member",
|
||
"asset_path",
|
||
"mime",
|
||
"scan_path",
|
||
"object_sha256",
|
||
"size",
|
||
)
|
||
RESULT_FIELDS = (
|
||
"scan_path",
|
||
"sha256",
|
||
"size",
|
||
"association_count",
|
||
"completed_engines",
|
||
"total_engines",
|
||
"malicious",
|
||
"suspicious",
|
||
"analysis_id",
|
||
"microsoft_category",
|
||
"microsoft_engine_version",
|
||
"microsoft_engine_update",
|
||
"virustotal_url",
|
||
)
|
||
|
||
|
||
def fail(message: str) -> None:
|
||
raise SystemExit(f"append-vt-notes: {message}")
|
||
|
||
|
||
def read_tsv(
|
||
path: pathlib.Path,
|
||
marker: str,
|
||
fields: Sequence[str],
|
||
) -> Tuple[Dict[str, int], List[Dict[str, str]]]:
|
||
if path.is_symlink() or not path.is_file() or path.stat().st_size > 16 * 1024 * 1024:
|
||
fail(f"missing, unsafe or oversized evidence file: {path}")
|
||
lines = path.read_text(encoding="utf-8").splitlines()
|
||
if not lines or lines[0] != f"# {marker}":
|
||
fail(f"wrong evidence marker in {path}")
|
||
metadata: Dict[str, int] = {}
|
||
cursor = 1
|
||
while cursor < len(lines) and lines[cursor].startswith("# "):
|
||
key, separator, raw_value = lines[cursor][2:].partition("=")
|
||
if not separator or not key or key in metadata or not raw_value.isdigit():
|
||
fail(f"malformed evidence metadata in {path}")
|
||
metadata[key] = int(raw_value)
|
||
cursor += 1
|
||
reader = csv.DictReader(lines[cursor:], delimiter="\t")
|
||
if tuple(reader.fieldnames or ()) != tuple(fields):
|
||
fail(f"unexpected TSV header in {path}")
|
||
rows = list(reader)
|
||
if any(None in row or any(row.get(field) is None for field in fields) for row in rows):
|
||
fail(f"TSV row has missing or surplus cells in {path}")
|
||
if not rows:
|
||
fail(f"empty evidence rows in {path}")
|
||
return metadata, rows
|
||
|
||
|
||
association_path = pathlib.Path(sys.argv[1])
|
||
results_path = pathlib.Path(sys.argv[2])
|
||
current_path = pathlib.Path(sys.argv[3])
|
||
updated_path = pathlib.Path(sys.argv[4])
|
||
repository = sys.argv[5]
|
||
version = sys.argv[6]
|
||
if re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repository) is None or not version:
|
||
fail("unsafe repository or version for public evidence links")
|
||
asset_base = (
|
||
f"https://github.com/{repository}/releases/download/"
|
||
f"{urllib.parse.quote(version, safe='')}"
|
||
)
|
||
association_meta, associations = read_tsv(
|
||
association_path,
|
||
"cbm-release-scan-associations-v3",
|
||
ASSOCIATION_FIELDS,
|
||
)
|
||
result_meta, results = read_tsv(
|
||
results_path,
|
||
"cbm-virustotal-results-v1",
|
||
RESULT_FIELDS,
|
||
)
|
||
if association_meta.get("associations") != len(associations):
|
||
fail("association manifest count does not match its rows")
|
||
if result_meta.get("scan_objects") != len(results):
|
||
fail("VirusTotal result count does not match its rows")
|
||
if result_meta.get("associations") != len(associations):
|
||
fail("VirusTotal results do not cover the association manifest")
|
||
|
||
results_by_path: Dict[str, Dict[str, str]] = {}
|
||
association_counts: Dict[str, int] = {}
|
||
executable_paths = {
|
||
association["scan_path"]
|
||
for association in associations
|
||
if association["kind"] == "binary"
|
||
}
|
||
for association in associations:
|
||
scan_path = association["scan_path"]
|
||
association_counts[scan_path] = association_counts.get(scan_path, 0) + 1
|
||
tolerated_paths: List[str] = []
|
||
for result in results:
|
||
scan_path = result["scan_path"]
|
||
if scan_path in results_by_path:
|
||
fail(f"duplicate VirusTotal result path: {scan_path}")
|
||
for numeric in (
|
||
"size",
|
||
"association_count",
|
||
"completed_engines",
|
||
"total_engines",
|
||
"malicious",
|
||
"suspicious",
|
||
):
|
||
if not result[numeric].isdigit():
|
||
fail(f"malformed numeric result field {numeric}: {scan_path}")
|
||
# Mirrors the gate's release policy: a single Microsoft machine-learning
|
||
# verdict is tolerated and disclosed in the notes; anything else must never
|
||
# have reached publication.
|
||
tolerated = (
|
||
int(result["malicious"]) == 1
|
||
and int(result["suspicious"]) == 0
|
||
and result["microsoft_category"] == "malicious"
|
||
)
|
||
if (int(result["malicious"]) != 0 or int(result["suspicious"]) != 0) and not tolerated:
|
||
fail(f"detected object reached release-note publication: {scan_path}")
|
||
if tolerated:
|
||
tolerated_paths.append(scan_path)
|
||
if (
|
||
re.fullmatch(r"[0-9a-f]{64}", result["sha256"]) is None
|
||
or result["virustotal_url"]
|
||
!= f"https://www.virustotal.com/gui/file/{result['sha256']}/detection"
|
||
):
|
||
fail(f"result SHA/link is malformed or mismatched: {scan_path}")
|
||
if int(result["association_count"]) != association_counts.get(scan_path):
|
||
fail(f"result association count mismatch: {scan_path}")
|
||
if scan_path in executable_paths:
|
||
if result["microsoft_category"] not in {"undetected", "harmless", "malicious"}:
|
||
fail(f"executable result lacks a decisive Microsoft verdict: {scan_path}")
|
||
if not result["microsoft_engine_version"] or not result["microsoft_engine_update"]:
|
||
fail(f"executable result lacks Microsoft version evidence: {scan_path}")
|
||
results_by_path[scan_path] = result
|
||
if set(results_by_path) != set(association_counts):
|
||
fail("VirusTotal result paths differ from association paths")
|
||
for association in associations:
|
||
result = results_by_path[association["scan_path"]]
|
||
if (
|
||
result["sha256"] != association["object_sha256"]
|
||
or result["size"] != association["size"]
|
||
):
|
||
fail(f"result hash/size differs from association: {association['scan_path']}")
|
||
|
||
member_rows = [row for row in associations if row["association_type"] == "member"]
|
||
if len(member_rows) != len(associations):
|
||
fail("association manifest contains a non-extracted scan target")
|
||
archive_hashes: Dict[str, str] = {}
|
||
for association in associations:
|
||
archive = association["archive"]
|
||
archive_sha256 = association["archive_sha256"]
|
||
if not archive or re.fullmatch(r"[0-9a-f]{64}", archive_sha256) is None:
|
||
fail("member association lacks valid archive SHA provenance")
|
||
previous_sha256 = archive_hashes.get(archive)
|
||
if previous_sha256 is not None and previous_sha256 != archive_sha256:
|
||
fail(f"conflicting archive SHA provenance: {archive}")
|
||
archive_hashes[archive] = archive_sha256
|
||
if len(archive_hashes) != association_meta.get("archives"):
|
||
fail("distinct archive provenance count is inconsistent")
|
||
|
||
engine_counts = [int(row["completed_engines"]) for row in results]
|
||
minimum = min(engine_counts)
|
||
maximum = max(engine_counts)
|
||
policy = result_meta.get("min_engines_policy")
|
||
if policy is None or minimum < policy:
|
||
fail("result engine counts do not satisfy their recorded policy")
|
||
engine_range = str(minimum) if minimum == maximum else f"{minimum}–{maximum}"
|
||
|
||
section = [
|
||
START,
|
||
"## Security Verification",
|
||
"",
|
||
(
|
||
f"VirusTotal completed **{len(results)} distinct extracted byte objects** covering "
|
||
f"**{len(associations)} exact extracted archive members**."
|
||
),
|
||
(
|
||
f"The extraction manifest binds those associations to "
|
||
f"**{len(archive_hashes)} downloadable "
|
||
f"{'archive' if len(archive_hashes) == 1 else 'archives'}** by SHA-256 provenance. "
|
||
"Downloadable .tar.gz/.zip release containers were not submitted to VirusTotal."
|
||
),
|
||
"",
|
||
(
|
||
f"Every scanned object returned **0 malicious and 0 suspicious** verdicts with "
|
||
f"{engine_range} decisive engine results (required minimum: {policy})."
|
||
if not tolerated_paths
|
||
else (
|
||
f"{len(results) - len(tolerated_paths)} of {len(results)} scanned objects returned "
|
||
f"**0 malicious and 0 suspicious** verdicts with {engine_range} decisive engine "
|
||
f"results (required minimum: {policy})."
|
||
)
|
||
),
|
||
(
|
||
f"Microsoft returned a decisive clean verdict for all "
|
||
f"{len(executable_paths)} executable objects."
|
||
if not tolerated_paths
|
||
else (
|
||
f"**{len(tolerated_paths)} object(s) carry a single Microsoft machine-learning "
|
||
f"detection** (`!ml`), which this project treats as a known false positive and "
|
||
f"publishes rather than hides. Every other engine returned clean. See "
|
||
f"[Antivirus False Positives](https://github.com/{repository}"
|
||
f"/blob/main/SECURITY.md#antivirus-false-positives) for the evidence and for how to "
|
||
f"verify these artifacts yourself: "
|
||
+ ", ".join(f"`{path}`" for path in sorted(tolerated_paths))
|
||
)
|
||
),
|
||
"",
|
||
(
|
||
"Durable public evidence: "
|
||
f"[associations]({asset_base}/virustotal-associations.tsv), "
|
||
f"[exact scan set]({asset_base}/virustotal-scan-set.tsv), "
|
||
f"[per-extracted-object results and report links]({asset_base}/virustotal-results.tsv), "
|
||
f"[evidence checksums]({asset_base}/virustotal-evidence-checksums.txt)."
|
||
),
|
||
"",
|
||
"Archive SHA-256 provenance (from the extraction manifest):",
|
||
"",
|
||
"| Downloadable archive | SHA-256 provenance |",
|
||
"|---|---|",
|
||
]
|
||
for archive, archive_sha256 in sorted(archive_hashes.items()):
|
||
section.append(f"| `{archive}` | `{archive_sha256}` |")
|
||
section.append(END)
|
||
replacement = "\n".join(section)
|
||
|
||
current = current_path.read_text(encoding="utf-8")
|
||
start_count = current.count(START)
|
||
end_count = current.count(END)
|
||
if start_count != end_count or start_count > 1:
|
||
fail("existing release notes contain malformed verification markers")
|
||
if start_count == 1:
|
||
if current.index(START) >= current.index(END):
|
||
fail("existing release-note verification markers are reversed")
|
||
pattern = re.compile(re.escape(START) + r".*?" + re.escape(END), re.DOTALL)
|
||
updated, replacements = pattern.subn(replacement, current, count=1)
|
||
if replacements != 1:
|
||
fail("existing release-note verification section could not be replaced")
|
||
else:
|
||
updated = current.rstrip() + ("\n\n" if current.strip() else "") + replacement + "\n"
|
||
updated_path.write_text(updated, encoding="utf-8")
|
||
PY
|
||
|
||
gh release edit "$VERSION" \
|
||
--notes-file "$WORK/updated.md" --repo "$GITHUB_REPOSITORY"
|