---
title: "Segurança em Rust: Memory Safety Guia | Rust Brasil"
url: "https://rustlang.com.br/artigos/seguranca-rust/"
markdown_url: "https://rustlang.com.br/artigos/seguranca-rust.MD"
description: "Segurança em Rust: memory safety, type safety, thread safety e como Rust previne vulnerabilidades comuns."
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Segurança em Rust: Memory Safety Guia | Rust Brasil

Segurança em Rust: memory safety, type safety, thread safety e como Rust previne vulnerabilidades comuns.


## Introdução

Rust é amplamente reconhecida por eliminar bugs de memória — use-after-free, buffer overflows, data races — em tempo de compilação. Porém, segurança de software vai **muito além** da segurança de memória. Aplicações Rust em produção enfrentam os mesmos desafios que qualquer outra linguagem: vulnerabilidades em dependências, ataques à cadeia de suprimentos, injeção de SQL, gerenciamento de segredos e validação inadequada de entrada.

Este artigo cobre as práticas de segurança que todo desenvolvedor Rust precisa conhecer para ir além do que o compilador garante automaticamente.

## O Problema: Falsa Sensação de Segurança

O maior risco para desenvolvedores Rust é confiar que o compilador resolve **todos** os problemas de segurança. Aqui estão vulnerabilidades que o borrow checker **NÃO** detecta:

### Não Faça Isso: Segredos Hardcoded

```rust
// ERRADO: Credenciais no código-fonte
const DATABASE_URL: &str = "postgres://admin:super_secreta_123@db.prod.exemplo.com/app";
const API_KEY: &str = "sk-live-abc123def456";

fn conectar_banco() -> Result<(), Box<dyn std::error::Error>> {
    // A senha vai parar no binário compilado e no histórico do git
    let _conn = DATABASE_URL; // simulação de conexão
    Ok(())
}
```

### Não Faça Isso: Construção de SQL com Concatenação

```rust
// ERRADO: Vulnerável a SQL Injection
fn buscar_usuario_inseguro(nome: &str) -> String {
    // Um atacante pode passar: '; DROP TABLE usuarios; --
    format!("SELECT * FROM usuarios WHERE nome = '{nome}'")
}

fn main() {
    let input_malicioso = "'; DROP TABLE usuarios; --";
    let query = buscar_usuario_inseguro(input_malicioso);
    println!("Query gerada: {query}");
    // Query gerada: SELECT * FROM usuarios WHERE nome = ''; DROP TABLE usuarios; --'
}
```

### Não Faça Isso: Input Não Validado

```rust
// ERRADO: Aceita qualquer input sem validação
fn criar_usuario(nome: &str, email: &str, idade: &str) -> String {
    let idade_num: i32 = idade.parse().unwrap_or(0);
    // Sem validação: nome pode ter 10MB, email pode não ser válido,
    // idade pode ser -1 ou 999
    format!("Usuário: {nome}, Email: {email}, Idade: {idade_num}")
}
```

## A Solução: Segurança em Camadas

### Camada 1: Auditoria de Dependências com `cargo-audit`

Instale e use `cargo-audit` para verificar se suas dependências têm vulnerabilidades conhecidas:

```bash
# Instalar
cargo install cargo-audit

# Verificar vulnerabilidades
cargo audit

# Saída típica:
# Crate:     chrono
# Version:   0.4.19
# Warning:   RUSTSEC-2020-0159
# Title:     Potential segfault in localtime_r invocations
# Solution:  Upgrade to >= 0.4.20
```

Adicione ao CI para verificação automática:

```yaml
# .github/workflows/security.yml
name: Security Audit
on:
  push:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # Toda segunda-feira às 6h

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: rustsec/audit-check@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
```

### Camada 2: Políticas de Dependência com `cargo-deny`

`cargo-deny` vai além do audit, verificando licenças, duplicações e fontes:

```bash
cargo install cargo-deny
cargo deny init  # Gera deny.toml
```

```toml
# deny.toml
[advisories]
vulnerability = "deny"
unmaintained = "warn"

[licenses]
allow = [
    "MIT",
    "Apache-2.0",
    "BSD-2-Clause",
    "BSD-3-Clause",
    "ISC",
]
unlicensed = "deny"

[bans]
multiple-versions = "warn"
deny = [
    # Crates que não queremos no projeto
    { name = "openssl", wrappers = ["openssl-sys"] },
]

[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
```

```bash
# Verificar todas as políticas
cargo deny check

# Verificar apenas licenças
cargo deny check licenses
```

### Camada 3: Gerenciamento de Segredos

```toml
# Cargo.toml
[dependencies]
dotenvy = "0.15"
secrecy = "0.10"
```

```rust
use secrecy::{ExposeSecret, SecretString};
use std::env;

struct AppConfig {
    database_url: SecretString,
    api_key: SecretString,
    porta: u16,
}

impl AppConfig {
    fn from_env() -> Result<Self, env::VarError> {
        // Carrega .env apenas em desenvolvimento
        let _ = dotenvy::dotenv();

        Ok(AppConfig {
            database_url: SecretString::from(env::var("DATABASE_URL")?),
            api_key: SecretString::from(env::var("API_KEY")?),
            porta: env::var("PORT")
                .unwrap_or_else(|_| "8080".to_string())
                .parse()
                .unwrap_or(8080),
        })
    }
}

fn conectar(config: &AppConfig) {
    // SecretString não implementa Display/Debug — impede vazamento acidental em logs
    // println!("{}", config.database_url); // NÃO COMPILA

    // Para acessar o valor, é explícito:
    let url = config.database_url.expose_secret();
    println!("Conectando ao banco...");
    // Use `url` apenas onde necessário
    let _ = url;
}

fn main() {
    let config = AppConfig::from_env().expect("Variáveis de ambiente não configuradas");
    conectar(&config);
}
```

A crate `secrecy` garante que segredos:
- Não apareçam em logs acidentalmente (sem `Display`/`Debug`)
- São apagados da memória quando descartados (implementa `Zeroize`)
- Requerem chamada explícita `.expose_secret()` para acesso

### Camada 4: Validação de Input com Tipos

```rust
use std::fmt;

/// Email validado — só pode ser criado se o formato estiver correto
#[derive(Debug, Clone)]
pub struct Email(String);

impl Email {
    pub fn new(valor: &str) -> Result<Self, ValidationError> {
        let valor = valor.trim().to_lowercase();

        if valor.is_empty() {
            return Err(ValidationError::Campo("email está vazio".into()));
        }

        // Validação básica de formato
        let partes: Vec<&str> = valor.split('@').collect();
        if partes.len() != 2 || partes[0].is_empty() || !partes[1].contains('.') {
            return Err(ValidationError::Campo(
                format!("email inválido: {valor}")
            ));
        }

        if valor.len() > 254 {
            return Err(ValidationError::Campo("email muito longo".into()));
        }

        Ok(Email(valor))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Display for Email {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Idade validada — garantida estar entre 0 e 150
#[derive(Debug, Clone, Copy)]
pub struct Idade(u8);

impl Idade {
    pub fn new(valor: u8) -> Result<Self, ValidationError> {
        if valor > 150 {
            return Err(ValidationError::Campo(
                format!("idade inválida: {valor}")
            ));
        }
        Ok(Idade(valor))
    }

    pub fn valor(&self) -> u8 {
        self.0
    }
}

/// Nome validado — não vazio, tamanho limitado, sem caracteres perigosos
#[derive(Debug, Clone)]
pub struct NomeUsuario(String);

impl NomeUsuario {
    pub fn new(valor: &str) -> Result<Self, ValidationError> {
        let valor = valor.trim();

        if valor.is_empty() {
            return Err(ValidationError::Campo("nome está vazio".into()));
        }

        if valor.len() > 100 {
            return Err(ValidationError::Campo("nome muito longo".into()));
        }

        // Rejeita caracteres perigosos
        if valor.contains(['<', '>', '\'', '"', ';', '\\']) {
            return Err(ValidationError::Campo(
                "nome contém caracteres não permitidos".into()
            ));
        }

        Ok(NomeUsuario(valor.to_string()))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

#[derive(Debug)]
pub enum ValidationError {
    Campo(String),
}

impl fmt::Display for ValidationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ValidationError::Campo(msg) => write!(f, "Erro de validação: {msg}"),
        }
    }
}

impl std::error::Error for ValidationError {}

// Agora é IMPOSSÍVEL criar um usuário com dados inválidos
struct UsuarioValidado {
    nome: NomeUsuario,
    email: Email,
    idade: Idade,
}

fn criar_usuario(
    nome: &str,
    email: &str,
    idade: u8,
) -> Result<UsuarioValidado, ValidationError> {
    Ok(UsuarioValidado {
        nome: NomeUsuario::new(nome)?,
        email: Email::new(email)?,
        idade: Idade::new(idade)?,
    })
}
```

Com essa abordagem, funções que recebem `Email`, `NomeUsuario` ou `Idade` têm a **garantia em tempo de compilação** de que os valores já foram validados.

### Camada 5: Prevenção de SQL Injection com Queries Parametrizadas

```toml
# Cargo.toml
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }
tokio = { version = "1", features = ["full"] }
```

```rust
use sqlx::PgPool;

struct Usuario {
    id: i64,
    nome: String,
    email: String,
}

// CORRETO: Query parametrizada — imune a SQL injection
async fn buscar_por_nome(pool: &PgPool, nome: &str) -> Result<Vec<Usuario>, sqlx::Error> {
    let usuarios = sqlx::query_as!(
        Usuario,
        r#"SELECT id, nome, email FROM usuarios WHERE nome = $1"#,
        nome  // Parâmetro seguro — nunca é interpolado na query
    )
    .fetch_all(pool)
    .await?;

    Ok(usuarios)
}

// CORRETO: Inserção segura com múltiplos parâmetros
async fn criar_usuario(
    pool: &PgPool,
    nome: &str,
    email: &str,
) -> Result<i64, sqlx::Error> {
    let resultado = sqlx::query_scalar!(
        r#"INSERT INTO usuarios (nome, email) VALUES ($1, $2) RETURNING id"#,
        nome,
        email
    )
    .fetch_one(pool)
    .await?;

    Ok(resultado)
}
```

O `sqlx::query_as!` verifica a query **em tempo de compilação** contra o banco de dados, garantindo que os tipos e colunas estejam corretos.

## Guia Passo a Passo: Auditoria de Segurança

### Passo 1: Configure Ferramentas

```bash
# Ferramentas essenciais de segurança
cargo install cargo-audit
cargo install cargo-deny
cargo install cargo-outdated
```

### Passo 2: Crie um Checklist de Segurança

Execute estes comandos regularmente:

```bash
# 1. Verificar vulnerabilidades conhecidas
cargo audit

# 2. Verificar licenças e políticas
cargo deny check

# 3. Verificar dependências desatualizadas
cargo outdated

# 4. Verificar código unsafe
grep -rn "unsafe" src/
```

### Passo 3: Revise Código `unsafe`

```rust
// ERRADO: unsafe desnecessário
unsafe fn somar(a: i32, b: i32) -> i32 {
    a + b // Nada aqui precisa de unsafe!
}

// CORRETO: unsafe apenas quando necessário, com justificativa documentada
/// # Safety
///
/// O ponteiro `dados` deve:
/// - Ser não-nulo e válido para leitura de `len` bytes
/// - Estar alinhado para o tipo `u8`
/// - Os dados não podem ser modificados durante a vida da slice
pub unsafe fn de_ponteiro_para_slice<'a>(dados: *const u8, len: usize) -> &'a [u8] {
    // SAFETY: O chamador garante que o ponteiro é válido conforme documentado
    std::slice::from_raw_parts(dados, len)
}
```

### Passo 4: Proteja o `.env` e Segredos

```gitignore
# .gitignore — NUNCA commite esses arquivos
.env
.env.local
.env.production
*.pem
*.key
secrets/
```

### Passo 5: Rate Limiting em APIs

```toml
# Cargo.toml
[dependencies]
tower = { version = "0.5", features = ["limit"] }
axum = "0.8"
tokio = { version = "1", features = ["full"] }
```

```rust
use axum::{routing::get, Router};
use tower::limit::RateLimitLayer;
use std::time::Duration;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/api/dados", get(handler))
        .layer(RateLimitLayer::new(100, Duration::from_secs(60))); // 100 req/min

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn handler() -> &'static str {
    "OK"
}
```

## Armadilhas Comuns

### 1. Confiar em HTTPS Sem Verificar Certificados

```rust
// ERRADO: Desabilita verificação de certificado
// Isso permite ataques man-in-the-middle
// reqwest::Client::builder()
//     .danger_accept_invalid_certs(true)
//     .build()?;

// CORRETO: Mantenha a verificação padrão
let client = reqwest::Client::new(); // Verifica certificados automaticamente
```

### 2. Logs com Informações Sensíveis

```rust
use secrecy::{ExposeSecret, SecretString};

struct LoginRequest {
    usuario: String,
    senha: SecretString,
}

// ERRADO: Logando dados sensíveis
fn autenticar_ruim(req: &LoginRequest) {
    println!("Tentativa de login: usuario={}, senha=***", req.usuario);
    // Em algum momento alguém vai "debugar" e trocar *** pela senha real
}

// CORRETO: Tipo SecretString impede exposição acidental
fn autenticar_bom(req: &LoginRequest) {
    println!("Tentativa de login: usuario={}", req.usuario);
    // req.senha não implementa Display, impossível logar acidentalmente
    let _senha = req.senha.expose_secret(); // Acesso explícito e rastreável
}
```

### 3. Deserialização Sem Limites

```rust
use serde::Deserialize;

#[derive(Deserialize)]
struct Payload {
    // ERRADO: Sem limite de tamanho — atacante pode enviar 10GB de dados
    // dados: Vec<u8>,

    // CORRETO: Valide limites após desserialização
    dados: Vec<u8>,
}

fn processar_payload(json: &str) -> Result<Payload, String> {
    let payload: Payload = serde_json::from_str(json)
        .map_err(|e| format!("JSON inválido: {e}"))?;

    // Valide o tamanho DEPOIS da desserialização
    if payload.dados.len() > 1_048_576 {
        return Err("Payload excede o limite de 1MB".into());
    }

    Ok(payload)
}
```

### 4. Timing Attacks em Comparação de Senhas

```rust
/// Comparação de tempo constante para evitar timing attacks
fn comparacao_segura(a: &[u8], b: &[u8]) -> bool {
    if a.len() != b.len() {
        return false;
    }

    let mut resultado: u8 = 0;
    for (x, y) in a.iter().zip(b.iter()) {
        resultado |= x ^ y;
    }
    resultado == 0
}
```

## Exemplo do Mundo Real: Middleware de Segurança para Axum

```rust
use axum::{
    extract::Request,
    http::{header, HeaderValue, StatusCode},
    middleware::Next,
    response::Response,
};

async fn headers_seguranca(request: Request, next: Next) -> Response {
    let mut response = next.run(request).await;

    let headers = response.headers_mut();

    // Previne XSS
    headers.insert(
        header::X_CONTENT_TYPE_OPTIONS,
        HeaderValue::from_static("nosniff"),
    );

    // Previne clickjacking
    headers.insert(
        header::X_FRAME_OPTIONS,
        HeaderValue::from_static("DENY"),
    );

    // Força HTTPS
    headers.insert(
        header::STRICT_TRANSPORT_SECURITY,
        HeaderValue::from_static("max-age=31536000; includeSubDomains"),
    );

    // Content Security Policy
    headers.insert(
        header::CONTENT_SECURITY_POLICY,
        HeaderValue::from_static("default-src 'self'"),
    );

    response
}
```

## Checklist de Segurança para Projetos Rust

1. **`cargo audit`** no CI — bloqueie merges com vulnerabilidades
2. **`cargo deny`** — controle licenças e fontes de dependências
3. **Variáveis de ambiente** — nunca hardcode segredos
4. **`secrecy`** — para valores sensíveis em memória
5. **Queries parametrizadas** — nunca concatene SQL
6. **Validação com tipos** — newtype pattern para dados validados
7. **Minimize `unsafe`** — documente cada uso com `# Safety`
8. **Headers de segurança** — CSP, HSTS, X-Frame-Options
9. **Rate limiting** — proteja APIs contra abuso
10. **Atualize dependências** — `cargo outdated` regularmente

---

## Veja Também

- [Receita: Conectar ao PostgreSQL](/receitas/conectar-postgresql/) — Exemplos de queries parametrizadas com SQLx
- [Boas Práticas de Error Handling](/artigos/boas-praticas-error-handling/) — Trate erros sem expor informações sensíveis
- [CI/CD para Projetos Rust](/artigos/ci-cd-rust/) — Automatize auditorias de segurança
- [Receita: Variáveis de Ambiente](/receitas/variaveis-ambiente/) — Como gerenciar configurações com segurança
- [Logging e Observabilidade](/artigos/logging-observabilidade/) — Logging seguro sem vazar dados sensíveis
- [Gerenciamento de Dependências](/artigos/gerenciamento-dependencias/) — Controle suas dependências com Cargo

---

Se você se interessa por segurança de memória e código seguro em outras linguagens, veja também:

- <a href="https://ziglang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'ziglang.com.br' })">Segurança em Zig: a abordagem de Zig para safety sem garbage collector</a>
- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Segurança em Go: práticas de segurança e o pacote crypto de Go</a>
