|
| 1 | +#!/bin/bash |
| 2 | +# Inject defects into the Envoy proxy codebase for code review benchmarking |
| 3 | +# Each defect simulates a realistic bug that an AI code reviewer should catch |
| 4 | +# 6 defects across 4 files, 3 require cross-file reasoning |
| 5 | + |
| 6 | +set -e |
| 7 | +cd /workspace |
| 8 | + |
| 9 | +# ── Defect 1: Return Continue instead of StopIteration when delay is active ── |
| 10 | +# Cross-file: fault_filter returns Continue, but postDelayInjection() later calls |
| 11 | +# continueDecoding() via filter_manager.cc — the stream has already moved past |
| 12 | +# this filter, causing undefined behavior or assertion failure |
| 13 | +python3 -c " |
| 14 | +path = 'source/extensions/filters/http/fault/fault_filter.cc' |
| 15 | +with open(path) as f: |
| 16 | + content = f.read() |
| 17 | +
|
| 18 | +old = ''' if (maybeSetupDelay(headers)) { |
| 19 | + return Http::FilterHeadersStatus::StopIteration; |
| 20 | + }''' |
| 21 | +
|
| 22 | +new = ''' if (maybeSetupDelay(headers)) { |
| 23 | + return Http::FilterHeadersStatus::Continue; |
| 24 | + }''' |
| 25 | +
|
| 26 | +content = content.replace(old, new) |
| 27 | +with open(path, 'w') as f: |
| 28 | + f.write(content) |
| 29 | +print('INJECTED defect-1: fault filter returns Continue instead of StopIteration when delay active') |
| 30 | +" |
| 31 | + |
| 32 | +# ── Defect 2: Invert header matching logic in HeaderUtility::matchHeaders ── |
| 33 | +# Cross-file: This utility is called by fault_filter (line 130), RBAC, ratelimit, |
| 34 | +# and other filters. Inverting it causes header-based fault injection to match |
| 35 | +# on the WRONG requests (those NOT matching the configured headers) |
| 36 | +python3 -c " |
| 37 | +path = 'source/common/http/header_utility.cc' |
| 38 | +with open(path) as f: |
| 39 | + content = f.read() |
| 40 | +
|
| 41 | +old = ''' if (!config_headers.empty()) { |
| 42 | + for (const HeaderDataPtr& cfg_header_data : config_headers) { |
| 43 | + if (!cfg_header_data->matchesHeaders(request_headers)) { |
| 44 | + return false; |
| 45 | + } |
| 46 | + } |
| 47 | + } |
| 48 | +
|
| 49 | + return true;''' |
| 50 | +
|
| 51 | +new = ''' if (!config_headers.empty()) { |
| 52 | + for (const HeaderDataPtr& cfg_header_data : config_headers) { |
| 53 | + if (cfg_header_data->matchesHeaders(request_headers)) { |
| 54 | + return false; |
| 55 | + } |
| 56 | + } |
| 57 | + } |
| 58 | +
|
| 59 | + return true;''' |
| 60 | +
|
| 61 | +content = content.replace(old, new) |
| 62 | +with open(path, 'w') as f: |
| 63 | + f.write(content) |
| 64 | +print('INJECTED defect-2: inverted header matching logic in matchHeaders') |
| 65 | +" |
| 66 | + |
| 67 | +# ── Defect 3: Remove route cache clearing from ext_authz onComplete ── |
| 68 | +# When ext_authz modifies request headers (adding auth headers, removing |
| 69 | +# sensitive headers), the route cache must be cleared so downstream filters |
| 70 | +# and the router see the updated route. Without this, stale routes are used. |
| 71 | +python3 -c " |
| 72 | +path = 'source/extensions/filters/http/ext_authz/ext_authz.cc' |
| 73 | +with open(path) as f: |
| 74 | + content = f.read() |
| 75 | +
|
| 76 | +old = ''' // Any changes to request headers or query parameters can affect how the request is going to be |
| 77 | + // routed. If we are changing the headers we also need to clear the route |
| 78 | + // cache. |
| 79 | + if (config_->clearRouteCache() && |
| 80 | + (!response->headers_to_set.empty() || !response->headers_to_append.empty() || |
| 81 | + !response->headers_to_remove.empty() || !response->query_parameters_to_set.empty() || |
| 82 | + !response->query_parameters_to_remove.empty())) { |
| 83 | + ENVOY_STREAM_LOG(debug, \"ext_authz is clearing route cache\", *decoder_callbacks_); |
| 84 | + decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); |
| 85 | + }''' |
| 86 | +
|
| 87 | +new = ''' // Any changes to request headers or query parameters can affect how the request is going to be |
| 88 | + // routed. Route cache clearing handled by downstream filters.''' |
| 89 | +
|
| 90 | +content = content.replace(old, new) |
| 91 | +with open(path, 'w') as f: |
| 92 | + f.write(content) |
| 93 | +print('INJECTED defect-3: removed route cache clearing from ext_authz onComplete') |
| 94 | +" |
| 95 | + |
| 96 | +# ── Defect 4: Invert required response header check in filter_manager ── |
| 97 | +# Cross-file: filter_manager calls HeaderUtility::checkRequiredResponseHeaders() |
| 98 | +# (defined in header_utility.cc). Inverting the status check sends 502 for all |
| 99 | +# valid responses and lets responses missing :status through |
| 100 | +python3 -c " |
| 101 | +path = 'source/common/http/filter_manager.cc' |
| 102 | +with open(path) as f: |
| 103 | + content = f.read() |
| 104 | +
|
| 105 | +old = ''' const auto status = HeaderUtility::checkRequiredResponseHeaders(headers); |
| 106 | + if (!status.ok()) { |
| 107 | + // If the check failed, then we reply with BadGateway, and stop the further processing. |
| 108 | + sendLocalReply(''' |
| 109 | +
|
| 110 | +new = ''' const auto status = HeaderUtility::checkRequiredResponseHeaders(headers); |
| 111 | + if (status.ok()) { |
| 112 | + // If the check failed, then we reply with BadGateway, and stop the further processing. |
| 113 | + sendLocalReply(''' |
| 114 | +
|
| 115 | +content = content.replace(old, new) |
| 116 | +with open(path, 'w') as f: |
| 117 | + f.write(content) |
| 118 | +print('INJECTED defect-4: inverted required response header check in filter_manager') |
| 119 | +" |
| 120 | + |
| 121 | +# ── Defect 5: Invert rate limit check causing active_faults gauge leak ── |
| 122 | +# In postDelayInjection, the gauge should be decremented when NO rate limit |
| 123 | +# is configured (fault is done). Inverting this causes the gauge to be |
| 124 | +# decremented when rate limit IS active (double-dec with onDestroy) and |
| 125 | +# NOT decremented when it's not (leak), eventually exhausting max_active_faults |
| 126 | +python3 -c " |
| 127 | +path = 'source/extensions/filters/http/fault/fault_filter.cc' |
| 128 | +with open(path) as f: |
| 129 | + content = f.read() |
| 130 | +
|
| 131 | +old = ''' ASSERT(fault_active_); |
| 132 | + ASSERT(delay_timer_ == nullptr); |
| 133 | + if (!isResponseRateLimitConfigured()) { |
| 134 | + config_->stats().active_faults_.dec(); |
| 135 | + fault_active_ = false; |
| 136 | + }''' |
| 137 | +
|
| 138 | +new = ''' ASSERT(fault_active_); |
| 139 | + ASSERT(delay_timer_ == nullptr); |
| 140 | + if (isResponseRateLimitConfigured()) { |
| 141 | + config_->stats().active_faults_.dec(); |
| 142 | + fault_active_ = false; |
| 143 | + }''' |
| 144 | +
|
| 145 | +content = content.replace(old, new) |
| 146 | +with open(path, 'w') as f: |
| 147 | + f.write(content) |
| 148 | +print('INJECTED defect-5: inverted rate limit check causes active_faults gauge leak') |
| 149 | +" |
| 150 | + |
| 151 | +# ── Defect 6: Remove isRemovableHeader guard from ext_authz header removal ── |
| 152 | +# ext_authz removes headers based on the external auth response. Without the |
| 153 | +# isRemovableHeader check, :-prefixed pseudo-headers and Host can be removed, |
| 154 | +# making the request malformed and causing 400 errors or protocol violations |
| 155 | +python3 -c " |
| 156 | +path = 'source/extensions/filters/http/ext_authz/ext_authz.cc' |
| 157 | +with open(path) as f: |
| 158 | + content = f.read() |
| 159 | +
|
| 160 | +old = ''' // We don't allow removing any :-prefixed headers, nor Host, as removing them would make the |
| 161 | + // request malformed. checkDecoderHeaderMutation also performs this check, however, so only |
| 162 | + // perform this check explicitly if decoder header mutation rules is empty. |
| 163 | + if (!config_->hasDecoderHeaderMutationRules() && |
| 164 | + !Http::HeaderUtility::isRemovableHeader(key)) { |
| 165 | + ENVOY_STREAM_LOG(trace, \"Ignoring invalid header removal '{}'.\", *decoder_callbacks_, key); |
| 166 | + continue; |
| 167 | + }''' |
| 168 | +
|
| 169 | +new = ''' // Header mutation rules handle validation if configured.''' |
| 170 | +
|
| 171 | +content = content.replace(old, new) |
| 172 | +with open(path, 'w') as f: |
| 173 | + f.write(content) |
| 174 | +print('INJECTED defect-6: removed isRemovableHeader guard from ext_authz header removal') |
| 175 | +" |
| 176 | + |
| 177 | +echo "All 6 defects injected successfully" |
0 commit comments