AgentSkillsCN

security-review

当您需要处理用户输入、操作内存、进行文件 I/O、执行网络操作,或实现安全敏感功能时,请使用此技能。它提供全面的 C++ 安全检查清单与最佳实践。

SKILL.md
--- frontmatter
name: security-review
description: Use this skill when handling user input, working with memory, file I/O, network operations, or implementing security-sensitive features. Provides comprehensive C++ security checklist and patterns.

C++ Security Review Skill

This skill ensures all C++ code follows security best practices and identifies potential vulnerabilities.

When to Activate

  • Handling user input or parsing data
  • Memory allocation and pointer operations
  • File I/O and filesystem operations
  • Network programming and sockets
  • Multi-threaded programming
  • Working with external libraries
  • System calls and OS interactions

Security Checklist

1. Memory Safety

Buffer Overflow Prevention

cpp
// NEVER: Unbounded buffer operations
char buffer[256];
strcpy(buffer, user_input);  // DANGEROUS!
sprintf(buffer, "%s", user_input);  // DANGEROUS!

// ALWAYS: Use bounded operations
std::string safe_string = user_input;

// If C-style buffers needed, use safe variants
char buffer[256];
strncpy(buffer, user_input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';

snprintf(buffer, sizeof(buffer), "%s", user_input);

Smart Pointer Usage

cpp
// NEVER: Raw new/delete
Resource* r = new Resource();
// ... code that might throw ...
delete r;  // May never be called!

// ALWAYS: Smart pointers
auto r = std::make_unique<Resource>();
// Automatic cleanup, exception-safe

// NEVER: Array new with raw pointer
int* arr = new int[100];
delete[] arr;

// ALWAYS: Use containers or smart pointers
std::vector<int> arr(100);
auto arr = std::make_unique<int[]>(100);

Avoid Use-After-Free

cpp
// DANGEROUS: Returning reference to local
std::string& get_name() {
    std::string name = "local";
    return name;  // UNDEFINED BEHAVIOR!
}

// SAFE: Return by value
std::string get_name() {
    std::string name = "local";
    return name;  // Move semantics apply
}

// DANGEROUS: Dangling pointer
int* ptr;
{
    int x = 42;
    ptr = &x;
}
*ptr = 10;  // UNDEFINED BEHAVIOR!

// SAFE: Proper lifetime management
std::unique_ptr<int> ptr = std::make_unique<int>(42);

Verification Steps

  • No raw new/delete (use smart pointers)
  • No unbounded string operations (strcpy, sprintf, gets)
  • No returning references/pointers to local variables
  • All pointers checked for nullptr before use
  • No manual memory management

2. Input Validation

String Input Validation

cpp
#include <regex>
#include <limits>

// Validate string length
bool validate_input(const std::string& input, size_t max_len) {
    if (input.empty()) {
        return false;  // Reject empty input
    }
    if (input.length() > max_len) {
        return false;  // Reject too long input
    }
    return true;
}

// Validate with regex
bool is_valid_email(const std::string& email) {
    static const std::regex email_regex(
        R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})"
    );
    return std::regex_match(email, email_regex);
}

// Safe integer parsing
std::optional<int> parse_int(const std::string& str) {
    try {
        size_t pos;
        int value = std::stoi(str, &pos);

        // Ensure entire string was parsed
        if (pos != str.length()) {
            return std::nullopt;
        }

        return value;
    } catch (const std::exception&) {
        return std::nullopt;
    }
}

Numeric Overflow Prevention

cpp
#include <limits>
#include <stdexcept>

// Safe addition
int safe_add(int a, int b) {
    if (b > 0 && a > std::numeric_limits<int>::max() - b) {
        throw std::overflow_error("Integer overflow");
    }
    if (b < 0 && a < std::numeric_limits<int>::min() - b) {
        throw std::underflow_error("Integer underflow");
    }
    return a + b;
}

// Safe multiplication
int safe_multiply(int a, int b) {
    if (a > 0 && b > 0 && a > std::numeric_limits<int>::max() / b) {
        throw std::overflow_error("Integer overflow");
    }
    if (a > 0 && b < 0 && b < std::numeric_limits<int>::min() / a) {
        throw std::underflow_error("Integer underflow");
    }
    if (a < 0 && b > 0 && a < std::numeric_limits<int>::min() / b) {
        throw std::underflow_error("Integer underflow");
    }
    if (a < 0 && b < 0 && a < std::numeric_limits<int>::max() / b) {
        throw std::overflow_error("Integer overflow");
    }
    return a * b;
}

// Safe size calculation for allocation
size_t safe_array_size(size_t count, size_t element_size) {
    if (count > 0 && element_size > std::numeric_limits<size_t>::max() / count) {
        throw std::overflow_error("Size overflow");
    }
    return count * element_size;
}

Verification Steps

  • All user inputs validated before use
  • String lengths checked and bounded
  • Integer operations checked for overflow
  • Array indices bounds-checked
  • File paths sanitized (no path traversal)

3. Format String Vulnerabilities

cpp
// NEVER: User input as format string
printf(user_input);  // DANGEROUS!
fprintf(file, user_input);  // DANGEROUS!

// ALWAYS: Use format string with user input as argument
printf("%s", user_input);
fprintf(file, "%s", user_input);

// BETTER: Use type-safe formatting
#include <fmt/core.h>
fmt::print("{}", user_input);

// Or iostream
std::cout << user_input << std::endl;

4. File Operations Security

Path Traversal Prevention

cpp
#include <filesystem>

namespace fs = std::filesystem;

// Validate file path is within allowed directory
bool is_safe_path(const fs::path& base_dir, const fs::path& user_path) {
    // Normalize paths
    auto canonical_base = fs::canonical(base_dir);
    auto requested = fs::weakly_canonical(base_dir / user_path);

    // Check if requested path is under base directory
    auto [end, nothing] = std::mismatch(
        canonical_base.begin(), canonical_base.end(),
        requested.begin()
    );

    return end == canonical_base.end();
}

// Safe file open
std::optional<std::ifstream> safe_open_file(
    const fs::path& base_dir,
    const std::string& filename
) {
    // Reject absolute paths and path traversal
    if (filename.find("..") != std::string::npos) {
        return std::nullopt;
    }

    fs::path file_path = base_dir / filename;

    if (!is_safe_path(base_dir, filename)) {
        return std::nullopt;
    }

    std::ifstream file(file_path);
    if (!file.is_open()) {
        return std::nullopt;
    }

    return file;
}

Secure File Permissions

cpp
#include <filesystem>
#include <fstream>

namespace fs = std::filesystem;

// Create file with restricted permissions
void create_secure_file(const fs::path& path, const std::string& content) {
    // Write content
    std::ofstream file(path);
    file << content;
    file.close();

    // Set restrictive permissions (owner read/write only)
    fs::permissions(path,
        fs::perms::owner_read | fs::perms::owner_write,
        fs::perm_options::replace
    );
}

// Check file permissions before reading sensitive data
bool is_secure_permissions(const fs::path& path) {
    auto perms = fs::status(path).permissions();

    // Check no group or other access
    bool group_access = (perms & fs::perms::group_all) != fs::perms::none;
    bool other_access = (perms & fs::perms::others_all) != fs::perms::none;

    return !group_access && !other_access;
}

Verification Steps

  • All file paths validated (no ".." traversal)
  • File permissions set appropriately
  • Temporary files created securely
  • File handles properly closed (RAII)
  • Symlink attacks considered

5. Secrets Management

cpp
// NEVER: Hardcoded secrets
const char* api_key = "sk-xxxxxxxxxxxxx";  // DANGEROUS!
std::string password = "admin123";  // DANGEROUS!

// ALWAYS: Environment variables
const char* api_key = std::getenv("API_KEY");
if (!api_key) {
    throw std::runtime_error("API_KEY not set");
}

// Secure secret handling class
class SecureString {
public:
    explicit SecureString(std::string value)
        : m_value(std::move(value)) {}

    ~SecureString() {
        // Overwrite memory before destruction
        std::fill(m_value.begin(), m_value.end(), '\0');
    }

    // No copying
    SecureString(const SecureString&) = delete;
    SecureString& operator=(const SecureString&) = delete;

    // Allow moving
    SecureString(SecureString&& other) noexcept
        : m_value(std::move(other.m_value)) {}

    const std::string& get() const { return m_value; }

private:
    std::string m_value;
};

Verification Steps

  • No hardcoded secrets in source code
  • Secrets loaded from environment or secure storage
  • Secrets cleared from memory when no longer needed
  • No secrets in logs or error messages
  • .gitignore includes sensitive files

6. Thread Safety

Race Condition Prevention

cpp
#include <mutex>
#include <shared_mutex>
#include <atomic>

class ThreadSafeCounter {
public:
    void increment() {
        std::lock_guard<std::mutex> lock(m_mutex);
        ++m_count;
    }

    int get() const {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_count;
    }

private:
    mutable std::mutex m_mutex;
    int m_count = 0;
};

// For read-heavy workloads
class ThreadSafeCache {
public:
    std::string get(const std::string& key) const {
        std::shared_lock<std::shared_mutex> lock(m_mutex);
        auto it = m_cache.find(key);
        return it != m_cache.end() ? it->second : "";
    }

    void set(const std::string& key, const std::string& value) {
        std::unique_lock<std::shared_mutex> lock(m_mutex);
        m_cache[key] = value;
    }

private:
    mutable std::shared_mutex m_mutex;
    std::unordered_map<std::string, std::string> m_cache;
};

// For simple counters, use atomics
class AtomicCounter {
public:
    void increment() { ++m_count; }
    int get() const { return m_count.load(); }

private:
    std::atomic<int> m_count{0};
};

Deadlock Prevention

cpp
// DANGEROUS: Inconsistent lock ordering
void transfer_dangerous(Account& from, Account& to, int amount) {
    std::lock_guard<std::mutex> lock1(from.mutex);
    std::lock_guard<std::mutex> lock2(to.mutex);  // May deadlock!
    // ...
}

// SAFE: Use std::lock for multiple mutexes
void transfer_safe(Account& from, Account& to, int amount) {
    std::scoped_lock lock(from.mutex, to.mutex);  // C++17
    // Or pre-C++17:
    // std::lock(from.mutex, to.mutex);
    // std::lock_guard<std::mutex> lock1(from.mutex, std::adopt_lock);
    // std::lock_guard<std::mutex> lock2(to.mutex, std::adopt_lock);

    from.balance -= amount;
    to.balance += amount;
}

Verification Steps

  • All shared data protected by mutex
  • Consistent lock ordering to prevent deadlocks
  • RAII lock guards used (no manual lock/unlock)
  • Atomic operations for simple counters
  • No data races (verified with thread sanitizer)

7. Command Injection Prevention

cpp
// NEVER: Build command strings from user input
std::string cmd = "ls " + user_input;
system(cmd.c_str());  // DANGEROUS!

// SAFER: Use execve-style functions with argument arrays
#include <unistd.h>
#include <sys/wait.h>

int execute_safely(const std::vector<std::string>& args) {
    pid_t pid = fork();

    if (pid == 0) {
        // Child process
        std::vector<char*> c_args;
        for (const auto& arg : args) {
            c_args.push_back(const_cast<char*>(arg.c_str()));
        }
        c_args.push_back(nullptr);

        execvp(c_args[0], c_args.data());
        _exit(127);  // exec failed
    }

    // Parent process
    int status;
    waitpid(pid, &status, 0);
    return WEXITSTATUS(status);
}

// Usage
execute_safely({"ls", "-la", sanitized_path});

8. Network Security

TLS/SSL for Network Communication

cpp
// Use libraries like OpenSSL, Boost.Asio with SSL

#include <boost/asio/ssl.hpp>

class SecureClient {
public:
    SecureClient(boost::asio::io_context& io_ctx)
        : m_ssl_ctx(boost::asio::ssl::context::tlsv12_client)
        , m_socket(io_ctx, m_ssl_ctx) {

        // Verify peer certificate
        m_ssl_ctx.set_verify_mode(
            boost::asio::ssl::verify_peer |
            boost::asio::ssl::verify_fail_if_no_peer_cert
        );

        // Load CA certificates
        m_ssl_ctx.set_default_verify_paths();
    }

    void connect(const std::string& host, const std::string& port) {
        // Set SNI hostname
        SSL_set_tlsext_host_name(m_socket.native_handle(), host.c_str());

        // Resolve and connect
        boost::asio::ip::tcp::resolver resolver(m_socket.get_executor());
        auto endpoints = resolver.resolve(host, port);

        boost::asio::connect(m_socket.lowest_layer(), endpoints);

        // Perform SSL handshake
        m_socket.handshake(boost::asio::ssl::stream_base::client);
    }

private:
    boost::asio::ssl::context m_ssl_ctx;
    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> m_socket;
};

Input Sanitization for Network Data

cpp
// Validate and sanitize network input
struct NetworkMessage {
    uint32_t type;
    uint32_t length;
    std::vector<uint8_t> data;
};

std::optional<NetworkMessage> parse_message(const std::vector<uint8_t>& buffer) {
    constexpr size_t HEADER_SIZE = 8;
    constexpr size_t MAX_MESSAGE_SIZE = 1024 * 1024;  // 1MB limit

    if (buffer.size() < HEADER_SIZE) {
        return std::nullopt;  // Incomplete header
    }

    NetworkMessage msg;
    std::memcpy(&msg.type, buffer.data(), 4);
    std::memcpy(&msg.length, buffer.data() + 4, 4);

    // Validate length
    if (msg.length > MAX_MESSAGE_SIZE) {
        return std::nullopt;  // Message too large
    }

    if (buffer.size() < HEADER_SIZE + msg.length) {
        return std::nullopt;  // Incomplete message
    }

    msg.data.assign(
        buffer.begin() + HEADER_SIZE,
        buffer.begin() + HEADER_SIZE + msg.length
    );

    return msg;
}

9. Cryptography Best Practices

cpp
#include <openssl/evp.h>
#include <openssl/rand.h>

// Generate cryptographically secure random bytes
std::vector<uint8_t> generate_random_bytes(size_t length) {
    std::vector<uint8_t> buffer(length);

    if (RAND_bytes(buffer.data(), static_cast<int>(length)) != 1) {
        throw std::runtime_error("Failed to generate random bytes");
    }

    return buffer;
}

// Secure password hashing (use a proper library like libsodium)
#include <sodium.h>

std::string hash_password(const std::string& password) {
    char hashed[crypto_pwhash_STRBYTES];

    if (crypto_pwhash_str(
            hashed,
            password.c_str(),
            password.length(),
            crypto_pwhash_OPSLIMIT_INTERACTIVE,
            crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0) {
        throw std::runtime_error("Password hashing failed");
    }

    return std::string(hashed);
}

bool verify_password(const std::string& password, const std::string& hash) {
    return crypto_pwhash_str_verify(
        hash.c_str(),
        password.c_str(),
        password.length()) == 0;
}

10. Static Analysis & Sanitizers

Compiler Warnings

cmake
# Enable all warnings
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(${PROJECT_NAME} PRIVATE
        -Wall
        -Wextra
        -Wpedantic
        -Werror
        -Wconversion
        -Wshadow
        -Wformat=2
        -Wformat-security
    )
endif()

if(MSVC)
    target_compile_options(${PROJECT_NAME} PRIVATE
        /W4
        /WX
    )
endif()

Address Sanitizer (ASan)

cmake
option(ENABLE_ASAN "Enable Address Sanitizer" OFF)

if(ENABLE_ASAN)
    target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=address -fno-omit-frame-pointer)
    target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=address)
endif()

Thread Sanitizer (TSan)

cmake
option(ENABLE_TSAN "Enable Thread Sanitizer" OFF)

if(ENABLE_TSAN)
    target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=thread)
    target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=thread)
endif()

Undefined Behavior Sanitizer (UBSan)

cmake
option(ENABLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF)

if(ENABLE_UBSAN)
    target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=undefined)
    target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=undefined)
endif()

Pre-Deployment Security Checklist

Before ANY production deployment:

  • Memory Safety: No raw new/delete, smart pointers used
  • Buffer Safety: No unbounded string operations
  • Input Validation: All inputs validated and sanitized
  • Integer Safety: Overflow checks on arithmetic
  • Format Strings: No user input as format strings
  • File Security: Path traversal prevented, permissions set
  • Secrets: No hardcoded secrets, secure handling
  • Thread Safety: Proper synchronization, no data races
  • Network Security: TLS used, input validated
  • Command Injection: No shell command construction
  • Cryptography: Using vetted libraries correctly
  • Compiler Warnings: All warnings enabled and fixed
  • Sanitizers: Tested with ASan, TSan, UBSan
  • Static Analysis: Passed clang-tidy, cppcheck
  • Dependencies: Updated, no known vulnerabilities

Security Testing

cpp
// Test for buffer overflow handling
TEST(SecurityTest, RejectsOversizedInput) {
    std::string oversized(10000, 'A');
    EXPECT_FALSE(validate_input(oversized, 1000));
}

// Test for path traversal
TEST(SecurityTest, RejectsPathTraversal) {
    EXPECT_FALSE(is_safe_path("/data", "../etc/passwd"));
    EXPECT_FALSE(is_safe_path("/data", "../../root/.ssh/id_rsa"));
}

// Test for integer overflow
TEST(SecurityTest, DetectsIntegerOverflow) {
    EXPECT_THROW(safe_add(INT_MAX, 1), std::overflow_error);
    EXPECT_THROW(safe_multiply(INT_MAX, 2), std::overflow_error);
}

Resources


Remember: Security is not optional. One vulnerability can compromise the entire system. When in doubt, err on the side of caution.