with one click
security-patterns
// Kailash Rust security — input validation, secrets, injection prevention. Hardcoded secrets BLOCKED.
// Kailash Rust security — input validation, secrets, injection prevention. Hardcoded secrets BLOCKED.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | security-patterns |
| description | Kailash Rust security — input validation, secrets, injection prevention. Hardcoded secrets BLOCKED. |
Mandatory security patterns for all Kailash SDK development. These patterns prevent common vulnerabilities and ensure secure application development.
| File | Topic | Origin |
|---|---|---|
constant-time-comparison-rs.md | Bitwise-OR credential loops, shared kailash-auth helper, anti-.any() pattern | journal 0021-RISK-r3-timing-leak-mcp-auth.md |
fail-closed-defaults-rs.md | Restrictive Default impls, registry reject-duplicates, 0o600 file permissions, allowlist path loading, unsafe Send/Sync invariants | journal 0018-RISK-six-high-security-findings.md (R1 six HIGH findings) |
network-security-rs.md | DNS rebinding guard on HTTP MCP, stdio argv/env allowlist, log content fingerprinting | R3 commits 173d054b, 0d4ebd12 |
security-auth-middleware-rs.md | Axum auth middleware composition, tower layers | prior |
Security patterns cover:
# ❌ WRONG - Hardcoded credentials
api_key = "sk-1234567890abcdef"
db_password = "mypassword123"
# ✅ CORRECT - Environment variables
import os
api_key = os.environ["API_KEY"]
db_password = os.environ["DATABASE_PASSWORD"]
# ❌ WRONG - No validation
def process_user_input(user_data):
return db.execute(f"SELECT * FROM users WHERE id = {user_data}")
# ✅ CORRECT - Parameterized queries (via DataFlow)
workflow.add_node("User_Read", "read_user", {
"id": validated_user_id # DataFlow handles parameterization
})
# ❌ WRONG - HTTP in production
workflow.add_node("APICallNode", "api", {
"url": "http://api.example.com/data" # Insecure!
})
# ✅ CORRECT - HTTPS always
workflow.add_node("APICallNode", "api", {
"url": "https://api.example.com/data"
})
| Vulnerability | Prevention Pattern |
|---|---|
| SQL Injection | Use DataFlow parameterized nodes |
| Code Injection | Avoid eval(), use PythonCodeNode safely |
| Credential Exposure | Environment variables, secret managers |
| XSS | Output encoding, CSP headers |
| CSRF | Token validation, SameSite cookies |
| Insecure Deserialization | Validate serialized data, deny_unknown_fields |
| SSRF | url_safety::check_url() on all provider URLs |
kailash-kaizen/src/llm/url_safety.rs)Canonical URL validation for all outbound HTTP. Blocks private IPs (10.x, 172.16-31.x, 192.168.x), loopback (127.x, ::1), link-local (169.254.x), cloud metadata, and non-HTTP schemes. Used by both LLM client (validate_base_url) and MCP transport (validate_url). DNS rebinding is a known limitation — documented, not syntactically fixable.
kailash-auth/src/jwt/mod.rs)JwtConfig implements Drop with zeroize() on the secret Vec<u8>. Prevents key material lingering in freed memory. Debug impl redacts the secret field.
kailash-auth/src/rate_limit/mod.rs)Window epoch (upper 32 bits) + counter (lower 32 bits) packed into a single AtomicU64. Window reset + counter reset is a single CAS operation — eliminates the race condition where two threads at a window boundary could both reset independently.
deny_unknown_fieldsAll 8 EATP constraint dimension sub-structs have #[serde(deny_unknown_fields)]. A misspelled field (e.g., max_transactin_cents) now produces a deserialization error instead of silently defaulting to 0 (maximum restriction).
ApiKeyConfig and JwtConfig both implement manual Debug that redacts sensitive fields. TenantContext is non-Clone with SecretString zeroed on drop.
Thread-local caller clearance MUST default to most restrictive level, not least restrictive.
// DO — fail-closed: unconfigured callers see only Public data
thread_local! {
static CALLER_CLEARANCE: RefCell<ClassificationLevel> = RefCell::new(ClassificationLevel::Public);
}
// DO NOT — fail-open: unconfigured callers bypass all redaction
static CALLER_CLEARANCE: RefCell<ClassificationLevel> = RefCell::new(ClassificationLevel::HighlyConfidential);
Why: Defaulting to the highest clearance makes the entire redaction system a no-op for any caller that forgets to set clearance. Fail-closed means unset callers get the safest behavior.
Registry::register MUST reject duplicate keys. Intentional rotation uses explicit replace(force_replace=true).
// DO — reject duplicate, require explicit force for rotation
if registry.contains_key(&org_id) {
return Err(AuthorityError::DuplicateOrgId { org_id });
}
// DO NOT — silently overwrite existing authority
registry.insert(org_id, new_authority); // attacker replaces legitimate authority
Audit stores, evidence sinks, and files containing PII/signatures MUST chmod 0o600 on creation.
// DO — restrictive permissions on security-sensitive files
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
}
// DO NOT — rely on umask (world-readable audit logs)
std::fs::write(&path, data)?; // inherits process umask, often 0o644
allowed_model_roots: Vec<PathBuf> pattern -- default-deny if empty.
// DO — canonicalize + symlink rejection + device-file rejection + root containment
fn validate_path(path: &Path, allowed_roots: &[PathBuf]) -> Result<PathBuf> {
if allowed_roots.is_empty() { return Err(ModelPathError::NoRootsConfigured); }
let canonical = path.canonicalize()?;
if canonical.is_symlink() { return Err(ModelPathError::SymlinkRejected); }
if !allowed_roots.iter().any(|r| canonical.starts_with(r)) {
return Err(ModelPathError::OutsideAllowedRoots);
}
Ok(canonical)
}
// DO NOT — trust caller-supplied paths
let model = load_model(user_supplied_path)?; // path traversal, symlink escape
When wrapping non-thread-safe C libraries, use an internal Mutex<()> to serialize all FFI calls.
// DO — inference latch serializes all FFI access
struct Backend {
ctx: *mut ffi::Context, // non-thread-safe C pointer
inference_latch: Mutex<()>, // serializes all FFI calls
}
impl Backend {
fn generate(&self, prompt: &str) -> Result<String> {
let _guard = self.inference_latch.lock().unwrap();
unsafe { ffi::generate(self.ctx, prompt) } // serialized
}
}
// DO NOT — rely on external RwLock read guard (&self still allows parallel reads)
unsafe impl Send for Backend {}
unsafe impl Sync for Backend {} // SAFETY comment says "RwLock protects" but &self methods run in parallel
Why: &self methods under an external RwLock<Backend> still allow multiple concurrent readers. The internal Mutex<()> is the only guarantee that FFI calls are truly serialized.
Security patterns are enforced by:
.claude/rules/security.md - Security rules.claude/hooks/validate-bash-command.js - Command validationgold-standards-validator agent - Compliance checkingUse this skill when:
For security-related questions, invoke:
security-reviewer - OWASP-based security analysisgold-standards-validator - Compliance checkingtesting-specialist - Security testing patterns