Skip to content

Commit 7321534

Browse files
authored
Merge pull request #15 from avidml/0din
0din
2 parents f2c9209 + 0a1342b commit 7321534

60 files changed

Lines changed: 37310 additions & 24 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

avidtools/connectors/url.py

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import Optional
99
import requests
1010
from bs4 import BeautifulSoup
11-
from openai import OpenAI
11+
from openai import OpenAI, AsyncOpenAI
1212

1313
from ..datamodels.report import Report, ReportMetadata
1414
from ..datamodels.components import (
@@ -50,6 +50,7 @@ def __init__(self, api_key: Optional[str] = None, model: str = "gpt-4o-mini"):
5050
)
5151
self.model = model
5252
self.client = OpenAI(api_key=self.api_key)
53+
self.async_client = AsyncOpenAI(api_key=self.api_key)
5354

5455
def scrape_url(self, url: str) -> dict:
5556
"""
@@ -114,19 +115,79 @@ def _create_ai_prompt(self, scraped_data: dict) -> str:
114115
"""
115116
prompt = f"""You are an AI security expert tasked with analyzing web content about AI/ML vulnerabilities, incidents, or security issues and extracting structured information to create an AVID (AI Vulnerability Database) report.
116117
117-
The AVID report structure includes:
118-
- **report_id**: A unique identifier (generate one like "AVID-YYYY-R-XXXX")
119-
- **affects**: Information about affected artifacts including:
120-
- developer: List of developers/organizations
121-
- deployer: List of deployers/organizations
122-
- artifacts: List of artifacts with type (MUST be one of: "Model", "Dataset", "System") and name
123-
- **problemtype**: Problem description with:
124-
- classof: Class (MUST be one of: "AIID Incident", "ATLAS Case Study", "CVE Entry", "LLM Evaluation", "Third-party Report", "Undefined"). Default to "Third-party Report" if unsure.
125-
- type: Type (MUST be one of: "Issue", "Advisory", "Measurement", "Detection")
126-
- description: language ("eng") and value (description text)
127-
- **description**: High-level description with lang and value
128-
- **references**: List of references with label and url (MUST include the source URL)
129-
- **reported_date**: Date in YYYY-MM-DD format (use today's date if not specified)
118+
The AVID Report follows this exact schema:
119+
120+
```json
121+
{{
122+
"data_type": "AVID",
123+
"data_version": "string (optional)",
124+
"metadata": {{
125+
"report_id": "string (required, format: AVID-YYYY-R-XXXX)"
126+
}},
127+
"affects": {{
128+
"developer": ["list of developer organizations of the model/system involved"],
129+
"deployer": ["list of deployer organizations"],
130+
"artifacts": [
131+
{{
132+
"type": "Model|Dataset|System (required)",
133+
"name": "artifact name (required)"
134+
}}
135+
]
136+
}},
137+
"problemtype": {{
138+
"classof": "AIID Incident|ATLAS Case Study|CVE Entry|LLM Evaluation|Third-party Report|Undefined (required, default: Third-party Report)",
139+
"type": "Issue|Advisory|Measurement|Detection (optional)",
140+
"description": {{
141+
"lang": "eng",
142+
"value": "description text"
143+
}}
144+
}},
145+
"metrics": [
146+
{{
147+
"name": "metric name",
148+
"detection_method": {{
149+
"type": "Significance Test|Static Threshold",
150+
"name": "method name"
151+
}},
152+
"results": {{}} or []
153+
}}
154+
],
155+
"references": [
156+
{{
157+
"label": "reference label",
158+
"url": "reference url (MUST include source URL)"
159+
}}
160+
],
161+
"description": {{
162+
"lang": "eng",
163+
"value": "high-level description"
164+
}},
165+
"impact": {{
166+
"avid": {{
167+
"risk_domain": ["list of risk domains"],
168+
"sep_view": ["list of SEP taxonomy IDs"],
169+
"lifecycle_view": ["list of lifecycle stage IDs"],
170+
"taxonomy_version": "version string"
171+
}},
172+
"atlas": [
173+
{{
174+
"tactic": "tactic name",
175+
"technique": "technique name",
176+
"subtechnique": "subtechnique name"
177+
}}
178+
]
179+
}},
180+
"credit": [
181+
{{
182+
"lang": "eng",
183+
"value": "credited person or organization"
184+
}}
185+
],
186+
"reported_date": "YYYY-MM-DD"
187+
}}
188+
```
189+
190+
All fields except those marked as required are optional. Omit fields if information is not available.
130191
131192
Here is the web content to analyze:
132193
@@ -239,10 +300,50 @@ def _build_report_from_json(self, data: dict) -> Report:
239300
artifacts = []
240301
if "artifacts" in affects_data:
241302
for artifact_data in affects_data["artifacts"]:
303+
artifact_type = ArtifactTypeEnum(artifact_data["type"])
304+
artifact_name = artifact_data["name"]
305+
306+
# Reclassify models as systems based on provider-specific rules
307+
if artifact_type == ArtifactTypeEnum.model:
308+
artifact_name_lower = artifact_name.lower()
309+
310+
# OpenAI: All LLMs are systems
311+
if "openai" in artifact_name_lower:
312+
artifact_type = ArtifactTypeEnum.system
313+
314+
# Anthropic: All LLMs are systems
315+
elif "anthropic" in artifact_name_lower:
316+
artifact_type = ArtifactTypeEnum.system
317+
318+
# Google: Gemini series are systems, Gemma are models
319+
elif "google" in artifact_name_lower or "gemini" in artifact_name_lower:
320+
if "gemini" in artifact_name_lower:
321+
artifact_type = ArtifactTypeEnum.system
322+
# gemma remains as model
323+
324+
# Cohere: All except Command R and Aya are systems
325+
elif "cohere" in artifact_name_lower:
326+
if "command r" not in artifact_name_lower and "aya" not in artifact_name_lower:
327+
artifact_type = ArtifactTypeEnum.system
328+
329+
# Mistral: Large, Medium, Moderation, Embed are systems
330+
elif "mistral" in artifact_name_lower:
331+
if any(variant in artifact_name_lower for variant in ["large", "medium", "moderation", "embed"]):
332+
artifact_type = ArtifactTypeEnum.system
333+
334+
# Alibaba: Qwen Max and Turbo are systems
335+
elif "alibaba" in artifact_name_lower or "qwen" in artifact_name_lower:
336+
if "qwen max" in artifact_name_lower or "qwen turbo" in artifact_name_lower:
337+
artifact_type = ArtifactTypeEnum.system
338+
339+
# Meta, Twitter/X, Mozilla remain as systems (closed APIs)
340+
elif any(provider in artifact_name_lower for provider in ["twitter", "grok"]):
341+
artifact_type = ArtifactTypeEnum.system
342+
242343
artifacts.append(
243344
Artifact(
244-
type=ArtifactTypeEnum(artifact_data["type"]),
245-
name=artifact_data["name"],
345+
type=artifact_type,
346+
name=artifact_name,
246347
)
247348
)
248349
affects = Affects(

avidtools/datamodels/components.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Component data classes used in AVID report and vulnerability datamodels.
33
"""
44

5-
from typing import Dict, List, Optional
5+
from typing import Any, Dict, List, Optional
66
from pydantic import BaseModel
77

88
from .enums import (
@@ -126,20 +126,51 @@ class CWETaxonomy(BaseModel):
126126
lang: Optional[str] = None
127127

128128

129+
class JailbreakTaxonomyItem(BaseModel):
130+
"""0DIN Jailbreak Taxonomy item with Category, Strategy, and Technique."""
131+
132+
Category: Optional[str] = None
133+
Strategy: Optional[str] = None
134+
Technique: Optional[str] = None
135+
136+
class Config: # Fields are excluded if None
137+
fields = {
138+
"Category": {"exclude": True},
139+
"Strategy": {"exclude": True},
140+
"Technique": {"exclude": True}
141+
}
142+
143+
144+
class OdinTaxonomy(BaseModel):
145+
"""0DIN taxonomy mapping for AI security disclosures."""
146+
147+
SocialImpactScore: Optional[str] = None
148+
JailbreakTaxonomy: Optional[List[JailbreakTaxonomyItem]] = None
149+
150+
class Config: # Fields are excluded if None
151+
fields = {
152+
"SocialImpactScore": {"exclude": True},
153+
"JailbreakTaxonomy": {"exclude": True}
154+
}
155+
156+
129157
class Impact(BaseModel):
130158
"""Impact information of a report/vulnerability.
131159
132160
E.g. different taxonomy mappings, harm and severity scores.
133161
"""
134162

135-
avid: AvidTaxonomy
163+
avid: Optional[AvidTaxonomy] = None
136164
atlas: Optional[List[AtlasTaxonomy]] = None
137165
cvss: Optional[CVSSScores] = None
138166
cwe: Optional[List[CWETaxonomy]] = None
167+
odin: Optional[OdinTaxonomy] = None
139168

140169
class Config: # Fields are excluded if None
141170
fields = {
171+
"avid": {"exclude": True},
142172
"atlas": {"exclude": True},
143173
"cvss": {"exclude": True},
144-
"cwe": {"exclude": True}
174+
"cwe": {"exclude": True},
175+
"odin": {"exclude": True}
145176
}

scripts/download_pages.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python3
2+
"""Download all disclosure pages from 0din.ai page 1 for offline testing."""
3+
4+
import httpx
5+
from pathlib import Path
6+
from bs4 import BeautifulSoup
7+
8+
def main():
9+
# Create output directory
10+
output_dir = Path(__file__).parent / "scraped_html"
11+
output_dir.mkdir(exist_ok=True)
12+
13+
# Get page 1 to extract UUIDs
14+
print("Fetching page 1 to get UUIDs...")
15+
page_url = "https://0din.ai/disclosures?page=1"
16+
response = httpx.get(page_url, timeout=30.0)
17+
soup = BeautifulSoup(response.text, 'html.parser')
18+
19+
# Extract UUIDs
20+
links = soup.find_all('a', {'data-turbo-frame': '_top'})
21+
uuids = set()
22+
for link in links:
23+
href = link.get('href', '')
24+
if '/disclosures/' in href and href.count('/') == 2:
25+
uuid = href.split('/')[-1]
26+
uuids.add(uuid)
27+
28+
print(f"Found {len(uuids)} UUIDs")
29+
30+
# Download each disclosure page
31+
for i, uuid in enumerate(sorted(uuids), 1):
32+
url = f"https://0din.ai/disclosures/{uuid}"
33+
output_file = output_dir / f"{uuid}.html"
34+
35+
if output_file.exists():
36+
print(f"[{i}/{len(uuids)}] Skipping {uuid} (already exists)")
37+
continue
38+
39+
print(f"[{i}/{len(uuids)}] Downloading {uuid}...")
40+
try:
41+
response = httpx.get(url, timeout=30.0)
42+
output_file.write_text(response.text, encoding='utf-8')
43+
print(f" ✓ Saved to {output_file}")
44+
except Exception as e:
45+
print(f" ✗ Error: {e}")
46+
47+
print(f"\nComplete! Downloaded pages to {output_dir}")
48+
49+
if __name__ == "__main__":
50+
main()

scripts/mileva.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ async def scrape_nvd_cve_details(
177177
details = {
178178
'cve_id': cve_id,
179179
'url': f"https://www.cve.org/CVERecord?id={cve_id}",
180+
'title': None,
180181
'description': None,
181182
'published_date': None,
182183
'last_modified_date': None,
@@ -193,6 +194,11 @@ async def scrape_nvd_cve_details(
193194
try:
194195
containers = cve_data.get('containers', {})
195196
cna = containers.get('cna', {})
197+
198+
# Get CNA title
199+
title = cna.get('title')
200+
if isinstance(title, str):
201+
details['title'] = title.strip()
196202

197203
# Get description
198204
descriptions = cna.get('descriptions', [])
@@ -268,7 +274,7 @@ async def scrape_nvd_cve_details(
268274
def create_description(cve_id: str, cve_details: dict) -> Optional[LangValue]:
269275
"""Create description LangValue object."""
270276
if cve_details['description']:
271-
return LangValue(lang="eng", value=cve_id + " Detail")
277+
return LangValue(lang="eng", value=cve_details['description'])
272278
return None
273279

274280

@@ -296,10 +302,7 @@ def create_references(cve_details: dict) -> List[Reference]:
296302

297303
def create_problemtype(cve_id: str, cve_details: dict) -> Problemtype:
298304
"""Create problemtype from CVE details."""
299-
problemtype_desc = cve_details['description'] or f"Vulnerability {cve_id}"
300-
if cve_details['cwe_ids']:
301-
cwe_list = ', '.join(cve_details['cwe_ids'])
302-
problemtype_desc = f"{cwe_list}: {problemtype_desc}"
305+
problemtype_desc = cve_details.get('title') or f"Vulnerability {cve_id}"
303306

304307
return Problemtype(
305308
classof=ClassEnum.cve,

0 commit comments

Comments
 (0)