This feature is off by default. detained_inspection = true will activate
it.
+===== script_detection
+
+Script detection is an alternative to detained inspection. When
+http_inspect detects the end of a script it immediately forwards the
+available part of the message body for early detection. This enables
+malicious Javascripts to be detected more quickly but consumes somewhat
+more of the sensor's resources.
+
+This feature is off by default. script_detection = true will activate it.
+
===== gzip
http_inspect by default decompresses deflate and gzip message bodies
delivers message data for reassembly once. reassemble() stores data received for a partial
inspection and prepends it to the buffer for the next inspection.
+Script detection is a different feature developed to solve the same problem. The scanning mechanism
+developed for detained inspection is repurposed to look for the end-of-script tag "</script>". When
+one is found an immediate partial inspection is performed. This avoids the adverse network
+consequences of detaining packets at the performance and memory cost of doing a much larger number
+of partial inspections. Code features that support both approaches are referred to as accelerated
+blocking.
+
HttpFlowData is a data class representing all HI information relating to a flow. It serves as
persistent memory between invocations of HI by the framework. It also glues together the inspector,
the client-to-server splitter, and the server-to-client splitter which pass information through the
return SCAN_NOT_FOUND;
}
-HttpBodyCutter::HttpBodyCutter(bool detained_inspection_, CompressId compression_) :
- detained_inspection(detained_inspection_), compression(compression_)
+HttpBodyCutter::HttpBodyCutter(AcceleratedBlocking accelerated_blocking_, CompressId compression_)
+ : accelerated_blocking(accelerated_blocking_), compression(compression_)
{
- if (detained_inspection && ((compression == CMP_GZIP) || (compression == CMP_DEFLATE)))
+ if (accelerated_blocking != AB_NONE)
{
- compress_stream = new z_stream;
- compress_stream->zalloc = Z_NULL;
- compress_stream->zfree = Z_NULL;
- compress_stream->next_in = Z_NULL;
- compress_stream->avail_in = 0;
- const int window_bits = (compression == CMP_GZIP) ? GZIP_WINDOW_BITS : DEFLATE_WINDOW_BITS;
- if (inflateInit2(compress_stream, window_bits) != Z_OK)
+ if ((compression == CMP_GZIP) || (compression == CMP_DEFLATE))
{
- assert(false);
- compression = CMP_NONE;
- delete compress_stream;
- compress_stream = nullptr;
+ compress_stream = new z_stream;
+ compress_stream->zalloc = Z_NULL;
+ compress_stream->zfree = Z_NULL;
+ compress_stream->next_in = Z_NULL;
+ compress_stream->avail_in = 0;
+ const int window_bits = (compression == CMP_GZIP) ? GZIP_WINDOW_BITS : DEFLATE_WINDOW_BITS;
+ if (inflateInit2(compress_stream, window_bits) != Z_OK)
+ {
+ assert(false);
+ compression = CMP_NONE;
+ delete compress_stream;
+ compress_stream = nullptr;
+ }
+ }
+
+ static const uint8_t detain_string[] = { '<', 's', 'c', 'r', 'i', 'p', 't' };
+ static const uint8_t detain_upper[] = { '<', 'S', 'C', 'R', 'I', 'P', 'T' };
+ static const uint8_t inspect_string[] = { '<', '/', 's', 'c', 'r', 'i', 'p', 't', '>' };
+ static const uint8_t inspect_upper[] = { '<', '/', 'S', 'C', 'R', 'I', 'P', 'T', '>' };
+
+ if (accelerated_blocking == AB_DETAIN)
+ {
+ match_string = detain_string;
+ match_string_upper = detain_upper;
+ string_length = sizeof(detain_string);
+ }
+ else
+ {
+ match_string = inspect_string;
+ match_string_upper = inspect_upper;
+ string_length = sizeof(inspect_string);
}
}
}
if (octets_seen + length < flow_target)
{
octets_seen += length;
- return need_detained_inspection(buffer, length) ? SCAN_NOT_FOUND_DETAIN : SCAN_NOT_FOUND;
+ return need_accelerated_blocking(buffer, length) ?
+ SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
}
if (!stretch)
num_flush = flow_target - octets_seen;
if (remaining > 0)
{
- need_detained_inspection(buffer, num_flush);
+ need_accelerated_blocking(buffer, num_flush);
return SCAN_FOUND_PIECE;
}
else
else
num_flush = flow_target - octets_seen;
remaining -= octets_seen + num_flush;
- need_detained_inspection(buffer, num_flush);
+ need_accelerated_blocking(buffer, num_flush);
return SCAN_FOUND_PIECE;
}
// Cannot stretch to the end of the message body. Cut at the original target.
num_flush = flow_target - octets_seen;
remaining -= flow_target;
- need_detained_inspection(buffer, num_flush);
+ need_accelerated_blocking(buffer, num_flush);
return SCAN_FOUND_PIECE;
}
{
// Not enough data yet to create a message section
octets_seen += length;
- return need_detained_inspection(buffer, length) ? SCAN_NOT_FOUND_DETAIN : SCAN_NOT_FOUND;
+ return need_accelerated_blocking(buffer, length) ?
+ SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
}
else if (stretch && (octets_seen + length <= flow_target + MAX_SECTION_STRETCH))
{
// Cut the section at the end of this TCP segment to avoid splitting a packet
num_flush = length;
- need_detained_inspection(buffer, num_flush);
+ need_accelerated_blocking(buffer, num_flush);
return SCAN_FOUND_PIECE;
}
else
// Cut the section at the target length. Either stretching is not allowed or the end of
// the segment is too far away.
num_flush = flow_target - octets_seen;
- need_detained_inspection(buffer, num_flush);
+ need_accelerated_blocking(buffer, num_flush);
return SCAN_FOUND_PIECE;
}
}
const uint32_t adjusted_target = stretch ? MAX_SECTION_STRETCH + flow_target : flow_target;
- bool detain_this_packet = false;
+ bool accelerate_this_packet = false;
for (int32_t k=0; k < static_cast<int32_t>(length); k++)
{
skip_amount = adjusted_target-data_seen;
}
- if (!detain_this_packet)
- detain_this_packet = need_detained_inspection(buffer+k, skip_amount);
+ accelerate_this_packet = need_accelerated_blocking(buffer+k, skip_amount) ||
+ accelerate_this_packet;
k += skip_amount - 1;
if ((expected -= skip_amount) == 0)
uint32_t skip_amount = length-k;
skip_amount = (skip_amount <= adjusted_target-data_seen) ? skip_amount :
adjusted_target-data_seen;
- if (!detain_this_packet)
- detain_this_packet = need_detained_inspection(buffer+k, skip_amount);
+ accelerate_this_packet = need_accelerated_blocking(buffer+k, skip_amount) ||
+ accelerate_this_packet;
k += skip_amount - 1;
if ((data_seen += skip_amount) == adjusted_target)
{
}
octets_seen += length;
- return detain_this_packet ? SCAN_NOT_FOUND_DETAIN : SCAN_NOT_FOUND;
+ return accelerate_this_packet ? SCAN_NOT_FOUND_ACCELERATE : SCAN_NOT_FOUND;
}
-ScanResult HttpBodyH2Cutter::cut(const uint8_t* buffer, uint32_t length,
- HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
+ScanResult HttpBodyH2Cutter::cut(const uint8_t* /*buffer*/, uint32_t length,
+ HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool /*stretch*/,
bool h2_body_finished)
{
- // FIXIT-E detained inspection not yet supported for HTTP/2
- UNUSED(buffer);
-
+ // FIXIT-E accelerated blocking not yet supported for HTTP/2
// FIXIT-E stretch not yet supported for HTTP/2 message bodies
- UNUSED(stretch);
// If the headers included a content length header (expected length >= 0), check it against the
// actual message body length. Alert if it does not match at the end of the message body or if
}
// This method searches the input stream looking for the beginning of a script or other dangerous
-// content that requires detained inspection. Exactly what we are looking for is encapsulated in
+// content that requires accelerated blocking. Exactly what we are looking for is encapsulated in
// dangerous().
//
// Return value true indicates a match and enables the packet that completes the matching sequence
-// to be detained.
+// to be detained (detained inspection) or sent for partial inspection (script detection).
//
// Once detained inspection is activated on a message body it never goes away. The first packet
// of every subsequent message section must be detained (detention_required). Supporting this
// requirement requires that the calling routine submit all data including buffers that are about
// to be flushed.
-bool HttpBodyCutter::need_detained_inspection(const uint8_t* data, uint32_t length)
+//
+// Script detection (AB_INSPECT) is similar in that the message data must be scanned by dangerous()
+// looking for a particular string. It differs in the string being searched for and that difference
+// is built into dangerous(). Script detection does not automatically apply to subsequent message
+// sections. It only recurs when a new end-of-script tag is found.
+//
+// Any attempt to optimize this code should be mindful that once you skip any part of the message
+// body, dangerous() loses the ability to unzip subsequent data.
+
+bool HttpBodyCutter::need_accelerated_blocking(const uint8_t* data, uint32_t length)
{
- if (!detained_inspection || packet_detained)
- return false;
- if (detention_required || dangerous(data, length))
+ switch (accelerated_blocking)
{
- packet_detained = true;
- detention_required = true;
- return true;
+ case AB_DETAIN:
+ // With detained inspection we have two basic principles here: 1) having detained a packet
+ // we don't need to detain another one while the first one is still being held and 2) once
+ // we detain a packet we don't need to keep scanning content. We are always going to detain
+ // a new packet as soon as we release the previous one.
+ if (!packet_detained && (detention_required || dangerous(data, length)))
+ {
+ packet_detained = true;
+ detention_required = true;
+ return true;
+ }
+ break;
+ case AB_INSPECT:
+ // Script detection requires continuous scanning of the data because every packet is a new
+ // decision regardless of any previous determinations.
+ if (dangerous(data, length))
+ return true;
+ break;
+ case AB_NONE:
+ break;
}
return false;
}
input_length = decomp_buffer_size - compress_stream->avail_out;
}
- static const uint8_t match_string[] = { '<', 's', 'c', 'r', 'i', 'p', 't' };
- static const uint8_t match_string_upper[] = { '<', 'S', 'C', 'R', 'I', 'P', 'T' };
- static const uint8_t string_length = sizeof(match_string);
for (uint32_t k = 0; k < input_length; k++)
{
// partial_match is persistent, enabling matches that cross data boundaries
{
if (++partial_match == string_length)
{
+ partial_match = 0;
delete[] decomp_output;
return true;
}
class HttpBodyCutter : public HttpCutter
{
public:
- HttpBodyCutter(bool detained_inspection_, HttpEnums::CompressId compression_);
+ HttpBodyCutter(HttpEnums::AcceleratedBlocking accelerated_blocking_,
+ HttpEnums::CompressId compression_);
~HttpBodyCutter() override;
void soft_reset() override { octets_seen = 0; packet_detained = false; }
void detain_ended() { packet_detained = false; }
protected:
- bool need_detained_inspection(const uint8_t* data, uint32_t length);
+ bool need_accelerated_blocking(const uint8_t* data, uint32_t length);
private:
bool dangerous(const uint8_t* data, uint32_t length);
- const bool detained_inspection;
+ const HttpEnums::AcceleratedBlocking accelerated_blocking;
bool packet_detained = false;
uint8_t partial_match = 0;
bool detention_required = false;
HttpEnums::CompressId compression;
z_stream* compress_stream = nullptr;
+ const uint8_t* match_string;
+ const uint8_t* match_string_upper;
+ uint8_t string_length;
};
class HttpBodyClCutter : public HttpBodyCutter
{
public:
- HttpBodyClCutter(int64_t expected_length, bool detained_inspection,
+ HttpBodyClCutter(int64_t expected_length,
+ HttpEnums::AcceleratedBlocking accelerated_blocking,
HttpEnums::CompressId compression) :
- HttpBodyCutter(detained_inspection, compression), remaining(expected_length)
+ HttpBodyCutter(accelerated_blocking, compression), remaining(expected_length)
{ assert(remaining > 0); }
HttpEnums::ScanResult cut(const uint8_t*, uint32_t length, HttpInfractions*, HttpEventGen*,
uint32_t flow_target, bool stretch, bool) override;
class HttpBodyOldCutter : public HttpBodyCutter
{
public:
- explicit HttpBodyOldCutter(bool detained_inspection, HttpEnums::CompressId compression) :
- HttpBodyCutter(detained_inspection, compression) {}
+ HttpBodyOldCutter(HttpEnums::AcceleratedBlocking accelerated_blocking,
+ HttpEnums::CompressId compression) :
+ HttpBodyCutter(accelerated_blocking, compression)
+ {}
HttpEnums::ScanResult cut(const uint8_t*, uint32_t, HttpInfractions*, HttpEventGen*,
uint32_t flow_target, bool stretch, bool) override;
};
class HttpBodyChunkCutter : public HttpBodyCutter
{
public:
- explicit HttpBodyChunkCutter(bool detained_inspection, HttpEnums::CompressId compression) :
- HttpBodyCutter(detained_inspection, compression) {}
+ HttpBodyChunkCutter(HttpEnums::AcceleratedBlocking accelerated_blocking,
+ HttpEnums::CompressId compression) :
+ HttpBodyCutter(accelerated_blocking, compression)
+ {}
HttpEnums::ScanResult cut(const uint8_t* buffer, uint32_t length,
HttpInfractions* infractions, HttpEventGen* events, uint32_t flow_target, bool stretch,
bool) override;
class HttpBodyH2Cutter : public HttpBodyCutter
{
public:
- explicit HttpBodyH2Cutter(int64_t expected_length, bool detained_inspection,
+ HttpBodyH2Cutter(int64_t expected_length,
+ HttpEnums::AcceleratedBlocking accelerated_blocking,
HttpEnums::CompressId compression) :
- HttpBodyCutter(detained_inspection, compression), expected_body_length(expected_length) {}
+ HttpBodyCutter(accelerated_blocking, compression), expected_body_length(expected_length)
+ {}
HttpEnums::ScanResult cut(const uint8_t*, uint32_t, HttpInfractions*, HttpEventGen*,
uint32_t flow_target, bool stretch, bool h2_body_finished) override;
private:
uint32_t total_octets_scanned = 0;
};
-
#endif
enum HTTP_BUFFER { HTTP_BUFFER_CLIENT_BODY = 1, HTTP_BUFFER_COOKIE, HTTP_BUFFER_HEADER,
HTTP_BUFFER_METHOD, HTTP_BUFFER_PARAM, HTTP_BUFFER_RAW_BODY, HTTP_BUFFER_RAW_COOKIE,
HTTP_BUFFER_RAW_HEADER, HTTP_BUFFER_RAW_REQUEST, HTTP_BUFFER_RAW_STATUS,
- HTTP_BUFFER_RAW_TRAILER, HTTP_BUFFER_RAW_URI, HTTP_BUFFER_STAT_CODE,
- HTTP_BUFFER_STAT_MSG, HTTP_BUFFER_TRAILER, HTTP_BUFFER_TRUE_IP,
- HTTP_BUFFER_URI, HTTP_BUFFER_VERSION, HTTP_BUFFER_MAX };
+ HTTP_BUFFER_RAW_TRAILER, HTTP_BUFFER_RAW_URI, HTTP_BUFFER_STAT_CODE, HTTP_BUFFER_STAT_MSG,
+ HTTP_BUFFER_TRAILER, HTTP_BUFFER_TRUE_IP, HTTP_BUFFER_URI, HTTP_BUFFER_VERSION,
+ HTTP_BUFFER_MAX };
// Peg counts
// This enum must remain synchronized with HttpModule::peg_names[] in http_tables.cc
enum PEG_COUNT { PEG_FLOW = 0, PEG_SCAN, PEG_REASSEMBLE, PEG_INSPECT, PEG_REQUEST, PEG_RESPONSE,
PEG_GET, PEG_HEAD, PEG_POST, PEG_PUT, PEG_DELETE, PEG_CONNECT, PEG_OPTIONS, PEG_TRACE,
PEG_OTHER_METHOD, PEG_REQUEST_BODY, PEG_CHUNKED, PEG_URI_NORM, PEG_URI_PATH, PEG_URI_CODING,
- PEG_CONCURRENT_SESSIONS, PEG_MAX_CONCURRENT_SESSIONS, PEG_DETAINED, PEG_PARTIAL_INSPECT,
- PEG_EXCESS_PARAMS, PEG_PARAMS, PEG_CUTOVERS, PEG_COUNT_MAX };
+ PEG_CONCURRENT_SESSIONS, PEG_MAX_CONCURRENT_SESSIONS, PEG_DETAINED, PEG_SCRIPT_DETECTION,
+ PEG_PARTIAL_INSPECT, PEG_EXCESS_PARAMS, PEG_PARAMS, PEG_CUTOVERS, PEG_COUNT_MAX };
// Result of scanning by splitter
-enum ScanResult { SCAN_NOT_FOUND, SCAN_NOT_FOUND_DETAIN, SCAN_FOUND, SCAN_FOUND_PIECE,
+enum ScanResult { SCAN_NOT_FOUND, SCAN_NOT_FOUND_ACCELERATE, SCAN_FOUND, SCAN_FOUND_PIECE,
SCAN_DISCARD, SCAN_DISCARD_PIECE, SCAN_ABORT };
// State machine for chunk parsing
enum ChunkState { CHUNK_NEWLINES, CHUNK_ZEROS, CHUNK_LEADING_WS, CHUNK_NUMBER, CHUNK_TRAILING_WS,
CHUNK_OPTIONS, CHUNK_HCRLF, CHUNK_DATA, CHUNK_DCRLF1, CHUNK_DCRLF2, CHUNK_BAD };
+enum AcceleratedBlocking { AB_DETAIN, AB_INSPECT, AB_NONE };
+
// List of possible HTTP versions.
enum VersionId { VERS__NO_SOURCE=-16, VERS__NOT_COMPUTE=-14, VERS__PROBLEMATIC=-12,
VERS__NOT_PRESENT=-11, VERS__OTHER=1, VERS_1_0, VERS_1_1, VERS_2_0, VERS_0_9 };
partial_inspected_octets[source_id] = 0;
section_size_target[source_id] = 0;
stretch_section_to_packet[source_id] = false;
- detained_inspection[source_id] = false;
+ accelerated_blocking[source_id] = AB_NONE;
file_depth_remaining[source_id] = STAT_NOT_PRESENT;
detect_depth_remaining[source_id] = STAT_NOT_PRESENT;
detection_status[source_id] = DET_REACTIVATING;
HttpEnums::CompressId compression[2] = { HttpEnums::CMP_NONE, HttpEnums::CMP_NONE };
HttpEnums::DetectionStatus detection_status[2] = { HttpEnums::DET_ON, HttpEnums::DET_ON };
bool stretch_section_to_packet[2] = { false, false };
- bool detained_inspection[2] = { false, false };
+ HttpEnums::AcceleratedBlocking accelerated_blocking[2] =
+ { HttpEnums::AB_NONE, HttpEnums::AB_NONE };
// *** Inspector's internal data about the current message
struct FdCallbackContext
ConfigLogger::log_flag("decompress_swf", params->decompress_swf);
ConfigLogger::log_flag("decompress_zip", params->decompress_zip);
ConfigLogger::log_flag("detained_inspection", params->detained_inspection);
+ ConfigLogger::log_flag("script_detection", params->script_detection);
ConfigLogger::log_flag("normalize_javascript", params->js_norm_param.normalize_javascript);
ConfigLogger::log_value("max_javascript_whitespaces",
params->js_norm_param.max_javascript_whitespaces);
{ "detained_inspection", Parameter::PT_BOOL, nullptr, "false",
"store-and-forward as necessary to effectively block alerting JavaScript" },
+ { "script_detection", Parameter::PT_BOOL, nullptr, "false",
+ "inspect JavaScript immediately upon script end" },
+
{ "normalize_javascript", Parameter::PT_BOOL, nullptr, "false",
"normalize JavaScript in response bodies" },
{
params->detained_inspection = val.get_bool();
}
+ else if (val.is("script_detection"))
+ {
+ params->script_detection = val.get_bool();
+ }
else if (val.is("normalize_javascript"))
{
params->js_norm_param.normalize_javascript = val.get_bool();
ParseWarning(WARN_CONF, "Meaningless to do bare byte when not doing UTF-8");
params->uri_param.utf8_bare_byte = false;
}
+
+ if (params->detained_inspection && params->script_detection)
+ {
+ ParseError("Cannot use detained inspection and script detection together.");
+ }
+
if (params->uri_param.iis_unicode)
{
params->uri_param.unicode_map = new uint8_t[65536];
bool decompress_swf = false;
bool decompress_zip = false;
bool detained_inspection = false;
+ bool script_detection = false;
struct JsNormParam
{
uint32_t& partial_detect_length = session_data->partial_detect_length[source_id];
uint8_t*& partial_detect_buffer = session_data->partial_detect_buffer[source_id];
- const int32_t total_length = js_norm_body.length() + partial_detect_length;
+ const int32_t total_length = partial_detect_length + js_norm_body.length();
const int32_t detect_length =
(total_length <= session_data->detect_depth_remaining[source_id]) ?
total_length : session_data->detect_depth_remaining[source_id];
memcpy(detect_buffer, partial_detect_buffer, partial_detect_length);
memcpy(detect_buffer + partial_detect_length, js_norm_body.start(),
js_norm_body.length());
- detect_data.set(total_length, detect_buffer, true);
+ detect_data.set(detect_length, detect_buffer, true);
}
else
{
setup_utf_decoding();
setup_file_decompression();
update_depth();
- session_data->detained_inspection[source_id] =
- params->detained_inspection && (source_id == SRC_SERVER);
+
+ if (source_id == SRC_SERVER)
+ {
+ if (params->script_detection)
+ session_data->accelerated_blocking[source_id] = AB_INSPECT;
+ else if (params->detained_inspection)
+ session_data->accelerated_blocking[source_id] = AB_DETAIN;
+ }
+
if (source_id == SRC_CLIENT)
{
HttpModule::increment_peg_counts(PEG_REQUEST_BODY);
case SEC_BODY_CL:
return (HttpCutter*)new HttpBodyClCutter(
session_data->data_length[source_id],
- session_data->detained_inspection[source_id],
+ session_data->accelerated_blocking[source_id],
session_data->compression[source_id]);
case SEC_BODY_CHUNK:
return (HttpCutter*)new HttpBodyChunkCutter(
- session_data->detained_inspection[source_id],
+ session_data->accelerated_blocking[source_id],
session_data->compression[source_id]);
case SEC_BODY_OLD:
return (HttpCutter*)new HttpBodyOldCutter(
- session_data->detained_inspection[source_id],
+ session_data->accelerated_blocking[source_id],
session_data->compression[source_id]);
case SEC_BODY_H2:
return (HttpCutter*)new HttpBodyH2Cutter(
session_data->data_length[source_id],
- session_data->detained_inspection[source_id],
+ AB_NONE,
session_data->compression[source_id]);
default:
assert(false);
switch (cut_result)
{
case SCAN_NOT_FOUND:
- case SCAN_NOT_FOUND_DETAIN:
+ case SCAN_NOT_FOUND_ACCELERATE:
if (cutter->get_octets_seen() == MAX_OCTETS)
{
*session_data->get_infractions(source_id) += INF_ENDLESS_HEADER;
cutter = nullptr;
return status_value(StreamSplitter::ABORT);
}
- if (cut_result == SCAN_NOT_FOUND_DETAIN)
- detain_packet(pkt);
+
+ if (cut_result == SCAN_NOT_FOUND_ACCELERATE)
+ {
+ if (session_data->accelerated_blocking[source_id] == AB_DETAIN)
+ detain_packet(pkt);
+ else
+ {
+ assert(session_data->accelerated_blocking[source_id] == AB_INSPECT);
+ HttpModule::increment_peg_counts(PEG_SCRIPT_DETECTION);
+ init_partial_flush(flow);
+#ifdef REG_TEST
+ if (HttpTestManager::use_test_input(HttpTestManager::IN_HTTP))
+ {
+ HttpTestManager::get_test_input_source()->flush(length);
+ }
+ else
+#endif
+ *flush_offset = length;
+ return status_value(StreamSplitter::FLUSH);
+ }
+ }
+
// Wait patiently for more data
return status_value(StreamSplitter::SEARCH);
case SCAN_ABORT:
{ CountType::NOW, "concurrent_sessions", "total concurrent http sessions" },
{ CountType::MAX, "max_concurrent_sessions", "maximum concurrent http sessions" },
{ CountType::SUM, "detains_requested", "packet hold requests for detained inspection" },
+ { CountType::SUM, "script_detections", "early inspections of scripts in HTTP responses" },
{ CountType::SUM, "partial_inspections", "pre-inspections for detained inspection" },
{ CountType::SUM, "excess_parameters", "repeat parameters exceeding max" },
{ CountType::SUM, "parameters", "HTTP parameters inspected" },