Skip to content

PT-BR Translate#136

Open
brunors-96 wants to merge 17 commits into
HercegDoo:mainfrom
brunors-96:main
Open

PT-BR Translate#136
brunors-96 wants to merge 17 commits into
HercegDoo:mainfrom
brunors-96:main

Conversation

@brunors-96
Copy link
Copy Markdown

Translate labels and messages to PT-BR

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds PT-BR localization support while also introducing new security-related utilities and behavior changes around input validation, XSS handling, and rate limiting.

Changes:

  • Add PT-BR label/message localization files and extend en_US labels with new keys.
  • Rename the mail action to GenerateEmailAction and update the frontend request target accordingly.
  • Introduce XSS, prompt-injection, and rate-limiting utilities and apply them across actions and request parsing.

Reviewed changes

Copilot reviewed 18 out of 21 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
vendor/composer/autoload_static.php Update classmap entry for renamed mail action.
vendor/composer/autoload_classmap.php Update classmap entry for renamed mail action.
src/localization/messages/pt_BR.inc Add PT-BR translated messages.
src/localization/labels/pt_BR.inc Add PT-BR translated labels (incl. new security/rate-limit strings).
src/localization/labels/en_US.inc Add new English labels for malicious-content + rate-limit messages.
src/Utilities/XSSProtection.php Add centralized escaping helpers.
src/Utilities/RateLimiter.php Add rate limiting helper and HTTP header generation.
src/Utilities/PromptInjectionProtection.php Add prompt-injection detection/sanitization helpers.
src/Utilities/TemplateObjectFiller.php Harden cookie-based textarea height; escape predefined instruction title/id.
src/Tasks/SettingsTask.php Escape dropdown labels/values when building HTML.
src/Actions/Settings/SaveInstruction.php Add prompt-injection validation, XSS escaping, and rate limiting to instruction saves.
src/Actions/Settings/DeleteInstruction.php Escape instruction id before processing.
src/Actions/Settings/AddInstruction.php Escape predefined instruction data before rendering edit form.
src/Actions/Mail/GenerateEmailAction.php Rename action; add rate limiting; change JSON response escaping; change instruction input method.
src/Actions/AbstractAction.php Change validation-error handling to JSON response.
src/AIEmailService/Request.php Add strict prompt-injection validation + size checks to all string inputs; add postInstruction().
src/AIEmailService/Providers/OpenAI.php Enable SSL verification; change token parameter name in request payload.
assets/src/utils.js Change HTML handling in mail formatting to avoid XSS.
assets/src/compose/commands/sendPostRequest.js Update endpoint slug to new action class name.
assets/dist/compose.bundle.js Rebuilt bundle reflecting JS changes.
README.md Add note about creativity values for specific models.
Comments suppressed due to low confidence (2)

src/Actions/Mail/GenerateEmailAction.php:81

  • respond is being HTML-escaped via XSSProtection::escapeJson(), which will change the generated email text (e.g., &&) and can surface as literal entities in the plaintext editor. Since this is already a JSON response, prefer returning the raw string via json_encode() (your JSON_HEX_* flags already protect against embedding JSON inside HTML) and do HTML escaping only when/where the client inserts text into the DOM.
    src/AIEmailService/Providers/OpenAI.php:132
  • This provider posts to /v1/chat/completions but switches the parameter from max_tokens to max_completion_tokens. That key is not used by the Chat Completions API (and will also break the existing tests/AIEmailService/Providers/OpenAITest.php::testSendRequestPostMethod, which asserts max_tokens). Consider keeping max_tokens for this endpoint (or, if migrating APIs/models, also update the endpoint + tests accordingly).
            $respond = $curl->post($this->apiUrl, [
                'model' => $this->model,
                'messages' => [
                    ['role' => 'system', 'content' => 'You are a helpful personal assistant.'],
                    ['role' => 'user', 'content' => $prompt],
                ],
                'max_completion_tokens' => $this->maxTokens,
                'temperature' => $this->creativity,
                'n' => 1,
                'stream' => false,
            ]);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +31 to +35
/**
* Armazenamento de tentativas (em produção, usar Redis/Memcached)
*/
private static array $attempts = [];
private static array $blocks = [];
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rate limiter state is kept in static in-memory arrays ($attempts/$blocks). In typical PHP deployments this is per-worker (not shared across workers/hosts), resets on worker restart, and can grow unbounded with new identifiers (memory risk). If this is intended beyond a dev-only safeguard, consider persisting counts/blocks in a shared store (Redis/Memcached) or at least expiring/pruning identifiers aggressively to cap memory usage.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +52
// Retornar erros como JSON sanitizado em vez de mostrar mensagens
header('Content-Type: application/json');
$errorMessages = XSSProtection::escapeArray($this->getErrors());

echo json_encode([
'status' => 'error',
'respond' => implode(', ', $errorMessages)
], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
exit;
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestHandler() now always returns a JSON payload when validation fails. This breaks actions like SaveInstruction that rely on Roundcube iframe responses (output->command(...) + send('iframe')) during validation errors (e.g., empty title/text), because those queued commands/messages will never be sent. Consider keeping the previous iframe-based error handling for non-AJAX actions, or making the error response strategy overridable per action (e.g., a protected method/flag that selects JSON vs iframe).

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +27
* Sanitiza string para JSON output
*/
public static function escapeJson(string $input): string
{
return htmlspecialchars($input, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

/**
* Sanitiza array de strings para JSON output
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escapeJson() currently uses htmlspecialchars(). HTML-escaping is not appropriate for JSON values and will mutate the payload (e.g., turning & into &), which then leaks into plaintext editors or stored data. For JSON responses, rely on json_encode() (optionally with JSON_HEX_* flags) and do context-appropriate escaping at the sink (HTML/attribute/JS), rather than HTML-escaping the JSON string itself.

Suggested change
* Sanitiza string para JSON output
*/
public static function escapeJson(string $input): string
{
return htmlspecialchars($input, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
/**
* Sanitiza array de strings para JSON output
* Prepara string para uso em JSON sem aplicar escaping de HTML.
* A serialização/escaping JSON deve ser feita posteriormente com json_encode().
*/
public static function escapeJson(string $input): string
{
return $input;
}
/**
* Prepara array de strings para JSON output sem mutar os valores.

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +26
// Sanitizar dados para prevenir XSS
$title = XSSProtection::escape($predefinedInstruction['title'] ?? '');
$content = XSSProtection::escape($predefinedInstruction['message'] ?? '');
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$title/$content are being HTML-escaped before passing into rcube_output::get_edit_field(). Roundcube form helpers typically escape values when rendering inputs/textarea, so pre-escaping here can lead to double-encoded text shown to the user (e.g., & displayed as &). Prefer keeping the stored values raw and letting the form renderer escape once at output time.

Suggested change
// Sanitizar dados para prevenir XSS
$title = XSSProtection::escape($predefinedInstruction['title'] ?? '');
$content = XSSProtection::escape($predefinedInstruction['message'] ?? '');
$title = $predefinedInstruction['title'] ?? '';
$content = $predefinedInstruction['message'] ?? '';

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +7
use HercegDoo\AIComposePlugin\Utilities\TranslationTrait;
use HercegDoo\AIComposePlugin\Utilities\XSSProtection;

Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use imports for TranslationTrait/XSSProtection are redundant here because the file is already in HercegDoo\AIComposePlugin\Utilities and the class references TranslationTrait via an in-class trait use. Removing the unnecessary imports avoids unused-import noise in static analysis.

Suggested change
use HercegDoo\AIComposePlugin\Utilities\TranslationTrait;
use HercegDoo\AIComposePlugin\Utilities\XSSProtection;

Copilot uses AI. Check for mistakes.

$labels['ai_insert_email'] = 'Inserir e-mail';
$labels['ai_generate_email'] = 'Gerar e-mail';
$labels['ai_generate_again'] = 'Gerar novamente';
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ai_generate_again is defined twice in this file (here and again at line 150). The later assignment overwrites the first one, which can make translations inconsistent/hard to maintain. Remove one of the duplicates and keep a single canonical value.

Suggested change
$labels['ai_generate_again'] = 'Gerar novamente';

Copilot uses AI. Check for mistakes.
Comment thread README.md
- Length: How long the generated email should be (short, medium, or long).

- Creativity: Adjust the level of creativity in the email (low, medium, or high).
**Note**: When using specific chat models, such as gpt-5.2-chat-latest, you must adjust the creativity values in the file src/AIEmailService/Providers/OpenAI.php, within the creativityMap. This model only accepts creativity/temperature values greater than 1.
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR title/description indicate this is only a PT-BR translation, but the diff also includes substantial behavioral/security changes (rate limiting, prompt injection filtering, XSS utilities, JSON response changes, OpenAI request changes, action rename). Please update the PR description to reflect the full scope, or split the non-translation changes into a separate PR to keep review risk manageable.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +40
// Sanitização XSS adicional
$name = XSSProtection::escape($name);
$text = XSSProtection::escape($text);
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Escaping $name/$text in validate() means you end up persisting HTML-entity-encoded values (and potentially double-encoding on edit/save). This will show up to users as literal entities (e.g., &) in the settings UI and in generated dropdowns. Prefer storing the raw strings in user prefs and only escaping at render time (e.g., when injecting into HTML/attributes).

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +69
// VALIDAÇÃO OBRIGATÓRIA CONTRA PROMPT INJECTION
$validation = PromptInjectionProtection::validateAndSanitize($data, true);
if (!$validation['valid']) {
if ($validation['blocked']) {
throw new \InvalidArgumentException('Malicious content detected in input');
}
// Se não bloqueado mas com warnings, usar versão sanitizada
$data = $validation['sanitized'];
}

// Limitar tamanho da entrada para prevenir DoS
if (strlen($data) > 10000) {
throw new \InvalidArgumentException('Input too long');
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request::input() throws InvalidArgumentException for “malicious content” / oversized input. Callers like GenerateEmailAction::validate() use Request::postString() without a try/catch, and AbstractAction::requestHandler() doesn’t catch exceptions either, so a single flagged word will produce a fatal/unhandled error instead of a controlled validation response. Consider returning a validation result (or null) and letting the action set a translated error, or catch exceptions in requestHandler() and convert them to the same error JSON/iframe response used for normal validation failures.

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +100
return [
'allowed' => true,
'reason' => 'ok',
'retry_after' => 0,
'remaining' => $limits['requests'] - $currentAttempts - 1,
'limit' => $limits['requests'],
'reset_time' => $windowStart + $limits['window']
];
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the allowed case, reset_time is computed as $windowStart + $limits['window'], but $windowStart is ($now - window), so this always evaluates to now. That makes X-RateLimit-Reset effectively useless/incorrect. Consider computing reset based on the oldest timestamp in the current window (e.g., min(attempts)+window) or simply returning now + window to indicate when the window will fully roll over.

Copilot uses AI. Check for mistakes.
@maniaba maniaba closed this Apr 29, 2026
@maniaba maniaba reopened this Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants