HIGH data exposurestrapitypescript

Data Exposure in Strapi (Typescript)

Data Exposure in Strapi with Typescript — how this specific combination creates or exposes the vulnerability

Strapi is a headless CMS that exposes content through REST or GraphQL APIs. When built with Typescript, developers often define entity interfaces and service methods with strong typing, but misconfigurations in route policies or controller logic can still lead to unintended data exposure. A common issue occurs when developers rely solely on Typescript interfaces for data shaping without enforcing server-side access controls, assuming that client-side filtering or TypeScript types prevent over-fetching.

For example, a Strapi content type like 'Article' might have sensitive fields such as 'internalNotes' or 'authorId' that should not be exposed to public endpoints. If the route configuration for GET /articles does not explicitly restrict which fields are returned, Strapi will return all fields defined in the model — including those marked as private — unless privateAttributes is set in the model schema or sanitizeEntity is used in the controller.

In a Typescript Strapi project, a developer might define an interface like:

interface Article {
  id: number;
  title: string;
  content: string;
  internalNotes?: string; // Should not be exposed
  authorId: number;
  createdAt: string;
  updatedAt: string;
}

And then create a service method that returns raw entity data:

// src/api/article/services/article.ts
import { factory } from '@strapi/strapi'

export default factory.createService(({ strapi }) => ({
  async find() {
    const entities = await strapi.entityService.findMany('api::article.article', {});
    return entities; // Returns all fields, including internalNotes
  }
}));

Even though Typescript ensures the Article interface is respected in development, at runtime Strapi serializes the full database entity. Without explicit field selection or sanitization, the API exposes internalNotes to anyone who can access the endpoint — a classic data exposure vulnerability (OWASP API1:2023 – Broken Object Property Level Authorization).

This risk is amplified in Strapi because its default behavior favors developer convenience over security: controllers return full entities unless restricted. Typescript does not prevent this; it only provides compile-time safety. The vulnerability exists in the gap between compile-time assurances and runtime data exposure.

Typescript-Specific Remediation in Strapi — concrete code fixes

To fix data exposure in a Strapi application using Typescript, you must enforce field-level access controls at the controller or service level, using Strapi’s built-in sanitization utilities. Relying on Typescript interfaces alone is insufficient; you must explicitly strip or omit sensitive fields before sending the response.

One effective approach is to use sanitizeEntity (or sanitizeOutput in Strapi v4) within your service or controller to remove private attributes based on the model definition. First, ensure your model’s schema marks sensitive fields as private:

// src/api/article/content-types/article/schema.json
{
  "kind": "collectionType",
  "collectionName": "articles",
  "info": {
    "singularName": "article",
    "pluralName": "articles",
    "displayName": "Article"
  },
  "options": {
    "draftAndPublish": true
  },
  "attributes": {
    "title": {
      "type": "string"
    },
    "content": {
      "type": "text"
    },
    "internalNotes": {
      "type": "text",
      "private": true // Marks field as private
    },
    "authorId": {
      "type": "integer"
    },
    "createdAt": {
      "type": "datetime"
    },
    "updatedAt": {
      "type": "datetime"
    }
  }
}

Then, in your service, use sanitizeEntity to strip private fields:

// src/api/article/services/article.ts
import { factory } from '@strapi/strapi'
import { sanitizeEntity } from '@strapi/utils'

export default factory.createService(({ strapi }) => ({
  async find() {
    const entities = await strapi.entityService.findMany('api::article.article', {});
    return entities.map(entity =>
      sanitizeEntity(entity, { model: strapi.getModel('api::article.article') })
    );
  }
}));

Alternatively, if you need fine-grained control (e.g., exposing different fields based on user role), you can manually construct the response using Typescript interfaces to ensure type safety:

interface PublicArticle {
  id: number;
  title: string;
  content: string;
  authorId: number;
  createdAt: string;
  updatedAt: string;
}

// Inside service method
const sanitized = entities.map(entity => ({
  id: entity.id,
  title: entity.title,
  content: entity.content,
  authorId: entity.authorId,
  createdAt: entity.createdAt,
  updatedAt: entity.updatedAt
} as PublicArticle));

return sanitized;

This approach guarantees that only intended fields are returned, and the as PublicArticle assertion ensures Typescript validates the shape at compile time. Combine this with route-level policies (<Policy> &lt;Policy> in routes.json) to enforce authentication where needed, but always sanitize data at the service or controller layer to prevent exposure regardless of how the endpoint is accessed.

Related CWEs: dataExposure

CWE IDNameSeverity
CWE-200Exposure of Sensitive Information HIGH
CWE-209Error Information Disclosure MEDIUM
CWE-213Exposure of Sensitive Information Due to Incompatible Policies HIGH
CWE-215Insertion of Sensitive Information Into Debugging Code MEDIUM
CWE-312Cleartext Storage of Sensitive Information HIGH
CWE-359Exposure of Private Personal Information (PII) HIGH
CWE-522Insufficiently Protected Credentials CRITICAL
CWE-532Insertion of Sensitive Information into Log File MEDIUM
CWE-538Insertion of Sensitive Information into Externally-Accessible File HIGH
CWE-540Inclusion of Sensitive Information in Source Code HIGH

Frequently Asked Questions

Does using Typescript in Strapi automatically prevent data exposure?
No. Typescript provides compile-time type safety but does not control what data is serialized and sent in API responses. Strapi will still return all model fields unless you explicitly sanitize the output using sanitizeEntity or manually construct the response to exclude private attributes.
How can I verify that a Strapi API endpoint is not exposing sensitive data?
Use a tool like middleBrick to scan the endpoint. It performs unauthenticated black-box testing and includes data exposure checks that will flag if sensitive fields (e.g., those marked as private in the schema) are returned in API responses without proper authorization.