Skip to content

Commit 7d3212d

Browse files
coolstartnowclaude
andcommitted
fix(security): SMTP-/WebDAV-Zugangsdaten in GET /admin/org-settings gefiltert
GHSA-63xg-pg3x-g36f (CVSS 6.5, gemeldet von @dav-rest): jede Rolle ab 'reader' konnte smtpSettings.pass und webdavSettings.appPassword im Klartext auslesen, da der Lese-Pfad nur mit authorize('reader') statt authorize('admin') gesichert war. Fix: Zugangsdaten werden für Rollen unterhalb 'admin' herausgefiltert, der restliche Endpunkt bleibt erreichbar (navOrder/Splash-Settings werden von allen Rollen beim Login benötigt). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H5yQKPm5aF2bFWaZz2TEDr
1 parent 35e40b4 commit 7d3212d

6 files changed

Lines changed: 72 additions & 29 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010

1111
## [Unreleased]
1212

13+
## [1.40.3] — 2026-09-02
14+
15+
### Security
16+
- **Broken access control in `GET /admin/org-settings` leaked SMTP-/WebDAV-Zugangsdaten** ([GHSA-63xg-pg3x-g36f](https://github.com/coolstartnow/isms-builder/security/advisories/GHSA-63xg-pg3x-g36f), CVSS 6.5/medium, gemeldet von @dav-rest). Der Lese-Pfad war mit `authorize('reader')` geschützt — der niedrigsten Rolle — und gab das komplette Organisationseinstellungen-Objekt unredigiert zurück, inklusive `smtpSettings.pass` und `webdavSettings.appPassword` im Klartext. Jede eingeloggte Rolle (bis hinunter zu `reader`) konnte damit die Zugangsdaten des organisationseigenen Mailservers und/oder WebDAV-Speichers auslesen, obwohl die Schwester-Endpunkte (`/admin/role-settings`, `/admin/users`) korrekt auf `admin` beschränkt waren. Da alle Rollen beim Login auf denselben Endpunkt angewiesen sind (Navigationsreihenfolge, Splash-Einstellungen), wurde der Zugriff nicht komplett gesperrt, sondern `server/routes/admin.js` filtert `smtpSettings.pass` und `webdavSettings.appPassword` jetzt für alle Rollen unterhalb `admin` heraus — alle übrigen (nicht-geheimen) Felder bleiben wie bisher lesbar. Betroffen: alle Versionen bis 1.40.2, sofern SMTP- und/oder WebDAV-Einstellungen konfiguriert wurden.
17+
1318
## [1.40.2] — 2026-08-30
1419

1520
### Fixed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- © 2026 Claude Hecker — ISMS Builder V 1.40.2 — AGPL-3.0 -->
1+
<!-- © 2026 Claude Hecker — ISMS Builder V 1.40.3 — AGPL-3.0 -->
22
![ISMS Builder Banner](isms-builder-banner.png)
33
# ISMS Builder
44

package-lock.json

Lines changed: 25 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "isms-build",
3-
"version": "1.40.2",
3+
"version": "1.40.3",
44
"description": "ISMS Template Builder – Build Mode",
55
"scripts": {
66
"start": "node server/index.js",

server/routes/admin.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const express = require('express')
44
const router = express.Router()
55
const fs = require('fs')
66
const path = require('path')
7-
const { requireAuth, authorize } = require('../auth')
7+
const { requireAuth, authorize, ROLE_RANK } = require('../auth')
88
const orgSettingsStore = require('../db/orgSettingsStore')
99
const auditStore = require('../db/auditStore')
1010
const customListsStore = require('../db/customListsStore')
@@ -214,8 +214,20 @@ router.post('/admin/list/:listId/reset', requireAuth, authorize('admin'), async
214214
})
215215

216216
// ── Organisationseinstellungen ──
217+
// GHSA-63xg-pg3x-g36f: alle Rollen brauchen diesen Endpunkt beim Login (navOrder,
218+
// Splash-Settings) — deshalb bleibt er bei 'reader', aber Zugangsdaten werden für
219+
// Nicht-Admins herausgefiltert statt den kompletten Endpunkt zu sperren.
217220
router.get('/admin/org-settings', requireAuth, authorize('reader'), async (req, res) => {
218-
res.json(await orgSettingsStore.get())
221+
const settings = await orgSettingsStore.get()
222+
if (req.roleRank < ROLE_RANK.admin) {
223+
const { smtpSettings, webdavSettings, ...rest } = settings
224+
return res.json({
225+
...rest,
226+
smtpSettings: smtpSettings ? { ...smtpSettings, pass: undefined } : smtpSettings,
227+
webdavSettings: webdavSettings ? { ...webdavSettings, appPassword: undefined } : webdavSettings,
228+
})
229+
}
230+
res.json(settings)
219231
})
220232
router.put('/admin/org-settings', requireAuth, authorize('admin'), async (req, res) => {
221233
const updated = await orgSettingsStore.update(req.body)

tests/admin.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,32 @@ describe('Org-Einstellungen', () => {
126126
const res = await authedPut(app, readerCookie, '/admin/org-settings', { companyName: 'X' })
127127
expect(res.status).toBe(403)
128128
})
129+
130+
// GHSA-63xg-pg3x-g36f: GET /admin/org-settings gab SMTP-/WebDAV-Zugangsdaten
131+
// im Klartext an jede Rolle ab 'reader' zurück. Fix: Felder werden für
132+
// Nicht-Admins herausgefiltert, der Rest (u. a. navOrder) bleibt lesbar,
133+
// da alle Rollen ihn beim Login brauchen.
134+
test('GET /admin/org-settings – reader sieht KEINE Zugangsdaten (GHSA-63xg-pg3x-g36f)', async () => {
135+
await authedPut(app, adminCookie, '/admin/org-settings', {
136+
smtpSettings: { host: 'smtp.example.com', user: 'notify@example.com', pass: 'super-secret-smtp' },
137+
webdavSettings: { enabled: true, baseUrl: 'https://cloud.example.com', username: 'isms', appPassword: 'super-secret-webdav' },
138+
})
139+
140+
const res = await authedGet(app, readerCookie, '/admin/org-settings')
141+
expect(res.status).toBe(200)
142+
expect(res.body.smtpSettings?.pass).toBeUndefined()
143+
expect(res.body.webdavSettings?.appPassword).toBeUndefined()
144+
// Nicht-geheime Felder bleiben für reader sichtbar
145+
expect(res.body.smtpSettings?.host).toBe('smtp.example.com')
146+
expect(res.body.webdavSettings?.username).toBe('isms')
147+
})
148+
149+
test('GET /admin/org-settings – admin sieht weiterhin die vollen Zugangsdaten', async () => {
150+
const res = await authedGet(app, adminCookie, '/admin/org-settings')
151+
expect(res.status).toBe(200)
152+
expect(res.body.smtpSettings?.pass).toBe('super-secret-smtp')
153+
expect(res.body.webdavSettings?.appPassword).toBe('super-secret-webdav')
154+
})
129155
})
130156

131157
describe('Audit-Log', () => {

0 commit comments

Comments
 (0)