Skip to content

Security Guide

maule edited this page Aug 16, 2026 · 1 revision

Security Guide

Security best practices for building Telegram bots with TGbotPHP.

Critical: Token Management

❌ NEVER Do This

// WRONG - Token in GET parameter
$token = $_GET['token'];

// WRONG - Token hardcoded
$token = '123456789:ABCdefGHIjklmnoPQRstuvWXyz_ABCDE';

// WRONG - Token in version control
// file: bot.php (NEVER COMMIT)
define('TOKEN', 'your_token_here');

✅ DO This Instead

// CORRECT - Environment variable
$token = getenv('TELEGRAM_BOT_TOKEN');

// CORRECT - PHP 8.1+ readonly property
readonly string $botToken = getenv('TELEGRAM_BOT_TOKEN');

// CORRECT - .env file (never committed)
// .env (in .gitignore)
TELEGRAM_BOT_TOKEN=your_token_here

Setup

  1. Create .env file (add to .gitignore):
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklmnoPQRstuvWXyz_ABCDE
DEBUG_MODE=false
  1. Load in webhook:
<?php
// Load .env
$env = parse_ini_file(__DIR__ . '/.env');
$token = $env['TELEGRAM_BOT_TOKEN'];
  1. Or use environment variables directly:
<?php
$token = $_ENV['TELEGRAM_BOT_TOKEN'] ?? getenv('TELEGRAM_BOT_TOKEN');

HTTPS Enforcement

Telegram requires HTTPS for webhooks.

✅ Always Enforce HTTPS

<?php
// Check HTTPS
if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') {
    http_response_code(403);
    error_log('HTTPS required');
    exit('HTTPS required');
}

.htaccess (Apache)

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Nginx Configuration

server {
    listen 443 ssl http2;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    # ... rest of config
}

server {
    listen 80;
    return 301 https://$server_name$request_uri;
}

Input Validation

Always validate webhook data.

<?php
$updates = file_get_contents("php://input");

// Validate JSON
$decoded = json_decode($updates, true);
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    error_log('Invalid JSON: ' . json_last_error_msg());
    exit;
}

// Validate Telegram IP (optional but recommended)
$allowedIps = [
    '149.154.160.0/20',
    '91.108.4.0/22',
];

$clientIp = $_SERVER['REMOTE_ADDR'];
if (!isIpInRange($clientIp, $allowedIps)) {
    http_response_code(403);
    error_log("Request from unauthorized IP: $clientIp");
    exit;
}

$bot = new botTG(token: $token, updates: $updates);

Webhook Secret Token

Add extra security layer (if available).

Generate Secret

$secretToken = bin2hex(random_bytes(32));
// Store in .env: TELEGRAM_SECRET_TOKEN=abc123def456...

Set Webhook

curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-domain.com/webhook.php",
    "secret_token": "your_secret_token"
  }'

Validate in Code

<?php
$secretToken = getenv('TELEGRAM_SECRET_TOKEN');
$headerToken = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? null;

if ($secretToken && $headerToken !== $secretToken) {
    http_response_code(403);
    error_log('Invalid webhook secret');
    exit;
}

Security Headers

Add HTTP security headers.

<?php
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
header('Content-Security-Policy: default-src \'none\'');
header('Referrer-Policy: no-referrer');

Or in .htaccess:

<IfModule mod_headers.c>
    Header set X-Content-Type-Options "nosniff"
    Header set X-Frame-Options "DENY"
    Header set X-XSS-Protection "1; mode=block"
    Header set Strict-Transport-Security "max-age=31536000"
    Header set Content-Security-Policy "default-src 'none'"
</IfModule>

Error Handling

Never expose sensitive information.

❌ WRONG

// WRONG - Exposes path and token in errors
error_reporting(E_ALL);
ini_set('display_errors', '1');

✅ CORRECT

// Production
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/bot-errors.log');

// Development
if (getenv('DEBUG_MODE')) {
    error_reporting(E_ALL);
    ini_set('display_errors', '1');
}

File Path Security

Prevent directory traversal attacks.

❌ WRONG

// WRONG - Can access any file on system
$photo = $_GET['photo']; // "../../etc/passwd"
$bot->sendMessage($chatId, photo: $photo);

✅ CORRECT

// Define safe directory
define('PHOTOS_DIR', __DIR__ . '/photos/');

function getSafePhotoPath($filename) {
    $path = realpath(PHOTOS_DIR . basename($filename));
    
    // Verify path is within photos directory
    if (!$path || strpos($path, PHOTOS_DIR) !== 0) {
        throw new Exception('Invalid file path');
    }
    
    return $path;
}

// Usage
$photo = getSafePhotoPath($_GET['photo'] ?? 'default.png');

Rate Limiting

Prevent spam and abuse.

<?php
class RateLimiter {
    private array $limits = [];
    private int $window = 60; // 1 minute
    
    public function isAllowed($userId, $limit = 10): bool {
        $now = time();
        $key = "user_$userId";
        
        if (!isset($this->limits[$key])) {
            $this->limits[$key] = [];
        }
        
        // Remove old requests
        $this->limits[$key] = array_filter(
            $this->limits[$key],
            fn($time) => $now - $time < $this->window
        );
        
        if (count($this->limits[$key]) >= $limit) {
            return false;
        }
        
        $this->limits[$key][] = $now;
        return true;
    }
}

// Usage
$limiter = new RateLimiter();

if (!$limiter->isAllowed($bot->getChatId(), limit: 10)) {
    $bot->sendMessage(
        $bot->getChatId(),
        "Too many requests. Please wait."
    );
    exit;
}

Logging & Monitoring

Log important events safely.

<?php
class BotLogger {
    private string $logFile;
    
    public function __construct(string $logFile) {
        $this->logFile = $logFile;
    }
    
    public function log(string $event, array $data = []): void {
        $log = sprintf(
            "[%s] %s | User: %s | Chat: %s\n",
            date('Y-m-d H:i:s'),
            $event,
            $data['user_id'] ?? 'unknown',
            $data['chat_id'] ?? 'unknown'
        );
        
        error_log($log, 3, $this->logFile);
    }
}

$logger = new BotLogger('/var/log/telegram-bot.log');
$logger->log('user_message', [
    'user_id' => $bot->update?->message?->from?->id,
    'chat_id' => $bot->getChatId(),
]);

SQL Injection (if using database)

Always use prepared statements.

<?php
// ❌ WRONG
$result = $db->query("SELECT * FROM users WHERE id = " . $_GET['id']);

// ✅ CORRECT - Prepared statement
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();

XSS Prevention

Escape output if generating HTML/JSON.

<?php
$userInput = $bot->getTextMessage();

// Safe JSON output
header('Content-Type: application/json');
echo json_encode(['message' => $userInput]);

// Safe HTML output (if needed)
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

Deployment Checklist

Before going live:

  • Token in environment variable
  • HTTPS enabled and enforced
  • Webhook secret token configured
  • Input validation implemented
  • Security headers added
  • Error logging configured
  • .env file in .gitignore
  • Debug mode disabled in production
  • Rate limiting implemented
  • File upload paths validated
  • SSL certificate valid and up-to-date
  • Regular backups enabled

Security Resources


Report security issues privately. See SECURITY.md for details.

Clone this wiki locally