Security Testing
CRITICAL: All authentication, authorization, and user input handling MUST have security tests.
Security Test Categories
| Category | What to Test | Example Payloads |
|---|---|---|
| SQL Injection | All database queries | ' OR '1'='1, '; DROP TABLE users; -- |
| Command Injection | Any shell/process calls | ; cat /etc/passwd, $(whoami), `id` |
| XSS | User-generated content | <script>alert('xss')</script> |
| Path Traversal | File operations | ../../../etc/passwd |
| Auth Bypass | Protected endpoints | Missing/invalid/expired tokens |
| IDOR | Resource access | Accessing other users' data |
SQL Injection Tests
#[tokio::test]
async fn test_sql_injection_vectors() {
let app = create_test_app().await;
let client = TestClient::new(app);
let payloads = vec![
"' OR '1'='1",
"'; DROP TABLE users; --",
"' UNION SELECT * FROM users --",
"1; SELECT * FROM passwords",
"admin'--",
"' OR 1=1 /*",
];
for payload in payloads {
let response = client
.post("/v1/search")
.json(&json!({ "query": payload }))
.send().await;
// Should never return 500 (indicates unhandled SQL error)
assert_ne!(
response.status(),
StatusCode::INTERNAL_SERVER_ERROR,
"SQL injection not handled: {}", payload
);
}
}
Command Injection Tests
#[tokio::test]
async fn test_command_injection_vectors() {
let payloads = vec![
"; cat /etc/passwd",
"| ls -la",
"$(whoami)",
"`id`",
"& ping -c 10 localhost",
"\n/bin/sh",
"|| cat /etc/shadow",
];
for payload in payloads {
let response = client
.post("/v1/process")
.json(&json!({ "filename": payload }))
.send().await;
assert_ne!(
response.status(),
StatusCode::INTERNAL_SERVER_ERROR,
"Command injection not handled: {}", payload
);
}
}
XSS Prevention Tests
Rust
#[tokio::test]
async fn test_xss_payloads_sanitized() {
let payloads = vec![
"<script>alert('xss')</script>",
"<img src=x onerror=alert('xss')>",
"<svg onload=alert('xss')>",
"javascript:alert('xss')",
"<body onload=alert('xss')>",
"'\"><script>alert('xss')</script>",
];
for payload in payloads {
let response = client
.post("/v1/profile")
.json(&json!({ "bio": payload }))
.send().await;
// Should sanitize or escape
assert_ne!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}
TypeScript
describe('XSS Prevention', () => {
const xssPayloads = [
'<script>alert("xss")</script>',
'<img src=x onerror=alert("xss")>',
'<svg onload=alert("xss")>',
'javascript:alert("xss")',
];
xssPayloads.forEach((payload) => {
it(`escapes XSS payload: ${payload.slice(0, 30)}...`, () => {
render(<UserContent content={payload} />);
// Should not execute any scripts
expect(document.querySelector('script')).toBeNull();
expect(document.querySelector('img[onerror]')).toBeNull();
});
});
});
Path Traversal Tests
#[tokio::test]
async fn test_path_traversal_prevented() {
let payloads = vec![
"../../../etc/passwd",
"..\\..\\..\\windows\\system32\\config\\sam",
"....//....//....//etc/passwd",
"%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd",
"/etc/passwd%00.jpg",
];
for payload in payloads {
let response = client
.get(&format!("/v1/files/{}", payload))
.send().await;
// Should reject path traversal attempts
assert!(
response.status() == StatusCode::BAD_REQUEST ||
response.status() == StatusCode::NOT_FOUND,
"Path traversal not blocked: {}", payload
);
}
}
Authentication Tests
#[tokio::test]
async fn test_auth_required_endpoints() {
let protected_endpoints = vec![
("GET", "/v1/me"),
("GET", "/v1/sessions"),
("POST", "/v1/logout"),
("DELETE", "/v1/account"),
];
for (method, path) in protected_endpoints {
let response = match method {
"GET" => client.get(path).send().await,
"POST" => client.post(path).send().await,
"DELETE" => client.delete(path).send().await,
_ => unreachable!(),
};
assert_eq!(
response.status(),
StatusCode::UNAUTHORIZED,
"{} {} should require auth", method, path
);
}
}
#[tokio::test]
async fn test_invalid_token_rejected() {
let invalid_tokens = vec![
"invalid_token",
"Bearer ",
"Bearer malformed",
"",
];
for token in invalid_tokens {
let response = client
.get("/v1/me")
.header("Authorization", token)
.send().await;
assert_eq!(
response.status(),
StatusCode::UNAUTHORIZED,
"Invalid token should be rejected: {}", token
);
}
}
Authorization Tests (IDOR)
#[tokio::test]
async fn test_user_cannot_access_other_user_data() {
let user1_token = create_token_for_user("user1");
let user2_id = "user2";
let response = client
.get(&format!("/v1/users/{}/profile", user2_id))
.header("Authorization", format!("Bearer {}", user1_token))
.send().await;
assert_eq!(response.status(), StatusCode::FORBIDDEN);
}
#[tokio::test]
async fn test_admin_only_endpoints() {
let user_token = create_token_for_user("regular_user");
let admin_endpoints = vec![
"/v1/admin/users",
"/v1/admin/settings",
"/v1/admin/audit",
];
for endpoint in admin_endpoints {
let response = client
.get(endpoint)
.header("Authorization", format!("Bearer {}", user_token))
.send().await;
assert_eq!(
response.status(),
StatusCode::FORBIDDEN,
"{} should require admin", endpoint
);
}
}
Running Security Tests
# Run all security tests
just test-security
# Run API security tests only
just test-api-security
Best Practices
- Test all user inputs - forms, query params, headers, JSON bodies
- Test boundary conditions - max lengths, special characters, unicode
- Test authentication flows - login, logout, token refresh, 2FA
- Test authorization - role checks, resource ownership
- Test rate limiting - prevent brute force attacks
- Test session management - concurrent sessions, session fixation