feat: add security sink detection patterns for JavaScript/TypeScript
- Introduced `sink-detect.js` with various security sink detection patterns categorized by type (e.g., command injection, SQL injection, file operations). - Implemented functions to build a lookup map for fast sink detection and to match sink calls against known patterns. - Added `package-lock.json` for dependency management.
This commit is contained in:
227
tests/load/router/spike-test.js
Normal file
227
tests/load/router/spike-test.js
Normal file
@@ -0,0 +1,227 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// spike-test.js
|
||||
// Sprint: SPRINT_5100_0005_0001_router_chaos_suite
|
||||
// Task: T1 - Load Test Harness
|
||||
// Description: k6 load test for router spike testing and backpressure validation.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
import { Rate, Trend, Counter } from 'k6/metrics';
|
||||
|
||||
// Custom metrics for throttle behavior
|
||||
const throttledRate = new Rate('throttled_requests');
|
||||
const retryAfterTrend = new Trend('retry_after_seconds');
|
||||
const recoveryTime = new Trend('recovery_time_ms');
|
||||
const throttle429Count = new Counter('throttle_429_count');
|
||||
const throttle503Count = new Counter('throttle_503_count');
|
||||
const successCount = new Counter('success_count');
|
||||
|
||||
export const options = {
|
||||
scenarios: {
|
||||
// Phase 1: Baseline load (normal operation)
|
||||
baseline: {
|
||||
executor: 'constant-arrival-rate',
|
||||
rate: 100,
|
||||
timeUnit: '1s',
|
||||
duration: '1m',
|
||||
preAllocatedVUs: 50,
|
||||
maxVUs: 100,
|
||||
},
|
||||
// Phase 2: 10x spike
|
||||
spike_10x: {
|
||||
executor: 'constant-arrival-rate',
|
||||
rate: 1000,
|
||||
timeUnit: '1s',
|
||||
duration: '30s',
|
||||
startTime: '1m',
|
||||
preAllocatedVUs: 500,
|
||||
maxVUs: 1000,
|
||||
},
|
||||
// Phase 3: 50x spike
|
||||
spike_50x: {
|
||||
executor: 'constant-arrival-rate',
|
||||
rate: 5000,
|
||||
timeUnit: '1s',
|
||||
duration: '30s',
|
||||
startTime: '2m',
|
||||
preAllocatedVUs: 2000,
|
||||
maxVUs: 5000,
|
||||
},
|
||||
// Phase 4: Recovery observation
|
||||
recovery: {
|
||||
executor: 'constant-arrival-rate',
|
||||
rate: 100,
|
||||
timeUnit: '1s',
|
||||
duration: '2m',
|
||||
startTime: '3m',
|
||||
preAllocatedVUs: 50,
|
||||
maxVUs: 100,
|
||||
},
|
||||
},
|
||||
thresholds: {
|
||||
// At least 95% of requests should succeed OR return proper throttle response
|
||||
'http_req_failed{expected_response:true}': ['rate<0.05'],
|
||||
// Throttled requests should have Retry-After header
|
||||
'throttled_requests': ['rate>0'], // We expect some throttling during spike
|
||||
// Recovery should happen within reasonable time
|
||||
'recovery_time_ms': ['p(95)<30000'], // 95% recover within 30s
|
||||
// Response time should be bounded even under load
|
||||
'http_req_duration{expected_response:true}': ['p(95)<5000'],
|
||||
},
|
||||
};
|
||||
|
||||
const ROUTER_URL = __ENV.ROUTER_URL || 'http://localhost:8080';
|
||||
const API_ENDPOINT = __ENV.API_ENDPOINT || '/api/v1/scan';
|
||||
|
||||
export function setup() {
|
||||
console.log(`Testing router at: ${ROUTER_URL}${API_ENDPOINT}`);
|
||||
|
||||
// Verify router is reachable
|
||||
const healthCheck = http.get(`${ROUTER_URL}/health`);
|
||||
if (healthCheck.status !== 200) {
|
||||
console.warn(`Router health check returned ${healthCheck.status}`);
|
||||
}
|
||||
|
||||
return {
|
||||
startTime: new Date().toISOString(),
|
||||
routerUrl: ROUTER_URL,
|
||||
};
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const payload = JSON.stringify({
|
||||
image: 'alpine:latest',
|
||||
requestId: `spike-test-${__VU}-${__ITER}`,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const params = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': `${__VU}-${__ITER}`,
|
||||
},
|
||||
tags: { expected_response: 'true' },
|
||||
timeout: '10s',
|
||||
};
|
||||
|
||||
const response = http.post(`${ROUTER_URL}${API_ENDPOINT}`, payload, params);
|
||||
|
||||
// Handle throttle responses (429 Too Many Requests)
|
||||
if (response.status === 429) {
|
||||
throttledRate.add(1);
|
||||
throttle429Count.add(1);
|
||||
|
||||
// Verify Retry-After header
|
||||
const retryAfter = response.headers['Retry-After'];
|
||||
check(response, {
|
||||
'429 has Retry-After header': (r) => r.headers['Retry-After'] !== undefined,
|
||||
'Retry-After is valid number': (r) => {
|
||||
const val = r.headers['Retry-After'];
|
||||
return val && !isNaN(parseInt(val));
|
||||
},
|
||||
'Retry-After is reasonable (1-300s)': (r) => {
|
||||
const val = parseInt(r.headers['Retry-After']);
|
||||
return val >= 1 && val <= 300;
|
||||
},
|
||||
});
|
||||
|
||||
if (retryAfter) {
|
||||
retryAfterTrend.add(parseInt(retryAfter));
|
||||
}
|
||||
}
|
||||
// Handle overload responses (503 Service Unavailable)
|
||||
else if (response.status === 503) {
|
||||
throttledRate.add(1);
|
||||
throttle503Count.add(1);
|
||||
|
||||
check(response, {
|
||||
'503 has Retry-After header': (r) => r.headers['Retry-After'] !== undefined,
|
||||
});
|
||||
|
||||
const retryAfter = response.headers['Retry-After'];
|
||||
if (retryAfter) {
|
||||
retryAfterTrend.add(parseInt(retryAfter));
|
||||
}
|
||||
}
|
||||
// Handle success responses
|
||||
else {
|
||||
throttledRate.add(0);
|
||||
successCount.add(1);
|
||||
|
||||
check(response, {
|
||||
'status is 200 or 202': (r) => r.status === 200 || r.status === 202,
|
||||
'response has body': (r) => r.body && r.body.length > 0,
|
||||
'response time < 5s': (r) => r.timings.duration < 5000,
|
||||
});
|
||||
}
|
||||
|
||||
// Track any errors
|
||||
if (response.status >= 500 && response.status !== 503) {
|
||||
check(response, {
|
||||
'no unexpected 5xx errors': () => false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function teardown(data) {
|
||||
console.log(`Test completed. Started at: ${data.startTime}`);
|
||||
console.log(`Router URL: ${data.routerUrl}`);
|
||||
}
|
||||
|
||||
export function handleSummary(data) {
|
||||
const summary = {
|
||||
testRun: {
|
||||
startTime: new Date().toISOString(),
|
||||
routerUrl: ROUTER_URL,
|
||||
},
|
||||
metrics: {
|
||||
totalRequests: data.metrics.http_reqs ? data.metrics.http_reqs.values.count : 0,
|
||||
throttled429: data.metrics.throttle_429_count ? data.metrics.throttle_429_count.values.count : 0,
|
||||
throttled503: data.metrics.throttle_503_count ? data.metrics.throttle_503_count.values.count : 0,
|
||||
successful: data.metrics.success_count ? data.metrics.success_count.values.count : 0,
|
||||
throttleRate: data.metrics.throttled_requests ? data.metrics.throttled_requests.values.rate : 0,
|
||||
retryAfterAvg: data.metrics.retry_after_seconds ? data.metrics.retry_after_seconds.values.avg : null,
|
||||
retryAfterP95: data.metrics.retry_after_seconds ? data.metrics.retry_after_seconds.values['p(95)'] : null,
|
||||
},
|
||||
thresholds: data.thresholds,
|
||||
checks: data.metrics.checks ? {
|
||||
passes: data.metrics.checks.values.passes,
|
||||
fails: data.metrics.checks.values.fails,
|
||||
rate: data.metrics.checks.values.rate,
|
||||
} : null,
|
||||
};
|
||||
|
||||
return {
|
||||
'results/spike-test-summary.json': JSON.stringify(summary, null, 2),
|
||||
stdout: textSummary(data, { indent: ' ', enableColors: true }),
|
||||
};
|
||||
}
|
||||
|
||||
function textSummary(data, options) {
|
||||
let output = '\n=== Router Spike Test Summary ===\n\n';
|
||||
|
||||
const totalReqs = data.metrics.http_reqs ? data.metrics.http_reqs.values.count : 0;
|
||||
const throttled429 = data.metrics.throttle_429_count ? data.metrics.throttle_429_count.values.count : 0;
|
||||
const throttled503 = data.metrics.throttle_503_count ? data.metrics.throttle_503_count.values.count : 0;
|
||||
const successful = data.metrics.success_count ? data.metrics.success_count.values.count : 0;
|
||||
|
||||
output += `Total Requests: ${totalReqs}\n`;
|
||||
output += `Successful (2xx): ${successful}\n`;
|
||||
output += `Throttled (429): ${throttled429}\n`;
|
||||
output += `Overloaded (503): ${throttled503}\n`;
|
||||
output += `Throttle Rate: ${((throttled429 + throttled503) / totalReqs * 100).toFixed(2)}%\n`;
|
||||
|
||||
if (data.metrics.retry_after_seconds) {
|
||||
output += `\nRetry-After Header:\n`;
|
||||
output += ` Avg: ${data.metrics.retry_after_seconds.values.avg.toFixed(2)}s\n`;
|
||||
output += ` P95: ${data.metrics.retry_after_seconds.values['p(95)'].toFixed(2)}s\n`;
|
||||
}
|
||||
|
||||
output += '\nThreshold Results:\n';
|
||||
for (const [name, result] of Object.entries(data.thresholds || {})) {
|
||||
output += ` ${result.ok ? 'PASS' : 'FAIL'}: ${name}\n`;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
55
tests/load/router/thresholds.json
Normal file
55
tests/load/router/thresholds.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"description": "Router chaos test thresholds for SPRINT_5100_0005_0001",
|
||||
"thresholds": {
|
||||
"recovery_time_seconds": {
|
||||
"max": 30,
|
||||
"description": "Maximum time to recover after load spike"
|
||||
},
|
||||
"throttle_rate_max": {
|
||||
"max": 0.95,
|
||||
"description": "Maximum percentage of requests that can be throttled during spike"
|
||||
},
|
||||
"success_rate_baseline": {
|
||||
"min": 0.99,
|
||||
"description": "Minimum success rate during baseline load"
|
||||
},
|
||||
"success_rate_recovery": {
|
||||
"min": 0.95,
|
||||
"description": "Minimum success rate during recovery phase"
|
||||
},
|
||||
"retry_after_max_seconds": {
|
||||
"max": 300,
|
||||
"description": "Maximum Retry-After value in seconds"
|
||||
},
|
||||
"retry_after_min_seconds": {
|
||||
"min": 1,
|
||||
"description": "Minimum Retry-After value in seconds"
|
||||
},
|
||||
"response_time_p95_ms": {
|
||||
"max": 5000,
|
||||
"description": "95th percentile response time under normal load"
|
||||
},
|
||||
"data_loss_rate": {
|
||||
"max": 0,
|
||||
"description": "No data loss allowed during throttling"
|
||||
}
|
||||
},
|
||||
"scenarios": {
|
||||
"baseline": {
|
||||
"expected_throttle_rate": 0.01,
|
||||
"expected_success_rate": 0.99
|
||||
},
|
||||
"spike_10x": {
|
||||
"expected_throttle_rate": 0.5,
|
||||
"expected_success_rate": 0.5
|
||||
},
|
||||
"spike_50x": {
|
||||
"expected_throttle_rate": 0.9,
|
||||
"expected_success_rate": 0.1
|
||||
},
|
||||
"recovery": {
|
||||
"expected_throttle_rate": 0.05,
|
||||
"expected_success_rate": 0.95
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user