Dolibarr/dolibarr · warning

Access refused to by SQL or Script injection protection in…

Error message

Access refused to ${ip} by SQL or Script injection protection in main.inc.php:analyseVarsForSqlAndScriptsInjection type=${type} Try to go back, fix data of your form and resubmit it. You can contact also your technical support.

What it means

Dolibarr's built-in WAF (waf.inc.php, analyseVarsForSqlAndScriptsInjection) scanned GET/POST/COOKIE parameters and found content matching SQL-injection or script-injection testSqlAndScriptInject patterns. It responds HTTP 403 with this message instead of executing the request. The detailed parameter name/value is appended to errormessage2 (and to the audit/security log) but deliberately hidden from the client to avoid text injection.

Solutions

  1. Identify the offending parameter from the server error log/audit table (paramkey/paramvalue in errormessage2) and remove/escape the suspicious content in the form data.
  2. If the field legitimately needs HTML, set the module's permission so Dolibarr sanitizes it properly, or allow the page/variable by defining the appropriate exception constant (e.g. MAIN_SECURITY_ALLOWED_PATTERN / use dol_htmlcleanlastbr and GETPOST with proper type) in code rather than weakening the WAF.
  3. Update Dolibarr: older versions had over-broad regexes that false-positived on innocuous input like 'select' in normal text.
  4. Check for a real attack: if you did not submit this data, investigate the client/IP — the block is working as intended.
  5. As a last resort for trusted internal pages only, guard the entry file with define('NOSCANPOSTFORINJECTION', 1) before including main.inc.php — never globally.

Example fix

// before (page killed by WAF)
$_POST['note'] = $_POST['note']; // raw HTML/SQL-ish content passed through
// after (sanitize before it reaches WAF-sensitive storage)
$note = GETPOST('note', 'restricthtml');
$object->note_public = dol_htmlcleanlastbr($note);
Defensive patterns

Strategy: validation

Validate before calling

// client-side: strip content the WAF considers injection before sending
function safeForDolibarr(string $value): string {
    if (preg_match('/(<\s*script|union\s+select|--\s|\/\*|on\w+\s*=)/i', $value)) {
        throw new InvalidArgumentException('Payload would trip Dolibarr WAF; encode or sanitize first');
    }
    return $value;
}

Type guard

function looksLikeInjection(string $v): bool {
    return (bool) preg_match('/(<\s*script|javascript:|union\s+select|base64_decode|on(?:load|error)\s*=)/i', $v);
}

Try / catch

try {
    $resp = $client->post($url, ['form_params' => $data]);
} catch (ClientException $e) {
    if ($e->getResponse()->getStatusCode() === 403
        && str_contains((string) $e->getResponse()->getBody(), 'injection protection')) {
        // sanitize/encode the offending field and retry once
    }
}

Prevention

When it happens

Trigger: Any request whose variable content contains patterns like UNION SELECT, <script>, onload=, base64-encoded script tags, or SQL comments, submitted via GET/POST/COOKIE on any Dolibarr page, when the value fails testSqlAndScriptInject() for the given variable type.

Common situations: A legitimate form field accepting rich text/HTML (notes, descriptions) tripping the filter; security scanners or penetration tests; users pasting code snippets into text fields; integrating an API client that sends raw SQL-looking strings or HTML payloads.

Understand the failure class

Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.

Related errors


AI-assisted analysis of Dolibarr/dolibarr@598aa4bdad (2026-09-14). Data as JSON: /api/errors/befde4fb559eb0a3. Report an issue: GitHub.

Appendix: source

Thrown at htdocs/waf.inc.php:275

{
	if (is_array($var)) {
		foreach ($var as $key => $value) {	// Warning, $key may also be used for attacks
			// Exclude check for some variable keys
			if ($type === 0 && defined('NOSCANPOSTFORINJECTION') && is_array(constant('NOSCANPOSTFORINJECTION')) && in_array($key, (array) constant('NOSCANPOSTFORINJECTION'))) {
				continue;
			}

			// Test on both the key (we force type to 1 for test on key, we must accept key like "delete=1" blocked with type 3) and the value
			if (analyseVarsForSqlAndScriptsInjection($key, 1, $stopcode) && analyseVarsForSqlAndScriptsInjection($value, $type, $stopcode)) {
				//$var[$key] = $value;	// This is useless
			} else {
				http_response_code(403);

				// Get remote IP: PS: We do not use getUserRemoteIP(), function is not yet loaded and we need a value that can't be spoofed
				$ip = (empty($_SERVER['REMOTE_ADDR']) ? 'unknown' : $_SERVER['REMOTE_ADDR']);

				if ($stopcode) {
					$errormessage = 'Access refused to '.htmlentities($ip, ENT_COMPAT, 'UTF-8').' by SQL or Script injection protection in main.inc.php:analyseVarsForSqlAndScriptsInjection type='.htmlentities((string) $type, ENT_COMPAT, 'UTF-8');
					//$errormessage .= ' paramkey='.htmlentities($key, ENT_COMPAT, 'UTF-8');	// Disabled to avoid text injection

					$errormessage2 = 'page='.htmlentities((empty($_SERVER["REQUEST_URI"]) ? '' : $_SERVER["REQUEST_URI"]), ENT_COMPAT, 'UTF-8');
					$errormessage2 .= ' paramtype='.htmlentities((string) $type, ENT_COMPAT, 'UTF-8');
					$errormessage2 .= ' paramkey='.htmlentities($key, ENT_COMPAT, 'UTF-8');
					$errormessage2 .= ' paramvalue='.htmlentities($value, ENT_COMPAT, 'UTF-8');

					print $errormessage;
					print "<br>\n";
					print 'Try to go back, fix data of your form and resubmit it. You can contact also your technical support.';

					print "\n".'<!--'."\n";
					print $errormessage2;
					print "\n".'-->';

					// Add entry into the PHP server error log
					if (function_exists('error_log')) {
						error_log($errormessage.' '.substr($errormessage2, 2000));

View on GitHub (pinned to 598aa4bdad)