AgentNewTwo's picture
Release ModelSentry v1.0
8b350c9
Raw
History Blame Contribute Delete
17.2 kB
from __future__ import annotations
import html
import json
import tempfile
import gradio as gr
from scanner import ScanError, ScanResult, result_json, scan_repository
def _summary_markdown(result: ScanResult) -> str:
counts = result.counts()
badges = " · ".join(
f"**{name.title()}: {counts.get(name, 0)}**"
for name in ("critical", "high", "medium", "low", "info")
)
artifacts = result.artifacts
priority = "No unresolved non-informational finding was detected."
for finding in result.finding_groups():
if finding["status"] != "unresolved" or finding["severity"] == "info":
continue
priority = f"**{finding['severity'].upper()} · {finding['rule_id']}** — {html.escape(finding['title'])}"
break
return f"""### Scan complete: `{html.escape(result.target)}`
{badges}
- **Type / revision:** {result.repo_type.title()} · `{result.revision[:12]}`
- **Coverage / OSV:** `{html.escape(str(artifacts.get('static_coverage', 'unknown')).upper())}` {artifacts.get('inspected_text_files', len(result.inspected_files))}/{artifacts.get('eligible_text_files', len(result.inspected_files))} · `{html.escape(str(result.osv_summary.get('status', 'not checked')).upper())}` {result.osv_summary.get('queried_packages', 0)}/{result.osv_summary.get('exact_packages', 0)}
- **Top priority:** {priority}
"""
def _safe(value: object, fallback: str = "not stated") -> str:
text = str(value) if value not in (None, "") else fallback
return html.escape(text)
def _overview_markdown(result: ScanResult) -> str:
metadata = result.metadata_summary
artifacts = result.artifacts
dependencies = ", ".join(f"`{_safe(item)}`" for item in result.dependencies) or "None declared in Hub metadata"
notes = "\n".join(f"- {_safe(note)}" for note in result.notes) or "- No additional note was produced."
exact = sum(1 for item in result.package_inventory if item.get("source_type") == "exact")
unresolved = sum(1 for item in result.package_inventory if item.get("source_type") != "exact")
return f"""### Repository details
- **License / task / library:** `{_safe(metadata.get('license'), 'undeclared')}` / `{_safe(metadata.get('pipeline_tag'), 'undeclared')}` / `{_safe(metadata.get('library'), 'undeclared')}`
- **Architecture:** `{_safe(metadata.get('architecture'), 'not identified')}`
- **Text/config files inspected:** {len(result.inspected_files)}
- **Candidate files skipped:** {result.skipped_files}
- **Published files:** {artifacts.get('repository_file_count', 0)} totaling {artifacts.get('repository_size', '0 B')}
- **Weight artifacts:** {artifacts.get('weight_file_count', 0)} totaling {artifacts.get('weight_size', '0 B')} ({_safe(artifacts.get('weight_formats') or 'none')})
- **Safetensors index:** {artifacts.get('index_referenced_shards', 0)} referenced shards; {artifacts.get('index_declared_tensor_size', '0 B')} declared tensor data
- **Declared dependencies:** {dependencies}
- **Package inventory:** {len(result.package_inventory)} components ({exact} exact; {unresolved} unresolved/direct)
### Important limitations
{notes}
"""
def _coverage_items(result: ScanResult) -> list[dict[str, str]]:
items = []
for item in result.checks:
status = item.status.upper().replace("_", " ")
items.append({
"label": f"{status} · {item.category}",
"detail": f"### {_safe(item.category)}\n\n**Status:** `{_safe(status)}`\n\n{_safe(item.detail)}",
})
return items or [{"label": "No coverage records", "detail": "No coverage record was produced."}]
def _dependency_items(result: ScanResult) -> list[dict[str, str]]:
items = []
for item in result.dependency_details:
relations = ", ".join(item.get("relation") or [])
items.append({
"label": f"{relations or 'declared'} · {item.get('repository', 'unknown')}",
"detail": f"### {_safe(item.get('repository'))}\n\n"
f"- **Relationship:** {_safe(relations)}\n"
f"- **Depth:** {_safe(item.get('depth'))}\n"
f"- **License:** {_safe(item.get('license'), 'undeclared')}\n"
f"- **Status:** {_safe(item.get('status'), 'unknown')}\n"
f"- **Revision:** `{_safe(item.get('revision'))}`\n"
f"- **Gated:** {_safe(item.get('gated', False))}",
})
if not items:
items = [{"label": f"declared · {item}", "detail": f"### {_safe(item)}\n\nDeclared dependency; details were not checked."} for item in result.dependencies]
return items or [{"label": "No upstream dependencies", "detail": "No upstream repository dependency was declared."}]
def _finding_items(result: ScanResult) -> list[dict[str, str]]:
items = []
for finding in result.finding_groups():
occurrences = finding["occurrences"]
locations = []
evidence = ""
for occurrence in occurrences[:3]:
location = occurrence.get("path") or "Repository metadata"
if occurrence.get("line"):
location += f":{occurrence['line']}"
locations.append(location)
if not evidence and occurrence.get("evidence"):
evidence = occurrence["evidence"]
location_text = ", ".join(locations) + (" …" if len(occurrences) > 3 else "")
evidence_line = f"\n- **Evidence:** `{_safe(evidence)}`" if evidence else ""
label = f"{finding['severity'].upper()} · {finding['rule_id']} · {finding['title']}"
items.append({
"label": label,
"detail": f"### {_safe(label)}\n\n"
f"- **Status:** {_safe(finding['status'].upper())}\n"
f"- **Occurrences:** {len(occurrences)}\n"
f"- **Locations:** {_safe(location_text)}"
f"{evidence_line}\n"
f"- **Confidence:** {_safe(finding['confidence'])}\n\n"
f"**Why it matters**\n\n{_safe(finding['detail'])}\n\n"
f"**Remediation**\n\n{_safe(finding['remediation'])}",
})
return items or [{"label": "No findings", "detail": "No evidence-linked finding was produced."}]
def _package_items(result: ScanResult) -> list[dict[str, str]]:
items = []
for item in result.package_inventory:
manifests = ", ".join(sorted({occurrence.get("path", "") for occurrence in item.get("occurrences", [])}))
version = item.get("version") or "unresolved"
label = f"{item.get('name', 'unknown')} · {version}"
items.append({
"label": label,
"detail": f"### {_safe(label)}\n\n"
f"- **Source type:** {_safe(item.get('source_type'))}\n"
f"- **Constraint:** `{_safe(item.get('constraint'))}`\n"
f"- **PURL:** `{_safe(item.get('purl'))}`\n"
f"- **Manifest:** {_safe(manifests)}",
})
return items or [{"label": "No packages", "detail": "No supported package declaration was found."}]
def _vulnerability_items(result: ScanResult) -> list[dict[str, str]]:
items = []
for item in result.vulnerabilities:
label = f"{item.get('severity', '').upper()} · {item.get('id', 'unknown')} · {item.get('package', 'unknown')}"
items.append({
"label": label,
"detail": f"### {_safe(label)}\n\n"
f"{_safe(item.get('summary'))}\n\n"
f"- **Package:** {_safe(item.get('package'))} `{_safe(item.get('version'))}`\n"
f"- **Aliases:** {_safe(', '.join(item.get('aliases') or []))}\n"
f"- **Fixed versions:** {_safe(', '.join(item.get('fixed_versions') or []))}\n"
f"- **Advisory:** {_safe(item.get('url'))}",
})
return items or [{"label": "No known matches", "detail": "No known OSV match was returned for the exactly pinned packages checked."}]
def _selector_update(items: list[dict[str, str]], label: str):
choices = [(item["label"], str(index)) for index, item in enumerate(items)]
return gr.Dropdown(choices=choices, value="0", label=f"{label} ({len(items)})")
def _select_detail(selection: str | None, state: dict[str, list[str]] | None, section: str) -> str:
details = (state or {}).get(section) or []
try:
index = int(selection or 0)
except (TypeError, ValueError):
index = 0
if not details:
return "Run a scan to populate this section."
return details[index] if 0 <= index < len(details) else details[0]
def _write_sbom(result: ScanResult) -> str:
with tempfile.NamedTemporaryFile(
mode="w", encoding="utf-8", prefix="modelsentry-", suffix=".cdx.json", delete=False,
) as handle:
json.dump(result.sbom, handle, indent=2, ensure_ascii=False)
handle.write("\n")
return handle.name
def run_scan(target: str):
try:
result = scan_repository(target)
except ScanError as exc:
raise gr.Error(str(exc)) from exc
except Exception as exc:
raise gr.Error("The public repository could not be scanned because of an unexpected service error. Please try again.") from exc
sections = {
"overview": [{"label": "Repository profile", "detail": _overview_markdown(result)}] + _coverage_items(result),
"dependencies": _dependency_items(result),
"packages": _package_items(result),
"vulnerabilities": _vulnerability_items(result),
"findings": _finding_items(result),
}
view_state = {name: [item["detail"] for item in items] for name, items in sections.items()}
return (
_summary_markdown(result),
_selector_update(sections["overview"], "Overview topic"),
sections["overview"][0]["detail"],
_selector_update(sections["dependencies"], "Upstream repository"),
sections["dependencies"][0]["detail"],
_selector_update(sections["packages"], "Package"),
sections["packages"][0]["detail"],
_selector_update(sections["vulnerabilities"], "Known vulnerability"),
sections["vulnerabilities"][0]["detail"],
_selector_update(sections["findings"], "Finding"),
sections["findings"][0]["detail"],
result_json(result),
_write_sbom(result),
view_state,
)
DESCRIPTION = """Inspect a public Hugging Face Model or Space at an immutable revision. ModelSentry never executes
repository code or downloads model weights; its findings are bounded static evidence, not a safety certification."""
APP_CSS = """
html,
body {
height: 100%;
overflow: hidden !important;
margin: 0;
}
.audit-cards,
.audit-cards p,
.audit-cards li,
.audit-cards code {
overflow-wrap: anywhere;
word-break: break-word;
white-space: normal;
}
.scan-summary {
border: 1px solid var(--border-color-primary);
border-left: 0.35rem solid var(--primary-500);
border-radius: var(--radius-lg);
padding: 0.25rem 1rem;
background: var(--background-fill-secondary);
min-height: 12rem;
}
.detail-card {
border: 1px solid var(--border-color-primary);
border-radius: var(--radius-lg);
padding: 0.75rem 1rem;
min-height: 22rem;
background: var(--background-fill-primary);
}
.compact-note {
font-size: 0.85rem;
color: var(--body-text-color-subdued);
}
.gradio-container {
width: 100%;
max-width: none !important;
height: 100vh !important;
overflow-y: scroll !important;
overflow-x: hidden !important;
scrollbar-gutter: stable;
scrollbar-width: auto;
scrollbar-color: rgba(100, 116, 139, 0.9) rgba(148, 163, 184, 0.22);
box-sizing: border-box;
padding: 1rem max(1rem, calc((100vw - 1200px) / 2)) 2rem !important;
}
.gradio-container::-webkit-scrollbar {
width: 14px;
}
.gradio-container::-webkit-scrollbar-track {
background: rgba(148, 163, 184, 0.22);
}
.gradio-container::-webkit-scrollbar-thumb {
background: rgba(100, 116, 139, 0.9);
border: 3px solid transparent;
border-radius: 999px;
background-clip: padding-box;
}
.gradio-container::-webkit-scrollbar-thumb:hover {
background: var(--primary-500, #6366f1);
background-clip: padding-box;
}
"""
with gr.Blocks(title="ModelSentry", delete_cache=(600, 1800)) as demo:
gr.Markdown("# 🛡️ ModelSentry · Hugging Face repository auditor")
gr.Markdown(DESCRIPTION)
with gr.Row():
target = gr.Textbox(
label="Public Model or Space",
placeholder="https://huggingface.co/spaces/owner/name or model:owner/name",
scale=5,
)
scan = gr.Button("Scan immutable revision", variant="primary", scale=1)
summary = gr.Markdown(
"### Ready to scan\n\nEnter `model:owner/name`, `space:owner/name`, or a public Hugging Face URL.\n\nThe result summary will stay in this fixed panel.",
elem_classes=["scan-summary", "audit-cards"],
)
view_state = gr.State({})
with gr.Tabs():
with gr.Tab("Overview"):
with gr.Row(equal_height=False):
with gr.Column(scale=1, min_width=280):
coverage_selector = gr.Dropdown(label="Overview topic", choices=[], interactive=True)
with gr.Column(scale=2, min_width=360):
coverage_detail = gr.Markdown(
"### Overview inspector\n\nRun a scan, then choose the repository profile or a coverage area.",
elem_classes=["detail-card", "audit-cards"],
)
with gr.Tab("Findings"):
with gr.Row(equal_height=False):
with gr.Column(scale=1, min_width=280):
finding_selector = gr.Dropdown(label="Finding", choices=[], interactive=True)
with gr.Column(scale=2, min_width=360):
finding_detail = gr.Markdown("Select a finding.", elem_classes=["detail-card", "audit-cards"])
with gr.Tab("Provenance"):
with gr.Row(equal_height=False):
with gr.Column(scale=1, min_width=280):
dependency_selector = gr.Dropdown(label="Upstream repository", choices=[], interactive=True)
with gr.Column(scale=2, min_width=360):
dependency_detail = gr.Markdown("Select an upstream repository.", elem_classes=["detail-card", "audit-cards"])
with gr.Tab("Packages"):
with gr.Row(equal_height=False):
with gr.Column(scale=1, min_width=280):
package_selector = gr.Dropdown(label="Package", choices=[], interactive=True)
with gr.Column(scale=2, min_width=360):
package_detail = gr.Markdown("Select a package.", elem_classes=["detail-card", "audit-cards"])
with gr.Tab("Vulnerabilities"):
with gr.Row(equal_height=False):
with gr.Column(scale=1, min_width=280):
vulnerability_selector = gr.Dropdown(label="Known vulnerability", choices=[], interactive=True)
with gr.Column(scale=2, min_width=360):
vulnerability_detail = gr.Markdown("Select a vulnerability.", elem_classes=["detail-card", "audit-cards"])
with gr.Tab("JSON / SBOM"):
report = gr.Code(language="json", label="modelsentry.scan.v4 JSON", lines=18)
sbom = gr.File(label="CycloneDX 1.6 SBOM", interactive=False)
gr.Markdown(
"Static evidence requires human review. SBOM download files are eligible for automatic cleanup within 30 minutes.",
elem_classes=["compact-note"],
)
outputs = [
summary,
coverage_selector,
coverage_detail,
dependency_selector,
dependency_detail,
package_selector,
package_detail,
vulnerability_selector,
vulnerability_detail,
finding_selector,
finding_detail,
report,
sbom,
view_state,
]
scan.click(run_scan, inputs=target, outputs=outputs)
target.submit(run_scan, inputs=target, outputs=outputs)
coverage_selector.change(
lambda selection, state: _select_detail(selection, state, "overview"),
inputs=[coverage_selector, view_state],
outputs=coverage_detail,
api_name=False,
)
dependency_selector.change(
lambda selection, state: _select_detail(selection, state, "dependencies"),
inputs=[dependency_selector, view_state],
outputs=dependency_detail,
api_name=False,
)
package_selector.change(
lambda selection, state: _select_detail(selection, state, "packages"),
inputs=[package_selector, view_state],
outputs=package_detail,
api_name=False,
)
vulnerability_selector.change(
lambda selection, state: _select_detail(selection, state, "vulnerabilities"),
inputs=[vulnerability_selector, view_state],
outputs=vulnerability_detail,
api_name=False,
)
finding_selector.change(
lambda selection, state: _select_detail(selection, state, "findings"),
inputs=[finding_selector, view_state],
outputs=finding_detail,
api_name=False,
)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft(), css=APP_CSS)