AtoM Heratio — Shell Execution Policy¶
Version: 1.0 Date: 2026-02-28 Author: The Archive and Heritage Group (Pty) Ltd
1. Overview¶
Shell command execution is a high-risk operation. Improperly escaped arguments can lead to command injection, allowing attackers to execute arbitrary system commands.
AtoM Heratio uses ShellCommandService as the centralized, safe interface for building shell commands. All code that invokes exec(), shell_exec(), system(), or passthru() MUST escape external input.
2. Service API¶
ShellCommandService (AtomFramework\Services\ShellCommandService)¶
| Method | Description |
|---|---|
buildMysqldumpCommand(...) |
Build mysqldump command with escapeshellarg() on all credentials |
buildMysqlRestoreCommand(...) |
Build mysql restore (supports gzipped input) |
execInDir($dir, $cmd, &$output, &$code) |
Validate directory with realpath(), then exec |
isAllowedService($name) |
Check service name against allowlist |
buildSystemctlCommand($action, $service) |
Build systemctl command (action + service allowlist) |
escapePostScript($input) |
Strip PostScript injection characters ( ) \ |
validateCommand($command) |
Reject shell metacharacters in command names |
buildTarCommand(...) |
Build tar with escaped paths, excludes, includes |
buildZipCommand(...) |
Build zip with escaped paths |
3. Rules¶
3.1 Mandatory Escaping¶
Every external value passed to a shell command MUST use escapeshellarg():
// CORRECT
exec('ls ' . escapeshellarg($userPath));
exec('cd ' . escapeshellarg($dir) . ' && git pull origin main 2>&1');
// WRONG — command injection via $userPath
exec("ls {$userPath}");
exec("cd {$dir} && git pull origin main 2>&1");
3.2 Never Use addslashes() for Shell Contexts¶
addslashes() is for SQL/string contexts only. It does NOT protect against shell injection:
// WRONG — addslashes does not escape shell metacharacters
$cmd = "mysqldump -p'" . addslashes($password) . "' " . addslashes($database);
// CORRECT — use ShellCommandService
$cmd = ShellCommandService::buildMysqldumpCommand($host, $port, $user, $password, $db, $outFile);
3.3 Service Name Allowlist¶
System service operations (restart, stop, status) MUST validate the service name:
// CORRECT — validates against allowlist
if (ShellCommandService::isAllowedService($serviceName)) {
$cmd = ShellCommandService::buildSystemctlCommand('restart', $serviceName);
exec($cmd);
}
// WRONG — attacker could inject: "nginx; rm -rf /"
exec("systemctl restart {$serviceName}");
3.4 Directory Validation¶
Before cd-ing into a directory, validate with realpath():
// CORRECT — validates directory exists and resolves symlinks
ShellCommandService::execInDir($directory, 'git status', $output, $code);
// WRONG — $directory could contain "; malicious_command"
exec("cd {$directory} && git status");
4. Fixed Vulnerabilities (Issue #197)¶
4.1 BackupService — Database Credentials (CRITICAL)¶
- File:
atom-framework/src/Services/BackupService.php - Problem:
addslashes()used for shell-context escaping of database host, port, user, password - Fix: Replaced with
ShellCommandService::buildMysqldumpCommand()andbuildMysqlRestoreCommand()
4.2 ServiceManager — Unescaped Paths (CRITICAL)¶
- File:
atom-framework/packaging/wizard/lib/ServiceManager.php - Problem:
$atomPathused directly inexec()without escaping; service name not validated - Fix: Added
realpath()validation,escapeshellarg(), and service name allowlist
4.3 PluginFetcher — Unescaped Paths (HIGH)¶
- File:
atom-framework/src/Extensions/PluginFetcher.php - Problem:
$sourcePath,$targetPath,$repoUrl,$tempPathunescaped inexec()calls - Fix: All paths wrapped in
escapeshellarg()
4.4 ExtensionCommand — Unescaped Paths (HIGH)¶
- File:
atom-framework/src/Console/ExtensionCommand.php - Problem:
$ahgPluginsPathand$repoPathunescaped inexec()calls - Fix: All paths wrapped in
escapeshellarg()
5. Known Issues in Locked Plugins¶
The following locked plugins contain shell execution without adequate escaping. These cannot be modified per project policy and require remediation in future releases.
5.1 ahgBackupPlugin¶
- Risk: Medium — backup/restore commands use paths that could be manipulated if settings are compromised
- Location: Plugin action classes
- Mitigation: Settings stored in DB, only admin-accessible
5.2 ahgSecurityClearancePlugin¶
- Risk: Low — limited shell usage
- Mitigation: Admin-only functionality
5.3 ahgPreservationPlugin (if locked)¶
- Risk: Medium — calls external tools (siegfried, clamdscan, tesseract)
- Location: Service classes
- Mitigation: Input paths are system-generated, not user-supplied
6. Audit Checklist¶
When reviewing code that uses exec(), shell_exec(), system(), or passthru():
- All variables in the command string use
escapeshellarg()or come from a trusted source (hardcoded constant) - No use of
addslashes()in shell context - Directory paths validated with
realpath()before use - Service names validated against an allowlist
- Command output is not directly reflected to users without escaping
- Error output is logged, not displayed
- If building complex commands, use
ShellCommandServicehelpers
Quick Grep Audit¶
# Find all exec() calls
grep -rn "exec(" atom-framework/src/ --include="*.php" | grep -v "PDO\|pdo\|->exec"
# Find any remaining addslashes in shell contexts
grep -rn "addslashes" atom-framework/src/ --include="*.php"
# Find unescaped variable interpolation in exec calls
grep -rn 'exec("' atom-framework/src/ --include="*.php"
grep -rn "exec('" atom-framework/src/ --include="*.php" | grep -v escapeshellarg