All writing

Structured data is still untrusted input

Sanitize metadata and JSON-LD before storage, then render a bounded fact document instead of placing arbitrary page markup into an LLM prompt.

JSON-LD, Open Graph tags, and page metadata look safer than raw HTML because they have familiar field names. They are still supplied by an external page. A document can contain oversized values, deep nesting, unexpected types, identifiers, tracking parameters, or text written to influence a downstream model.

Treat structured web data as hostile at ingestion. Store only a bounded allowlist, derive a compact fact representation, and keep the original page document out of the prompt path.

Accept unknown and reduce it deliberately

This section defines a sanitizer that starts from unknown. Casting the payload to an application type before validation only hides the trust boundary from the type system.

A useful policy is easier to review when its limits are explicit:

type MetadataPolicy = {
  allowedFields: string[];
  allowedSchemaTypes: string[];
  maximumTextLength: number;
  maximumObjectDepth: number;
  maximumArrayItems: number;
  maximumProperties: number;
};

Apply those limits while traversing the input, not after serializing an unrestricted object. Field allowlists control meaning; text, depth, width, and collection limits control resource use. A small top-level object can still contain a deeply nested or extremely wide value, so no single size check covers the boundary.

Normalization should remove control characters and collapse meaningless whitespace, but it should not silently reinterpret content. If a field is malformed, dropping it with a reason is safer than coercing it into something that looks valid.

Remove identifiers before persistence

This section addresses URLs that may contain email addresses, search terms, campaign identifiers, session tokens, or user-generated fragments. An allowlist of query keys is safer than trying to enumerate every sensitive parameter name.

Parse the URL, keep only fields with a documented downstream use, bound their values, and remove credentials and fragments. When no query parameter is needed, persist only the origin and path. Invalid URLs should become an explicit rejected value rather than falling back to the raw string.

Sanitization must happen before the write. Removing sensitive fields only when building a prompt leaves them available to logs, analytics jobs, debugging tools, and future code paths. Retaining the raw URL “just in case” is a data-governance decision, not a harmless implementation detail.

Render facts instead of arbitrary objects

This section converts sanitized metadata into a stable fact document for classification or retrieval. The renderer chooses fields in a known order, labels each value, and enforces a total output budget. That predictability improves cacheability and makes prompt changes easier to review.

Do not serialize the sanitized object wholesale. An explicit renderer documents which facts the model can see and avoids exposing a newly added storage field automatically. Separate repeated values such as tags from free text, and decide how many can contribute before the total budget is applied.

The prompt can label the block as untrusted evidence and ask the model to classify its subject matter rather than follow instructions inside it. Prompt wording is a secondary defense; the data reduction performed before the model call is the stronger boundary.

Make safety properties testable

This section verifies limits instead of checking only a normal document. Test oversized strings, deep objects, wide arrays, unexpected schema types, cyclic values, malformed URLs, credentials, fragments, and sensitive query parameters.

Each test should assert both what remains and what is removed. A sanitizer that returns a plausible title while accidentally retaining an unknown field has still failed. Property-based tests are useful here because combinations of depth, width, and type often expose traversal bugs that hand-written examples miss.

Finally, test the rendered fact document rather than stopping at the stored object. The model boundary should never exceed its total budget, include an unapproved field, or vary ordering for equivalent input.

Structured input earns trust through validation, not syntax. A bounded fact document reduces privacy exposure, prompt size, injection surface, and classification noise at the same time.