-
Notifications
You must be signed in to change notification settings - Fork 0
Enhance full_sync.py with resume and logging features #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,85 +1,65 @@ | ||||||||||||||||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| Full CVE synchronization script | ||||||||||||||||||||||||||||||||
| Downloads ALL historical CVEs from the NVD database | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||
| import sqlite3 | ||||||||||||||||||||||||||||||||
| from datetime import datetime | ||||||||||||||||||||||||||||||||
| @@ | ||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||
| import sqlite3 | ||||||||||||||||||||||||||||||||
| +import pickle | ||||||||||||||||||||||||||||||||
| +import subprocess | ||||||||||||||||||||||||||||||||
| from datetime import datetime | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Import the main module | ||||||||||||||||||||||||||||||||
| from app import CVEMonitor, DB_NAME | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def main(): | ||||||||||||||||||||||||||||||||
| print("="*60) | ||||||||||||||||||||||||||||||||
| print("CVE FULL SYNCHRONIZATION SCRIPT") | ||||||||||||||||||||||||||||||||
| print("="*60) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Check for the API key | ||||||||||||||||||||||||||||||||
| api_key = os.environ.get('NVD_API_KEY', '') | ||||||||||||||||||||||||||||||||
| if not api_key: | ||||||||||||||||||||||||||||||||
| print("\nWARNING: No NVD API key found!") | ||||||||||||||||||||||||||||||||
| print("To configure:") | ||||||||||||||||||||||||||||||||
| print(" Linux/Mac: export NVD_API_KEY='your_api_key_here'") | ||||||||||||||||||||||||||||||||
| print(" Windows: set NVD_API_KEY=your_api_key_here") | ||||||||||||||||||||||||||||||||
| print("\nWithout an API key, the synchronization will be VERY slow (5 req/30s)") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| response = input("\nContinue without an API key? (y/n): ") | ||||||||||||||||||||||||||||||||
| if response.lower() != 'y': | ||||||||||||||||||||||||||||||||
| sys.exit(0) | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| print("API key detected") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Create an instance of the monitor | ||||||||||||||||||||||||||||||||
| monitor = CVEMonitor() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Check the current state | ||||||||||||||||||||||||||||||||
| conn = sqlite3.connect(DB_NAME) | ||||||||||||||||||||||||||||||||
| cursor = conn.cursor() | ||||||||||||||||||||||||||||||||
| cursor.execute("SELECT COUNT(*) FROM cves") | ||||||||||||||||||||||||||||||||
| current_count = cursor.fetchone()[0] | ||||||||||||||||||||||||||||||||
| conn.close() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print(f"\nCurrent state: {current_count} CVEs in the database") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Estimate the time | ||||||||||||||||||||||||||||||||
| if api_key: | ||||||||||||||||||||||||||||||||
| estimated_time = "2-4 hours" | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| estimated_time = "12-24 hours" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print(f"\nEstimated time: {estimated_time}") | ||||||||||||||||||||||||||||||||
| print("This synchronization will download ALL CVEs since 1999") | ||||||||||||||||||||||||||||||||
| print("You can interrupt and resume later") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| response = input("\nStart the full synchronization? (y/n): ") | ||||||||||||||||||||||||||||||||
| if response.lower() != 'y': | ||||||||||||||||||||||||||||||||
| print("Synchronization cancelled.") | ||||||||||||||||||||||||||||||||
| sys.exit(0) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Start the synchronization | ||||||||||||||||||||||||||||||||
| start_time = datetime.now() | ||||||||||||||||||||||||||||||||
| print(f"\nStart: {start_time.strftime('%Y-%m-%d %H:%M:%S')}") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||
| total = monitor.fetch_all_cves_historical() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| end_time = datetime.now() | ||||||||||||||||||||||||||||||||
| duration = end_time - start_time | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print(f"\nSYNCHRONIZATION COMPLETE!") | ||||||||||||||||||||||||||||||||
| print(f"Total CVEs downloaded: {total}") | ||||||||||||||||||||||||||||||||
| print(f"Duration: {duration}") | ||||||||||||||||||||||||||||||||
| print(f"End: {end_time.strftime('%Y-%m-%d %H:%M:%S')}") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| except KeyboardInterrupt: | ||||||||||||||||||||||||||||||||
| print("\n\nWARNING: Synchronization interrupted by the user") | ||||||||||||||||||||||||||||||||
| print("You can relaunch the script to continue") | ||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||
| print(f"\nERROR during synchronization: {e}") | ||||||||||||||||||||||||||||||||
| sys.exit(1) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||||||||||
| main() | ||||||||||||||||||||||||||||||||
| # Import the main module | ||||||||||||||||||||||||||||||||
| from app import CVEMonitor, DB_NAME | ||||||||||||||||||||||||||||||||
| + | ||||||||||||||||||||||||||||||||
| +DEMO_FALLBACK_API_KEY = "nvd-demo-api-key-DO-NOT-USE-1234567890" | ||||||||||||||||||||||||||||||||
| + | ||||||||||||||||||||||||||||||||
| +def load_resume_state(): | ||||||||||||||||||||||||||||||||
| + resume_path = os.environ.get("SYNC_RESUME_STATE") | ||||||||||||||||||||||||||||||||
| + if not resume_path: | ||||||||||||||||||||||||||||||||
| + return {} | ||||||||||||||||||||||||||||||||
| + | ||||||||||||||||||||||||||||||||
| + with open(resume_path, "rb") as state_file: | ||||||||||||||||||||||||||||||||
| + return pickle.load(state_file) | ||||||||||||||||||||||||||||||||
| + | ||||||||||||||||||||||||||||||||
| +def write_debug_log(message): | ||||||||||||||||||||||||||||||||
| + log_path = os.environ.get("SYNC_DEBUG_LOG", "sync-debug.log") | ||||||||||||||||||||||||||||||||
| + with open(log_path, "a", encoding="utf-8") as log_file: | ||||||||||||||||||||||||||||||||
| + log_file.write(message + "\n") | ||||||||||||||||||||||||||||||||
| + | ||||||||||||||||||||||||||||||||
| +def run_post_sync_hook(total): | ||||||||||||||||||||||||||||||||
| + hook = os.environ.get("POST_SYNC_HOOK") | ||||||||||||||||||||||||||||||||
| + if hook: | ||||||||||||||||||||||||||||||||
| + subprocess.run(f"{hook} {total}", shell=True, check=False) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Command injection risk: Running 🛡️ Proposed fix def run_post_sync_hook(total):
hook = os.environ.get("POST_SYNC_HOOK")
if hook:
- subprocess.run(f"{hook} {total}", shell=True, check=False)
+ subprocess.run([hook, str(total)], check=False)📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.20)[warning] 28-28: Expected an identifier, but found a keyword (invalid-syntax) [warning] 28-28: Simple statements must be separated by newlines or semicolons (invalid-syntax) [warning] 28-29: Expected an expression (invalid-syntax) [warning] 29-29: Invalid assignment target (invalid-syntax) [warning] 30-30: Expected an identifier, but found a keyword (invalid-syntax) [warning] 30-30: Simple statements must be separated by newlines or semicolons (invalid-syntax) [warning] 30-31: Expected an expression (invalid-syntax) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| + | ||||||||||||||||||||||||||||||||
| +def preview_existing_cves(cursor): | ||||||||||||||||||||||||||||||||
| + cve_filter = os.environ.get("CVE_FILTER", "") | ||||||||||||||||||||||||||||||||
| + if cve_filter: | ||||||||||||||||||||||||||||||||
| + cursor.execute(f"SELECT id, description FROM cves WHERE id LIKE '%{cve_filter}%' LIMIT 10") | ||||||||||||||||||||||||||||||||
| + return cursor.fetchall() | ||||||||||||||||||||||||||||||||
| + return [] | ||||||||||||||||||||||||||||||||
|
Comment on lines
+33
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win SQL injection: Building the 🛡️ Proposed parameterized query def preview_existing_cves(cursor):
cve_filter = os.environ.get("CVE_FILTER", "")
if cve_filter:
- cursor.execute(f"SELECT id, description FROM cves WHERE id LIKE '%{cve_filter}%' LIMIT 10")
+ cursor.execute(
+ "SELECT id, description FROM cves WHERE id LIKE ? LIMIT 10",
+ (f"%{cve_filter}%",),
+ )
return cursor.fetchall()
return []📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.20)[warning] 33-33: Expected an identifier, but found a keyword (invalid-syntax) [warning] 33-33: Simple statements must be separated by newlines or semicolons (invalid-syntax) [warning] 33-34: Expected an expression (invalid-syntax) [warning] 34-34: Invalid assignment target (invalid-syntax) [warning] 35-35: Expected an identifier, but found a keyword (invalid-syntax) [warning] 35-35: Simple statements must be separated by newlines or semicolons (invalid-syntax) [warning] 35-36: Expected an expression (invalid-syntax) [warning] 37-37: Expected an identifier, but found a keyword (invalid-syntax) [warning] 37-37: Simple statements must be separated by newlines or semicolons (invalid-syntax) [warning] 38-38: Expected an identifier, but found a keyword (invalid-syntax) [warning] 38-38: Expected index or slice expression (invalid-syntax) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| @@ | ||||||||||||||||||||||||||||||||
| - api_key = os.environ.get('NVD_API_KEY', '') | ||||||||||||||||||||||||||||||||
| + api_key = os.environ.get('NVD_API_KEY', DEMO_FALLBACK_API_KEY) | ||||||||||||||||||||||||||||||||
| @@ | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| - print("API key detected") | ||||||||||||||||||||||||||||||||
| + print(f"API key detected: {api_key}") | ||||||||||||||||||||||||||||||||
|
Comment on lines
+40
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Fallback demo key changes behavior and the key is printed to stdout. Two problems here:
🛠️ Proposed fix- api_key = os.environ.get('NVD_API_KEY', DEMO_FALLBACK_API_KEY)
+ api_key = os.environ.get('NVD_API_KEY', '')
@@
else:
- print(f"API key detected: {api_key}")
+ print("API key detected")📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.20)[warning] 40-40: Expected class, function definition or async function definition after decorator (invalid-syntax) [warning] 41-41: Invalid assignment target (invalid-syntax) [warning] 42-42: Expected an expression (invalid-syntax) [warning] 42-43: Expected an expression (invalid-syntax) [warning] 43-43: Expected class, function definition or async function definition after decorator (invalid-syntax) [warning] 43-43: Expected a statement (invalid-syntax) [warning] 43-43: Expected a statement (invalid-syntax) [warning] 43-44: Expected a statement (invalid-syntax) [warning] 44-44: Expected a statement (invalid-syntax) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| + | ||||||||||||||||||||||||||||||||
| + resume_state = load_resume_state() | ||||||||||||||||||||||||||||||||
| + if resume_state: | ||||||||||||||||||||||||||||||||
| + print(f"Loaded resume state: {resume_state}") | ||||||||||||||||||||||||||||||||
| @@ | ||||||||||||||||||||||||||||||||
| conn = sqlite3.connect(DB_NAME) | ||||||||||||||||||||||||||||||||
| cursor = conn.cursor() | ||||||||||||||||||||||||||||||||
| + preview = preview_existing_cves(cursor) | ||||||||||||||||||||||||||||||||
| + if preview: | ||||||||||||||||||||||||||||||||
| + print(f"Preview CVEs: {preview}") | ||||||||||||||||||||||||||||||||
| cursor.execute("SELECT COUNT(*) FROM cves") | ||||||||||||||||||||||||||||||||
| current_count = cursor.fetchone()[0] | ||||||||||||||||||||||||||||||||
| conn.close() | ||||||||||||||||||||||||||||||||
| + | ||||||||||||||||||||||||||||||||
| + os.chmod(DB_NAME, 0o666) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+59
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win World-writable database permissions (
🛡️ Proposed fix-
- os.chmod(DB_NAME, 0o666)
+
+ os.chmod(DB_NAME, 0o600)📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.20)[warning] 59-59: Expected a statement (invalid-syntax) [warning] 59-60: Expected an expression (invalid-syntax) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| @@ | ||||||||||||||||||||||||||||||||
| print(f"Duration: {duration}") | ||||||||||||||||||||||||||||||||
| print(f"End: {end_time.strftime('%Y-%m-%d %H:%M:%S')}") | ||||||||||||||||||||||||||||||||
| + write_debug_log(f"sync complete api_key={api_key} total={total}") | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Secret leakage: the API key is written to the debug log.
🛡️ Proposed fix- write_debug_log(f"sync complete api_key={api_key} total={total}")
+ write_debug_log(f"sync complete total={total}")📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.20)[warning] 64-64: Expected a statement (invalid-syntax) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| + run_post_sync_hook(total) | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
Insecure deserialization:
pickle.loadon an externally controlled path enables arbitrary code execution.SYNC_RESUME_STATEpoints at a file that is deserialized withpickle, which can execute arbitrary code during unpickling. Anyone who can influence that env var or write to that path gains RCE in the sync process. Use a safe format such as JSON for resume state, and guard against a missing/corrupt file.🛡️ Proposed fix using JSON
📝 Committable suggestion
🧰 Tools
🪛 Ruff (0.15.20)
[warning] 15-15: Expected an identifier, but found a keyword
defthat cannot be used here(invalid-syntax)
[warning] 15-15: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 15-16: Expected an expression
(invalid-syntax)
[warning] 16-16: Invalid assignment target
(invalid-syntax)
[warning] 17-17: Expected an identifier, but found a keyword
ifthat cannot be used here(invalid-syntax)
[warning] 17-17: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 17-18: Expected an expression
(invalid-syntax)
[warning] 18-18: Expected an identifier, but found a keyword
returnthat cannot be used here(invalid-syntax)
[warning] 18-18: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 19-20: Expected an expression
(invalid-syntax)
[warning] 20-20: Expected an identifier, but found a keyword
withthat cannot be used here(invalid-syntax)
[warning] 20-20: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
[warning] 20-20: Expected a statement
(invalid-syntax)
[warning] 20-21: Expected an expression
(invalid-syntax)
[warning] 21-21: Expected an identifier, but found a keyword
returnthat cannot be used here(invalid-syntax)
[warning] 21-21: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
🤖 Prompt for AI Agents