diff --git a/aperisolve/__init__.py b/aperisolve/__init__.py index e69de29..538f764 100644 --- a/aperisolve/__init__.py +++ b/aperisolve/__init__.py @@ -0,0 +1,3 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable diff --git a/aperisolve/analyzers/__init__.py b/aperisolve/analyzers/__init__.py index e69de29..538f764 100644 --- a/aperisolve/analyzers/__init__.py +++ b/aperisolve/analyzers/__init__.py @@ -0,0 +1,3 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable diff --git a/aperisolve/analyzers/binwalk.py b/aperisolve/analyzers/binwalk.py index 6c05b9a..9b8cfac 100644 --- a/aperisolve/analyzers/binwalk.py +++ b/aperisolve/analyzers/binwalk.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Binwalk Analyzer for Image Submissions.""" import shutil diff --git a/aperisolve/analyzers/decomposer.py b/aperisolve/analyzers/decomposer.py index e6440d3..cff14d5 100644 --- a/aperisolve/analyzers/decomposer.py +++ b/aperisolve/analyzers/decomposer.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Bits Decomposer Analyzer for Image Submissions.""" from pathlib import Path diff --git a/aperisolve/analyzers/exiftool.py b/aperisolve/analyzers/exiftool.py index e13e2e9..5ed107d 100644 --- a/aperisolve/analyzers/exiftool.py +++ b/aperisolve/analyzers/exiftool.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Exiftool Analyzer for Image Submissions.""" import subprocess diff --git a/aperisolve/analyzers/foremost.py b/aperisolve/analyzers/foremost.py index 5c55972..eb0fc8c 100644 --- a/aperisolve/analyzers/foremost.py +++ b/aperisolve/analyzers/foremost.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Foremost Analyzer for Image Submissions.""" import shutil diff --git a/aperisolve/analyzers/image_resize.py b/aperisolve/analyzers/image_resize.py index bf30897..17db9b5 100644 --- a/aperisolve/analyzers/image_resize.py +++ b/aperisolve/analyzers/image_resize.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Image size bruteforcer for common PNG sizes.""" from pathlib import Path diff --git a/aperisolve/analyzers/openstego.py b/aperisolve/analyzers/openstego.py index 597356a..aa6e989 100644 --- a/aperisolve/analyzers/openstego.py +++ b/aperisolve/analyzers/openstego.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """OpenStego Analyzer for Image Submissions.""" import shutil diff --git a/aperisolve/analyzers/outguess.py b/aperisolve/analyzers/outguess.py index 976979b..9fc6a78 100644 --- a/aperisolve/analyzers/outguess.py +++ b/aperisolve/analyzers/outguess.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Outguess Analyzer for Image Submissions.""" import shutil diff --git a/aperisolve/analyzers/pngcheck.py b/aperisolve/analyzers/pngcheck.py index a398bd5..730e67c 100644 --- a/aperisolve/analyzers/pngcheck.py +++ b/aperisolve/analyzers/pngcheck.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Pngcheck Analyzer for Image Submissions.""" import subprocess diff --git a/aperisolve/analyzers/steghide.py b/aperisolve/analyzers/steghide.py index d74d077..0602bd6 100644 --- a/aperisolve/analyzers/steghide.py +++ b/aperisolve/analyzers/steghide.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Steghide Analyzer for Image Submissions.""" import re diff --git a/aperisolve/analyzers/strings.py b/aperisolve/analyzers/strings.py index 42c0d53..6757fdb 100644 --- a/aperisolve/analyzers/strings.py +++ b/aperisolve/analyzers/strings.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Strings Analyzer for Image Submissions.""" import subprocess diff --git a/aperisolve/analyzers/template_analyzer.py b/aperisolve/analyzers/template_analyzer.py index bf86779..0008497 100644 --- a/aperisolve/analyzers/template_analyzer.py +++ b/aperisolve/analyzers/template_analyzer.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Custom Analyzer for Image Submissions.""" import shutil diff --git a/aperisolve/analyzers/utils.py b/aperisolve/analyzers/utils.py index da86987..3387106 100644 --- a/aperisolve/analyzers/utils.py +++ b/aperisolve/analyzers/utils.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Utility functions for analyzers modules.""" import fcntl diff --git a/aperisolve/analyzers/zsteg.py b/aperisolve/analyzers/zsteg.py index f530390..0435841 100644 --- a/aperisolve/analyzers/zsteg.py +++ b/aperisolve/analyzers/zsteg.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Zsteg Analyzer for Image Submissions.""" import subprocess diff --git a/aperisolve/app.py b/aperisolve/app.py index a3215b2..3b1ee5d 100644 --- a/aperisolve/app.py +++ b/aperisolve/app.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Aperi'Solve Flask application.""" import hashlib @@ -9,46 +12,18 @@ from pathlib import Path from typing import Any, Optional import sentry_sdk -from sentry_sdk.integrations.flask import FlaskIntegration -from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration -from sentry_sdk.integrations.rq import RqIntegration -from sentry_sdk.integrations.threading import ThreadingIntegration - from flask import Flask, Response, abort, jsonify, render_template, request, send_file from redis import Redis from rq import Queue +from .sentry import initialize_sentry + +initialize_sentry() + from .cleanup import cleanup_old_entries from .config import IMAGE_EXTENSIONS, RESULT_FOLDER, WORKER_FILES from .models import Image, Submission, db, init_db -SENTRY_DSN = os.environ.get("SENTRY_DSN") -ENVIRONMENT = os.environ.get("ENVIRONMENT", "development") - -if SENTRY_DSN: - sentry_sdk.init( - dsn=SENTRY_DSN, - integrations=[ - FlaskIntegration(), - SqlalchemyIntegration(), - RqIntegration(), - ThreadingIntegration(propagate_hub=True), - ], - # Set traces_sample_rate to 1.0 to capture 100% of transactions for performance monitoring. - # Adjust this value in production (e.g., 0.1 for 10%) - traces_sample_rate=float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "0.1")), - # Set profiles_sample_rate to 1.0 to profile 100% of sampled transactions. - profiles_sample_rate=float(os.environ.get("SENTRY_PROFILES_SAMPLE_RATE", "0.1")), - # Environment tag (production, staging, development) - environment=ENVIRONMENT, - # Release version (use git commit hash or version number) - release=os.environ.get("SENTRY_RELEASE", "1.0.0"), - # Send default PII (user IPs, etc.) - set to False for privacy - send_default_pii=False, - # Enable automatic breadcrumb tracking - enable_tracing=True, - ) - app: Flask = Flask(__name__) app.json.sort_keys = False # type: ignore app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DB_URI") diff --git a/aperisolve/cleanup.py b/aperisolve/cleanup.py index 402cd25..5a63f96 100644 --- a/aperisolve/cleanup.py +++ b/aperisolve/cleanup.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Cleanup old entries in the database and file system.""" import shutil diff --git a/aperisolve/config.py b/aperisolve/config.py index 96016c3..f4a6458 100644 --- a/aperisolve/config.py +++ b/aperisolve/config.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Aperi'Solve configuration variables.""" from pathlib import Path diff --git a/aperisolve/models.py b/aperisolve/models.py index fa0d345..b064689 100644 --- a/aperisolve/models.py +++ b/aperisolve/models.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """This module defines the database models for the Aperi'Solve application.""" import itertools diff --git a/aperisolve/sentry.py b/aperisolve/sentry.py new file mode 100644 index 0000000..bb87683 --- /dev/null +++ b/aperisolve/sentry.py @@ -0,0 +1,46 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable +"""Sentry initialization for Aperisolve.""" + +import os + +import sentry_sdk +from sentry_sdk.integrations.flask import FlaskIntegration +from sentry_sdk.integrations.rq import RqIntegration +from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration +from sentry_sdk.integrations.threading import ThreadingIntegration + +SENTRY_DSN = os.environ.get("SENTRY_DSN") +ENVIRONMENT = os.environ.get("ENVIRONMENT", "development") + + +def initialize_sentry() -> None: + """Initialize Sentry SDK once, safely.""" + + # No DSN → do nothing + dsn = os.environ.get("SENTRY_DSN") + if not dsn: + return + + # Prevent double initialization (Flask + RQ, CLI, tests, etc.) + if sentry_sdk.Hub.current.client is not None: + return + + sentry_sdk.init( + dsn=dsn, + integrations=[ + FlaskIntegration(), + SqlalchemyIntegration(), + RqIntegration(), + ThreadingIntegration(propagate_hub=True), + ], + traces_sample_rate=float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "0.1")), + profiles_sample_rate=float( + os.environ.get("SENTRY_PROFILES_SAMPLE_RATE", "0.1") + ), + environment=os.environ.get("ENVIRONMENT", "development"), + release=os.environ.get("SENTRY_RELEASE", "1.0.0"), + send_default_pii=False, + enable_tracing=True, + ) diff --git a/aperisolve/workers.py b/aperisolve/workers.py index 247b40b..72b7861 100644 --- a/aperisolve/workers.py +++ b/aperisolve/workers.py @@ -1,32 +1,19 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Asynchronous worker for analyzing image submissions.""" -import os import threading from pathlib import Path from typing import Any import sentry_sdk -from sentry_sdk.integrations.rq import RqIntegration -from sentry_sdk.integrations.threading import ThreadingIntegration -from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration -# Initialize Sentry for workers (separate process from Flask app) -SENTRY_DSN = os.environ.get("SENTRY_DSN") -if SENTRY_DSN: - sentry_sdk.init( - dsn=SENTRY_DSN, - integrations=[ - RqIntegration(), - ThreadingIntegration(propagate_hub=True), # Capture exceptions in threads - SqlalchemyIntegration(), - ], - traces_sample_rate=float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "0.1")), - environment=os.environ.get("ENVIRONMENT", "development"), - release=os.environ.get("SENTRY_RELEASE", "1.0.0"), - send_default_pii=False, - ) +from .sentry import initialize_sentry -from .analyzers.binwalk import analyze_binwalk +initialize_sentry() + +from .analyzers.binwalk import analyze_binwalk # pylint: disable=C0413 from .analyzers.decomposer import analyze_decomposer from .analyzers.exiftool import analyze_exiftool from .analyzers.foremost import analyze_foremost @@ -74,8 +61,7 @@ def analyze_image(submission_hash: str) -> None: if not submission or not image: # No submission found sentry_sdk.capture_message( - f"Submission or image not found: {submission_hash}", - level="warning" + f"Submission or image not found: {submission_hash}", level="warning" ) return @@ -127,10 +113,10 @@ def analyze_image(submission_hash: str) -> None: for thread in threads: thread.join() - submission.status = "completed" + submission.status = "completed" # type: ignore except Exception as e: sentry_sdk.capture_exception(e) - submission.status = "error" + submission.status = "error" # type: ignore finally: db.session.commit() # pylint: disable=no-member # Flush Sentry events - wait up to 5 seconds for events to be sent diff --git a/aperisolve/wsgi.py b/aperisolve/wsgi.py index 0bf784b..a220be7 100644 --- a/aperisolve/wsgi.py +++ b/aperisolve/wsgi.py @@ -1,3 +1,6 @@ +# flake8: noqa: E203,E501,W503 +# pylint: disable=C0413,W0718,R0903,R0801 +# mypy: disable-error-code=unused-awaitable """Aperi'Solve WSGI runner.""" from .app import app as application