Download app/module_inputs.py from wuhp/bbscanner: direct link, hf CLI and curl.
- Browser
- Download file 5.39 kB
-
https://huggingface.co/spaces/wuhp/bbscanner/resolve/main/app/module_inputs.py
- Command line
-
hf download hf://spaces/wuhp/bbscanner/app/module_inputs.py
-
curl -L -o module_inputs.py https://huggingface.co/spaces/wuhp/bbscanner/resolve/main/app/module_inputs.py
5.39 kB
| """In-memory per-job artifact and endpoint inputs; never execute supplied content.""" | |
| import json | |
| import re | |
| from typing import Literal | |
| from urllib.parse import urlsplit | |
| from pydantic import BaseModel, Field, field_validator | |
| try: | |
| from .replay_inputs import RaceReplay, NoSQLReplay | |
| except ImportError: | |
| from replay_inputs import RaceReplay, NoSQLReplay | |
| def clean_sbom(text): | |
| if not text.strip(): | |
| return "" | |
| try: | |
| source = json.loads(text) | |
| except ValueError as exc: | |
| raise ValueError("SBOM must be CycloneDX JSON") from exc | |
| if not isinstance(source, dict) or source.get("bomFormat") != "CycloneDX": | |
| raise ValueError("Only CycloneDX JSON SBOMs are supported") | |
| items = source.get("components", []) | |
| if not isinstance(items, list) or not 1 <= len(items) <= 300: | |
| raise ValueError("SBOM requires 1-300 application components") | |
| cleaned = [] | |
| seen = set() | |
| for item in items: | |
| if not isinstance(item, dict): | |
| raise ValueError("Invalid SBOM component") | |
| purl, name, version = item.get("purl"), item.get("name"), item.get("version") | |
| if not all(isinstance(v, str) and 0 < len(v) <= 500 for v in (purl, name, version)): | |
| raise ValueError("Each SBOM component requires bounded name, version and purl") | |
| if not purl.startswith("pkg:") or any(c in purl for c in "\r\n\x00"): | |
| raise ValueError("Invalid component purl") | |
| if purl in seen: | |
| continue | |
| seen.add(purl) | |
| cleaned.append({"type": "library", "name": name, "version": version, | |
| "purl": purl, "bom-ref": purl}) | |
| return json.dumps({"bomFormat": "CycloneDX", "specVersion": "1.5", "version": 1, | |
| "components": cleaned}, separators=(",", ":")) | |
| class ModuleInputs(BaseModel): | |
| race: RaceReplay | None = None | |
| nosql: NoSQLReplay | None = None | |
| source_text: str = Field(default="", max_length=200000) | |
| sast_language: Literal["", "python", "javascript", "typescript"] = "" | |
| trufflehog_verify: bool = False | |
| sbom_text: str = Field(default="", max_length=1000000) | |
| api_target: bool = False | |
| graphql_path: str = Field(default="", max_length=250) | |
| cors_path: str = Field(default="", max_length=250) | |
| websocket_path: str = Field(default="", max_length=250) | |
| idor_path: str = Field(default="", max_length=250) | |
| idor_parameter: str = Field(default="id", max_length=50) | |
| idor_baseline: str = Field(default="", max_length=100) | |
| idor_values: list[str] = Field(default_factory=list, max_length=3) | |
| llm_path: str = Field(default="", max_length=250) | |
| prompt_field: str = Field(default="prompt", max_length=50) | |
| response_field: str = Field(default="response", max_length=50) | |
| def validate_sbom(cls, text): | |
| return clean_sbom(text) | |
| def validate_llm_path(cls, path): | |
| if not path: | |
| return path | |
| parts = urlsplit(path) | |
| if (not path.startswith("/") or path.startswith("//") or parts.scheme or parts.netloc | |
| or parts.query or parts.fragment or ".." in path or | |
| any(c in path for c in "\r\n\x00\\")): | |
| raise ValueError("Endpoint must be a same-origin path without query/fragment") | |
| return path | |
| def validate_field(cls, value): | |
| if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]{0,49}", value): | |
| raise ValueError("Fields must be simple property or parameter names") | |
| return value | |
| def validate_idor_baseline(cls, value): | |
| if value and not re.fullmatch(r"[A-Za-z0-9_.:@-]{1,100}", value): | |
| raise ValueError("IDOR values must be simple numeric IDs, UUIDs or reference strings") | |
| return value | |
| def validate_idor_values(cls, values): | |
| if len(values) != len(set(values)): | |
| raise ValueError("IDOR values must be unique") | |
| if any(not re.fullmatch(r"[A-Za-z0-9_.:@-]{1,100}", value) for value in values): | |
| raise ValueError("Supply up to three simple IDOR reference values") | |
| return values | |
| def public_summary(self): | |
| return {"race_configured": self.race is not None, "nosql_configured": self.nosql is not None, | |
| "race_state_verifier": bool(self.race and self.race.verification), "graphql_configured": bool(self.graphql_path), "cors_configured": bool(self.cors_path), | |
| "websocket_configured": bool(self.websocket_path), "sast_language": self.sast_language, | |
| "trufflehog_verify": self.trufflehog_verify, | |
| "source_bytes": len(self.source_text.encode("utf-8")), | |
| "sbom_components": len(json.loads(self.sbom_text)["components"]) if self.sbom_text else 0, | |
| "api_target": self.api_target, "llm_path": self.llm_path, | |
| "idor_configured": bool(self.idor_path and self.idor_baseline and self.idor_values), | |
| "idor_value_count": len(self.idor_values), | |
| "prompt_field": self.prompt_field, "response_field": self.response_field} | |