Files
Emanuel Almeida 6b3a6f2698 feat: refactor 30+ skills to Anthropic progressive disclosure pattern
- All SKILL.md files now <500 lines (avg reduction 69%)
- Detailed content extracted to references/ subdirectories
- Frontmatter standardised: only name + description (Anthropic standard)
- New skills: brand-guidelines, spec-coauthor, report-templates, skill-creator
- Design skills: anti-slop guidelines, premium-proposals reference
- Removed non-standard frontmatter fields (triggers, version, author, category)

Plugins affected: infraestrutura, marketing, dev-tools, crm-ops, gestao,
core-tools, negocio, perfex-dev, wordpress, design-media

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:05:03 +00:00

10 KiB

name, description
name description
perfex-security Segurança para módulos Perfex CRM — validação de input, CSRF, prevenção XSS e protecção contra directory traversal. Baseado apenas na documentação oficial.

/perfex-security - Segurança Módulos Perfex CRM

Práticas de segurança para módulos. Zero assumptions, zero hallucinations - apenas documentação oficial.


Documentação Base


1. Prevenção de Acesso Directo

OBRIGATÓRIO em todos os ficheiros PHP:

<?php

defined('BASEPATH') or exit('No direct script access allowed');

// Resto do código

Isto previne execução directa via URL.


2. Prevenção de Directory Listing

Incluir index.html vazio em todas as pastas:

modules/meu_modulo/
├── index.html          ← OBRIGATÓRIO
├── controllers/
│   └── index.html      ← OBRIGATÓRIO
├── models/
│   └── index.html      ← OBRIGATÓRIO
├── views/
│   └── index.html      ← OBRIGATÓRIO
└── libraries/
    └── index.html      ← OBRIGATÓRIO

Conteúdo do index.html:

<!DOCTYPE html>
<html>
<head>
    <title>403 Forbidden</title>
</head>
<body>
    <p>Directory access is forbidden.</p>
</body>
</html>

3. Input Validation (CRÍTICO)

Usar Input Class do CodeIgniter

// CORRECTO - Input class escapa automaticamente
$name = $this->input->post('name');
$id = $this->input->get('id');
$data = $this->input->post();  // Array de todos os POST

// ERRADO - Nunca usar $_POST/$_GET directamente
$name = $_POST['name'];  // VULNERÁVEL!

Sanitização Adicional

// Inteiros
$id = (int) $this->input->post('id');

// Escape para output HTML
$name = html_escape($this->input->post('name'));

// Strip tags
$clean = strip_tags($this->input->post('content'));

// XSS clean (built-in)
$safe = $this->security->xss_clean($this->input->post('data'));

4. CSRF Protection

O Perfex CRM tem CSRF activado por defeito.

Em Formulários (OBRIGATÓRIO)

<?php echo form_open(admin_url('meu_modulo/save')); ?>
    <!-- form_open() gera token CSRF automaticamente -->
    <input type="text" name="name">
    <button type="submit">Guardar</button>
<?php echo form_close(); ?>

NUNCA usar HTML <form> directo:

<!-- ERRADO - Sem CSRF token -->
<form action="..." method="post">

Em AJAX (jQuery)

jQuery do Perfex inclui CSRF automaticamente. Para outras bibliotecas:

// Obter token CSRF
var csrfName = '<?php echo $this->security->get_csrf_token_name(); ?>';
var csrfHash = '<?php echo $this->security->get_csrf_hash(); ?>';

// Incluir em requests
$.ajax({
    url: admin_url + 'meu_modulo/ajax_save',
    type: 'POST',
    data: {
        [csrfName]: csrfHash,
        name: $('#name').val()
    }
});

Excluir URLs de CSRF (v2.9.0+)

Para webhooks que não podem incluir CSRF:

// modules/meu_modulo/config/csrf_exclude_uris.php

<?php

defined('BASEPATH') or exit('No direct script access allowed');

return [
    'meu_modulo/webhook',
    'meu_modulo/api/.*',  // Regex suportado
];

5. SQL Injection Prevention

Usar Query Builder (SEMPRE)

// CORRECTO - Query Builder escapa parâmetros
$this->db->where('id', $id);
$this->db->where('status', $status);
$result = $this->db->get(db_prefix() . 'meu_modulo')->result();

// CORRECTO - Binding de parâmetros
$sql = "SELECT * FROM " . db_prefix() . "meu_modulo WHERE id = ? AND status = ?";
$result = $this->db->query($sql, [$id, $status])->result();

NUNCA Concatenar Input

// ERRADO - SQL Injection vulnerável!
$id = $_GET['id'];
$sql = "SELECT * FROM tblmeu_modulo WHERE id = " . $id;

// ERRADO - Mesmo com input class
$id = $this->input->get('id');
$sql = "SELECT * FROM tblmeu_modulo WHERE id = " . $id;  // VULNERÁVEL!

6. XSS Prevention

Em Views (SEMPRE)

<!-- CORRECTO - Escape HTML -->
<td><?php echo html_escape($item->name); ?></td>
<input value="<?php echo html_escape($item->value); ?>">

<!-- ERRADO - XSS vulnerável -->
<td><?php echo $item->name; ?></td>

Função html_escape()

// Texto simples
echo html_escape($string);

// Para atributos HTML
echo html_escape($string, true);

7. File Upload Security

public function upload()
{
    // Configurar upload
    $config['upload_path']   = './uploads/meu_modulo/';
    $config['allowed_types'] = 'pdf|doc|docx|xls|xlsx';  // NUNCA permitir php!
    $config['max_size']      = 2048;  // KB
    $config['encrypt_name']  = true;  // Nome aleatório

    $this->load->library('upload', $config);

    if (!$this->upload->do_upload('ficheiro')) {
        // Erro
        $error = $this->upload->display_errors();
        set_alert('danger', $error);
    } else {
        // Sucesso
        $data = $this->upload->data();
        $filename = $data['file_name'];
    }
}

Tipos Permitidos (Seguros)

// Documentos
'pdf|doc|docx|xls|xlsx|ppt|pptx|txt|csv'

// Imagens
'gif|jpg|jpeg|png|webp'

// NUNCA permitir
'php|php3|php4|php5|phtml|exe|sh|bat|js|html'

8. Verificação de Ownership

Sempre verificar se o utilizador tem acesso ao recurso:

public function view($id)
{
    $item = $this->meu_modulo_model->get($id);

    // Verificar se existe
    if (!$item) {
        show_404();
    }

    // Se for área cliente, verificar ownership
    if (!is_staff_logged_in()) {
        if ($item->client_id != get_client_user_id()) {
            show_404();  // Não revelar que existe
        }
    }

    // Ou para staff com view_own apenas
    if (!is_admin() && !staff_can('view_all', 'meu_modulo')) {
        if ($item->created_by != get_staff_user_id()) {
            access_denied('meu_modulo');
        }
    }

    // ... mostrar dados
}

9. Session Security

// Regenerar session após login (já feito pelo Perfex)
$this->session->sess_regenerate();

// Destruir session
$this->session->sess_destroy();

// Guardar dados em session
$this->session->set_userdata('key', 'value');

// Ler dados da session
$value = $this->session->userdata('key');

10. Password Handling

NUNCA guardar passwords em plain text.

// Hash password (se necessário no módulo)
$hashed = password_hash($password, PASSWORD_DEFAULT);

// Verificar password
if (password_verify($input_password, $stored_hash)) {
    // Válido
}

Checklist de Segurança

Ficheiros

[ ] defined('BASEPATH') em todos os PHP
[ ] index.html em todas as pastas
[ ] Nenhum ficheiro executável em uploads

Input/Output

[ ] $this->input->post/get para todos os inputs
[ ] html_escape() em todos os outputs
[ ] Query Builder para todas as queries
[ ] form_open() para todos os formulários

Acesso

[ ] staff_can() antes de operações
[ ] Ownership verificado para dados de cliente
[ ] CSRF token em formulários e AJAX
[ ] Tipos de ficheiro restritos em uploads

Exemplo Seguro Completo

Controller

<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Meu_modulo extends AdminController
{
    public function __construct()
    {
        parent::__construct();
        $this->load->model('meu_modulo/meu_modulo_model');
    }

    public function save()
    {
        // 1. Verificar permissão
        if (!staff_can('create', 'meu_modulo')) {
            access_denied('meu_modulo');
        }

        // 2. Verificar se é POST (CSRF verificado automaticamente)
        if (!$this->input->post()) {
            redirect(admin_url('meu_modulo'));
        }

        // 3. Validar e sanitizar input
        $data = [
            'name'        => trim($this->input->post('name')),
            'description' => $this->input->post('description'),
            'amount'      => (float) $this->input->post('amount'),
            'client_id'   => (int) $this->input->post('client_id'),
        ];

        // 4. Validação de negócio
        if (empty($data['name'])) {
            set_alert('danger', _l('field_required', _l('name')));
            redirect(admin_url('meu_modulo/create'));
        }

        // 5. Guardar via model (usa Query Builder)
        $id = $this->meu_modulo_model->add($data);

        if ($id) {
            set_alert('success', _l('added_successfully'));
        } else {
            set_alert('danger', _l('error_occurred'));
        }

        redirect(admin_url('meu_modulo'));
    }
}

View

<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
<?php init_head(); ?>

<div id="wrapper">
    <div class="content">
        <div class="panel_s">
            <div class="panel-body">

                <?php echo form_open(admin_url('meu_modulo/save')); ?>

                    <div class="form-group">
                        <label><?php echo _l('name'); ?></label>
                        <input type="text"
                               name="name"
                               class="form-control"
                               value="<?php echo isset($item) ? html_escape($item->name) : ''; ?>"
                               required>
                    </div>

                    <button type="submit" class="btn btn-primary">
                        <?php echo _l('save'); ?>
                    </button>

                <?php echo form_close(); ?>

            </div>
        </div>
    </div>
</div>

<?php init_tail(); ?>
</body>
</html>

Anti-Patterns (NUNCA FAZER)

Anti-Pattern Risco Alternativa
$_POST/$_GET directo XSS, Injection $this->input->post/get
Echo sem escape XSS html_escape()
SQL concatenado SQL Injection Query Builder
<form> HTML CSRF bypass form_open()
Upload sem validação RCE allowed_types restrito
Sem verificar ownership Data breach Validar sempre

Versão: 1.0.0 | Autor: Descomplicar® Fonte: help.perfexcrm.com/module-security