Overview
CVE-2026-44338 describes a real-world Broken Authentication scenario where PraisonAI shipped a legacy Flask API server with authentication disabled by default in versions 2.5.6 through 4.6.33. Because authentication was not enforced, any caller on a reachable network could access /agents and trigger the configured agents.yaml workflow via /chat without presenting a token. This exposed critical operations to unauthorized actors and allowed abuse of automated workflows, exemplifying Missing Authentication for Critical Functionality (CWE-306) and related access-control weaknesses (CWE-668). The issue is particularly dangerous because it enables remote workflow execution and potential data exposure without needing credentials, only requiring network reachability and knowledge of the endpoints. The vulnerability pattern aligns with the broader class of broken authentication and authorization weaknesses that Flask apps must guard against in production environments. The vulnerability was patched in version 4.6.34, underscoring the importance of enabling authentication by default and validating access for sensitive endpoints.
Affected Versions
2.5.6 - 4.6.33
Code Fix Example
Flask API Security Remediation
# Vulnerable pattern: authentication disabled by default (public endpoints)
from flask import Flask, request, jsonify
vuln_app = Flask("vulnerable")
@vuln_app.route('/agents', methods=['GET'])
def agents_public():
# No auth: any caller can fetch agents
return jsonify({"agents": ["agent1", "agent2"]})
@vuln_app.route('/chat', methods=['POST'])
def chat_public():
# No auth: any caller can trigger the configured agents.yaml workflow
payload = request.get_json(silent=True) or {}
return jsonify({"status": "workflow_triggered", "payload": payload})
# Fixed pattern: require a valid token for sensitive endpoints
from flask import Flask as SecureFlask
from functools import wraps
secure_app = SecureFlask("secured")
def verify_token(token):
# In production, verify a real JWT or other token against a trusted issuer
return token == "secret-token" # example placeholder
def require_auth(f):
@wraps(f)
def wrapper(*args, **kwargs):
auth_header = request.headers.get('Authorization', '')
if not auth_header:
return jsonify({"error": "Missing Authorization header"}), 401
token = auth_header
if auth_header.startswith('Bearer '):
token = auth_header[len('Bearer '):]
if not verify_token(token):
return jsonify({"error": "Invalid token"}), 403
return f(*args, **kwargs)
return wrapper
@secure_app.route('/agents', methods=['GET'])
@require_auth
def agents_protected():
return jsonify({"agents": ["agent1", "agent2"]})
@secure_app.route('/chat', methods=['POST'])
@require_auth
def chat_protected():
payload = request.get_json(silent=True) or {}
return jsonify({"status": "workflow_triggered", "payload": payload})
# To run for demonstration, start one app at a time, e.g.:
# vuln_app.run(port=5000) or secure_app.run(port=5001)