Valtik Studios
Back to blog
Strapihigh2026-03-0112 min

Strapi CMS Security: JWT Forgery, Plugin Vulnerabilities, and the Default Admin Problem

Strapi is the most popular open-source headless CMS, with tens of thousands of production deployments. It's also a recurring finding on our platform audits. JWT secrets that can be guessed, plugin vulnerabilities that haven't been patched, admin panels exposed to the internet, and role permissions that commonly grant too much. A deep dive into the Strapi attack patterns and hardening.

TT
Tre Trebucchi·Founder, Valtik Studios. Penetration Tester

Founder of Valtik Studios. Pentester. Based in Connecticut, serving US mid-market.

Why Strapi keeps appearing on audits

Here's the part consultants don't put in the glossy PDF.

Strapi bills itself as the leading open-source headless CMS. Self-hosted, Node.js-based, SQL database (SQLite, Postgres, MySQL). Customizable via plugins. Admin panel for content management. REST and GraphQL APIs auto-generated from content types.

Adoption is huge. The GitHub repo has 60,000+ stars and Strapi deployments power content for thousands of websites, mobile apps, and B2B applications. It's particularly popular in European markets and among agencies building for SMB clients.

It's also consistently misconfigured. The default setup requires thoughtful hardening that many deployments skip. The plugin ecosystem. A core feature. Introduces variable-quality third-party code. The JWT-based authentication has specific risks. And self-hosted deployments frequently lag on updates, accumulating known CVEs.

This post walks through the specific Strapi attack patterns we find on penetration tests, the underlying misconfigurations. And the hardening every production Strapi deployment needs.

Attack pattern 1: Admin panel exposed to the internet

Strapi's admin panel lives at /admin. Full content management, user management, role editing, plugin configuration. All accessible through this interface after authentication.

The misconfiguration: developers run Strapi and expose the entire application (including /admin) to the public internet, relying on admin credentials to protect sensitive operations.

Enumeration is trivial. The /admin path returns a Strapi-branded login page that's easily identified. Shodan queries for "strapi" in HTTP bodies find thousands of exposed admin panels.

Attack:

  1. Find exposed Strapi instance at target.example.com/admin
  2. Enumerate Strapi version from login page (via source code, embedded scripts)
  3. Check for known CVEs affecting that version
  4. Attempt default credentials (if fresh deployment not yet configured)
  5. Brute-force credentials (Strapi has rate limiting but it's not aggressive)

Real finding: a company's Strapi admin panel was at api.example.com/admin. Their admin account used the email address of the company's founder and a password that matched her LinkedIn-scrapeable data. Credential-stuffing test took 10 minutes. Full content access followed.

The fix:

  • Never expose the /admin path to the public internet. Reverse proxy it behind VPN, IP allowlist, or authenticated proxy.

# nginx configuration
location /admin {
  # IP allowlist for admin panel
  allow 203.0.113.0/24; # office VPN range
  deny all;
  
  proxy_pass http://strapi_backend;
}

Location /api {
  # Public API access
  proxy_pass http://strapi_backend;
}

Location /uploads {
  # Public file access (if using local provider)
  proxy_pass http://strapi_backend;
}
  • Strong admin credentials via password manager
  • MFA on admin accounts. Strapi supports TOTP-based MFA via the users-permissions plugin or Strapi Cloud

Attack pattern 2: JWT secret exposure / weak generation

Strapi uses JWTs for authentication. The admin panel and API both generate JWT tokens signed with a server-side secret.

Common vulnerabilities:

JWT secret not configured

Strapi generates a JWT secret during initial setup. If the environment variable JWT_SECRET (for admin) or API_TOKEN_SALT (for API tokens) isn't set, Strapi generates a default or uses a predictable fallback in older versions.

JWT secret in public repository

Developers commit Strapi config files containing secrets:

// config/server.js
module.exports = ({ env }) => ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  app: {
    keys: env.array('APP_KEYS', ['key1', 'key2', 'key3', 'key4']), // default keys committed
  },
});

Or in environment files:

#.env committed to public repo
JWT_SECRET=mySecretKey123
API_TOKEN_SALT=abcdef1234567890

Weak JWT secret

A secret like admin or strapi123 is trivially crackable. Attackers who can obtain a legitimate JWT can run offline brute-force on the signature until they find the secret.

JWT secret sharing across environments

Development and production Strapi instances sharing the same JWT secret means development-environment compromise affects production.

Attack:

# Obtain a JWT from a legitimate session or account
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Crack the JWT secret offline
hashcat -m 16500 jwt.txt wordlist.txt

# Once cracked, forge admin JWTs
python3 << 'EOF'
import jwt
import time

Secret = "cracked_secret_here"
payload = {
    'id': 1, # admin user ID
    'isAdmin': True,
    'iat': int(time.time()),
    'exp': int(time.time()) + 3600 * 24 * 365, # 1 year
}
token = jwt.encode(payload, secret, algorithm='HS256')
print(token)
EOF

Forged token with admin privileges. Full Strapi compromise.

The fix:

  • Generate strong JWT secrets. At least 32 random bytes:
openssl rand -hex 32

  • Different secrets per environment. Never share between dev and prod
  • Secrets in environment variables. Never in source code
  • Rotate secrets periodically. Every 6-12 months minimum
  • Audit git history for committed secrets (rotate anything that was ever exposed)

Attack pattern 3: Role permission over-provisioning

Strapi has a role-based permission model. Default roles:

  • Super Admin. Full access
  • Editor. Content management
  • Author. Create/edit own content
  • Authenticated. API role for logged-in users (not admin)
  • Public. API role for unauthenticated users

Each role has granular permissions per content type and per action (find, findOne, create, update, delete, etc.).

Common misconfigurations:

  • Public role with broader permissions than intended. Often during development, widened for testing and not tightened
  • Authenticated role allows any authenticated user to modify any content. No ownership check
  • Custom roles inheriting more than intended from parent roles (Strapi's role model isn't strictly hierarchical, but assumptions can drift)
  • API permissions don't match UI permissions. The admin UI appropriately shows restricted options to limited roles, but the API exposes the same data without the UI constraints

Real finding: a marketing agency's Strapi powered multiple client websites. The Public role had find permissions on the User collection. Any unauthenticated request returned the email and username of every user. Both admin users and customer accounts across all clients. Single-request data breach affecting 50+ clients.

The fix:

  • Audit Public role carefully. Should have find and findOne only on truly public content types (blog posts, pages). Never on User, AdminUser, or admin-adjacent collections.
  • Audit Authenticated role. Appropriate access for logged-in users, not implicit "logged-in = can do anything"
  • Test role permissions via API calls. Don't rely on admin UI behavior
  • Use custom roles for complex permission models instead of stretching default roles

Attack pattern 4: Plugin vulnerabilities and dependencies

Strapi's plugin ecosystem is a core value proposition and a persistent security concern. Plugins include:

  • Official Strapi plugins. Generally well-maintained but still have had CVEs
  • Community plugins. Variable quality
  • Custom plugins. Written by the organization itself

Each plugin runs with Strapi's privileges. Plugin vulnerabilities translate directly to Strapi compromise.

Common plugin security issues:

  • Plugins with known CVEs that haven't been updated
  • Plugins that accept untrusted user input without validation
  • Plugins that expose administrative endpoints without appropriate permission checks
  • Plugins that log sensitive data
  • Plugins with their own dependency vulnerabilities

Real finding: a customer used a community-authored plugin for image manipulation. The plugin shelled out to ImageMagick with user-provided parameters → RCE via crafted filename. The plugin hadn't been updated in 18 months despite ImageMagick CVEs during that period.

The fix:

  • Inventory all plugins. Official, community, custom
  • Keep plugins updated. Quarterly review minimum
  • Security review of community plugins before use
  • Dependency scanning. npm audit on Strapi projects catches dependency-level CVEs
  • Minimize plugin usage. Each plugin is attack surface
  • Consider removing unused plugins even if they're installed but disabled

Attack pattern 5: API token over-privileging

Strapi supports API tokens for server-to-server authentication. Tokens can be:

  • Read-only. Can only read content
  • Full access. Full admin-equivalent access
  • Custom. Specific permissions

Common misconfigurations:

  • Full-access tokens used for frontend rendering. The token is in client-side code, any visitor can copy it and make admin-level API calls
  • Tokens without expiration. Stay valid indefinitely
  • Shared tokens across integrations. Single compromise affects multiple systems
  • Tokens in git history. Leaked and not rotated

Real finding: an e-commerce site used a Strapi "Full Access" token for its Next.js frontend to fetch product data. The token was in NEXT_PUBLIC_STRAPI_TOKEN, meaning it was bundled into every page served to visitors. Attackers extracted the token within minutes of visiting the homepage → full admin access to the Strapi backend, which was used to modify product prices, create fake products. And delete legitimate content.

The fix:

  • Never use full-access tokens in client-side code
  • Use read-only or custom tokens for frontend purposes
  • Set token expiration where possible
  • Per-integration tokens with minimum necessary permissions
  • Token rotation on schedule and on staff changes

Attack pattern 6: CORS misconfiguration

Strapi has configurable CORS. Common mistakes:

  • CORS configured to allow any origin (*)
  • CORS allowlist includes localhost or development origins in production
  • CORS allows credentials with wildcard origin ( blocked by browsers but some servers incorrectly accept)
  • CORS allowlist includes dead subdomains that could be taken over

The fix:

// config/middlewares.js
module.exports = [
  //...
  {
    name: 'strapi::cors',
    config: {
      origin: ['https://www.example.com', 'https://app.example.com'],
      methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'],
      headers: ['Content-Type', 'Authorization'],
      credentials: true,
    },
  },
  //...
];

Production CORS should have:

  • Specific origins only
  • No wildcards
  • No localhost in production
  • Regular audit of allowed origins

Attack pattern 7: File upload vulnerabilities

Strapi has built-in file upload via the upload plugin. Common issues:

  • Upload provider misconfigured (local filesystem writable by wrong user)
  • S3 / cloud provider credentials leaked via config
  • Upload size limits not enforced
  • File type validation bypasses (magic bytes vs extension mismatch)
  • Uploaded files serve with execution permissions (.php, .js in upload directories)

Attack:

  • Upload a malicious file disguised as image
  • Trigger execution via path traversal or direct request
  • RCE via uploaded file

The fix:

  • Validate file types by content inspection, not extension
  • Store uploaded files in cloud storage (S3, GCS) with appropriate access controls
  • Never serve upload directories with script execution enabled
  • Limit upload sizes appropriately
  • Scan uploads for malicious content where possible

Attack pattern 8: GraphQL introspection and query complexity

If you use the GraphQL plugin, you get similar issues to any GraphQL server:

  • Introspection enabled in production → full schema disclosure
  • No query depth limit → DoS via deeply nested queries
  • No query complexity scoring → expensive query DoS
  • Field-level permissions not enforced → data exposure via clever queries

The fix:

// config/plugins.js
module.exports = {
  graphql: {
    config: {
      endpoint: '/graphql',
      shadowCRUD: true,
      playgroundAlways: false, // disable playground in production
      depthLimit: 7, // limit query depth
      amountLimit: 100, // limit returned amount
      apolloServer: {
        tracing: false,
        introspection: false, // disable introspection in production
      },
    },
  },
};

Attack pattern 9: Outdated Strapi versions

Strapi's release cadence has been fast. Strapi 5 released in late 2024. Strapi 4 is still supported but gets fewer updates. Strapi 3 is EOL.

Common issue: self-hosted Strapi deployments running versions 2-3 years old with known CVEs.

The fix:

  • Strapi 5 for new deployments
  • Migration plan for Strapi 4 → 5 (breaking changes exist)
  • Strapi 3 must be migrated or replaced. No longer receives security updates
  • Subscribe to Strapi security advisories
  • Quarterly update cadence

Attack pattern 10: Database exposure

Self-hosted Strapi connects to a database (PostgreSQL, MySQL, or SQLite). Common database-level issues:

  • Database port exposed to public internet
  • Database credentials in config files committed to git
  • Database backups stored publicly (S3 bucket misconfigurations)
  • SQLite database file accessible via web (if stored in public directory)
  • Shared database credentials between environments

The fix:

  • Database on private network only
  • Database credentials in environment variables
  • Backups with encryption at rest and appropriate access controls
  • Never store SQLite database file in publicly-served directory

The hardening checklist

For any production Strapi deployment:

Infrastructure

  • [ ] Admin panel /admin not exposed to public internet
  • [ ] Reverse proxy with TLS in front of Strapi
  • [ ] Database on private network
  • [ ] Regular backups with tested restoration

Authentication

  • [ ] Strong JWT secrets (32+ random bytes)
  • [ ] Different secrets per environment
  • [ ] MFA on admin accounts
  • [ ] Strong admin credentials via password manager
  • [ ] Regular admin access review

Roles and permissions

  • [ ] Public role reviewed and minimized
  • [ ] Authenticated role reviewed
  • [ ] Custom roles documented with intended permissions
  • [ ] Permissions tested via API calls, not UI

API tokens

  • [ ] No full-access tokens in client-side code
  • [ ] Tokens have expiration where possible
  • [ ] Per-integration tokens with minimum permissions
  • [ ] Regular token rotation

Plugins

  • [ ] All plugins inventoried
  • [ ] Plugins updated quarterly minimum
  • [ ] Community plugins reviewed before use
  • [ ] Dependency scanning (npm audit) in CI

Configuration

  • [ ] CORS specifically configured for production origins
  • [ ] GraphQL introspection disabled in production
  • [ ] File upload validation implemented
  • [ ] CSP and security headers configured

Operations

  • [ ] Strapi version current (quarterly updates)
  • [ ] Monitoring of admin panel access
  • [ ] Audit logging enabled
  • [ ] Incident response plan

Strapi-specific hardening tips

Use Strapi Cloud for small teams

If you're a small team running Strapi for client work, Strapi Cloud (managed hosting by the Strapi team) handles infrastructure-level security. More expensive than self-hosting but reduces operational burden.

For agencies managing multiple client sites

  • Separate Strapi instance per client (not shared)
  • Per-client admin credentials
  • Automated security updates
  • Standardized hardening template for new deployments

For enterprises

  • Integration with enterprise SSO (Strapi Enterprise plan)
  • Audit log forwarding to SIEM
  • Formal change management for content type modifications
  • Annual penetration testing

For custom plugin development

  • Security review in development lifecycle
  • Input validation at plugin boundaries
  • Minimum-privilege operation
  • Dependency management and scanning

For Valtik clients

Valtik's CMS security audits include Strapi-specific reviews:

  • Admin panel exposure and authentication review
  • JWT secret strength and handling audit
  • Role and permission matrix audit
  • Plugin inventory and security assessment
  • API token inventory and exposure check
  • Configuration review (CORS, GraphQL, CSP)
  • Plugin code review for custom plugins
  • Database and infrastructure security

If you run Strapi in production and haven't had a security review, reach out via https://valtikstudios.com.

The honest summary

Strapi is a capable platform with specific attack surfaces that require attention. The patterns in this post. Exposed admin, weak JWT secrets, over-permissive roles, over-privileged tokens, unpatched plugins. Appear in every unaudited Strapi deployment.

The hardening is tractable. It requires explicit attention to configuration than relying on defaults. Audit yours before it becomes the case study.

Sources

  1. Strapi Documentation
  2. Strapi Security Best Practices
  3. Strapi GitHub Security Advisories
  4. Strapi Authentication Docs
  5. Strapi Roles and Permissions
  6. Strapi GraphQL Plugin
  7. JWT Security Best Practices. RFC 8725
  8. OWASP API Security Top 10
  9. CMS Security Research. Valtik
  10. npm audit Documentation
strapiheadless cmsplatform securityjwtnode.jspenetration testingapplication securityvulnerability assessmentresearch

Want us to check your Strapi setup?

Our scanner detects this exact misconfiguration. plus dozens more across 38 platforms. Free website check available, no commitment required.

Get new research in your inbox
No spam. No newsletter filler. Only new posts as they publish.