Skip to content

[Security] 18 findings (7 Critical, 8 High, 2 Medium, 1 Low) #2

Description

@pvz122

Security report for Delta-Compute/DeltaCFOAgent

Hello maintainers,

I am a security researcher studying security risks in vibe-coded software. During this research, I reviewed this repository and identified the findings below. These findings were identified in commit 34ae558. Each finding has undergone human analysis, but the report may still contain mistakes or incomplete interpretations. Please review this report and apply the necessary security fixes.

This report contains 18 confirmed findings identified during security review. Validation details below distinguish code evidence from runtime observations and note any limitations.

Summary

ID Severity Category Affected area
SEC-001 Critical Cryptographic Failures apply_schema_direct.py:16
apply_schema_sa.py:14
apply_schema_simple.py:14
check_database.py:15
create_test_user.py:40
crypto_invoice_system/api/invoice_api.py
SEC-002 Critical Cryptographic Failures main.py:89
main.py:1381
main.py:1570
web_ui/app_db.py:5127
check_database.py:11
SEC-003 Critical Broken Access Control web_ui/app_db.py:359-396
web_ui/app_db.py:13565-13573
SEC-004 Critical Injection smart_ingestion.py:1001
smart_ingestion.py:1013
smart_ingestion.py:1023
smart_ingestion.py:1001, 1013, 1023
SEC-005 Critical Security Misconfiguration cloudbuild-dev.yaml:40
invoice_processing/web/dashboard.py:257
invoice_processing/web/upload_interface.py:356
invoice_processing/improved_visual_system.py:2690
web_ui/app.py:224
SEC-006 Critical Cryptographic Failures web_ui/app_db.py:132
cloudbuild.yaml:45
SEC-007 Critical Injection web_ui/app_db.py:12577
app_db.py
web_ui/app_db.py:11825
web_ui/app_db.py:11825-11840
SEC-008 High Injection web_ui/app_db.py:1914
web_ui/app_db.py:1920
web_ui/app_db.py:1922
web_ui/app_db.py:1927
web_ui/app_db.py:1929
web_ui/app_db.py:1934
web_ui/app_db.py:1942
web_ui/app_db.py:1963
web_ui/app_db.py:2001
web_ui/app_db.py:2013
web_ui/app_db.py:5858
web_ui/app_db.py:2470-2471
web_ui/ai_tools.py
analyze_db_schema.py:34
analyze_db_schema.py:40
migrate_data_to_postgresql.py:209
migrate_data_to_postgresql.py:214
migrate_data_to_postgresql.py:240
check_database.py
web_ui/app_db.py:1914, 1920, 1922, 1927, 1929, 1934, 1942, 1963, 2001, 2013
analyze_db_schema.py:34, 40
migrate_data_to_postgresql.py:209, 214, 240
web_ui/app_db.py:5858, 2470-2471
SEC-009 High Security Misconfiguration B2Bee/bumblebee-backend/app/main.py:13-19
B2Bee/bumblebee-backend/app/main.py:15
SEC-010 High Broken Access Control web_ui/app_db.py:13164-13180
SEC-011 High Broken Access Control web_ui/app_db.py:13565-13573
SEC-012 High Broken Access Control frontend/middleware.ts
frontend/src/app/(dashboard)/layout.tsx:19-45
frontend/src/app/(super-admin)/layout.tsx:28-33
SEC-013 High Broken Access Control frontend/src/context/auth-context.tsx:207-222
frontend/src/context/tenant-context.tsx:92-108
frontend/src/app/(dashboard)/dashboard/page.tsx:531
frontend/src/app/(dashboard)/dashboard/page.tsx:698
frontend/src/lib/api.ts
middleware/auth_middleware.py
tenant-context.tsx:92-108
dashboard/page.tsx:531, 698
frontend/src/context/*.tsx
web_ui/app_db.py
SEC-014 High Injection frontend/src/app/(dashboard)/invoices/[id]/page.tsx:654
frontend/src/app/(dashboard)/invoices/[id]/page.tsx:781
invoice_processing/improved_visual_system.py
frontend/src/app/(dashboard)/invoices/[id]/page.tsx:654, 781
add_tenant_switcher_to_all_templates.py
SEC-015 High Broken Access Control frontend/src/lib/api.ts:40
frontend/src/context/tenant-context.tsx:53
frontend/src/lib/api.ts:40-48
SEC-016 Medium Cryptographic Failures main.py:1972
main.py:2269
web_ui/app_db.py:2298
web_ui/app_db.py:2608
web_ui/app_db.py:4688
web_ui/app_db.py:11314
web_ui/app_db.py:11329
web_ui/services/file_storage_service.py:109
invoice_processing/core/email_monitor.py:123
main.py:1972, 2269
web_ui/app_db.py:2298, 2608, 4688, 11314, 11329
SEC-017 Medium Cryptographic Failures web_ui/historical_currency_converter.py:308
SEC-018 Low Injection web_ui/reporting_api.py:3446
web_ui/app.py:141
web_ui/app.py:144
web_ui/app.py:141, 144

1. Remove hardcoded credentials from source code and scripts

ID: SEC-001
Severity: Critical
Category: Cryptographic Failures
Affected code: apply_schema_direct.py:16, apply_schema_sa.py:14, apply_schema_simple.py:14, check_database.py:15, create_test_user.py:40, crypto_invoice_system/api/invoice_api.py

Impact

Repository readers, including former readers, can extract plaintext database and exchange credentials, potentially enabling unauthorized access to production services.

Technical details

  • Git-tracked files contain literal database passwords, including DB_PASSWORD = "..." in apply_schema_direct.py:16, apply_schema_sa.py:14, and related scripts.
  • crypto_invoice_system/run_server.sh:8 and crypto_invoice_system/api/invoice_api.py contain plaintext MEXC API credentials and fallback literals.
  • The affected values are used as source-code or shell-script defaults rather than being required from the runtime environment.

Relevant code

apply_schema_direct.py:10-22

# Configurações
DB_HOST = "34.27.251.47"
DB_PORT = 5432
DB_NAME = "delta_cfo"
DB_USER = "postgres"
DB_PASSWORD = [REDACTED_SECRET]

def wait_for_connection(max_retries=10):
    """Aguardar instância estar acessível"""
    for i in range(max_retries):
        try:
            conn = psycopg2.connect(

Validation

Code evidence

  • apply_schema_direct.py:10-22 defines DB_HOST, DB_USER, and a literal DB_PASSWORD.

  • crypto_invoice_system/run_server.sh:8 and invoice_api.py contain MEXC key and secret fallback values.
    Limitations

  • No credential-validity or live-service access test was supplied.

Recommended remediation

Rotate every exposed credential, purge secrets from repository history, and load them from required environment or secret-manager variables that fail startup when unset.

2. Remove committed .env.production file containing live secrets

ID: SEC-002
Severity: Critical
Category: Cryptographic Failures
Affected code: main.py:89, main.py:1381, main.py:1570, web_ui/app_db.py:5127, check_database.py:11

Impact

A tracked production environment file exposes infrastructure identifiers and a Flask secret value to repository readers; duplicated database defaults increase exposure and configuration drift.

Technical details

  • .env.production is tracked and contains DB_HOST=34.39.143.82, a Cloud SQL connection name, and a Flask secret key in a comment.
  • main.py:89,1381,1570, web_ui/app_db.py:5127, and check_database.py:11 use the same production database host as a fallback.
  • The supplied record states that .gitignore has no .env.production rule.

Relevant code

main.py:83-95

        # Try to load from database first (SaaS architecture)
        try:
            import psycopg2
            import os as os_module

            # Get database credentials from environment
            db_host = os_module.environ.get('DB_HOST', '34.39.143.82')
            db_port = os_module.environ.get('DB_PORT', '5432')
            db_name = os_module.environ.get('DB_NAME', 'delta_cfo')
            db_user = os_module.environ.get('DB_USER', 'delta_user')
            db_password = [REDACTED_SECRET] 'nWr0Y8bU51ypLjMIfx8bTe+V/1iOV59r90T8wJEsSGo=')

            # Use tenant_id from constructor

Validation

Code evidence

  • The tracked .env.production contains production connection values and a commented Flask key.

  • main.py uses os.environ.get('DB_HOST', '34.39.143.82') at the reported locations.
    Limitations

  • No live authentication or database-access test was supplied.

Recommended remediation

Rotate exposed values, remove .env.production and its secrets from Git history, add it to .gitignore, and require deployment-time secret injection without production fallbacks.

3. Enforce authentication on all sensitive Flask routes

ID: SEC-003
Severity: Critical
Category: Broken Access Control
Affected code: web_ui/app_db.py:359-396, web_ui/app_db.py:13565-13573

Impact

Unauthenticated callers may read financial data, perform mutations or uploads, export records, and enumerate or delete Firebase users.

Technical details

  • The repository contains approximately 235 Flask routes, while only 16-18 reportedly use an authentication decorator.
  • GET /api/transactions and GET /api/stats lack authentication; the admin Firebase routes at web_ui/app_db.py:359-396 also lack decorators.
  • Upload and mutation routes, including /api/invoices/upload, /api/invoices/upload-batch, and attachment uploads, are listed without required authentication and tenant/role checks.

Relevant code

web_ui/app_db.py:1-30

#!/usr/bin/env python3
"""
Delta CFO Agent - Database-Backed Web Dashboard
Advanced web interface for financial transaction management with Claude AI integration
"""

import os
import sys
import json
import sqlite3  # Kept for backward compatibility - main DB uses database.py manager
import pandas as pd
import time
import threading
import traceback
from datetime import datetime, timedelta
from flask import Flask, render_template, request, jsonify, send_file, session, Response
from concurrent.futures import ThreadPoolExecutor, as_completed
import random
import anthropic
from typing import List, Dict, Any, Optional
from werkzeug.utils import secure_filename
import subprocess
import shutil
import hashlib
import uuid
import base64
import zipfile
import re
import logging
from dotenv import load_dotenv

Validation

Code evidence

  • web_ui/app_db.py defines sensitive routes under @app.route without consistent @require_auth enforcement.

  • The reported admin routes can return or modify Firebase user records without a decorator.
    Runtime evidence

  • Unauthenticated GET https://deltacfoagent.vercel.app/api/transactions reportedly returned HTTP 200.

  • Unauthenticated GET https://deltacfoagent.vercel.app/api/stats reportedly returned HTTP 200 with financial KPIs.
    Limitations

  • The supplied runtime results cover read endpoints, not every listed mutation or upload route.

Recommended remediation

Require authentication on every sensitive route and enforce authorization, role, and tenant ownership checks server-side before reads, writes, exports, uploads, or administrative actions.

4. Replace eval() calls in smart_ingestion.py with a safe expression evaluator

ID: SEC-004
Severity: Critical
Category: Injection
Affected code: smart_ingestion.py:1001, smart_ingestion.py:1013, smart_ingestion.py:1023, smart_ingestion.py:1001, 1013, 1023

Impact

Expression evaluation can provide a code-execution path if an attacker can influence the mapping expressions or the AI-generated upload-processing input.

Technical details

  • smart_ingestion.py:1001, 1013, and 1023 call eval() for condition_expr, origin_expr, and destination_expr.
  • The calls pass {"__builtins__": {}} as globals but expose pd and str in local_vars; this is not a complete Python sandbox.
  • The reported path begins at the upload-processing functionality, where expressions are generated or influenced during CSV processing.

Relevant code

smart_ingestion.py:995-1007

                            for row_idx, row in df.iterrows():
                                try:
                                    # Create local namespace with row data
                                    local_vars = {'row': row, 'pd': pd, 'str': str}

                                    # Evaluate condition
                                    if eval(condition_expr, {"__builtins__": {}}, local_vars):
                                        matches.append(row_idx)
                                except Exception as e:
                                    # Skip rows that fail evaluation
                                    continue

                            if matches:

Validation

Code evidence

  • smart_ingestion.py:995-1007 executes eval(condition_expr, {"__builtins__": {}}, local_vars).

  • The two corresponding origin and destination evaluations are reported at lines 1013 and 1023.
    Limitations

  • The supplied Vercel probe returned 404 for /api/upload-with-progress; runtime exploitation was not demonstrated.

  • The record identifies Cloud Run as the primary exposure target, but does not provide a Cloud Run URL.

Recommended remediation

Replace eval() with a strict allowlisted expression grammar or safe evaluator that supports only required operators, fields, and functions; reject unsupported expressions.

5. Disable Flask debug mode in all deployment and source configurations

ID: SEC-005
Severity: Critical
Category: Security Misconfiguration
Affected code: cloudbuild-dev.yaml:40, invoice_processing/web/dashboard.py:257, invoice_processing/web/upload_interface.py:356, invoice_processing/improved_visual_system.py:2690, web_ui/app.py:224

Impact

If the reported Cloud Run configuration is deployed, Werkzeug's interactive debugger may expose a Python console and turn application errors into code execution.

Technical details

  • cloudbuild-dev.yaml:40 sets FLASK_DEBUG=true while also configuring an unauthenticated Cloud Run service.
  • web_ui/app.py:224 and invoice_processing/web/dashboard.py:257 reportedly call app.run(debug=True); additional hardcoded debug settings are listed in related modules.
  • The deployment manifest supplies the debug flag through environment variables, so source-level defaults are not the only activation path.

Relevant code

cloudbuild-dev.yaml:34-46

      - '--cpu-throttling'
      - '--execution-environment'
      - 'gen2'
      - '--max-instances'
      - '10'
      - '--set-env-vars'
      - 'DB_TYPE=postgresql,DB_SOCKET_PATH=/cloudsql/$PROJECT_ID:southamerica-east1:delta-cfo-db,DB_NAME=delta_cfo,DB_USER=delta_user,FLASK_ENV=development,PORT=8080,PYTHONUNBUFFERED=1,FLASK_DEBUG=true,GCS_BUCKET_NAME=deltacfo-uploads-dev,GOOGLE_CLOUD_PROJECT=$PROJECT_ID'
      - '--set-cloudsql-instances'
      - '$PROJECT_ID:southamerica-east1:delta-cfo-db'
      - '--set-secrets'
      - 'DB_PASSWORD=db_password_sa:latest,ANTHROPIC_API_KEY=ANTHROPIC_API_KEY:latest'

images:

Validation

Code evidence

  • cloudbuild-dev.yaml:34-46 includes FLASK_DEBUG=true in --set-env-vars.

  • web_ui/app.py:224 and invoice_processing/web/dashboard.py:257 are reported to use debug=True.
    Runtime evidence

  • The Vercel probe reportedly returned 404 for /__debugger__/ and /console.
    Limitations

  • The supplied runtime result does not validate the Cloud Run deployment described by cloudbuild-dev.yaml.

Recommended remediation

Remove debug flags from deployment manifests and production entry points. Permit debug mode only through an explicitly controlled development configuration that cannot be enabled in production.

6. Remove insecure fallback Flask secret key and require it at startup

ID: SEC-006
Severity: Critical
Category: Cryptographic Failures
Affected code: web_ui/app_db.py:132, cloudbuild.yaml:45

Impact

Anyone who knows the fallback Flask key, or obtains the committed production key, may forge Flask session cookies and impersonate users or alter session state.

Technical details

  • web_ui/app_db.py:132 assigns app.secret_key from FLASK_SECRET_KEY with the public fallback dev-secret-key-delta-cfo-agent-2024.
  • cloudbuild.yaml:45 reportedly injects database, Anthropic, and Firebase secrets but not FLASK_SECRET_KEY.
  • The tracked .env.production contains a production Flask key in a comment.

Relevant code

web_ui/app_db.py:126-138

    app.config['TEMPLATES_AUTO_RELOAD'] = True
    app.jinja_env.auto_reload = True

# Configure Flask secret key for sessions
# Use a fixed key in development for session persistence across restarts
# In production, set FLASK_SECRET_KEY environment variable
app.secret_key = os.getenv('FLASK_SECRET_KEY', 'dev-secret-key-delta-cfo-agent-2024')

# Lazy blueprint registration function to avoid circular imports
def register_auth_blueprints():
    """
    Register authentication blueprints using lazy loading.
    This function is called after app initialization to avoid circular imports.

Validation

Code evidence

  • web_ui/app_db.py:126-138 uses os.getenv('FLASK_SECRET_KEY', 'dev-secret-key-delta-cfo-agent-2024').

  • cloudbuild.yaml:45 lacks FLASK_SECRET_KEY in the reported --set-secrets configuration.
    Runtime evidence

  • The deployed URL reportedly served Flask responses.
    Limitations

  • No session-cookie forgery or authenticated-privilege test was supplied.

Recommended remediation

Rotate all exposed Flask keys, remove the literal fallback, require FLASK_SECRET_KEY at startup, and inject it through the deployment secret mechanism.

7. Eliminate user-controlled input in subprocess Python code string execution

ID: SEC-007
Severity: Critical
Category: Injection
Affected code: web_ui/app_db.py:12577, app_db.py, web_ui/app_db.py:11825, web_ui/app_db.py:11825-11840

Impact

If an attacker controls the interpolated filename, the subprocess Python command may execute injected Python code. A weak Flask session key and reported unauthenticated upload behavior increase the potential attack path.

Technical details

  • The duplicate-processing pipeline builds a python -c string containing filename_safe and invokes it with subprocess.run.
  • The reported sanitization replaces backslashes but does not escape single quotes or newlines before interpolation.
  • The route source at web_ui/app_db.py:12571 includes @require_auth, but the supplied runtime note states the deployed endpoint did not return an authentication challenge.

Relevant code

web_ui/app_db.py:12571-12583

@app.route('/api/process-duplicates', methods=['POST'])
@require_auth
def process_duplicates():
    """Process a file that was already uploaded with specific duplicate handling - requires authentication"""
    try:
        duplicate_handling = request.form.get('duplicateHandling', 'overwrite')
        filename = request.form.get('filename', '')

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

        print(f"[PROCESS] Processing duplicates for {filename} with mode: {duplicate_handling}")

Validation

Code evidence

  • The reported subprocess construction occurs at web_ui/app_db.py:11825-11840 and 12577/12620.

  • filename_safe = filename.replace(chr(92), '/') is insufficient for embedding untrusted data in Python source.
    Runtime evidence

  • A POST to /api/process-duplicates without credentials reportedly returned HTTP 400 File not found, not 401.

  • The supplied note also reports /api/upload accepted unauthenticated access.
    Limitations

  • No command-execution result was supplied.

Recommended remediation

Do not generate Python source from request data. Pass values as argv or environment variables to a fixed worker, validate filenames against an upload directory, and enforce authentication consistently.

8. Eliminate dynamic SQL identifier interpolation to prevent SQL injection

ID: SEC-008
Severity: High
Category: Injection
Affected code: web_ui/app_db.py:1914, web_ui/app_db.py:1920, web_ui/app_db.py:1922, web_ui/app_db.py:1927, web_ui/app_db.py:1929, web_ui/app_db.py:1934, web_ui/app_db.py:1942, web_ui/app_db.py:1963, web_ui/app_db.py:2001, web_ui/app_db.py:2013, web_ui/app_db.py:5858, web_ui/app_db.py:2470-2471, web_ui/ai_tools.py, analyze_db_schema.py:34, analyze_db_schema.py:40, migrate_data_to_postgresql.py:209, migrate_data_to_postgresql.py:214, migrate_data_to_postgresql.py:240, check_database.py, web_ui/app_db.py:1914, 1920, 1922, 1927, 1929, 1934, 1942, 1963, 2001, 2013, analyze_db_schema.py:34, 40, migrate_data_to_postgresql.py:209, 214, 240, web_ui/app_db.py:5858, 2470-2471

Impact

An attacker may inject an SQL identifier into transaction updates; the unauthenticated endpoint may also permit modification of tenant-related columns and undermine tenant isolation.

Technical details

  • api_update_transaction obtains field = data.get('field') and passes unknown values to update_transaction_field without an allowlist.
  • The fallback query uses UPDATE transactions SET {field} = %s WHERE tenant_id = %s AND transaction_id = %s at web_ui/app_db.py:2470-2471.
  • The reported endpoint at web_ui/app_db.py:5852 has no authentication decorator.

Relevant code

web_ui/app_db.py:1908-1920

            placeholder = "%s" if is_postgresql else "?"

            # Build WHERE clause from filters
            where_clause, params = build_filter_where_clause(filters, tenant_id, is_postgresql)

            # Total transactions with filters
            cursor.execute(f"SELECT COUNT(*) as total FROM transactions WHERE {where_clause}", params)
            result = cursor.fetchone()
            total_transactions = result['total'] if is_postgresql else result[0]

            # Revenue and expenses with filters
            if is_postgresql:
                cursor.execute(f"SELECT COALESCE(SUM(amount), 0) as revenue FROM transactions WHERE {where_clause} AND amount > 0 AND amount::text != 'NaN'", params)

Validation

Code evidence

  • The reported fallback directly interpolates {field} into an SQL statement; parameter binding protects values but not identifiers.

  • Other reported query construction sites include web_ui/app_db.py:1914-2013, analyze_db_schema.py:34,40, and migration scripts.
    Runtime evidence

  • A POST to /api/update_transaction with field=confidence reportedly reached the endpoint and returned HTTP 500 rather than an authentication challenge.
    Limitations

  • The supplied probe did not demonstrate successful SQL manipulation or data alteration.

Recommended remediation

Allow only a fixed set of writable column names before query construction, reject unknown fields, keep values parameterized, and require authentication plus tenant authorization.

9. Restrict wildcard CORS combined with credentials in FastAPI subservice

ID: SEC-009
Severity: High
Category: Security Misconfiguration
Affected code: B2Bee/bumblebee-backend/app/main.py:13-19, B2Bee/bumblebee-backend/app/main.py:15

Impact

If the B2Bee service is reachable, arbitrary origins may be able to issue credentialed cross-origin requests, depending on the Starlette version and response behavior.

Technical details

  • B2Bee/bumblebee-backend/app/main.py:13-19 configures CORSMiddleware with allow_origins=["*"] and allow_credentials=True.
  • The same configuration permits all methods and headers with allow_methods=["*"] and allow_headers=["*"].
  • This policy does not restrict browser callers to a trusted origin set.

Relevant code

B2Bee/bumblebee-backend/app/main.py:7-25

app = FastAPI(
    title="BumbleBee API",
    description="AI Assistant Backend",
    version="0.1.0",
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/health")
async def health_check():
    return {"status": "healthy"}

Validation

Code evidence

  • B2Bee/bumblebee-backend/app/main.py:7-25 contains the reported wildcard CORS configuration.
    Runtime evidence

  • The B2Bee API was reportedly not reachable at the supplied Vercel URL; API paths returned 404.
    Limitations

  • No deployed B2Bee endpoint or credentialed cross-origin request result was supplied.

Recommended remediation

Replace the wildcard origin with an explicit allowlist of trusted origins and restrict methods and headers to those required by the service.

10. Fix path traversal in unauthenticated invoice PDF download endpoint

ID: SEC-010
Severity: High
Category: Broken Access Control
Affected code: web_ui/app_db.py:13164-13180

Impact

An unauthenticated caller may request a path containing traversal segments and cause the application to serve files outside the invoice directory.

Technical details

  • /api/invoices/pdf/<path:filename> accepts slashes through Flask's path converter and has no reported authentication decorator.
  • The handler joins filename directly with base_dir/invoices/issued using os.path.join.
  • A filename such as ../../.env resolves outside the intended directory before send_file is called.

Relevant code

web_ui/app_db.py:13158-13186

    except Exception as e:
        print(f"Error creating invoice: {e}")
        import traceback
        traceback.print_exc()
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/api/invoices/pdf/<path:filename>')
def api_download_invoice_pdf(filename):
    """API endpoint to download invoice PDF"""
    try:
        from flask import send_file
        import os

        # Construct the full path (use absolute path)
        base_dir = os.path.dirname(os.path.abspath(__file__))
        pdf_path = os.path.join(base_dir, 'invoices', 'issued', filename)

        # Check if file exists
        if not os.path.exists(pdf_path):
            return jsonify({'success': False, 'error': 'PDF file not found'}), 404

        # Send the file
        return send_file(pdf_path, mimetype='application/pdf', as_attachment=True, download_name=filename)

    except Exception as e:
        print(f"Error downloading invoice PDF: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/api/invoices')

Validation

Code evidence

  • web_ui/app_db.py:13158-13186 constructs pdf_path = os.path.join(base_dir, 'invoices', 'issued', filename) and sends it when it exists.

  • The route declaration at line 13164 uses <path:filename>.
    Runtime evidence

  • The supplied Vercel probe for /api/invoices/pdf/test.pdf returned HTML 404.
    Limitations

  • The current Vercel routing did not expose the route, so file disclosure was not demonstrated there.

Recommended remediation

Require authentication and use send_from_directory or safe_join with a fixed base directory. Reject traversal and verify the canonical resolved path remains within the invoice directory.

11. Validate archive member paths before extraction to prevent Zip Slip

ID: SEC-011
Severity: High
Category: Broken Access Control
Affected code: web_ui/app_db.py:13565-13573

Impact

A crafted archive may write files outside the intended extraction directory, potentially overwriting application or configuration files if the endpoint is deployed and writable.

Technical details

  • The ZIP branch calls zip_ref.extractall(extract_dir) at web_ui/app_db.py:13567 without validating archive member paths.
  • Archive entries containing traversal components such as ../../../../... can resolve outside extract_dir.
  • The reported batch upload route lacks @require_auth.

Relevant code

web_ui/app_db.py:13559-13579

        file_ext = os.path.splitext(file_path)[1].lower()

        os.makedirs(extract_dir, exist_ok=True)

        # Extract archive based on file type
        if file_ext == '.zip':
            with zipfile.ZipFile(file_path, 'r') as zip_ref:
                zip_ref.extractall(extract_dir)

        elif file_ext == '.7z':
            if not PY7ZR_AVAILABLE:
                return {'error': '7z support not available - py7zr package required'}
            with py7zr.SevenZipFile(file_path, mode='r') as archive:
                archive.extractall(path=extract_dir)

        elif file_ext == '.rar':
            return {'error': 'RAR format requires additional setup. Please use ZIP or 7Z format.'}

        else:
            return {'error': f'Unsupported archive format: {file_ext}'}

Validation

Code evidence

  • web_ui/app_db.py:13559-13579 extracts ZIP contents directly with extractall(extract_dir).

  • The route is reported as /api/invoices/upload-batch at line 13668 without an authentication decorator.
    Runtime evidence

  • The supplied Vercel probe returned 404 for /api/invoices/upload-batch.
    Limitations

  • No file-write or application-overwrite result was demonstrated in a live deployment.

Recommended remediation

Require authentication and validate every archive member's canonical destination against the extraction root before writing. Reject absolute paths, traversal components, and links.

12. Add server-side authentication enforcement to Next.js middleware and layouts

ID: SEC-012
Severity: High
Category: Broken Access Control
Affected code: frontend/middleware.ts, frontend/src/app/(dashboard)/layout.tsx:19-45, frontend/src/app/(super-admin)/layout.tsx:28-33

Impact

Unauthenticated users may receive dashboard HTML, and client-side-only role checks do not provide server-side protection for super-admin content or operations.

Technical details

  • frontend/middleware.ts only invokes next-intl routing and performs no session validation.
  • The dashboard layout's if (!isAuthenticated) return null guard is reportedly commented out.
  • The super-admin role check is performed in useEffect, after the server has delivered the page.

Relevant code

frontend/middleware.ts:1-21

import createMiddleware from "next-intl/middleware";
import { locales, defaultLocale } from "./src/i18n/config";

export default createMiddleware({
  // A list of all locales that are supported
  locales,

  // Used when no locale matches
  defaultLocale,

  // Don't redirect to locale prefix for default locale
  localePrefix: "as-needed",
});

export const config = {
  // Match all pathnames except for:
  // - API routes
  // - Static files
  // - Next.js internals
  matcher: ["/((?!api|_next|.*\\..*).*)"],
};

Validation

Code evidence

  • frontend/middleware.ts:1-21 contains locale middleware without an authentication check.

  • frontend/src/app/(dashboard)/layout.tsx:41-45 reportedly comments out the unauthenticated guard.
    Runtime evidence

  • Unauthenticated GET https://deltacfoagent.vercel.app/dashboard reportedly returned HTTP 200 with full dashboard HTML.
    Limitations

  • The supplied result does not establish whether protected backend APIs independently enforce authorization.

Recommended remediation

Validate the session and required role in server-side middleware or layouts before rendering protected routes, and enforce the same authorization on every backend operation.

13. Implement CSRF protection on all cookie-authenticated state-changing requests

ID: SEC-013
Severity: High
Category: Broken Access Control
Affected code: frontend/src/context/auth-context.tsx:207-222, frontend/src/context/tenant-context.tsx:92-108, frontend/src/app/(dashboard)/dashboard/page.tsx:531, frontend/src/app/(dashboard)/dashboard/page.tsx:698, frontend/src/lib/api.ts, middleware/auth_middleware.py, tenant-context.tsx:92-108, dashboard/page.tsx:531, 698, frontend/src/context/*.tsx, web_ui/app_db.py

Impact

Cookie-authenticated state-changing requests may be triggered cross-site, allowing unauthorized tenant switches or mutations when a victim is logged in.

Technical details

  • requirements.txt:11 includes Flask-WTF, but the supplied repository review found no CSRFProtect initialization or flask_wtf imports.
  • No SESSION_COOKIE_SAMESITE setting is reported.
  • frontend/src/context/auth-context.tsx:209-211 sends a credentialed POST to /api/auth/switch-tenant/<tenantId> without an Authorization header.
  • The supplied record states that session cookies are accepted as an authentication fallback.

Relevant code

frontend/src/context/auth-context.tsx:201-228

    const firebaseUser = firebaseAuth.currentUser;
    if (firebaseUser) {
      await syncWithBackend(firebaseUser);
    }
  }

  async function switchTenant(tenantId: string) {
    try {
      const response = await fetch(`/api/auth/switch-tenant/${tenantId}`, {
        method: "POST",
        credentials: "include",
      });

      if (response.ok) {
        const data = await response.json();
        if (data.success) {
          setCurrentTenant(data.tenant);
        }
      }
    } catch (error) {
      console.error("Switch tenant error:", error);
    }
  }

  return (
    <AuthContext.Provider
      value={{
        user,

Validation

Code evidence

  • auth-context.tsx:201-228 uses credentials: "include" for a state-changing POST without a CSRF token.

  • web_ui/app_db.py:9442 reportedly defines bulk_update_transactions without @require_auth.
    Runtime evidence

  • The supplied probe for /api/bulk_update_transactions returned 404 on Vercel.
    Limitations

  • No cross-site request or state-change result was demonstrated.

Recommended remediation

Initialize global CSRF protection for cookie-authenticated state changes, require and validate CSRF tokens, configure an appropriate SameSite cookie policy, and enforce authentication on every mutation.

14. Prevent XSS from unvalidated URLs and innerHTML assignments

ID: SEC-014
Severity: High
Category: Injection
Affected code: frontend/src/app/(dashboard)/invoices/[id]/page.tsx:654, frontend/src/app/(dashboard)/invoices/[id]/page.tsx:781, invoice_processing/improved_visual_system.py, frontend/src/app/(dashboard)/invoices/[id]/page.tsx:654, 781, add_tenant_switcher_to_all_templates.py

Impact

Attacker-controlled tenant or invoice content may execute JavaScript in users' browsers through unescaped HTML; unvalidated attachment URLs may also permit dangerous URI schemes in the frontend.

Technical details

  • The supplied deployed-template evidence reports tenant.company_name and tenant.role inserted into innerHTML without escaping in business_overview.html:726 and invoices.html:2841.
  • The Next.js invoice page uses <a href={attachment.url}> at lines 654 and 781 without an explicit HTTP/HTTPS scheme allowlist.
  • The record states the Next.js frontend is not the application currently served at the supplied Vercel URL.

Relevant code

frontend/src/app/(dashboard)/invoices/[id]/page.tsx:648-660

                          </p>
                        </div>
                      </div>
                      <div className="flex items-center gap-2">
                        <Button variant="ghost" size="sm" asChild>
                          <a
                            href={attachment.url}
                            target="_blank"
                            rel="noopener noreferrer"
                          >
                            <Download className="h-4 w-4" />
                          </a>
                        </Button>

Validation

Code evidence

  • Reported template code assigns interpolated tenant values to tenantItem.innerHTML.

  • frontend/src/app/(dashboard)/invoices/[id]/page.tsx:648-660 renders href={attachment.url}.
    Runtime evidence

  • GET https://deltacfoagent.vercel.app/ reportedly returned HTTP 200 and serves the Flask template.

  • The reported /api/auth/me probe returned 404.
    Limitations

  • No live XSS payload execution was supplied.

  • The Next.js URL sink was not confirmed in the deployed Vercel application.

Recommended remediation

Render untrusted values as text or through context-appropriate escaping, avoid innerHTML, and allow only http and https attachment schemes before rendering links.

15. Derive tenant identity from authenticated session rather than localStorage

ID: SEC-015
Severity: High
Category: Broken Access Control
Affected code: frontend/src/lib/api.ts:40, frontend/src/context/tenant-context.tsx:53, frontend/src/lib/api.ts:40-48

Impact

Client-controlled tenant identifiers can route API requests to another tenant, risking cross-tenant data access when backend authorization relies on the supplied header.

Technical details

  • frontend/src/lib/api.ts:40-48 falls back to localStorage.getItem("tenantId") when the in-memory tenant ID is absent.
  • The value is reportedly sent as the X-Tenant-ID header on API calls.
  • frontend/src/context/tenant-context.tsx:53 uses the hardcoded fallback "delta" when tenant values are missing.

Relevant code

frontend/src/lib/api.ts:34-46

  currentTenantId = tenantId;
}

/**
 * Get the current tenant ID
 * Falls back to localStorage if not set in memory
 */
export function getApiTenantId(): string | null {
  if (currentTenantId) return currentTenantId;
  // Fallback to localStorage for persistence across page refreshes
  if (typeof window !== "undefined") {
    return localStorage.getItem("tenantId");
  }

Validation

Code evidence

  • getApiTenantId() returns a browser-controlled localStorage value after the in-memory value is unavailable.

  • The tenant context expression config.tenant_id || response.tenant_id || "delta" supplies a fixed tenant fallback.
    Runtime evidence

  • The deployed application reportedly returned HTTP 200; the supplied record states both code paths are active in the production bundle.
    Limitations

  • No cross-tenant request or unauthorized data response was supplied.

Recommended remediation

Derive tenant identity exclusively from the authenticated server-side session or token, ignore client-supplied tenant headers for authorization, and fail closed when tenant context is absent.

16. Replace MD5 usage in security-relevant contexts with strong hash algorithms

ID: SEC-016
Severity: Medium
Category: Cryptographic Failures
Affected code: main.py:1972, main.py:2269, web_ui/app_db.py:2298, web_ui/app_db.py:2608, web_ui/app_db.py:4688, web_ui/app_db.py:11314, web_ui/app_db.py:11329, web_ui/services/file_storage_service.py:109, invoice_processing/core/email_monitor.py:123, main.py:1972, 2269, web_ui/app_db.py:2298, 2608, 4688, 11314, 11329

Impact

MD5 collisions can cause incorrect deduplication or integrity decisions, potentially suppressing distinct financial or email records.

Technical details

  • hashlib.md5(...) is reported in main.py:1972,2269, web_ui/app_db.py:2298,2608,4688,11314,11329, web_ui/services/file_storage_service.py:109, and invoice_processing/core/email_monitor.py:123.
  • main.py:1972 derives a 12-character transaction ID from date, description, and amount using MD5.
  • file_storage_service.py:109 reportedly uses MD5 for file integrity checking.

Relevant code

main.py:1966-1978

            if 'transaction_id' not in df.columns or pd.isna(row.get('transaction_id')):
                # Generate transaction_id from date + description + amount
                date_str = str(row[date_col]) if date_col in df.columns else ''
                desc_str = str(row[desc_col]) if desc_col in df.columns else ''
                amount_str = str(row[amount_col]) if amount_col in df.columns else ''
                identifier = f"{date_str}{desc_str}{amount_str}"
                transaction_id = hashlib.md5(identifier.encode()).hexdigest()[:12]
                df.at[idx, 'transaction_id'] = transaction_id

        # Run enhanced processing pipeline if requested
        if enhance:
            print("\n Running enhanced processing pipeline...")

Validation

Code evidence

  • main.py:1966-1978 constructs identifier and assigns hashlib.md5(identifier.encode()).hexdigest()[:12] as transaction_id.
    Runtime evidence

  • The deployed application root reportedly returned HTTP 200.
    Limitations

  • No collision-based record suppression or integrity bypass was demonstrated at runtime.

Recommended remediation

Use SHA-256 or stronger for non-password integrity and identifiers. For password hashing, use a password-specific algorithm such as Argon2 or bcrypt; review whether truncated identifiers require collision-resistant redesign.

17. Transmit Fixer.io API key over HTTPS instead of plaintext HTTP

ID: SEC-017
Severity: Medium
Category: Cryptographic Failures
Affected code: web_ui/historical_currency_converter.py:308

Impact

The Fixer.io API key is sent over plaintext HTTP and may be observed or modified by network intermediaries, proxies, or logs.

Technical details

  • web_ui/historical_currency_converter.py:308 builds http://data.fixer.io/api/{date_str}.
  • The request passes self.backup_apis['fixer'] as the access_key query parameter.
  • The credential-bearing request therefore lacks transport encryption.

Relevant code

web_ui/historical_currency_converter.py:302-314

        to_currency: str,
        rate_date: datetime
    ) -> Optional[Dict]:
        """Fetch from fixer.io (backup API)"""
        try:
            date_str = rate_date.strftime('%Y-%m-%d')
            url = f"http://data.fixer.io/api/{date_str}"
            params = {
                'access_key': self.backup_apis['fixer'],
                'base': from_currency,
                'symbols': to_currency
            }

Validation

Code evidence

  • web_ui/historical_currency_converter.py:302-314 shows the HTTP URL and query parameter containing access_key.
    Runtime evidence

  • The deployed application root reportedly returned HTTP 200.
    Limitations

  • No outbound request capture was supplied to confirm invocation in the deployed environment.

Recommended remediation

Use the Fixer.io HTTPS endpoint and verify certificate validation. Do not place long-lived credentials in URLs where possible; use an HTTPS-only alternative if the service plan does not support TLS.

18. Reject non-finite float values (NaN/Inf) from query parameters

ID: SEC-018
Severity: Low
Category: Injection
Affected code: web_ui/reporting_api.py:3446, web_ui/app.py:141, web_ui/app.py:144, web_ui/app.py:141, 144

Impact

Special floating-point values can silently alter financial report filtering and produce incorrect dashboard results.

Technical details

  • web_ui/reporting_api.py:3446 converts the min_amount query parameter directly with float(...).
  • The value is reportedly passed to SQL HAVING SUM(amount) >= %s comparisons without a finiteness check.
  • The deprecated web_ui/app.py:141,144 contains the same parsing pattern.

Relevant code

web_ui/reporting_api.py:3440-3452

        try:
            start_time = datetime.now()

            # Parse parameters
            start_date_str = request.args.get('start_date')
            end_date_str = request.args.get('end_date')
            min_amount = float(request.args.get('min_amount', 1000))
            max_categories = int(request.args.get('max_categories', 8))

            # NEW: Parse filter parameters
            keyword = request.args.get('keyword', '').strip()
            entity = request.args.get('entity', '').strip()
            accounting_category = request.args.get('accounting_category', '').strip()

Validation

Code evidence

  • web_ui/reporting_api.py:3440-3452 parses min_amount using float(request.args.get('min_amount', 1000)) with no math.isfinite() validation.
    Runtime evidence

  • The supplied request to /api/reports/sankey-flow?min_amount=nan returned HTTP 404 on Vercel.
    Limitations

  • The deployed route was unavailable, so incorrect query results were not observed at runtime.

Recommended remediation

Parse numeric inputs and reject values for which math.isfinite() is false. Return a validation error before constructing or executing the report query.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions