Standard Notes Security Testing: Default Credentials, API Key, Database, and JWT Secret

Standard Notes is a widely deployed self-hosted end-to-end encrypted notes server — it is used by privacy-conscious individuals and security professionals who want encrypted note synchronization under their own control. Key assessment areas: SECRET_KEY_BASE is the Rails session signing key; JWT access tokens authenticate all API calls; while note content is E2E encrypted by the client, session token compromise enables account takeover including access to encrypted blobs, session revocation across all devices, and account manipulation; and the database stores user account metadata and session tokens. This guide covers systematic Standard Notes security assessment.

Table of Contents

  1. Authentication API and Credential Testing
  2. Session Token and Account Access
  3. SECRET_KEY_BASE and Database Extraction
  4. Standard Notes Security Hardening

Authentication API and Credential Testing

# Standard Notes — authentication API and credential testing
SN_URL="https://notes.example.com"

# Standard Notes uses a Rails API for account management
# Authentication returns JWT access token + refresh token

# Test login with known credentials
curl -s -X POST "${SN_URL}/v1/auth/sign_in" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","password":"user-password","api":20200115}' \
  2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
if 'session' in d:
    sess = d.get('session',{})
    user = d.get('user',{})
    print(f'OK user_uuid={user.get(\"uuid\",\"\")[:8]} email={user.get(\"email\")}')
    print(f'  access_token={sess.get(\"access_token\",\"\")[:30]}...')
    print(f'  refresh_token={sess.get(\"refresh_token\",\"\")[:30]}...')
    print(f'  access_expiration={sess.get(\"access_expiration\")}')
elif 'error' in d:
    print(f'FAIL: {d.get(\"error\",{}).get(\"message\",\"\")}')
else:
    print(f'Response: {list(d.keys())}')
" 2>/dev/null

# Check registration endpoint — may be open allowing account creation
curl -s -X POST "${SN_URL}/v1/users" \
  -H "Content-Type: application/json" \
  -d '{"email":"test@attacker.com","password":"testpassword123","api":20200115,
       "pw_nonce":"random_nonce","version":"004"}' \
  2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
if 'session' in d:
    print('OPEN REGISTRATION: Account created successfully')
    print(f'  UUID: {d.get(\"user\",{}).get(\"uuid\")}')
elif 'error' in d:
    print(f'Registration restricted: {d.get(\"error\",{}).get(\"message\",\"\")}')
" 2>/dev/null

Session Token and Account Access

# Standard Notes session token and account access
SN_URL="https://notes.example.com"
ACCESS_TOKEN="your-sn-access-token"

# Get user account information
curl -s "${SN_URL}/v1/users/{user-uuid}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
user = d.get('user',d)
print(f'Email: {user.get(\"email\")}')
print(f'UUID: {user.get(\"uuid\")}')
print(f'Created: {user.get(\"created_at\")}')
print(f'Storage used: {user.get(\"bytes_used\",0)//1024//1024}MB / quota')
" 2>/dev/null

# List all active sessions (access to all logged-in devices)
curl -s "${SN_URL}/v1/sessions" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
sessions = d.get('sessions',[])
print(f'Active sessions: {len(sessions)}')
for s in sessions:
    print(f'  [{str(s.get(\"uuid\",\"\"))[:8]}] {s.get(\"user_agent\",\"\")[:60]}')
    print(f'    updated={str(s.get(\"updated_at\",\"\"))[:19]} current={s.get(\"current\")}')
" 2>/dev/null
# Can revoke other sessions — log out all user devices

# Get all items (encrypted note blobs)
curl -s "${SN_URL}/v1/items/all" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
items = d.get('retrieved_items',[])
print(f'Encrypted items: {len(items)}')
for item in items[:10]:
    print(f'  [{item.get(\"uuid\",\"\")[:8]}] content_type={item.get(\"content_type\")}')
    # content is encrypted — requires user encryption key to decrypt
    content_len = len(str(item.get('content','')))
    print(f'    encrypted_content_length={content_len} bytes updated={str(item.get(\"updated_at\",\"\"))[:10]}')
" 2>/dev/null
# Note: content is E2E encrypted client-side — server sees ciphertext only

SECRET_KEY_BASE and Database Extraction

# Standard Notes SECRET_KEY_BASE and database extraction

# Environment configuration
cat /var/www/standardnotes/.env /app/.env 2>/dev/null | \
  grep -E "SECRET_KEY_BASE|DB_|RAILS_ENV|DISABLE_USER_REGISTRATION|PSEUDO_KEY_PARAMS"
# SECRET_KEY_BASE — Rails session signing key
# DB_HOST, DB_USERNAME, DB_PASSWORD, DB_DATABASE
# DISABLE_USER_REGISTRATION — controls open registration
# PSEUDO_KEY_PARAMS — server-side auth parameters

# Docker environment inspection
docker inspect standard-notes 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
    for e in c.get('Config',{}).get('Env',[]):
        if any(k in e for k in ['SECRET','PASSWORD','DB_','RAILS','JWT','PSEUDO']):
            print(e)
" 2>/dev/null

# MySQL/PostgreSQL — Standard Notes database
mysql -h localhost -u standardnotes -pPASSWORD standardnotes 2>/dev/null << 'EOF'
-- All user accounts
SELECT u.uuid, u.email,
       u.pw_cost, u.pw_nonce, u.version,
       u.created_at, u.updated_at,
       u.bytes_used
FROM users u
ORDER BY u.created_at ASC
LIMIT 20;

-- All active sessions (bearer tokens for account access)
SELECT s.uuid, s.user_uuid,
       s.hashed_access_token,
       s.access_expiration,
       s.refresh_expiration,
       s.user_agent, s.created_at
FROM sessions s
ORDER BY s.created_at DESC
LIMIT 20;
-- hashed_access_token: SHA256 hash of bearer token
-- refresh_expiration: long-lived token for session renewal

-- All encrypted items (note blobs)
SELECT i.uuid, i.user_uuid,
       i.content_type,
       LENGTH(i.content) as content_size,
       i.created_at, i.updated_at
FROM items i
WHERE i.deleted = 0
ORDER BY i.updated_at DESC
LIMIT 20;
-- content: AES-256 encrypted by client — server cannot read plaintext
EOF

Standard Notes Security Hardening

Standard Notes Security Hardening Checklist:
Security TestMethodRisk
Open registration account creationPOST /v1/users — if DISABLE_USER_REGISTRATION is not set, create accounts on private server; resource consumption; potential for multi-user data co-mingling depending on configurationMedium
JWT access token session enumeration and revocationGET /v1/sessions with valid token — list all active sessions across all devices; revoke specific sessions to log out user from devices; denial of service by revoking all sessionsHigh
Database session table token extractionSELECT hashed_access_token FROM sessions — session token hashes; access tokens can be brute-forced or observed in transit; account takeover without password knowledgeHigh
SECRET_KEY_BASE extraction and Rails session forgeryRead .env SECRET_KEY_BASE — forge Rails session cookies for server admin interface access; broader than user API access — administrative operations on all accountsHigh
Encrypted item blob bulk downloadGET /v1/items/all — all user's encrypted note ciphertext; while content is E2E encrypted, offline cryptanalysis may be possible if user uses weak password; note count, creation dates, and content size are metadata that reveals usage patternsMedium

Automate Standard Notes Security Testing

Ironimo tests Standard Notes deployments for open user registration endpoint access, JWT session token enumeration and cross-device revocation capability, database session table token extraction, SECRET_KEY_BASE Rails session forgery, encrypted item blob bulk download for offline cryptanalysis assessment, account metadata exposure (email, storage usage, note count), Docker SECRET_KEY_BASE/DB_PASSWORD exposure, authentication rate limiting assessment, password strength policy enforcement, and encrypted blob size analysis for content pattern inference.

Start free scan