Dolibarr/dolibarr · error

Bad value for parameter modulepart

Error message

Bad value for parameter modulepart

What it means

After sanitizing the path (removing '..' traversal), document.php requires modulepart to be non-empty before calling dol_check_secure_access_document(); otherwise accessforbidden() is raised. modulepart is mandatory here because it selects the security rules and directory mapping used to authorize the file read.

Solutions

  1. Always pass a valid modulepart in the URL/form (e.g. facture, produit, ecm, medias)
  2. Regenerate links with Dolibarr helpers that include modulepart automatically
  3. Check form/POST handling preserves the modulepart hidden field
  4. Confirm no upstream code unsets or empties $modulepart before the check

Example fix

// before
header('Location: document.php?original_file='.$relative);
// after
header('Location: document.php?modulepart=medias&original_file='.$relative);
Defensive patterns

Strategy: validation

Validate before calling

if (empty($modulepart) || !in_array($modulepart, $allowedModuleParts, true)) {
  http_response_code(400); exit('valid modulepart required');
}

Prevention

When it happens

Trigger: A request reaching this later check with modulepart still empty — e.g. document.php?original_file=... with no modulepart and no hashp path taken, or modulepart stripped by intermediate processing.

Common situations: Custom integrations building download URLs missing modulepart; POST forms that dropped the hidden modulepart field; URLs manipulated so modulepart ends up empty.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at htdocs/document.php:285

	$type = GETPOST('type', 'alpha');
} else {
	$type = dol_mimetype($original_file);
}
// Security: Force to octet-stream if file is a dangerous file. For example when it is a .noexe file
// We do not force if file is a javascript to be able to get js from website module with <script src="
// Note: Force whatever is $modulepart seems ok.
if (!in_array($type, array('text/x-javascript')) && !dolIsAllowedForPreview($original_file)) {
	$type = 'application/octet-stream';
}

// Security: Delete string ../ or ..\ into $original_file
$original_file = preg_replace('/\.\.+/', '..', $original_file);	// Replace '... or more' with '..'
$original_file = str_replace('../', '/', $original_file);
$original_file = str_replace('..\\', '/', $original_file);

// Security check
if (empty($modulepart)) {
	accessforbidden('Bad value for parameter modulepart');
}

// Check security and set return info with full path of file
$check_access = dol_check_secure_access_document($modulepart, $original_file, (int) $entity, $user, '', 'read');
$accessallowed              = $check_access['accessallowed'];
$sqlprotectagainstexternals = $check_access['sqlprotectagainstexternals'];
$fullpath_original_file     = $check_access['original_file']; // $fullpath_original_file is now a full path name
//var_dump($modulepart.' '.$entity.' '.$fullpath_original_file.' '.$original_file.' '.$accessallowed);exit;

if (!empty($hashp) && $hashp != 'shared') {
	$accessallowed = 1; // When using hashp, link is public so we force $accessallowed
	$sqlprotectagainstexternals = '';
} else {
	// Basic protection (against external users only)
	if ($user->socid > 0) {
		if ($sqlprotectagainstexternals) {
			$resql = $db->query($sqlprotectagainstexternals);
			if ($resql) {

View on GitHub (pinned to 598aa4bdad)