Skip to content

Field mappings need consumed-source semantics to prevent plaintext passthrough and PII exposure #119

Description

@alexminza

Summary

WebCM currently passes an event payload to component listeners, but there is no public contract for fields that have been mapped from one source path into a component-specific destination path.

For tracking components, this architecture fails to reliably prevent PII exposure. A source field can be mapped into a destination-specific customer-information field, while the original plaintext source field remains in the payload and is later forwarded as generic event/custom data.

WebCM should support a per-component field-mapping consumption contract so mapped source fields containing PII can be removed before the component sends data to a tracking endpoint, or so mapping metadata is available to the component when it builds the outbound request.

Problem

The core problem is that WebCM has no place to represent "this source field has already been used to populate a component-specific destination field and must not be forwarded again as generic payload data."

That is a privacy boundary, not just a convenience feature. For analytics and advertising components, mapped destination fields often have special handling such as normalization, hashing, customer-data placement, consent handling, or provider-specific validation. If the original source field remains in the generic payload after mapping, the same PII value can be sent twice:

  • once through the intended destination-specific path,
  • again as plaintext custom/event data.

The current WebCM architecture makes that hard to prevent because:

As a result, a component can mutate the payload it receives, but it cannot know whether an arbitrary source field was already mapped into a destination field unless the manager removes that source field or passes mapping metadata. Without that contract, components that forward leftover payload data cannot distinguish intentional custom data from already-consumed PII source fields.

Concrete example: Facebook Pixel

The Facebook Pixel integration shows the failure mode clearly.

Current component behavior:

That behavior is correct when the incoming payload uses the exact Meta destination keys. The gap appears when a manager maps a custom source field into one of those destination keys but preserves the original source field in the payload. The component can hash/delete the destination key, but it has no way to know that the original source field was consumed by that mapping.

Failure mode:

  • mapped customer-information value is sent in the destination-specific field, for example Meta user_data
  • original plaintext source field remains in the component payload
  • original source field is eligible for generic event/custom data forwarding
  • the tracking service can receive both the hashed destination value and the original plaintext source value

The issue is not a specific field name. Source field names are customer-defined. A hard-coded denylist of common aliases cannot solve this reliably.

Proposed solution

Add a WebCM / Component Manager contract for component-specific payload projection and consumed source fields.

Preferred shape:

  1. Preserve the original payload. Keep the request payload immutable.
  2. Create a component-specific payload. Build a fresh payload for each component invocation.
  3. Apply mappings in the manager. Resolve source paths into that component/action's destination fields.
  4. Record consumed source paths. Mark mapped source paths as consumed when they populate destination-specific fields.
  5. Exclude consumed fields from passthrough data. Remove consumed source paths before dispatch, or exclude them from any generic custom-data passthrough.
  6. Dispatch only the projected payload. Invoke the component with the component-specific payload.

This keeps privacy-sensitive cleanup in the manager layer, where field mappings are known.

Alternative acceptable design

If manager-side removal is not the right abstraction, expose mapping metadata to the component, for example:

event.mappings = [
  {
    source: "event.customer_email",
    destination: "em",
    consumeSource: true
  }
]

event.consumedFields = ["event.customer_email"]

The component could then remove consumed source fields before building an outbound tracking-service request.

This is less robust than manager-side projection because every component must implement cleanup correctly, but it is still better than leaving components to guess source aliases.

Alternatives considered

Hard-coded PII/source-field denylists in individual components are incomplete because source field names are customer-defined.

Expecting each component manager to invent its own cleanup convention works only locally and does not establish an interoperable Managed Components contract.

Leaving cleanup to the destination component is not possible with the current public event contract unless the component also receives mapping metadata.

Impact

This affects any component manager or integration that:

  • lets users map source event properties into component-specific destination fields,
  • passes the original event payload through to the component, and
  • relies on the component to forward remaining payload keys as generic event/custom data.

For analytics and advertising components, the failure mode can expose plaintext customer-information source fields alongside normalized or hashed destination fields.

Prior-art search

Related but not duplicate:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions