Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions subtitle-downloader/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.env
.DS_Store
*.log
.idea/
.vscode/
*.swp
*.swo
.flake8
.pytest_cache/
downloads/
149 changes: 149 additions & 0 deletions subtitle-downloader/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""Flask application for Subtitle Downloader"""

import os
import logging
from flask import Flask, render_template, request, jsonify, send_file
from io import BytesIO
from config import config
from subtitle_handler import OpenSubtitlesHandler, validate_input, get_language_code

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Create Flask app
app = Flask(__name__)

# Load configuration
env = os.environ.get('FLASK_ENV', 'development')
app.config.from_object(config[env])
Comment on lines +18 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard unsupported FLASK_ENV values at startup.

Lines 18-19 index the config registry directly, so a typo or unexpected env value will fail during import and the app never starts. Please switch to an explicit lookup and raise a clear startup error or apply a deliberate fallback.

Suggested fix
 env = os.environ.get('FLASK_ENV', 'development')
-app.config.from_object(config[env])
+config_obj = config.get(env)
+if config_obj is None:
+    raise RuntimeError(f"Unsupported FLASK_ENV: {env!r}")
+app.config.from_object(config_obj)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env = os.environ.get('FLASK_ENV', 'development')
app.config.from_object(config[env])
env = os.environ.get('FLASK_ENV', 'development')
config_obj = config.get(env)
if config_obj is None:
raise RuntimeError(f"Unsupported FLASK_ENV: {env!r}")
app.config.from_object(config_obj)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@subtitle-downloader/app.py` around lines 18 - 19, Current code indexes config
with config[env] which will raise a KeyError on typos/unexpected FLASK_ENV
values; change to an explicit lookup: check whether env is in the config
registry (e.g., if env not in config) and either raise a clear startup error
(RuntimeError with a message including the invalid env and allowed keys) or
assign a deliberate fallback (e.g., fallback = 'development') before calling
app.config.from_object; use the env variable name and
app.config.from_object(config_lookup) to locate the change points.


# Initialize OpenSubtitles handler
handler = OpenSubtitlesHandler()


@app.route('/')
def index():
"""Render main page"""
return render_template('index.html')


@app.route('/api/search', methods=['POST'])
def search_subtitles():
"""
API endpoint to search for subtitles

Expected JSON:
{
"series_name": "Breaking Bad",
"season_episode": "S01E01",
"language": "English"
}
"""
try:
data = request.get_json()

if not data:
return jsonify({'error': 'No JSON data provided'}), 400

series_name = data.get('series_name', '').strip()
season_episode = data.get('season_episode', '').strip()
language = data.get('language', '').strip()

Comment on lines +43 to +52
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "app.py" -path "*/subtitle-downloader/*" -type f

Repository: f/prompts.chat

Length of output: 86


🏁 Script executed:

cat -n ./subtitle-downloader/app.py | head -100

Repository: f/prompts.chat

Length of output: 3501


🌐 Web query:

Flask request.get_json() behavior malformed JSON exception werkzeug

💡 Result:

flask.request.get_json() is implemented by Werkzeug’s Request.get_json(), and its error behavior depends on (a) whether the request claims to be JSON and (b) whether parsing succeeds.

What happens on malformed JSON?

  • If the request has a JSON content type (e.g. Content-Type: application/json) but the body is invalid JSON, get_json() calls on_json_loading_failed(); by default this raises a werkzeug.exceptions.BadRequest (HTTP 400). [1]

What happens if Content-Type isn’t JSON?

  • If the mimetype does not indicate JSON, Werkzeug will also call on_json_loading_failed() (with e=None). In newer Werkzeug/Flask stacks this commonly surfaces as HTTP 415 Unsupported Media Type (this changed over time; see changelogs). [1][2][3]

Parameters that control this

  • silent=True: suppresses mimetype/parsing errors and returns None instead of raising. [1]
  • force=True: ignores the mimetype and tries to parse JSON anyway. [1]
  • cache=True: caches the parsed result for subsequent calls (and Flask fixed some edge cases around caching failures with silent=True). [1][2]

Practical patterns

1) Return a clean 400 instead of an exception page

from flask import request
from werkzeug.exceptions import BadRequest

try:
    data = request.get_json()  # default: raises on bad JSON
except BadRequest:
    return {"error": "Malformed JSON"}, 400

2) Treat malformed JSON the same as “no JSON provided”

data = request.get_json(silent=True)
if data is None:
    return {"error": "Missing or invalid JSON"}, 400

3) Centralize handling

  • Register an error handler for BadRequest, or override Request.on_json_loading_failed() to customize the response. The default behavior is to raise BadRequest. [1]

Sources: Werkzeug Request.get_json / on_json_loading_failed docs (including 400 vs 415 notes) [1], Flask changelog notes about get_json behavior [2], Werkzeug changelog notes about 415 vs 400 behavior changes [3].

[1] (werkzeug.palletsprojects.com)
[2] (flask.palletsprojects.com)
[3] (werkzeug.palletsprojects.com)

Citations:


🏁 Script executed:

python3 <<'EOF'
# Test dict.get() behavior with different value types
data = {'key_with_null': None, 'key_with_num': 42, 'key_missing': None}

print("=== dict.get() behavior ===")
print(f"Missing key with default '': {repr(data.get('key_missing', ''))}")
print(f"Null value with default '': {repr(data.get('key_with_null', ''))}")
print(f"Numeric value with default '': {repr(data.get('key_with_num', ''))}")

print("\n=== str.strip() behavior ===")
try:
    result = None.strip()
except AttributeError as e:
    print(f"None.strip() → AttributeError: {e}")

try:
    result = 42.strip()
except AttributeError as e:
    print(f"42.strip() → AttributeError: {e}")

print(f"'  hello  '.strip() → {repr('  hello  '.strip())}")
EOF

Repository: f/prompts.chat

Length of output: 161


🏁 Script executed:

python3 <<'EOF'
# Test dict.get() behavior with different value types
data = {'key_with_null': None, 'key_with_num': 42, 'key_missing': None}

print("=== dict.get() behavior ===")
print(f"Missing key with default '': {repr(data.get('key_missing', ''))}")
print(f"Null value with default '': {repr(data.get('key_with_null', ''))}")
print(f"Numeric value with default '': {repr(data.get('key_with_num', ''))}")

print("\n=== str.strip() behavior ===")
try:
    None.strip()
except AttributeError as e:
    print(f"None.strip() → AttributeError: {e}")

try:
    num = 42
    num.strip()
except AttributeError as e:
    print(f"42.strip() → AttributeError: {e}")

print(f"'  hello  '.strip() → {repr('  hello  '.strip())}")
EOF

Repository: f/prompts.chat

Length of output: 387


🏁 Script executed:

cat -n ./subtitle-downloader/app.py | sed -n '43,82p'

Repository: f/prompts.chat

Length of output: 1658


Bad JSON and type mismatches currently return 500 instead of 400.

Lines 44–51 can fail before proper validation. request.get_json() raises BadRequest on malformed JSON; dict.get('key', '') returns None (not the default) when the key exists with a null value or non-string; and .strip() on None or numeric types raises AttributeError. Because the exception handler at lines 80–82 catches all exceptions indiscriminately, these client-side errors are returned as 500 instead of 400. Use silent=True to suppress BadRequest, validate the data structure and field types before calling .strip(), and let only unexpected server errors reach the broad exception handler.

Suggested fix
-        data = request.get_json()
+        data = request.get_json(silent=True)
 
-        if not data:
-            return jsonify({'error': 'No JSON data provided'}), 400
+        if not isinstance(data, dict):
+            return jsonify({'error': 'Invalid JSON body'}), 400
 
-        series_name = data.get('series_name', '').strip()
-        season_episode = data.get('season_episode', '').strip()
-        language = data.get('language', '').strip()
+        series_name = data.get('series_name')
+        season_episode = data.get('season_episode')
+        language = data.get('language')
+
+        if not all(isinstance(value, str) for value in (series_name, season_episode, language)):
+            return jsonify({
+                'error': 'series_name, season_episode, and language must be strings'
+            }), 400
+
+        series_name = series_name.strip()
+        season_episode = season_episode.strip()
+        language = language.strip()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@subtitle-downloader/app.py` around lines 43 - 52, Change request.get_json()
to request.get_json(silent=True) and validate before calling .strip(): ensure
the returned data is a dict (return jsonify error 400 if not), then for each
field (series_name, season_episode, language) fetch raw = data.get('field') and
if raw is None or not isinstance(raw, str) return a 400 JSON error; only after
those checks call raw.strip() into series_name/season_episode/language. Also
adjust the broad exception handling so that client-side issues (malformed JSON,
type errors, missing fields) return 400 rather than being swallowed as 500—e.g.,
explicitly return 400 for known client errors and let unexpected exceptions
propagate to the existing server-error handler.

# Validate input
validation = validate_input(series_name, season_episode, language)
if not validation['valid']:
return jsonify({
'error': 'Validation failed',
'errors': validation['errors']
}), 400

# Get language code
language_code = get_language_code(language)

# Search for subtitles
subtitles = handler.search_subtitles(series_name, season_episode, language_code)

if not subtitles:
return jsonify({
'success': True,
'message': 'No subtitles found for this series and episode',
'results': []
}), 200

return jsonify({
'success': True,
'message': f'Found {len(subtitles)} subtitle(s)',
'results': subtitles
}), 200

except Exception as e:
logger.error(f"Search error: {str(e)}")
return jsonify({'error': 'An error occurred during search'}), 500


@app.route('/api/download/<subtitle_id>', methods=['GET'])
def download_subtitle(subtitle_id):
"""
API endpoint to download a subtitle file

Args:
subtitle_id: ID of the subtitle to download
"""
try:
# Download subtitle
content, file_name = handler.download_subtitle(subtitle_id)

if content is None:
return jsonify({'error': 'Failed to download subtitle'}), 500

# Return file as attachment
return send_file(
BytesIO(content),
mimetype='text/plain',
as_attachment=True,
download_name=file_name
)

except Exception as e:
logger.error(f"Download error: {str(e)}")
return jsonify({'error': 'An error occurred during download'}), 500


@app.route('/api/languages', methods=['GET'])
def get_languages():
"""API endpoint to get list of supported languages"""
try:
languages = handler.get_supported_languages()
return jsonify({
'success': True,
'languages': languages
}), 200
except Exception as e:
logger.error(f"Languages error: {str(e)}")
return jsonify({'error': 'Failed to fetch languages'}), 500


@app.errorhandler(404)
def not_found(error):
"""Handle 404 errors"""
return jsonify({'error': 'Not found'}), 404


@app.errorhandler(500)
def server_error(error):
"""Handle 500 errors"""
logger.error(f"Server error: {str(error)}")
return jsonify({'error': 'Internal server error'}), 500


if __name__ == '__main__':
# Create downloads folder if it doesn't exist
os.makedirs(app.config['DOWNLOAD_FOLDER'], exist_ok=True)

# Run the Flask app
app.run(
host='0.0.0.0',
port=5000,
debug=app.config['DEBUG']
Comment on lines +145 to +148
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "app.py" | head -20

Repository: f/prompts.chat

Length of output: 86


🏁 Script executed:

head -160 subtitle-downloader/app.py | tail -30

Repository: f/prompts.chat

Length of output: 825


🏁 Script executed:

cat -n subtitle-downloader/app.py | sed -n '130,160p'

Repository: f/prompts.chat

Length of output: 693


🏁 Script executed:

head -50 subtitle-downloader/app.py

Repository: f/prompts.chat

Length of output: 1270


🏁 Script executed:

grep -n "DEBUG\|config\|Config" subtitle-downloader/app.py | head -20

Repository: f/prompts.chat

Length of output: 318


🏁 Script executed:

find . -type f -name "config.py" | head -10

Repository: f/prompts.chat

Length of output: 89


🏁 Script executed:

cat -n subtitle-downloader/config.py

Repository: f/prompts.chat

Length of output: 1985


Default Flask dev server to 127.0.0.1 instead of 0.0.0.0.

Lines 145-148 bind the dev server to all network interfaces. While this code only runs via python app.py (not in production), unnecessarily exposing the dev server to the network increases risk, especially when debug mode is enabled. Default to 127.0.0.1 (localhost only) and allow wider binding through explicit environment variable configuration.

Suggested fix
     app.run(
-        host='0.0.0.0',
+        host=os.environ.get('HOST', '127.0.0.1'),
         port=5000,
         debug=app.config['DEBUG']
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.run(
host='0.0.0.0',
port=5000,
debug=app.config['DEBUG']
app.run(
host=os.environ.get('HOST', '127.0.0.1'),
port=5000,
debug=app.config['DEBUG']
)
🧰 Tools
🪛 Ruff (0.15.5)

[error] 146-146: Possible binding to all interfaces

(S104)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@subtitle-downloader/app.py` around lines 145 - 148, Change the dev server
bind in the app.run call so it defaults to localhost instead of all interfaces:
replace the hardcoded '0.0.0.0' host in the app.run(...) invocation with a
configurable value that defaults to '127.0.0.1' (e.g., read from an environment
variable such as FLASK_RUN_HOST or a custom HOST env var) while keeping the
existing app.config['DEBUG'] usage; update the app.run call site so it uses that
env-derived host to allow explicit wider binding when needed.

)
63 changes: 63 additions & 0 deletions subtitle-downloader/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import os

class Config:
"""Base configuration"""
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded fallback secret key is a security risk in production.

The fallback 'dev-secret-key-change-in-production' will be used if the environment variable is not set, which could lead to predictable session tokens in production. Consider failing explicitly when SECRET_KEY is not set in production mode.

🛡️ Proposed fix
 class Config:
     """Base configuration"""
-    SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production'
+    SECRET_KEY = os.environ.get('SECRET_KEY')
     DEBUG = False
     TESTING = False

Then in ProductionConfig:

 class ProductionConfig(Config):
     """Production configuration"""
     DEBUG = False
+    
+    def __init__(self):
+        if not self.SECRET_KEY:
+            raise ValueError("SECRET_KEY environment variable must be set in production")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production'
SECRET_KEY = os.environ.get('SECRET_KEY')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@subtitle-downloader/config.py` at line 5, The config currently sets
SECRET_KEY = os.environ.get('SECRET_KEY') or
'dev-secret-key-change-in-production', which provides a dangerous production
fallback; remove the hardcoded fallback and make ProductionConfig require the
env var explicitly (e.g. read via os.environ['SECRET_KEY'] or validate and raise
RuntimeError/AssertionError if SECRET_KEY is missing) so that the app fails to
start when SECRET_KEY is not provided; update the SECRET_KEY declaration and/or
override it in the ProductionConfig class (referencing SECRET_KEY and
ProductionConfig) to enforce this check and include a clear error message.

DEBUG = False
TESTING = False

# OpenSubtitles API settings
OPENSUBTITLES_API_URL = 'https://api.opensubtitles.com/api/v1'
OPENSUBTITLES_USER_AGENT = 'DownloadSubtitles v1.0'

# Supported languages mapping
LANGUAGES = {
'English': 'en',
'Spanish': 'es',
'French': 'fr',
'German': 'de',
'Italian': 'it',
'Portuguese': 'pt',
'Russian': 'ru',
'Japanese': 'ja',
'Korean': 'ko',
'Chinese': 'zh',
'Dutch': 'nl',
'Polish': 'pl',
'Turkish': 'tr',
'Greek': 'el',
'Swedish': 'sv',
'Norwegian': 'no',
'Danish': 'da',
'Finnish': 'fi',
'Arabic': 'ar',
'Hebrew': 'he',
}

# File settings
DOWNLOAD_FOLDER = os.path.join(os.path.dirname(__file__), 'downloads')
MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50 MB max file size


class DevelopmentConfig(Config):
"""Development configuration"""
DEBUG = True


class ProductionConfig(Config):
"""Production configuration"""
DEBUG = False


class TestingConfig(Config):
"""Testing configuration"""
TESTING = True


# Configuration dictionary
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig,
'default': DevelopmentConfig
}
3 changes: 3 additions & 0 deletions subtitle-downloader/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Flask==2.3.0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check latest Flask version and security advisories
curl -s https://pypi.org/pypi/flask/json | jq '.info.version'

# Check for security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: PIP, package: "flask") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Repository: f/prompts.chat

Length of output: 226


🌐 Web query:

Flask 2.3.0 security vulnerabilities GHSA-m2qf-hxjv-5gpq GHSA-68rp-wp8r-4726

💡 Result:

Flask 2.3.0 is affected by both advisories

Advisory What it is Affected versions (includes Flask 2.3.0) Fix
GHSA-m2qf-hxjv-5gpq (CVE-2023-30861) Missing Vary: Cookie in a session refresh edge-case can let a caching proxy cache a response (and potentially a Set-Cookie), risking session-cookie disclosure when specific conditions are met. NVD lists affected as 2.3.0 to < 2.3.2 (and older branches up to < 2.2.5). [2] GitHub advisory describes the cache/proxy + session.permanent=True conditions and patched releases. [1] Upgrade to 2.3.2 (or 2.2.5 on the 2.2 line). [1][2]
GHSA-68rp-wp8r-4726 (CVE-2026-27205) Session access via some patterns (notably the Python in operator) may fail to add Vary: Cookie, enabling cache poisoning / sensitive-response caching behind certain proxies. 3.1.2 and below (i.e., < 3.1.3). [3][4] Upgrade to 3.1.3. [3][4]

Practical remediation for a Flask 2.3.0 app

  • For GHSA-m2qf-hxjv-5gpq: upgrade at least to 2.3.2. [1][2]
  • For GHSA-68rp-wp8r-4726: there is no fix in the 2.3.x line in the advisory data; upgrading to 3.1.3 is the stated fix. [3][4]
  • If you can’t upgrade immediately, mitigate cache risk by ensuring responses are not cached (e.g., set Cache-Control: private, no-store where appropriate) and avoid relying on session access patterns that might not trigger Vary: Cookie. [3][4]

Sources: GitHub Security Advisory for GHSA-m2qf-hxjv-5gpq [1], NVD CVE-2023-30861 [2], OSV GHSA-68rp-wp8r-4726 [3], NVD CVE-2026-27205 [4].


🌐 Web query:

Flask 2.3.0 session cookie vulnerability Vary Cookie header

💡 Result:

Flask 2.3.0 is affected by CVE-2023-30861: under certain conditions, Flask may send a refreshed permanent session cookie (Set-Cookie) without also adding Vary: Cookie, which can let a shared/caching proxy cache and replay a response (and potentially the Set-Cookie header) across different clients. This can result in disclosure of another user’s session cookie. [1][2]

When it can happen (all must apply): behind a caching proxy that doesn’t ignore/strip cookies, session.permanent = True, your view does not access/modify session, SESSION_REFRESH_EACH_REQUEST=True (default), and you don’t mark responses as private/not cacheable (e.g., missing protective Cache-Control). [1][2]

Fix: upgrade Flask to 2.3.2+ (or 2.2.5 on the 2.2.x line). [1][2]

Mitigations if you can’t upgrade immediately:

  • Ensure sensitive/session-dependent responses are not shared-cached (e.g., Cache-Control: private or no-store). [1][2]
  • (Defense-in-depth) Add Vary: Cookie on responses where a session cookie might be refreshed. [1][2]

Sources: GitHub Advisory (GHSA-m2qf-hxjv-5gpq) / CVE-2023-30861, and NVD entry. [1][2]


🌐 Web query:

Flask security advisory PYSEC-2023-62

💡 Result:

PYSEC-2023-62 (Flask) maps to CVE-2023-30861 / GHSA-m2qf-hxjv-5gpq: a scenario where a caching proxy can cache and replay responses across clients, potentially causing one client’s permanent session cookie (Set-Cookie) to be sent to other clients when Vary: Cookie is missing/insufficient. [1]

When it can happen (all required):

  • App is behind a caching proxy that might cache responses even with cookies. [1]
  • session.permanent = True. [1]
  • The request does not access/modify the session, but the session cookie gets refreshed. [1]
  • SESSION_REFRESH_EACH_REQUEST = True (default). [1]
  • No Cache-Control marking the response as private / not cacheable. [1]

Affected versions (per PyPI advisory aggregations):

  • Flask < 2.2.5, and 2.3.0–2.3.1 (i.e., >2.3.0, <2.3.2). [2]

Fixed versions:

  • Upgrade to Flask 2.2.5 (2.2.x line) or Flask 2.3.2 (2.3.x line). [1][2]

Mitigations (if you can’t upgrade immediately):

  • Ensure responses that may set/refresh cookies are not cacheable (e.g., appropriate Cache-Control), and/or configure your proxy/CDN to not cache responses with Set-Cookie / vary on cookies correctly. [1]

Sources: GitHub Security Advisory (Flask) [1]; PYSEC-2023-62 aggregation with affected ranges [2].

[1] (github.com)
[2] (resolvedsecurity.com)

Citations:


Upgrade Flask from 2.3.0 to patch critical session cookie vulnerabilities.

Flask 2.3.0 is affected by two HIGH severity vulnerabilities:

  • GHSA-m2qf-hxjv-5gpq (CVE-2023-30861): Missing Vary: Cookie header in session refresh scenarios can allow caching proxies to leak session cookies across clients when session.permanent=True and responses lack protective Cache-Control headers. Fixed in Flask 2.3.2+ (or 2.2.5 for 2.2.x line).
  • GHSA-68rp-wp8r-4726 (CVE-2026-27205): Session access via certain patterns (e.g., Python in operator) may fail to add Vary: Cookie, enabling cache poisoning behind shared proxies. Requires Flask 3.1.3+ (no patch available in 2.3.x line).

Minimum remediation: upgrade to Flask 2.3.2. For complete coverage of both vulnerabilities, upgrade to 3.1.3.

🧰 Tools
🪛 OSV Scanner (2.3.3)

[HIGH] 1-1: flask 2.3.0: undefined

(PYSEC-2023-62)


[HIGH] 1-1: flask 2.3.0: Flask session does not add Vary: Cookie header when accessed in some ways

(GHSA-68rp-wp8r-4726)


[HIGH] 1-1: flask 2.3.0: Flask vulnerable to possible disclosure of permanent session cookie due to missing Vary: Cookie header

(GHSA-m2qf-hxjv-5gpq)


[HIGH] 1-1: flask 2.3.0: undefined

(PYSEC-2023-62)


[HIGH] 1-1: flask 2.3.0: Flask session does not add Vary: Cookie header when accessed in some ways

(GHSA-68rp-wp8r-4726)


[HIGH] 1-1: flask 2.3.0: Flask vulnerable to possible disclosure of permanent session cookie due to missing Vary: Cookie header

(GHSA-m2qf-hxjv-5gpq)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@subtitle-downloader/requirements.txt` at line 1, Update the Flask dependency
entry in requirements.txt from "Flask==2.3.0" to a patched version; change the
line to "Flask==2.3.2" (minimum remediation) or "Flask==3.1.3" for full coverage
of both CVEs, then re-run any lock/installation steps (e.g., pip install -r
requirements.txt or your dependency lock generation) to ensure the updated
package is applied; locate the single dependency line "Flask==2.3.0" in
requirements.txt and replace it accordingly.

requests==2.31.0
python-dotenv==1.0.0
Loading