Developer Security Guidelines
This guide provides security best practices for contributors to arrayops. All contributors should follow these guidelines when writing code, reviewing pull requests, or making changes to the codebase.
Overview
arrayops prioritizes security through:
Rust’s memory safety guarantees
Comprehensive input validation
Safe error handling
Minimal unsafe code usage
Security-focused code review
Secure Coding Practices
Rust/PyO3 Security
1. Input Validation
Always validate inputs before processing:
// ✅ Good: Validate input type and properties
pub fn my_function(py: Python, array: &PyAny) -> PyResult<PyObject> {
let input_type = detect_input_type(array)?;
let typecode = get_typecode_unified(array, input_type)?;
validate_for_operation(array, input_type, false)?;
// ... process array
}
// ❌ Bad: No validation
pub fn my_function(py: Python, array: &PyAny) -> PyResult<PyObject> {
// Missing validation - unsafe!
// ... process array
}
Validation requirements:
Check input type (array.array, numpy.ndarray, memoryview, Arrow)
Validate typecode is supported
Check array properties (dimensionality, contiguity, writability)
Verify buffer accessibility before use
2. Buffer Safety
Use PyO3’s safe buffer APIs:
// ✅ Good: Use PyBuffer with safe APIs
let buffer = PyBuffer::<i32>::get(array)?;
let slice = buffer.as_slice(py).ok_or_else(|| PyTypeError::new_err("..."))?;
// slice is guaranteed to be valid and bounds-checked
// ❌ Bad: Raw pointer manipulation (never do this)
// let ptr = array.as_ptr(); // Unsafe - don't do this!
Buffer safety rules:
Always use
PyBuffertype for buffer accessUse
.as_slice()for safe slice access (never use raw pointers)Check buffer validity with
.ok_or_else()before useNever store buffer references beyond function scope (lifetime safety)
3. Error Handling
Return appropriate error types:
// ✅ Good: Specific error types
if !is_valid {
return Err(PyTypeError::new_err("Expected array.array"));
}
if size > MAX_SIZE {
return Err(PyValueError::new_err("Array too large"));
}
// ❌ Bad: Generic or unsafe errors
// return Err(PyException::new_err("Error")); // Too generic
// panic!("Invalid input"); // Never panic in library code!
Error handling rules:
Use specific error types (
PyTypeError,PyValueError,PyBufferError)Never panic in library code (convert panics to Python exceptions)
Error messages should be helpful but not leak sensitive information
Don’t expose internal implementation details in error messages
4. Unsafe Code Guidelines
Minimize unsafe code usage:
// ✅ Good: Use safe Rust APIs
let slice = buffer.as_slice(py)?; // Safe API
// ⚠️ If unsafe is necessary, document safety invariants:
// SAFETY: This is safe because:
// 1. Pointer is valid (checked above)
// 2. Size is correct (validated)
// 3. Lifetime is managed by Rust
unsafe {
// minimal unsafe code with safety comments
}
Unsafe code rules:
Avoid unsafe code when possible (use safe Rust/PyO3 APIs)
Document all unsafe blocks with safety comments explaining why it’s safe
Justify each unsafe block (why safe APIs can’t be used)
Test unsafe code thoroughly
Review unsafe code carefully in PRs
5. Integer Overflow
Handle integer operations safely:
// ✅ Good: Use checked arithmetic or handle overflow
let result = a.checked_add(b).ok_or_else(|| PyValueError::new_err("Overflow"))?;
// Or use Rust's default behavior (wrapping) if appropriate for the use case
// ❌ Bad: Ignore potential overflow
// let result = a + b; // May overflow silently
Integer overflow rules:
Consider overflow behavior for arithmetic operations
Use checked arithmetic when overflow is an error condition
Document overflow behavior in function documentation
Note: Python’s integer semantics may differ from Rust’s
Python Security
1. Callable Execution
When executing user callables, be aware of security implications:
// ✅ Good: Execute callable with proper error handling
let result = callback.call1(py, (element,))?;
// GIL is held (required for Python interop)
// Exceptions are propagated correctly
// ❌ Bad: No error handling or unsafe execution
// callback.call1(py, (element,)); // Ignoring errors
Callable execution rules:
Always handle exceptions from user callables
GIL must be held when calling Python code
Don’t assume callables are safe (users can provide any Python code)
Document that callables execute with full Python interpreter privileges
2. Error Message Security
Error messages should not leak sensitive information:
// ✅ Good: Generic but helpful error
return Err(PyTypeError::new_err("Expected array.array"));
// ❌ Bad: Leaks internal details
// return Err(PyTypeError::new_err(format!("Buffer pointer {:p} invalid", ptr)));
// return Err(PyTypeError::new_err(format!("Internal state: {:?}", internal_state)));
Error message rules:
Don’t expose internal implementation details
Don’t include memory addresses or pointers
Don’t include stack traces or debug information
Keep messages helpful for users but generic enough to be safe
Avoid exposing system-specific information
Code Review Security Checklist
When reviewing code, check for:
Input Validation
[ ] All inputs are validated before use
[ ] Type checking is performed
[ ] Array properties are verified (size, dimensionality, contiguity)
[ ] Edge cases are handled (empty arrays, single elements, very large arrays)
Memory Safety
[ ] No raw pointer manipulation
[ ] Buffer access uses safe PyO3 APIs
[ ] No use-after-free patterns
[ ] Lifetime management is correct
Error Handling
[ ] Errors are handled appropriately
[ ] No panics in library code
[ ] Error messages are safe (no sensitive info)
[ ] Appropriate error types are used
Unsafe Code
[ ] Unsafe code is minimized
[ ] All unsafe blocks have safety comments
[ ] Safety invariants are documented
[ ] Unsafe code is justified
Testing
[ ] Security-sensitive code has tests
[ ] Edge cases are tested
[ ] Error conditions are tested
[ ] Large input tests are included
Testing Security Considerations
Security-Focused Tests
When writing tests, include:
Input validation tests: Invalid inputs should be rejected
Edge case tests: Empty arrays, single elements, boundary values
Large array tests: Very large arrays (DoS considerations)
Error handling tests: Ensure errors are handled safely
Type confusion tests: Attempt to use wrong types
Example Security Test
def test_security_large_array():
"""Test that very large arrays are handled (DoS consideration)."""
# This test verifies that large arrays don't cause crashes
# but note: they may consume significant memory/CPU
large_array = array.array('i', [0] * 10_000_000)
result = arrayops.sum(large_array)
assert result == 0
def test_security_invalid_type():
"""Test that invalid types are rejected safely."""
with pytest.raises(TypeError, match="Expected array.array"):
arrayops.sum([1, 2, 3]) # list, not array
Dependency Security
Adding New Dependencies
Before adding a new dependency:
Check security: Review the crate/package for known vulnerabilities
Minimize dependencies: Only add if necessary
Review maintenance: Check if the dependency is actively maintained
Check license: Ensure license is compatible
Document rationale: Explain why the dependency is needed
Updating Dependencies
Regular updates: Keep dependencies up to date
Security updates: Apply security patches promptly
Test updates: Test thoroughly after updating dependencies
Review changelogs: Check for security-related changes
Security Incident Response
If you discover a security vulnerability:
Do NOT open a public issue or PR
Email the maintainer: odosmatthews@gmail.com
Include:
Description of the vulnerability
Potential impact
Steps to reproduce
Suggested fix (if any)
Wait for response before public disclosure
See SECURITY.md for the full security policy.
Security Resources
Code Examples
Secure Function Template
/// Brief description of the function.
///
/// # Security Considerations
/// - Validates input type and properties
/// - Uses safe buffer APIs
/// - Handles errors appropriately
/// - No unsafe code
pub fn secure_function(py: Python, array: &PyAny) -> PyResult<PyObject> {
// 1. Validate input
let input_type = detect_input_type(array)?;
let typecode = get_typecode_unified(array, input_type)?;
validate_for_operation(array, input_type, false)?;
// 2. Use safe buffer access
dispatch_by_typecode!(typecode, array, |buffer| {
let slice = buffer.as_slice(py)
.ok_or_else(|| PyTypeError::new_err("Failed to get buffer slice"))?;
// 3. Process with safe operations
let result = process_slice(slice)?;
Ok(result.to_object(py))
})
}
fn process_slice<T: Element>(slice: &[T]) -> PyResult<T::Output> {
// Safe slice operations - bounds-checked by Rust
// ...
}
Security Anti-Patterns to Avoid
// ❌ DON'T: Skip validation
pub fn bad_function(py: Python, array: &PyAny) -> PyResult<PyObject> {
// Missing validation!
let buffer = PyBuffer::<i32>::get(array)?; // May fail unexpectedly
}
// ❌ DON'T: Use raw pointers
pub fn bad_function(py: Python, array: &PyAny) -> PyResult<PyObject> {
let ptr = array.as_ptr(); // Unsafe!
unsafe { /* pointer manipulation */ }
}
// ❌ DON'T: Panic in library code
pub fn bad_function(py: Python, array: &PyAny) -> PyResult<PyObject> {
if !is_valid { panic!("Invalid!"); } // Never panic!
}
// ❌ DON'T: Leak sensitive info in errors
pub fn bad_function(py: Python, array: &PyAny) -> PyResult<PyObject> {
return Err(PyTypeError::new_err(format!("Pointer {:p} invalid", ptr)));
}
Questions?
If you have questions about security:
Check the Security Documentation
Review existing code for examples
Ask in PR comments or discussions
Contact the maintainer for sensitive questions
Last updated: 2024