analyzeCommandSecurity function
Analyze a command for security risks.
Implementation
CommandSecurityAnalysis analyzeCommandSecurity(String input) {
final tokens = tokenizeBash(input);
final cmdList = parseCommand(input);
final allCmds = cmdList.allCommands;
var hasCommandSubstitution = false;
var hasVariableExpansion = false;
var hasGlobbing = false;
var hasRedirection = false;
var hasPiping = false;
var hasBackgroundExec = false;
var hasSubshell = false;
var hasEval = false;
var hasExec = false;
var hasSudo = false;
var hasChown = false;
var hasRemove = false;
var hasNetworkAccess = false;
var hasDiskWrite = false;
final writtenPaths = <String>[];
final readPaths = <String>[];
final executables = <String>[];
// Token-level analysis.
for (final t in tokens) {
switch (t.type) {
case BashTokenType.subshell:
hasCommandSubstitution = true;
hasSubshell = true;
break;
case BashTokenType.backtick:
hasCommandSubstitution = true;
break;
case BashTokenType.variable:
hasVariableExpansion = true;
break;
case BashTokenType.glob:
hasGlobbing = true;
break;
case BashTokenType.redirect:
case BashTokenType.heredocMarker:
hasRedirection = true;
break;
case BashTokenType.pipe:
hasPiping = true;
break;
case BashTokenType.background:
hasBackgroundExec = true;
break;
case BashTokenType.lparen:
hasSubshell = true;
break;
default:
break;
}
}
// Check for background in list operators.
for (final entry in cmdList.entries) {
if (entry.operator_ == ListOperator.background) {
hasBackgroundExec = true;
}
}
// Command-level analysis.
for (final cmd in allCmds) {
final exe = cmd.executable.split('/').last;
if (exe.isNotEmpty) executables.add(exe);
if (exe == 'eval') hasEval = true;
if (exe == 'exec') hasExec = true;
if (exe == 'sudo') hasSudo = true;
if (exe == 'chown' || exe == 'chmod' || exe == 'chgrp') hasChown = true;
if (exe == 'rm' || exe == 'rmdir' || exe == 'shred') hasRemove = true;
if (_networkExecutables.contains(exe)) hasNetworkAccess = true;
// Detect disk writes.
if (_writeCommands.contains(exe) || exe == 'rm' || exe == 'rmdir') {
hasDiskWrite = true;
// Collect written paths from arguments.
for (final arg in cmd.arguments) {
if (!arg.startsWith('-') && !arg.startsWith('\$')) {
writtenPaths.add(arg);
}
}
}
// Detect reads.
if (_readCommands.contains(exe)) {
for (final arg in cmd.arguments) {
if (!arg.startsWith('-') && !arg.startsWith('\$')) {
readPaths.add(arg);
}
}
}
// Redirections in the command.
for (final redir in cmd.redirects) {
hasRedirection = true;
if (redir.type == RedirectType.output ||
redir.type == RedirectType.append ||
redir.type == RedirectType.both ||
redir.type == RedirectType.errorOutput ||
redir.type == RedirectType.errorAppend) {
hasDiskWrite = true;
if (redir.target.isNotEmpty && !redir.target.startsWith('\$')) {
writtenPaths.add(redir.target);
}
}
if (redir.type == RedirectType.input ||
redir.type == RedirectType.inputOutput) {
if (redir.target.isNotEmpty && !redir.target.startsWith('\$')) {
readPaths.add(redir.target);
}
}
}
// Sudo elevates everything that follows.
if (exe == 'sudo') {
hasSudo = true;
for (final arg in cmd.arguments) {
if (!arg.startsWith('-')) {
final sudoExe = arg.split('/').last;
if (sudoExe == 'rm' || sudoExe == 'rmdir') hasRemove = true;
if (sudoExe == 'chown' || sudoExe == 'chmod') hasChown = true;
if (_networkExecutables.contains(sudoExe)) hasNetworkAccess = true;
break;
}
}
}
}
return CommandSecurityAnalysis(
hasCommandSubstitution: hasCommandSubstitution,
hasVariableExpansion: hasVariableExpansion,
hasGlobbing: hasGlobbing,
hasRedirection: hasRedirection,
hasPiping: hasPiping,
hasBackgroundExec: hasBackgroundExec,
hasSubshell: hasSubshell,
hasEval: hasEval,
hasExec: hasExec,
hasSudo: hasSudo,
hasChown: hasChown,
hasRemove: hasRemove,
hasNetworkAccess: hasNetworkAccess,
hasDiskWrite: hasDiskWrite,
writtenPaths: writtenPaths,
readPaths: readPaths,
executables: executables,
);
}