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
10 changes: 9 additions & 1 deletion .github/workflows/main-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ jobs:
ci/check_loldrivers_hash_only.py \
ci/check_mock_services_loopback.py \
ci/check_no_real_rmm_license.py \
ci/check_no_suspicious_pth.py; do
ci/check_no_suspicious_pth.py \
ci/check_snowflake_report_integrity.py; do
name=$(basename "$check" .py)
if python3 "$check"; then
echo "- ✅ \`${name}\`" >> $GITHUB_STEP_SUMMARY
Expand Down Expand Up @@ -276,6 +277,13 @@ jobs:
mkdir -p _site/dashboard/assets
cp reports/databricks-apps-assessment/assets/*.svg _site/dashboard/assets/ 2>/dev/null || true

# Copy Snowflake assessment (static HTML/CSS — no build step needed).
# Explicit allowlist so future *.md / notes / TODOs stay out of _site.
mkdir -p _site/snowflake/assets
cp reports/snowflake-platform-assessment/*.html _site/snowflake/
cp reports/snowflake-platform-assessment/assets/*.css _site/snowflake/assets/
test -f _site/snowflake/index.html || { echo "assemble: snowflake/index.html missing"; exit 1; }

# Copy CVE README as an index
cp cves/README.md _site/cve-index.md 2>/dev/null || true

Expand Down
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ The report at `reports/databricks-apps-assessment/` is a concatenated Streamlit
- Build: `python build.py` — Check: `python build.py --check`
- All imports belong in `_00_header.py` only; no cross-imports between `src/` files

## Snowflake Report

The report at `reports/snowflake-platform-assessment/` is a set of linked static HTML pages (no build step).

- Edit pages directly: `index.html`, `threat-landscape.html`, `cve-inventory.html`, `attack-chains.html`, `detection.html`, `recommendations.html`
- Shared styles: `assets/style.css`
- Serve locally: `python -m http.server 8080` from the report directory, then open `http://localhost:8080/`
- The CI pipeline copies the directory as-is to `_site/snowflake/`
- Working notes / findings: `docs/analysis/snowflake-platform-attack-surface-2026.md`

---

## Index
Expand Down Expand Up @@ -166,6 +176,7 @@ The report at `reports/databricks-apps-assessment/` is a concatenated Streamlit
→ [docs/analysis/firmware-landscape-2026/README.md](docs/analysis/firmware-landscape-2026/README.md) — Hydroph0bia, LogoFAIL successors, UEFI cert expiry
→ [docs/analysis/apple-mie-impact.md](docs/analysis/apple-mie-impact.md) — Apple Memory Integrity Enforcement
→ [docs/analysis/vishing-2026-market.md](docs/analysis/vishing-2026-market.md) — deepfake vishing economics + healthcare targeting
→ [docs/analysis/snowflake-platform-attack-surface-2026.md](docs/analysis/snowflake-platform-attack-surface-2026.md) — CVE inventory, UNC5537 analysis, Cortex AI/Native Apps/SPCS attack surface, chains A–E, detection gaps

### Research Docs — Methodology
→ [docs/methodology/callstack-spoofing.md](docs/methodology/callstack-spoofing.md)
Expand Down Expand Up @@ -202,3 +213,4 @@ The report at `reports/databricks-apps-assessment/` is a concatenated Streamlit

### Reports
→ [reports/databricks-apps-assessment/](reports/databricks-apps-assessment/) — Streamlit report (see build notes above)
→ [reports/snowflake-platform-assessment/](reports/snowflake-platform-assessment/) — Static HTML report; findings at [docs/analysis/snowflake-platform-attack-surface-2026.md](docs/analysis/snowflake-platform-attack-surface-2026.md)
76 changes: 76 additions & 0 deletions ci/check_snowflake_report_integrity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""CI gate: Snowflake report HTML pages must share an identical nav block
and every internal href must resolve to a sibling file."""
import re
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
REPORT_DIR = ROOT / "reports" / "snowflake-platform-assessment"

NAV_RE = re.compile(r"<aside class=\"nav\">.*?</aside>", re.DOTALL)
HREF_RE = re.compile(r'href="([^"#?]+)(?:[#?][^"]*)?"')


def collect_pages():
return sorted(REPORT_DIR.glob("*.html"))


def check_nav_parity(pages):
navs = {}
for p in pages:
m = NAV_RE.search(p.read_text())
if not m:
return [f"{p.relative_to(ROOT)}: no <aside class=\"nav\"> block"]
navs[p.name] = m.group(0)
baseline = navs[pages[0].name]
errors = []
for name, nav in navs.items():
if nav != baseline:
errors.append(
f"{REPORT_DIR.relative_to(ROOT)}/{name}: nav block differs from {pages[0].name}"
)
return errors


def check_internal_links(pages):
errors = []
page_names = {p.name for p in pages}
for p in pages:
for href in HREF_RE.findall(p.read_text()):
if href.startswith(("http://", "https://", "mailto:", "/")):
continue
target = (p.parent / href).resolve()
try:
target.relative_to(REPORT_DIR.resolve())
except ValueError:
continue
if target.suffix == ".html":
if target.name not in page_names:
errors.append(
f"{p.relative_to(ROOT)}: dead internal href {href!r}"
)
elif not target.exists():
errors.append(f"{p.relative_to(ROOT)}: missing asset {href!r}")
return errors


def main():
if not REPORT_DIR.exists():
print("OK: snowflake report directory absent (nothing to check)")
return
pages = collect_pages()
if not pages:
print("FAIL: snowflake report directory has no HTML pages")
sys.exit(1)
errors = check_nav_parity(pages) + check_internal_links(pages)
if errors:
print("FAIL: snowflake report integrity:")
for e in errors:
print(f" {e}")
sys.exit(1)
print(f"OK: snowflake report ({len(pages)} pages) nav parity + internal links")


if __name__ == "__main__":
main()
Loading
Loading