How Stubsmith keeps cleartext out of its servers

Security deep-dive: how edge-side masking keeps raw API payload values out of Stubsmith's servers, the narrow exceptions, and what it means for GDPR.

A fixture service is, at its core, a copy of your API traffic. That framing is also the right one for evaluating any vendor’s privacy claim about one. Before reaching for a tool that generates stubs from real request/response pairs, ask the question directly: does the vendor’s server ever see my payload content? If the answer is anything other than a structural “no”, enforced by where masking runs rather than by an access policy, you are trusting the vendor with production data.

This document covers Stubsmith’s architecture at the level a security reviewer would forward to a data protection officer. It explains what happens in each zone of the data flow, what crosses your network boundary, what Stubsmith’s server can and cannot do with what it receives, and what this means for your obligations as a data controller.

The threat model

Start with what goes wrong in a naive fixture service.

A developer captures a real API response and commits it to a test fixture file. It contains PII. Now it’s in version history, accessible to everyone with repository access, outside the data retention and access controls that govern production. This is the cassette problem described in any honest comparison of record-and-replay tools: the default is to record everything, filtering is opt-in, and filter rules fail open.

A fixture service that captures traffic server-side, in its own infrastructure, is a different shape of the same problem. The vendor promises to mask sensitive fields. You are trusting a policy promise, implemented in their code, running on their servers. A misconfiguration, a staff action, or a breach can expose payload content. Even if the vendor applies server-side at-rest protection, the server held the plaintext briefly before applying it.

Stubsmith’s architecture is designed so that neither scenario applies, because masking happens before any body value leaves your infrastructure.

The edge boundary

Stubsmith’s SDK operates as middleware inside your own service’s process. When a request/response pair is captured, two things happen inside your infrastructure before any network call is made:

Your infrastructure
┌────────────────────────────────────────────────────┐
│                                                    │
│  API service  ──►  Stubsmith middleware            │
│                    │                               │
│                    └─ Anonymizer rules applied     │
│                          (mask / redact values,    │
│                           fail-closed)             │
│                                                    │
└────────────────────────────┬───────────────────────┘
                             │
                 masked body + structural fingerprint
                             │
                             ▼
              Stubsmith ingest endpoint
              (receives masked values and field names only)

Masking removes sensitive content while preserving structure. This step executes in your process, on your hardware or your cloud instances, under your network perimeter. No raw payload value crosses the boundary between your infrastructure and Stubsmith’s, apart from the three exceptions set out under Honest limits below.

This is the core guarantee. It is structural, enforced by where masking runs, not a policy that could be modified by a configuration change on Stubsmith’s servers.

What actually leaves your network

Three things cross the boundary:

Masked sample bodies. The request/response body after anonymizer rules have run. Values are replaced with typed placeholders: strings become "<masked>", numbers become 0, booleans become false. With the opt-in format-preserving mode (STUBSMITH_MASK_SALT), string placeholders keep their shape (a masked email is still a syntactically valid email, a masked UUID is still a valid UUID), but the value is derived from a keyed hash of the original, not the original itself. Either way, Stubsmith’s server stores the masked body. The values your users sent or your API returned are not present in what we receive, with one exception: a scalar field you reviewed and gave an explicit keep rule is transmitted as it was sent. By default no field has one.

Structural fingerprints. A fingerprint is the structural signature of the request: HTTP method, path template, and a digest over the sorted key-paths, query-parameter names and content type. The SDK computes it on the raw body inside your process, before masking, and by default no values enter the digest (a variant key you opt in adds that value’s hash). Fingerprints are how Stubsmith deduplicates traffic: requests with the same method, path and key structure produce the same fingerprint and are grouped under one canonical fixture rather than stored as near-identical copies. Your endpoint names, HTTP methods, path templates and key names are visible. The values, meaning what your users sent or what your API returned, are not, apart from the hash of a variant key you opted in. Response schema shapes are recorded alongside fingerprints but are not digest inputs.

Call metadata. The method, the domain, the templated path with its query values masked, the response status, and how long the call took. The transport headers listed under Honest limits travel with it.

Shape-preserving masking semantics

The anonymizer runs your masking rules over the payload. The masking semantics are deliberately conservative: masked values keep their structural shape but lose their content.

  • String fields are replaced with "<masked>".
  • Numeric fields become 0.
  • Boolean fields become false.
  • Null fields remain null.

The point of shape-preserving masking is that the resulting stub still generates valid test behavior. A payment amount replaced with 0 still occupies the correct field at the correct type position in the response. A name field replaced with a placeholder still causes your parser to allocate a string of roughly that size. The fixture is usable for testing; the value is not useful to anyone who intercepts or reads it.

For cases where type shape alone is not enough, Stubsmith also supports format-preserving placeholders. When STUBSMITH_MASK_SALT is set at the capture point, a masked email becomes a syntactically valid email, a masked UUID becomes a different valid UUID, and a masked IBAN carries a correct mod-97 checksum. The placeholder is derived from a keyed hash of the original value, so it is deterministic and the same input always produces the same placeholder. This is opt-in; the constants above remain the default. Low-cardinality types such as currency_code, country_code, and booleans always use constant placeholders regardless of the salt setting, because a keyed hash over a small value space is recoverable from a lookup table.

Masking is content removal with shape retention. The shape is retained because that is what stub generation requires; the content is removed because that is what privacy requires. Raw values are absent from what Stubsmith receives, not because a key is missing, but because masking already ran. The exception inside the body, and the only one, is a field you explicitly kept; outside the body, see the limits below. The result is realistic fixtures that contain no cleartext sensitive data.

Fail-closed defaults

Any masking system has an obvious failure mode: a new field appears in an API response, it isn’t covered by a masking rule, and it passes through unmasked. The default behavior when that happens matters.

Stubsmith’s defaults are designed to fail closed. When the system encounters a situation where it cannot safely determine how to handle a payload (an unknown response structure, an ambiguous rule application), the SDK masks it rather than passing it through. Unrecognised fields are masked by default, and the server adds a further gate: a request shape Stubsmith has not seen before is held as pending, with no bodies stored, only the request shape and a count, until a human approves the shape. Ingest also replaces any body declared as an image, unless it is already the canonical 1x1 placeholder, with that placeholder. The conservative path is always to mask or hold more aggressively, not to pass through and handle it later.

Fail-closed defaults change the failure mode from a silent data leak to a visible operational event. An over-aggressive mask produces stubs that fail in test because a field is missing or zero; that failure is immediately visible to the developer and fixable. A silent data leak produces nothing visible until an audit or an incident. The first failure mode is cheaper.

The practical implication: when you add a new third-party integration or an upstream API starts returning new fields, update your masking configuration before enabling capture for that endpoint. The fail-closed defaults give you a safe interim: new unrecognised fields are masked, and a new request shape is held as pending, with a count recorded and no bodies stored, until a human approves it.

What Stubsmith stores

Stubsmith stores two kinds of data on its servers:

Masked sample bodies. Request and response bodies after masking rules have run. Values have been replaced; types and structure are preserved. The amount field is 0. The name field is "<masked>". The email field is "<masked>" (or a keyed-hash-derived email at your placeholder domain if format-preserving masking is enabled). This data is useful for stub generation; it does not contain your users’ personal data.

Structural fingerprints. The key-paths, HTTP method, path pattern, and content type that characterise the request/response shape. By default no values; a variant key you opt in adds that value’s hash. Fingerprints are how Stubsmith deduplicates traffic and organises stubs. They are visible to Stubsmith staff: if your API path encodes sensitive information (e.g. /users/{email}/profile), that structure is visible.

GDPR posture: Stubsmith as a processor that cannot read content

Under GDPR, a vendor that processes personal data on behalf of a controller is a data processor and requires a Data Processing Agreement. The key question for your DPA analysis is whether Stubsmith actually processes personal data, meaning whether the server has access to content that identifies or relates to an individual.

The edge-masking architecture is designed so that Stubsmith’s server stores only already-masked payloads. If masking rules are correctly configured, no personal data crosses the boundary in the first place. The masked body that arrives at the ingest endpoint was produced from a payload from which personal data had already been removed.

The structural fingerprint that accompanies the body contains key names, path structure and content type, not personal data under any normal interpretation. Metadata of that kind may be commercially sensitive, but it is not personal data that triggers GDPR obligations on Stubsmith’s end.

This substantially limits Stubsmith’s role as a processor compared to any service that receives plaintext payloads. The architecture is designed so that “processor” is a technical stretch: the server holds masked bodies (with values replaced) and structural metadata about your API shape, not payload content it could act on, save for the fields you explicitly kept.

Honest limits

Edge masking is a strong control. It is not an absolute guarantee. A security reviewer should understand what it does not cover.

Keep rules you approve. A field not covered by any rule is masked, because masking is fail-closed. The way a real body value reaches Stubsmith is that you reviewed the field in the Request Types editor and gave it an explicit keep rule. That is a deliberate act, and for bodies it is the only path. Which means the guarantee is not “Stubsmith cannot receive a real value” but “Stubsmith receives no real body value you did not approve one for”. Review your keep rules the way you would review an outbound data flow, because that is what they are.

Metadata visibility. Endpoint paths, HTTP methods, key names and content types are visible to Stubsmith in fingerprints. Fingerprint data is structural metadata about your API, not payload content, but it is visible to Stubsmith.

Path segments the SDK cannot recognise. The SDK normalises a path into a template before it sends anything, and it masks every query value you have not kept, but it collapses a segment only when the segment is entirely digits, UUID-shaped, or 16 or more hexadecimal characters, or when one of your curated request-type templates matches. Any other segment is transmitted as written. An endpoint shaped like /users/ana@example.com therefore carries an address out of your process. Nothing on the server catches this. Ingest does not scan captures for personal information: masking happens in your process before anything is sent, and a server-side scan could only ever recognise an arbitrary subset of formats, so a path like /users/john-smith or an identifier such as a Dutch BSN would pass it anyway. The only fix is a curated template for that endpoint.

Transport headers. content-type, content-length, accept, accept-encoding, accept-language, user-agent, host, cache-control and transfer-encoding are sent as your client set them, because a stub that does not know its content type cannot be replayed. Every other header value is masked unless you gave that header a keep rule.

Your own infrastructure scope. The edge boundary is the boundary of your infrastructure. Masking happens before transmission, inside your perimeter. The security of that perimeter (access to the host running the middleware, to the SDK configuration, to the masking rule definitions) is your responsibility. Stubsmith’s server-side controls operate on what arrives after masking; they do not compensate for an insecure deployment environment.

The SDK is open source and the masking path is auditable. Captures, samples, and stubs are processed in the EU, and no data is processed by a US-headquartered cloud provider.

For teams beginning a formal evaluation, see pricing for plan details. The free tier is available without a card.

Integration tests from real API behavior

Capture once, replay in CI, with values masked before they leave your process.