Configure CSP/HSTS headers in AppRouter, enable CSRF, add rate limiting, and scan for committed secrets.
A functionally complete app is not automatically a secure one. Production systems on BTP are internet-accessible — malicious actors scan for misconfigured endpoints, weak tokens, and missing headers within hours of a deployment. Running through the security checklist before go-live is cheaper than responding to an incident.
@requires or @restrict/admin/*) require the admin role/health endpoint returns only status — no internal datatenant-mode: dedicated (single-tenant) or shared (multi-tenant) — never default"kind":"mocked") are disabled in production (not in [production] profile)Strict-Transport-Security: max-age=31536000; includeSubDomains$top limit enforced on all list endpoints to prevent large payload attacksstrict: true in CDS modelpackage.json.env file is in .gitignore// approuter/xs-app.json
{
"compression": { "enabled": true },
"responseHeaders": [
{
"name": "Content-Security-Policy",
"value": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://*.hana.ondemand.com; frame-ancestors 'none'"
},
{
"name": "X-Frame-Options",
"value": "DENY"
},
{
"name": "X-Content-Type-Options",
"value": "nosniff"
},
{
"name": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains"
}
]
}
// approuter/xs-app.json route
{
"source": "^/api/(.*)$",
"target": "/api/$1",
"destination": "cap-backend",
"csrfProtection": true, // AppRouter will require X-CSRF-Token header on mutations
"authenticationType": "xsuaa"
}
The React frontend fetches the CSRF token:
// Fetch the CSRF token before any POST/PUT/DELETE
const tokenRes = await fetch('/api/xsrf-token', {
headers: { 'X-CSRF-Token': 'Fetch' }
});
const csrfToken = tokenRes.headers.get('X-CSRF-Token');
// Use it in subsequent mutations
await fetch('/api/odata/v4/OrderService/Orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ ... })
});
express-rate-limitnpm install express-rate-limit
// srv/server.js
const rateLimit = require('express-rate-limit');
const cds = require('@sap/cds');
cds.on('bootstrap', app => {
// Strict limit on auth-adjacent endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 20,
message: 'Too many requests from this IP, please try again later'
});
app.use('/api/auth', authLimiter);
app.use('/api/upload', authLimiter);
// General API limit
const generalLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 200
});
app.use('/api', generalLimiter);
});
# Install truffleHog
pip install truffleHog3
# Scan the git history for secrets
trufflehog3 --regex --entropy=False .
# Or use gitleaks
brew install gitleaks
gitleaks detect --source . -v
If a secret is found in git history:
1. Rotate the credential immediately — assume it is compromised
2. Remove it from git history (git filter-repo --path-glob '*.env' --invert-paths)
3. Force-push the cleaned history (coordinate with the team)
4. Audit CF environment variables and service keys for the same credential
// package.json — production profile overrides
{
"cds": {
"requires": {
"[production]": {
"auth": { "kind": "xsuaa" }
},
"[development]": {
"auth": {
"kind": "mocked",
"users": { ... }
}
}
}
}
}
CAP activates the [production] profile when NODE_ENV=production (CF sets this automatically).
| Mistake | Fix |
|---|---|
Leaving "kind":"mocked" in the top-level requires (not profile-scoped) |
Scope mock auth to [development] only |
| No CSRF on AppRouter routes | Set "csrfProtection": true on all mutation routes |
frame-ancestors * in CSP |
Use 'none' to prevent clickjacking |
Service keys in package.json |
Use CF environment variables or cds bind |
| No rate limiting on upload endpoints | A single client can exhaust Object Store bandwidth |
You have worked through the security control inventory, configured CSP/HSTS/X-Frame-Options headers in AppRouter, enabled CSRF protection, added rate limiting on sensitive endpoints, and know how to scan for committed secrets and rotate exposed credentials.
Sign in for progress tracking & Q&A →