Reducing PCI DSS Scope with Cribl Stream Routing

Drop or reroute the out-of-scope noise before it reaches your cardholder-data tools.

Scope, in one paragraph

PCI DSS draws its boundary around the cardholder data environment: the systems that create, process, store, or transmit cardholder data, plus the components that can reach those systems. Every tool and log stream inside that boundary carries its share of assessment, hardening, and monitoring work. In a real network, only a slice of all log volume actually touches that environment, and the rest is fleet noise: batch jobs, internal tooling, health checks. The fewer of those noisy sources whose data reaches the tools in or next to the cardholder data environment, the smaller your compliance footprint and the less surface an attacker can cross. Where exactly the boundary sits is a decision for you and your assessor, and the Cribl pages this post cites do not answer it for you. What the pages cover, completely, is the mechanics: deciding per stream which data is in scope, dropping what is not, and routing the rest where you want it.

Decide per stream whether data is in scope

In Cribl Stream, the scoping decision is made before any transformation happens. Before incoming events are transformed by a processing Pipeline, Stream uses a set of filters to select a subset of events to deliver to the correct Pipeline, and that selection is normally made via Routes, as the Routes documentation describes. Each Route carries a filter expression, configured with JavaScript-syntax-compatible expressions. The page's own example pairs the event's source with a field value:

source=='foo.log' && fieldA=='bar'

The source half of that expression is the key a scoping decision hangs on. If a Source belongs to your cardholder data environment, its events may be in scope; if it does not, the same expression keeps that stream out of your cardholder-data Pipelines from the first hop. Routes are evaluated in their display order, from first to last, and the docs recommend always ending the list with a catch-all, typically named default, for events that do not match any other filter.

Display order does the work because of the Final toggle. When Final is on, the default, matched events go to the Route's Pipeline and do not continue to the next Route; only non-matched events fall through. When Final is off, all events continue down the list, and what the Route processes is technically a clone of the original. That gives you two shapes: consume the bulk of out-of-scope volume early and stop, or clone a narrow set ahead, let the original keep flowing, and clone as late in the list as possible. The page's design advice points the same way: move data through as few Routes as possible, and reduce event volume as early as possible.

You can also watch the split operate. The Events (In) column on the Routing table shows Events In, Out, or Dropped, or Bytes In or Out, for the most recent 15 minutes. Once the decisions are encoded in Route filters, a bad filter announces itself there as a Dropped or Out number you did not expect.

Drop what is out of scope

Pointing a stream somewhere else is one lever; deleting it is the sharper one. The Drop Function documentation is blunt about what happens. The Drop Function deletes any events that meet its Filter expression, which is useful when you want to prevent certain events from continuing to a Pipeline's downstream Functions. That answers exactly what it does and where it sits: it is one of the Functions inside a Pipeline, and the events it matches are deleted from that Pipeline's path. Nothing downstream sees them. For out-of-scope data you have no business wanting, deletion beats rerouting, because a rerouted event still arrives somewhere while a dropped event arrives nowhere.

Two defaults carry the weight of the decision. The Filter field is the JavaScript expression that selects the data the Function handles, and it defaults to true, which the docs describe as meaning it evaluates all events. Left at its default, a Drop Function deletes everything, so you always set its Filter to the condition that names the noise. The Function's Final toggle is described as a toggle on to stop feeding data to the downstream Functions, and its default is toggled off: Function-level finality, separate from the Route-level Final toggle.

The page's two scenarios map directly onto scope work. Scenario A assumes you care only about errors and want to filter out any event that contains the word success, regardless of case. It does that with the JavaScript search() method on the _raw field's contents, evaluating the Function as true when the return value is >= 0:

_raw.search(/success/i)>=0

Scenario B drops specific JSON events by key-value pair. Its Filter expression checks, per event, whether channel has a different data type or value from auth, and matching events are dropped, ensuring that only events with "channel":"auth" pass to the next Function:

channel !== 'auth'

The same pattern exists at the Route layer. The Routes page notes that if a Route drops matching events and Final is on, those events do not reach lower Routes. Either placement ends with the same property from the cardholder-data tool's point of view: the event never arrives.

Route the rest

Once the out-of-scope noise is dropped or pointed elsewhere, your in-scope stream should arrive at your cardholder-data tools in exactly the shape you want. The filter and transform data documentation describes the machinery for that stretch. Throughout Cribl Stream, many settings let you use custom JavaScript code and Cribl Expressions to control how data gets routed or processed. The two surfaces that matter here are the Route's filter expression, which dynamically controls where data gets routed, and the Filter field on every Function, which narrows which data that Function processes.

The semantics on both surfaces are a single rule. A filter expression must evaluate to either true or false: if true, the data gets processed by that Route or Function; if false, it passes through unchanged. By default, the Filter field is set to true, meaning the Route or Function applies to all events, and you replace it with a custom JavaScript expression to apply your logic only to data that matches your conditions. Wherever JavaScript is accepted, Cribl Expressions, the pre-built helpers that begin with C. such as C.Lookup or C.Time, are available.

Value expressions are the other half of the flow: inside a Function, they assign or calculate values, create new fields, modify existing ones, or transform data. The page's examples include converting a timestamp to an hour-based value with Math.floor(_time / 3600) and masking part of a Source string with source.replace(/.{3}/, 'XXX'). On a cardholder-data Pipeline, that is where you enrich in-scope events with the context your detection tooling needs and redact the fields that do not need to travel further. The page's examples are generic; pick your transformations from your own requirements, not from the docs.

Before any of it goes live, the page documents a testing path. Click the Advanced mode button inside the Filter expression field and a modal opens, where you paste the expression, select a sample JSON input from the drop-down, or enter your own in the Sample input field. The Output shows whether the expression is valid: for filter expressions it resolves to true when valid and correctly applied to the sample data, while false means you need to fix the syntax or make sure the expression applies to the sample.

A plain statement of where this post stops: none of the three pages cited here makes any claim specific to PCI DSS. Which of your system components sit inside the cardholder data environment, and what an assessor will accept as evidence of a scoped boundary, are things the Cribl documentation does not address. The docs cover the machinery completely: the filters that decide, the drop that deletes, and the processing flow that carries the rest. What you do with that machinery is your architecture and your compliance program.

Frequently asked questions

How does Cribl Stream decide which events go to which pipeline?

Before events are transformed by a processing pipeline, Cribl Stream Routes apply a filter expression to select the subset of events that pipeline should receive. Routes are evaluated in their display order from first to last, and the docs recommend ending the list with a catch-all Route for events no other filter matched.

What does the Cribl Stream Drop Function do, and where does it sit?

The Drop Function deletes any events that meet its Filter expression, so those events never continue to the pipeline's downstream Functions. It is a Function placed inside a pipeline, and a Route with Final on can drop matched events before lower Routes see them.

What is the default Filter setting on Cribl Stream Routes and Functions?

The default is true, which means the Route or Function applies to all events. A Drop Function left at its default matches every event, so you set its Filter to the expression that names exactly what you are dropping.

Can I test a Cribl Stream filter expression against sample data before saving it?

You can. The Advanced mode button inside the Filter expression field opens a modal where you paste the expression and select or enter sample JSON input, and the Output tells you whether the expression is valid against that sample. A result of false means the syntax needs fixing or the expression does not apply to the sample data.

Source

Verified against Cribl Stream 4.20 documentation on January 01, 2026.