Passbolt is an open-source team password manager built around PGP encryption, used by security-conscious organizations as a self-hosted credential sharing platform. Its security assessment covers several important areas: Passbolt's healthcheck endpoint at /healthcheck is accessible without authentication and exposes database connection status, GPG server key configuration, PHP version, and SSL certificate details — this information disclosure aids targeted exploitation; Passbolt's REST API uses GPG-signed challenge-response authentication — valid API access tokens stored in browser extension local storage or extracted from application context give access to all passwords shared with the authenticated user; Passbolt's LDAP directory synchronization feature (Pro/Cloud) stores LDAP bind credentials in the Passbolt application database — compromise of the Passbolt database yields LDAP service account credentials; account recovery in Passbolt uses administrator-orchestrated key recovery requiring admin cooperation — the account recovery process emails recovery tokens that must be carefully managed; and Passbolt's public key server exposes all user PGP public keys at /api/v1/gpgkeys — this endpoint enables user enumeration and harvesting of all registered user email addresses and key fingerprints without authentication. This guide covers systematic Passbolt security assessment.
# Passbolt healthcheck — accessible without authentication by default
PASSBOLT_URL="https://passbolt.example.com"
# Check healthcheck endpoint (reveals server configuration)
curl -s "${PASSBOLT_URL}/healthcheck" 2>/dev/null | python3 -c "
import json,sys
try:
d = json.load(sys.stdin)
print('Healthcheck response:')
for section, checks in d.items():
if isinstance(checks, dict):
print(f' {section}:')
for k, v in list(checks.items())[:3]:
print(f' {k}: {v}')
except:
# JSON parse failure — check for HTML response
content = sys.stdin.read() if hasattr(sys.stdin,'read') else ''
print(f'HTML response (first 200 chars): {content[:200]}')
" 2>/dev/null
# Alternative: /api/v1/healthcheck.json
curl -s "${PASSBOLT_URL}/api/v1/healthcheck.json" 2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
body = d.get('body',{})
print(f'Database: {body.get(\"database\",{}).get(\"connect\",\"?\")}')
print(f'GPG key: {body.get(\"gpg\",{}).get(\"gpgKeyPublicReadable\",\"?\")}')
print(f'SSL: {body.get(\"ssl\",{}).get(\"peerValid\",\"?\")}')
print(f'App version: {body.get(\"application\",{}).get(\"appVersion\",\"?\")}')
" 2>/dev/null
# Passbolt exposes all user PGP public keys without authentication
# This enables user enumeration — harvesting all registered email addresses
PASSBOLT_URL="https://passbolt.example.com"
# List all GPG keys (unauthenticated)
curl -s "${PASSBOLT_URL}/api/v1/gpgkeys.json" 2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
keys = d.get('body',[])
print(f'PGP keys registered: {len(keys)} users')
for k in keys[:10]:
uid = k.get('uid','?') # Contains 'Name '
fingerprint = k.get('fingerprint','?')[:16]
print(f' {uid} fingerprint={fingerprint}...')
" 2>/dev/null
# Also check if user list is accessible
curl -s "${PASSBOLT_URL}/api/users.json" 2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
if d.get('header',{}).get('status') == 'success':
users = d.get('body',[])
print(f'Users enumerable: {len(users)}')
for u in users[:5]:
profile = u.get('Profile',{})
print(f' {profile.get(\"first_name\",\"?\")} {profile.get(\"last_name\",\"?\")} — {u.get(\"username\",\"?\")}')
" 2>/dev/null
| Security Test | Method | Risk |
|---|---|---|
| /healthcheck exposes DB status and server configuration | GET /healthcheck or /api/v1/healthcheck.json — reveals database connectivity, GPG key status, PHP version, SSL state | Medium |
| User enumeration via GPG keys API | GET /api/v1/gpgkeys.json — returns all registered user UIDs containing email addresses without authentication | Medium |
| API JWT token with access to all user-accessible passwords | Extract JWT from browser extension storage — provides API access to all passwords shared with that user account | Critical |
| LDAP bind credentials in Passbolt configuration | Access /etc/passbolt/passbolt.php or database ldap_directories table — yields LDAP service account credentials | High |
| Brute force on passphrase-less PGP key import | If users configure PGP keys without passphrases, a stolen private key file gives immediate API access | High |
| Account recovery links not revoked after compromise | Review active recovery links in admin panel — unrevoced recovery links from previous compromises remain usable | High |
Ironimo tests Passbolt deployments for healthcheck endpoint exposing database connection status, GPG key configuration, and PHP version without authentication, user enumeration via the GPG keys API returning all registered user email addresses without credentials, API JWT tokens stored in browser extension local storage providing access to all user-accessible password shares, LDAP bind credentials stored in the Passbolt database or configuration files, MFA bypass paths for admin accounts, and unrevoced account recovery links remaining valid after user offboarding.
Start free scan