syfscan 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
syfscan-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,4 @@
1
+ Metadata-Version: 2.4
2
+ Name: syfscan
3
+ Version: 0.1.0
4
+ Summary: Détecteur de vulnérabilités de dépendances
@@ -0,0 +1,155 @@
1
+ # SyfScan
2
+
3
+ > Dependency vulnerability scanner for Python projects — powered by [OSV.dev](https://osv.dev) and Claude AI.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ SyfScan is a command-line tool that scans your `requirements.txt` against the [OSV.dev](https://osv.dev) vulnerability database. It displays a severity-ranked report directly in your terminal and optionally generates a plain-language security summary using Claude AI — making security accessible even to developers unfamiliar with CVEs.
10
+
11
+ ---
12
+
13
+ ## Features
14
+
15
+ - 🔍 Scans all dependencies in `requirements.txt` against OSV.dev
16
+ - 🎨 Color-coded terminal output ranked by severity
17
+ - 🤖 AI-generated summary with risks, fixes, and recommendations via Claude
18
+ - 📄 Export report to JSON, HTML, or PDF
19
+ - ⚡ Fast and lightweight — no local database required
20
+
21
+ ---
22
+
23
+ ## Requirements
24
+
25
+ - Python 3.12+
26
+ - An Anthropic API key *(optional — only required for AI summary)*
27
+
28
+ ---
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ git clone https://github.com/yourname/syfscan
34
+ cd syfscan
35
+ pip install -e .
36
+ ```
37
+
38
+ If you want the AI summary, create a `.env` file at the root of the project:
39
+
40
+ ```bash
41
+ # .env
42
+ ANTHROPIC_API_KEY=sk-ant-...
43
+ ```
44
+
45
+ > ⚠️ Never commit your `.env` file. Add it to `.gitignore`.
46
+
47
+ ---
48
+
49
+ ## Usage
50
+
51
+ ```bash
52
+ # Full scan with AI summary
53
+ syfscan requirements.txt
54
+
55
+ # Scan without AI summary
56
+ syfscan requirements.txt --no-ai
57
+
58
+ # Show only the N most critical vulnerabilities per package
59
+ syfscan requirements.txt -xvuln 5
60
+
61
+ # Export report to JSON
62
+ syfscan requirements.txt --json
63
+
64
+ # Export report to HTML
65
+ syfscan requirements.txt --html
66
+
67
+ # Export report to PDF
68
+ syfscan requirements.txt --pdf
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Options
74
+
75
+ | Flag | Description |
76
+ |---|---|
77
+ | `--no-ai` | Disable Claude AI summary |
78
+ | `-xvuln N` | Limit output to the N most critical vulnerabilities per package |
79
+ | `--json` | Export report to `rapport.json` |
80
+ | `--html` | Export report to `rapport.html` |
81
+ | `--pdf` | Export report to `rapport.pdf` |
82
+
83
+ ---
84
+
85
+ ## Example output
86
+
87
+ ```
88
+ ███████╗██╗ ██╗███████╗███████╗ ██████╗ █████╗ ███╗ ██╗
89
+ ██╔════╝╚██╗ ██╔╝██╔════╝██╔════╝██╔════╝██╔══██╗████╗ ██║
90
+ ███████╗ ╚████╔╝ █████╗ ███████╗██║ ███████║██╔██╗ ██║
91
+ ╚════██║ ╚██╔╝ ██╔══╝ ╚════██║██║ ██╔══██║██║╚██╗██║
92
+ ███████║ ██║ ██║ ███████║╚██████╗██║ ██║██║ ╚████║
93
+ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝
94
+ v1.0 — Python dependency vulnerability scanner.
95
+
96
+ > Fichier cible : requirements.txt
97
+
98
+ > Scan numpy 2.2.2
99
+ > Scan pillow 12.1.1
100
+ ...
101
+
102
+ pillow 12.1.1 — 6 vulnérabilité(s)
103
+ > CVE-2026-42311 — OOB Write with Invalid PSD Tile Extents
104
+ > CVE-2026-40192 — FITS GZIP decompression bomb
105
+ > CVE-2026-42309 — Heap buffer overflow with nested list coordinates
106
+ > CVE-2026-42310 — PDF Parsing Trailer Infinite Loop (DoS)
107
+ > CVE-2026-42308 — Integer overflow when processing fonts
108
+
109
+ ────────────────── AI Summary ──────────────────
110
+
111
+ 🔧 How to Fix
112
+ Upgrade Pillow from 12.1.1 to 12.2.0. Run pip install --upgrade Pillow.
113
+
114
+ ⚠️ Risks
115
+ Attackers could achieve arbitrary code execution, denial of service,
116
+ or full system compromise via malicious image or document files.
117
+
118
+ 🛡️ Additional Measures
119
+ - Validate and sanitize all uploaded files before processing.
120
+ - Run image workloads in a sandboxed environment.
121
+ - Enable automated dependency scanning in your CI/CD pipeline.
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Project structure
127
+
128
+ ```
129
+ syfscan/
130
+ ├── syfscan/
131
+ │ ├── cli.py
132
+ │ └── __main__.py
133
+ ├── parsers/
134
+ │ └── requirement.py
135
+ ├── scanner/
136
+ │ └── osv.py
137
+ ├── output/
138
+ │ ├── terminal.py
139
+ │ ├── json.py
140
+ │ ├── html.py
141
+ │ └── pdf.py
142
+ ├── ia/
143
+ │ └── claude_summarizer.py
144
+ ├── test/
145
+ ├── .env
146
+ ├── .gitignore
147
+ ├── pyproject.toml
148
+ └── README.md
149
+ ```
150
+
151
+ ---
152
+
153
+ ## License
154
+
155
+ MIT — see [LICENSE](LICENSE) for details.
File without changes
@@ -0,0 +1,121 @@
1
+ # ia/claude_summarizer.py
2
+ import anthropic
3
+ from dotenv import load_dotenv
4
+ from rich.console import Console
5
+ from rich.rule import Rule
6
+ import os
7
+
8
+ load_dotenv()
9
+
10
+ client = anthropic.Anthropic()
11
+
12
+ console = Console()
13
+
14
+
15
+ def generate_summary(report):
16
+ vulnerabilities_text = build_vulnerabilities_text(report)
17
+
18
+ if not vulnerabilities_text:
19
+ return None
20
+
21
+ message = client.messages.create(
22
+ model="claude-opus-4-6",
23
+ max_tokens=400,
24
+ messages=[
25
+ {
26
+ "role": "user",
27
+ "content": f"""You are a security expert. Analyze these vulnerabilities and respond in exactly this format:
28
+
29
+ HOW TO FIX
30
+ [One paragraph. List every package to upgrade and to which version.]
31
+
32
+ RISKS
33
+ [One paragraph. What could concretely happen if left unpatched.]
34
+
35
+ ADDITIONAL MEASURES
36
+ [3 bullet points max. Practical extra steps the developer can take.]
37
+
38
+ Vulnerabilities:
39
+ {vulnerabilities_text}"""
40
+ }
41
+ ]
42
+ )
43
+
44
+ return message.content[0].text
45
+
46
+
47
+ def display_summary(ai_text):
48
+ sections = {
49
+ "HOW TO FIX": ("🔧 How to Fix", "green"),
50
+ "RISKS": ("⚠️ Risks", "red"),
51
+ "ADDITIONAL MEASURES": ("🛡️ Additional Measures", "yellow"),
52
+ }
53
+
54
+ console.print()
55
+ console.rule("[bold]AI Summary[/bold]")
56
+ console.print()
57
+
58
+ current_section = None
59
+ current_lines = []
60
+
61
+ for line in ai_text.splitlines():
62
+ line = line.strip()
63
+
64
+ matched = False
65
+ for key in sections:
66
+ if key in line.upper():
67
+ if current_section and current_lines:
68
+ title, color = sections[current_section]
69
+ content = "\n".join(current_lines).strip()
70
+ console.print(f"[bold {color}]{title}[/bold {color}]")
71
+ console.print(content)
72
+ console.print()
73
+
74
+ current_section = key
75
+ current_lines = []
76
+ matched = True
77
+ break
78
+
79
+ if not matched and line:
80
+ current_lines.append(line)
81
+
82
+ # Flush last section
83
+ if current_section and current_lines:
84
+ title, color = sections[current_section]
85
+ content = "\n".join(current_lines).strip()
86
+ console.print(f"[bold {color}]{title}[/bold {color}]")
87
+ console.print(content)
88
+ console.print()
89
+
90
+
91
+ def get_fixed_version(vuln):
92
+ try:
93
+ for affected in vuln.get("affected", []):
94
+ for r in affected.get("ranges", []):
95
+ for event in r.get("events", []):
96
+ if "fixed" in event:
97
+ return event["fixed"]
98
+ except (KeyError, IndexError):
99
+ pass
100
+ return None
101
+
102
+
103
+ def build_vulnerabilities_text(report):
104
+ lines = []
105
+
106
+ for dep in report:
107
+ if not dep["vulnerabilites"]:
108
+ continue
109
+ for vuln in dep["vulnerabilites"]:
110
+ fixed = get_fixed_version(vuln)
111
+ severity = vuln.get("database_specific", {}).get("severity", "UNKNOWN")
112
+ line = (
113
+ f"- {dep['nom']} {dep['version']} : "
114
+ f"{vuln.get('id', 'UNKNOWN')} | "
115
+ f"{vuln.get('summary', 'No description')} | "
116
+ f"Severity: {severity} | "
117
+ f"Fixed in: {fixed or 'unknown'}"
118
+ )
119
+ lines.append(line)
120
+
121
+ return "\n".join(lines)
File without changes
@@ -0,0 +1,96 @@
1
+ import json
2
+ from .terminal import get_label, get_score, score_couleur
3
+
4
+ # Aide IA pour cette fonction car orange3 n'existe pas en HTML, et orange n'offre pas le bon affichage au niveau du terminal
5
+ def convert_couleur_html(couleur):
6
+ map = {
7
+ "red":"red",
8
+ "yellow":"gold",
9
+ "orange3":"orange"
10
+ }
11
+ return map.get(couleur, "black")
12
+
13
+ def rapport_html (rapport, output_path, max_vulns):
14
+
15
+ #https://www.w3schools.com/tags/tryit.asp?filename=tryhtml_span
16
+ #https://www-sololearn-com.translate.goog/en/Discuss/2715062/how-to-code-html-in-python?_x_tr_sl=en&_x_tr_tl=fr&_x_tr_hl=fr&_x_tr_pto=rq#
17
+
18
+ html="""
19
+ <!DOCTYPE html>
20
+ <html>
21
+ <head>
22
+ <meta charset ="UTF-8">
23
+ <style>
24
+
25
+ body {
26
+ background-color: white;
27
+ font-family: arial;
28
+ }
29
+ h1{
30
+ color: black
31
+ }
32
+ h2{
33
+ color: darkred
34
+ }
35
+ p{
36
+ color: black
37
+ }
38
+ </style>
39
+ </head>
40
+ <body>
41
+ <h1>Rapport syfscan </h1>
42
+ """
43
+
44
+
45
+
46
+ for dep in rapport:
47
+ nom = dep["nom"]
48
+ version = dep["version"]
49
+ vulns = dep["vulnerabilites"]
50
+
51
+ if max_vulns is not None:
52
+ vulns = vulns[:max_vulns]
53
+ couleur_h2 = "green" if not vulns else"darkred"
54
+
55
+ html+=f"<h2 style='color:{couleur_h2}'>{nom} {version}</h2>"
56
+
57
+ if not vulns:
58
+ html+=f"<p>Aucune vulnérabilité détectée</p>"
59
+ continue
60
+
61
+
62
+ html+=f"<p>{len(vulns)} vulnérabilités détectées"
63
+ for v in vulns:
64
+
65
+ score = get_score(v)
66
+ couleur = convert_couleur_html(score_couleur(score))
67
+ cve = get_label(v)
68
+ summary = v.get("summary", "no description")
69
+
70
+ html+=f"""
71
+ <p>
72
+ <span style="color:{couleur}; font-weight:bold">{cve}
73
+ </span>
74
+ > Score: {score} <br>
75
+ {summary}
76
+ </p>
77
+ """
78
+
79
+
80
+
81
+ html+="""
82
+ </body>
83
+ </html>
84
+
85
+ """
86
+
87
+ with open(output_path, "w", encoding="utf-8") as f :
88
+ f.write(html)
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
@@ -0,0 +1,57 @@
1
+ import json
2
+ from .terminal import get_label, get_score, score_couleur
3
+
4
+ def rapport_json (rapport, output_path, max_vulns):
5
+
6
+ results =[]
7
+
8
+ for dep in rapport:
9
+ nom = dep["nom"]
10
+ version = dep["version"]
11
+ vulns = dep["vulnerabilites"]
12
+
13
+ if max_vulns is not None:
14
+ vulns = vulns[:max_vulns]
15
+
16
+ if not vulns:
17
+ results.append({
18
+ "nom": nom,
19
+ "version": version,
20
+ "statut" : "non vulnerable",
21
+ "nombre de vulnerabilite(s)": 0,
22
+
23
+ },)
24
+ continue
25
+
26
+ vulns_list=[]
27
+
28
+ for v in vulns:
29
+
30
+ score = get_score(v)
31
+ couleur = score_couleur(score)
32
+ cve = get_label(v)
33
+ summary = v.get("summary", "no description")
34
+ vulns_list.append({
35
+ "score": score,
36
+ "cve": cve,
37
+ "summary": summary,
38
+ },)
39
+
40
+ results.append({
41
+ "nom": nom,
42
+ "version": version,
43
+ "statut" : "vulnerable",
44
+ "nombre de vulnerabilite(s)": len(vulns),
45
+ "vulnerabilites": vulns_list
46
+ },)
47
+
48
+ with open(output_path, "w") as f :
49
+ json.dump(results, f, indent=4) #conversion en json de la liste results
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
@@ -0,0 +1,74 @@
1
+ from reportlab.lib.pagesizes import letter
2
+ from reportlab.pdfgen import canvas
3
+ from reportlab.lib import colors
4
+ from .terminal import get_label, get_score, score_couleur
5
+ def rapport_pdf(rapport, output_path, max_vulns=None):
6
+ c = canvas.Canvas(str(output_path), pagesize=letter)
7
+ width, height = letter
8
+
9
+ y = height - 40
10
+
11
+ c.setFont("Helvetica-Bold", 14)
12
+ c.drawString(40, y, "Rapport SyfScan - Vulnérabilités")
13
+
14
+ y -= 30
15
+
16
+ c.setFont("Helvetica", 10)
17
+
18
+ for dep in rapport:
19
+ nom = dep.get("nom")
20
+ version = dep.get("version")
21
+ vulns = dep.get("vulnerabilites", [])
22
+
23
+ if max_vulns:
24
+ vulns = vulns[:max_vulns]
25
+ if not vulns :
26
+ c.setFillColor(colors.green)
27
+ else:
28
+ c.setFillColor(colors.red)
29
+
30
+
31
+ c.setFont("Helvetica-Bold", 11)
32
+ c.drawString(40, y, f"{nom} ({version})")
33
+ y -= 15
34
+
35
+ if not vulns:
36
+
37
+ c.setFont("Helvetica", 10)
38
+ c.drawString(60, y, "Non vulnérable")
39
+ y -= 20
40
+ continue
41
+
42
+
43
+
44
+
45
+ c.setFont("Helvetica", 10)
46
+ c.drawString(60, y, f"{len(vulns)} vulnérabilité(s)")
47
+ y -= 15
48
+
49
+ for v in vulns:
50
+ #cve = v.get("id", "N/A")
51
+ cve=get_label(v)
52
+ summary = v.get("summary", "no description")
53
+ score = get_score(v)
54
+ couleur = score_couleur(score)
55
+ text = f"- {cve}: {summary[:60]}"
56
+
57
+ if couleur =="red":
58
+ c.setFillColor(colors.red)
59
+ elif couleur =="orange3":
60
+ c.setFillColor(colors.orange)
61
+ else:
62
+ c.setFillColor(colors.gold)
63
+
64
+ if y < 40:
65
+ c.showPage()
66
+ c.setFont("Helvetica", 10)
67
+ y = height - 40
68
+
69
+ c.drawString(80, y, text)
70
+ y -= 15
71
+
72
+ y -= 10
73
+
74
+ c.save()
@@ -0,0 +1,72 @@
1
+ from rich.console import Console
2
+
3
+ console = Console()
4
+
5
+ # https://ossf.github.io/osv-schema/#database_specific-field
6
+ def get_score(vuln):
7
+ # Si on a le score dans severity[]
8
+ for severity in vuln.get("severity", []):
9
+ if severity.get("type") in ("CVSS_V3", "CVSS_V2"):
10
+ try:
11
+ return float(severity.get("score", 0.0))
12
+ except ValueError:
13
+ pass
14
+
15
+ score_dictionnary = {"CRITICAL": 9.5, "HIGH": 8.0, "MEDIUM": 5.0, "LOW": 2.0}
16
+
17
+ # Si le score est dans database_specific
18
+ db_severity = vuln.get("database_specific", {}).get("severity", "")
19
+ if db_severity.upper() in score_dictionnary:
20
+ return score_dictionnary[db_severity.upper()]
21
+
22
+ # Si le score est dans affected[0].ecosystem_specific
23
+ affected = vuln.get("affected", [])
24
+ if affected:
25
+ eco_severity = affected[0].get("ecosystem_specific", {}).get("severity", "")
26
+ if eco_severity.upper() in score_dictionnary:
27
+ return score_dictionnary[eco_sev.upper()]
28
+
29
+ return 0.0
30
+
31
+
32
+ def score_couleur(score):
33
+ if score >= 9.0:
34
+ return "red"
35
+ elif score >= 7.0:
36
+ return "orange3"
37
+ else:
38
+ return "yellow"
39
+
40
+
41
+ # Fonction faite avec IA
42
+ def get_label(vuln):
43
+ aliases = vuln.get("aliases", [])
44
+ return next((a for a in aliases if a.startswith("CVE-")), vuln.get("id", "N/A"))
45
+
46
+
47
+ def display_report(rapport, max_vulns=None):
48
+ console.print("\n[bold green]// Rapport syfscan[/bold green]\n")
49
+
50
+ for dep in rapport:
51
+ nom = dep["nom"]
52
+ version = dep["version"]
53
+ vulns = dep["vulnerabilites"]
54
+
55
+ if max_vulns is not None:
56
+ vulns = vulns[:max_vulns]
57
+
58
+ if not vulns:
59
+ console.print(f"[green]{nom} {version} — 0 vulnérabilité[/green]")
60
+ console.print()
61
+ continue
62
+
63
+ console.print(f"[red]{nom} {version} — {len(vulns)} vulnérabilité(s)[/red]")
64
+
65
+ for v in vulns:
66
+ score = get_score(v)
67
+ couleur = score_couleur(score)
68
+ cve = get_label(v)
69
+ summary = v.get("summary", "no description")
70
+ console.print(f" [dim]>[/dim] [{couleur}]{cve} — {summary}[/{couleur}]")
71
+
72
+ console.print()
@@ -0,0 +1,41 @@
1
+ from packaging.requirements import Requirement
2
+ from rich.console import Console
3
+
4
+ console = Console()
5
+
6
+ def parse_requirements(fichier):
7
+ finale_liste =[]
8
+ try :
9
+ with open (fichier, "r") as f :
10
+ lignes = f.readlines()
11
+
12
+ for ligne in lignes :
13
+ ligne = ligne.strip()
14
+
15
+ if not ligne or ligne.startswith('#'):
16
+ continue
17
+
18
+ try :
19
+ req = Requirement(ligne)
20
+ nom = req.name
21
+ version ="0.0.0"
22
+
23
+ if req.specifier :#determiner la bonne version si elle existe (borne basse)
24
+ for spec in req.specifier :
25
+ if spec.operator in ["==", ">=", "~=", "==="]:
26
+ version =spec.version
27
+ break
28
+ finale_liste.append({"nom":nom, "version" :version})
29
+
30
+ except Exception :
31
+ console.print(f"[yellow][ WARNING ][/yellow] [dim]Ligne ignorée —[/dim] [cyan]{ligne}[/cyan]")
32
+
33
+
34
+
35
+
36
+ except Exception as e :
37
+ console.print(f"[bold red][ ERROR ][/bold red] [red]impossible de lire le fichier — {e}[/red]")
38
+ cons
39
+
40
+ console.print()
41
+ return finale_liste
@@ -0,0 +1,16 @@
1
+ # https://www.reddit.com/r/learnpython/comments/182cdyz/how_can_i_create_a_command_line_tool_and_execute/
2
+ # https://ycopin.pages.in2p3.fr/Informatique-Python/Cours/packaging.html
3
+
4
+ [project]
5
+ name = "syfscan"
6
+ description = "Détecteur de vulnérabilités de dépendances"
7
+ version = "0.1.0"
8
+
9
+ [project.scripts]
10
+ syfscan = "syfscan.__main__:main"
11
+
12
+ [tool.setuptools.packages]
13
+ find = {}
14
+
15
+
16
+
File without changes
@@ -0,0 +1,48 @@
1
+ import requests
2
+ from rich.console import Console
3
+
4
+ from output.terminal import get_score
5
+
6
+ console = Console()
7
+
8
+ OSV_API_URL = "https://api.osv.dev/v1/query"
9
+
10
+ def scan_dependencies(dependencies):
11
+
12
+ rapport = []
13
+
14
+ for dep in dependencies:
15
+ nom = dep["nom"]
16
+ version = dep["version"]
17
+
18
+ console.print(f"[green]>[/green] [dim]Scan[/dim] [cyan]{nom}[/cyan] [dim]{version}[/dim]")
19
+
20
+ try:
21
+ response = requests.post(OSV_API_URL, json={
22
+ "package": {
23
+ "name": nom,
24
+ "ecosystem": "PyPI"
25
+ },
26
+ "version": version
27
+ })
28
+ response.raise_for_status()
29
+ data = response.json()
30
+
31
+ vulns = sorted(data.get("vulns", []), key=get_score, reverse=True)
32
+
33
+ rapport.append({
34
+ "nom": nom,
35
+ "version": version,
36
+ "vulnerabilites": vulns
37
+ })
38
+
39
+ except requests.exceptions.RequestException as e:
40
+ console.print(f"[bold red][ ERROR ][/bold red] [red]{nom} — {e}[/red]")
41
+ rapport.append({
42
+ "nom": nom,
43
+ "version": version,
44
+ "vulnerabilites": [],
45
+ "erreur": str(e)
46
+ })
47
+
48
+ return rapport
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,4 @@
1
+ from syfscan.cli import main
2
+
3
+ if __name__== "__main__":
4
+ main()
@@ -0,0 +1,120 @@
1
+ import argparse
2
+ import sys
3
+ from pathlib import Path
4
+ from rich.console import Console
5
+
6
+ from parsers.requirement import parse_requirements
7
+ from scanner.osv import scan_dependencies
8
+ from ia.claude_summarizer import generate_summary, display_summary
9
+ from output.terminal import display_report
10
+ from output.json import rapport_json
11
+ from output.html import rapport_html
12
+ from output.pdf import rapport_pdf
13
+
14
+ console = Console()
15
+
16
+ BANNER = """
17
+ [green]
18
+ ███████╗██╗ ██╗███████╗███████╗ ██████╗ █████╗ ███╗ ██╗
19
+ ██╔════╝╚██╗ ██╔╝██╔════╝██╔════╝██╔════╝██╔══██╗████╗ ██║
20
+ ███████╗ ╚████╔╝ █████╗ ███████╗██║ ███████║██╔██╗ ██║
21
+ ╚════██║ ╚██╔╝ ██╔══╝ ╚════██║██║ ██╔══██║██║╚██╗██║
22
+ ███████║ ██║ ██║ ███████║╚██████╗██║ ██║██║ ╚████║
23
+ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝
24
+ [/green][dim green] v1.0 — Python dependency vulnerability scanner. [/dim green]
25
+ """
26
+
27
+ def main():
28
+ # Fait avec https://docs.python.org/3/library/argparse.html
29
+ parser = argparse.ArgumentParser(
30
+ prog="syfscan",
31
+ description="SyfScan — Détecteur de vulnérabilités de dépendances",
32
+ epilog="Exemple : syfscan requirements.txt"
33
+ )
34
+
35
+ parser.add_argument(
36
+ "file",
37
+ help="Chemin vers le fichier requirements.txt"
38
+ )
39
+
40
+ parser.add_argument(
41
+ "-xvuln",
42
+ type=int,
43
+ default=None,
44
+ metavar="X",
45
+ help="Afficher uniquement les X vulnérabilités les plus graves (ex: -xvuln 5)"
46
+ )
47
+
48
+ parser.add_argument(
49
+ "--no-ai",
50
+ action="store_true",
51
+ help="Désactiver le résumé IA"
52
+ )
53
+
54
+ parser.add_argument(
55
+ "--json",
56
+ action="store_true",
57
+ help="Générer un fichier au format JSON"
58
+ )
59
+
60
+ parser.add_argument(
61
+ "--html",
62
+ action="store_true",
63
+ help="Générer un fichier au format HTML"
64
+ )
65
+
66
+ parser.add_argument(
67
+ "--pdf",
68
+ action="store_true",
69
+ help="Générer un fichier au format PDF"
70
+ )
71
+
72
+
73
+ args = parser.parse_args()
74
+ file = Path(args.file)
75
+
76
+ console.print(BANNER)
77
+
78
+ # Verification validité des entrées clients
79
+ if not file.exists():
80
+ console.print(f"[bold red][ERREUR][/bold red] Fichier '{file}' introuvable.")
81
+ sys.exit(1)
82
+
83
+
84
+
85
+ if file.name != "requirements.txt":
86
+ console.print("[bold red][ERREUR][/bold red] SyfScan accepte uniquement les fichiers requirements.txt")
87
+ sys.exit(1)
88
+
89
+ # Parsing
90
+ console.print(f"[green]>[/green] [dim]Fichier cible :[/dim][cyan]{file}[/cyan]")
91
+ dependencies = parse_requirements(file) # fonction parse à récupérer dans parser/requirements.py
92
+
93
+
94
+ # Interrogation OSV.dev
95
+ report = scan_dependencies(dependencies) # fonction scan_dependencies à récupérer dans scanner/osv.py
96
+
97
+ # Output
98
+
99
+ #permet de choisir sous quel format exporter les données, par defaut dans le terminal
100
+ if args.json:
101
+ rapport_json(report, Path("rapport.json"), args.xvuln)
102
+ console.print("\n[bold green]Rapport JSON généré :[/bold green] rapport.json\n")
103
+ elif args.html:
104
+ rapport_html(report, Path("rapport.html"), args.xvuln)
105
+ console.print("\n[bold green]Rapport HTML généré :[/bold green] rapport.html\n")
106
+ elif args.pdf:
107
+ rapport_pdf(report, Path("rapport.pdf"), args.xvuln)
108
+ console.print("\n[bold green]Rapport PDF généré :[/bold green] rapport.pdf\n")
109
+
110
+ else :
111
+ display_report(report, args.xvuln)
112
+
113
+ # Résumé IA
114
+ if not args.no_ai:
115
+ print("Generating AI summary...")
116
+ ai_report = generate_summary(report)
117
+ display_summary(ai_report)
118
+
119
+ if __name__ == "__main__" :
120
+ main()
@@ -0,0 +1,4 @@
1
+ Metadata-Version: 2.4
2
+ Name: syfscan
3
+ Version: 0.1.0
4
+ Summary: Détecteur de vulnérabilités de dépendances
@@ -0,0 +1,20 @@
1
+ README.md
2
+ pyproject.toml
3
+ ia/__init__.py
4
+ ia/claude_summarizer.py
5
+ output/__init__.py
6
+ output/html.py
7
+ output/json.py
8
+ output/pdf.py
9
+ output/terminal.py
10
+ parsers/requirement.py
11
+ scanner/__init__.py
12
+ scanner/osv.py
13
+ syfscan/__init__.py
14
+ syfscan/__main__.py
15
+ syfscan/cli.py
16
+ syfscan.egg-info/PKG-INFO
17
+ syfscan.egg-info/SOURCES.txt
18
+ syfscan.egg-info/dependency_links.txt
19
+ syfscan.egg-info/entry_points.txt
20
+ syfscan.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ syfscan = syfscan.__main__:main
@@ -0,0 +1,7 @@
1
+ dist
2
+ ia
3
+ output
4
+ parsers
5
+ scanner
6
+ syfscan
7
+ test