Back to Blog
critical SEVERITY8 min read

Unauthenticated Internal Metrics Endpoints: A Silent Recon Gateway

A critical security vulnerability was discovered in `api/extensions/ext_app_metrics.py` where internal operational endpoints exposing thread states and database connection pool statistics were accessible without any authentication. This silent reconnaissance gateway allowed attackers to map application internals, identify database infrastructure, and plan targeted denial-of-service attacks. The fix closes these unauthenticated endpoints, eliminating a significant information disclosure risk.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published May 12, 2026•Reviewed June 3, 2026

Answer Summary

Unauthenticated internal metrics endpoints (CWE-306) in Python Flask applications expose sensitive operational data like thread states and database connection pool statistics to unauthorized users. This information disclosure vulnerability enables attackers to map application internals and plan targeted attacks. The fix requires implementing authentication middleware on all internal metrics endpoints, ensuring only authorized administrators can access operational telemetry data.

Vulnerability at a Glance

cweCWE-306 (Missing Authentication for Critical Function)
fixAdd authentication middleware to protect metrics endpoints
riskInformation disclosure enabling reconnaissance and targeted DoS attacks
languagePython (Flask)
root causeInternal operational endpoints exposed without authentication checks
vulnerabilityUnauthenticated Internal Metrics Endpoints

Vulnerability ID: V-005 | Severity: HIGH | File: api/extensions/ext_app_metrics.py

Introduction

Not every security vulnerability announces itself with a dramatic exploit. Some of the most dangerous weaknesses quietly sit in the background, handing attackers exactly the intelligence they need to plan a devastating strike. Unauthenticated internal metrics endpoints are precisely this kind of threat — subtle, easy to overlook, and enormously valuable to an adversary.

In this post, we'll break down a high-severity vulnerability discovered in api/extensions/ext_app_metrics.py, where two endpoints — /threads and /db-pool-stat — were serving sensitive operational data to anyone who asked, no credentials required.

Whether you're a backend developer, a DevOps engineer, or a security-conscious architect, this is a pattern worth understanding deeply. It appears in codebases far more often than it should.


The Vulnerability Explained

What Was Exposed?

The /threads and /db-pool-stat endpoints in api/extensions/ext_app_metrics.py were designed to expose internal operational metrics — the kind of data that's genuinely useful for monitoring and debugging. The problem wasn't what they exposed; it was who they exposed it to.

Without any authentication requirement, these endpoints were publicly accessible. Here's what an attacker could learn:

  • /threads: Thread counts, thread names, thread states (running, waiting, blocked), and potentially stack traces or task identifiers.
  • /db-pool-stat: Database connection pool statistics, which can include hostnames, connection counts, pool sizes, idle connections, and timeout configurations.

Why Is This Dangerous?

Think of it this way: before a burglar breaks into a house, they case the joint. They want to know how many people are inside, what the layout looks like, and where the valuables are stored. Unauthenticated metrics endpoints are the digital equivalent of leaving your floor plan taped to the front door.

Here's what an attacker gains from this information:

1. Infrastructure Mapping

Database connection strings or hostnames in pool statistics reveal your backend infrastructure. An attacker now knows what database engine you're running, potentially where it's hosted, and how it's named — information that dramatically narrows the attack surface for further exploitation.

2. Resource Exhaustion Thresholds

Pool statistics reveal exactly how many database connections your application can handle. If your pool maxes out at 20 connections, an attacker knows they only need to generate enough load to exhaust those 20 slots to bring your application to its knees.

# What an attacker sees from /db-pool-stat (example response)
{
  "pool_size": 20,
  "checked_out": 17,
  "overflow": 3,
  "checked_in": 3,
  "db_host": "prod-db-cluster.internal.example.com",
  "dialect": "postgresql"
}

With this data, a denial-of-service attack doesn't require brute force — it requires precision.

3. Thread State Analysis

Thread counts and states reveal application behavior patterns. An attacker can:
- Determine when the application is under load vs. idle (useful for timing attacks)
- Identify background workers and their schedules
- Detect when the application is in a degraded state and more vulnerable

A Real-World Attack Scenario

Imagine an attacker targeting your API. They start with passive reconnaissance:

# No credentials needed — just a simple GET request
curl https://api.yourapp.com/threads
curl https://api.yourapp.com/db-pool-stat

Within seconds, they know:
- Your database is PostgreSQL hosted at prod-db.internal.example.com
- Your connection pool maxes out at 15 connections
- You have 8 background worker threads that run every 30 seconds

Now they craft a targeted attack: flood the API with 15 concurrent database-heavy requests, watch the pool exhaust via the metrics endpoint in real time, and trigger a cascade failure — all while staying well below the threshold of a traditional DDoS.

This is the insidious nature of information disclosure vulnerabilities. They don't break your application directly; they hand attackers the blueprint to break it themselves.

OWASP and CWE Classification

This vulnerability maps directly to well-established security standards:

  • OWASP Top 10 - A05:2021: Security Misconfiguration
  • OWASP Top 10 - A01:2021: Broken Access Control
  • CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
  • CWE-306: Missing Authentication for Critical Function

The Fix

What Changed?

The fix applied to api/extensions/ext_app_metrics.py introduces authentication requirements for the /threads and /db-pool-stat endpoints. These endpoints should never have been publicly accessible — they are operational tools, not public APIs.

The core principle of the fix is straightforward: internal operational data requires internal authorization.

The Security Improvement

Before the fix, the endpoints followed a pattern like this:

# VULNERABLE: No authentication required
@app.route('/threads')
def get_threads():
    threads = []
    for thread in threading.enumerate():
        threads.append({
            'name': thread.name,
            'id': thread.ident,
            'alive': thread.is_alive(),
            'daemon': thread.daemon
        })
    return jsonify({'threads': threads, 'count': len(threads)})

@app.route('/db-pool-stat')
def get_db_pool_stat():
    engine = db.engine
    pool = engine.pool
    return jsonify({
        'pool_size': pool.size(),
        'checked_out': pool.checkedout(),
        'overflow': pool.overflow(),
        'checked_in': pool.checkedin()
    })

After the fix, these endpoints are protected. A properly secured implementation looks like this:

# SECURE: Authentication enforced before serving sensitive data
from functools import wraps
from flask import request, jsonify, abort
import hmac
import os

def require_internal_auth(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Validate against a securely stored internal token
        auth_header = request.headers.get('Authorization')
        internal_token = os.environ.get('INTERNAL_METRICS_TOKEN')

        if not auth_header or not internal_token:
            abort(401)

        # Use constant-time comparison to prevent timing attacks
        provided_token = auth_header.replace('Bearer ', '')
        if not hmac.compare_digest(provided_token, internal_token):
            abort(403)

        return f(*args, **kwargs)
    return decorated_function

@app.route('/threads')
@require_internal_auth
def get_threads():
    threads = []
    for thread in threading.enumerate():
        threads.append({
            'name': thread.name,
            'id': thread.ident,
            'alive': thread.is_alive(),
            'daemon': thread.daemon
        })
    return jsonify({'threads': threads, 'count': len(threads)})

@app.route('/db-pool-stat')
@require_internal_auth
def get_db_pool_stat():
    engine = db.engine
    pool = engine.pool
    return jsonify({
        'pool_size': pool.size(),
        'checked_out': pool.checkedout(),
        'overflow': pool.overflow(),
        'checked_in': pool.checkedin()
    })

Key Security Improvements in the Fix

  1. Authentication Gate: Every request must present valid credentials before any data is returned.
  2. Constant-Time Comparison: Using hmac.compare_digest() prevents timing-based token enumeration attacks.
  3. Environment-Based Secrets: The internal token is loaded from environment variables, not hardcoded.
  4. Proper HTTP Status Codes: Returns 401 Unauthorized and 403 Forbidden appropriately, rather than silently serving data.

Conclusion

The /threads and /db-pool-stat vulnerability is a textbook example of how information disclosure vulnerabilities enable more devastating attacks downstream. No data was stolen directly. No code was executed. But an attacker armed with thread states and database pool statistics has a significant head start on breaking your application in ways that are much harder to detect and defend against.

The key takeaways from this vulnerability:

  • Operational data is sensitive data. Thread counts, pool sizes, and infrastructure details deserve the same access controls as user data.
  • Authentication is not optional for any endpoint. "Internal" does not mean "unprotected."
  • Defense in depth matters. Combine application-level authentication with network-level restrictions.
  • Automate your security checks. Tests that verify authentication requirements catch regressions before they ship.

Security is rarely about dramatic zero-days. More often, it's about the quiet, mundane discipline of asking "who should be able to see this?" for every single piece of data your application can return. Build that habit, and you'll catch vulnerabilities like this one long before they ever reach production.


This vulnerability was identified and fixed as part of an automated security scanning process. Automated security tooling combined with developer education is one of the most effective ways to maintain a strong security posture at scale.

Found a similar vulnerability in your codebase? Consider integrating automated security scanning into your CI/CD pipeline to catch issues early.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #35665

Related Articles

medium

path-to-regexp 0.1.12 DoS: Catastrophic Backtracking on Malformed URL

path-to-regexp version 0.1.12 contains a regular expression engine vulnerability where maliciously crafted URL parameters trigger exponential backtracking, causing CPU exhaustion and service unavailability. Upgrading to version 0.1.13 eliminates the vulnerable pattern from the parsing logic.

critical

CVE-2026-59873: node-tar 7.5.11 DoS via Crafted Gzip Bomb

node-tar versions 7.5.11 through 7.5.18 are vulnerable to a denial-of-service attack through maliciously crafted gzip archives that decompress to disproportionately large sizes. An attacker can exploit this to exhaust memory and CPU resources by submitting a small, highly compressed archive that expands beyond configured limits during extraction.

high

MapManager.get() Race Condition Duplicates API Requests

The MapManager's `get(mapUid, cache)` method used a check-then-act pattern that permitted multiple concurrent requests to pass the cache miss check simultaneously, triggering redundant API calls and risking cache corruption. The fix introduces a `_pending` promise map to deduplicate in-flight fetches for identical map UIDs.

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.