TinyURL-RS implements comprehensive URL validation to prevent malicious input:
// RFC-compliant URL parsing
match Url::parse(&self.url) {
Ok(_) => {},
Err(_) => return Err(AppError::InvalidUrl("Invalid URL format".to_string())),
}- Allowed:
http://,https:// - Blocked:
javascript:,data:,file:,ftp:// - Validation: Automatic scheme checking during URL parsing
// Future enhancement: Domain blacklist checking
const BLOCKED_DOMAINS: &[&str] = &[
"localhost",
"127.0.0.1",
"0.0.0.0",
// Add malicious domains
];// Only allow alphanumeric characters and hyphens
if !code.chars().all(|c| c.is_alphanumeric() || c == '-') {
return Err(AppError::Validation(
"Custom code can only contain alphanumeric characters and hyphens".to_string()
));
}- Minimum: 1 character
- Maximum: 20 characters
- Prevents: Buffer overflow and database constraints
All database operations use parameterized queries:
// Safe: Uses parameter binding
sqlx::query_as::<_, TinyUrl>(
"SELECT * FROM tinyurls WHERE short_code = $1"
)
.bind(short_code)
.fetch_optional(&*self.pool)
.await// VULNERABLE: String concatenation
let query = format!("SELECT * FROM tinyurls WHERE short_code = '{}'", user_input);// SAFE: Parameter binding
sqlx::query_as::<_, TinyUrl>(query_string)
.bind(parameter)
.fetch_optional(&pool)
.await- No authentication required for basic operations
- Suitable for: Internal networks, development, low-risk environments
// Header-based API key validation
#[derive(Debug)]
pub struct ApiKeyAuth {
key: String,
}
impl ApiKeyAuth {
pub fn validate(&self, request_key: &str) -> bool {
// Constant-time comparison to prevent timing attacks
use subtle::ConstantTimeEq;
self.key.as_bytes().ct_eq(request_key.as_bytes()).into()
}
}// Future: Implement rate limiting per API key/IP
pub struct RateLimiter {
// Redis-based rate limiting
// Window-based or token bucket algorithm
}# Use SSL/TLS for database connections
DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=require-- Create dedicated application user
CREATE USER tinyurl_app WITH PASSWORD 'secure_random_password';
-- Grant minimal required permissions
GRANT SELECT, INSERT, UPDATE, DELETE ON tinyurls TO tinyurl_app;
GRANT USAGE, SELECT ON SEQUENCE tinyurls_id_seq TO tinyurl_app;
-- Revoke unnecessary permissions
REVOKE ALL ON SCHEMA public FROM PUBLIC;# Use Redis AUTH
REDIS_URL=redis://username:password@redis-host:6379/0# Bind Redis to specific interface
bind 127.0.0.1 ::1
# Disable dangerous commands
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command KEYS ""server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/private.key;
# Modern TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
# Security headers
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}# docker-compose.yml
version: '3.8'
services:
tinyurl-app:
networks:
- app-network
postgres:
networks:
- app-network
# Don't expose ports externally
redis:
networks:
- app-network
networks:
app-network:
driver: bridge
internal: true # No external access// Future implementation with Redis
use std::collections::HashMap;
use std::time::{Duration, Instant};
pub struct RateLimiter {
requests: HashMap<String, Vec<Instant>>,
max_requests: usize,
window: Duration,
}
impl RateLimiter {
pub fn check_rate_limit(&mut self, client_id: &str) -> bool {
let now = Instant::now();
let requests = self.requests.entry(client_id.to_string()).or_default();
// Remove old requests outside the window
requests.retain(|&time| now.duration_since(time) < self.window);
if requests.len() >= self.max_requests {
false // Rate limit exceeded
} else {
requests.push(now);
true
}
}
}# Nginx rate limiting
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /shorten {
limit_req zone=api burst=20 nodelay;
proxy_pass http://localhost:8080;
}
}
}// In Actix-Web configuration
use actix_web::web;
App::new()
.app_data(web::JsonConfig::default().limit(1024)) // 1KB limit
.app_data(web::PayloadConfig::new(1024)) // 1KB payload limit// Database connection pooling prevents connection exhaustion
.max_connections(20)
.idle_timeout(Some(Duration::from_secs(600)))use serde_json::json;
// Log security events
log::warn!(
"{}",
json!({
"event": "invalid_url_attempt",
"url": sanitized_url,
"client_ip": client_ip,
"timestamp": chrono::Utc::now().to_rfc3339()
})
);// Log all URL creation events
log::info!(
"URL created: short_code={}, client_ip={}, user_agent={}",
short_code,
client_ip,
user_agent
);- Failed request rate spikes
- Unusual traffic patterns
- Database connection failures
- Cache service unavailability
# Monitor for suspicious patterns
tail -f /var/log/tinyurl-rs/app.log | grep -E "(ERROR|WARN|invalid_url)"
# Track request rates
grep "URL created" /var/log/tinyurl-rs/app.log | wc -l# Use specific version tags
FROM rust:1.75-slim as builder
# Create non-root user
RUN useradd --create-home --shell /bin/bash app
# Install only necessary packages
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy source and build
WORKDIR /app
COPY --chown=app:app . .
USER app
RUN cargo build --release
# Runtime stage
FROM debian:bookworm-slim
RUN useradd --create-home --shell /bin/bash app
# Install runtime dependencies only
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder --chown=app:app /app/target/release/tinyurl-rs .
USER app
EXPOSE 8080
CMD ["./tinyurl-rs"]# Use secrets management instead of environment variables
# AWS Secrets Manager, HashiCorp Vault, Kubernetes secrets
# Example with Kubernetes
kubectl create secret generic tinyurl-secrets \
--from-literal=db-password=secure_password \
--from-literal=redis-password=another_password# Avoid logging sensitive environment variables
export RUST_LOG=info # Don't use debug in production
unset HISTFILE # Disable command history for secrets-
Detection
- Monitor logs for anomalies
- Set up alerting for security events
- Regular security scans
-
Response
- Isolate affected systems
- Preserve logs and evidence
- Assess scope of impact
- Implement containment measures
-
Recovery
- Apply security patches
- Update configurations
- Reset compromised credentials
- Restore from clean backups if needed
-
Post-Incident
- Conduct root cause analysis
- Update security measures
- Document lessons learned
- Update incident response procedures
Maintain updated contact information for:
- Security team
- Infrastructure team
- Database administrators
- Cloud provider support
- Legal/compliance team
These security measures provide defense-in-depth protection while maintaining the performance and usability of the TinyURL-RS service.