In Chapter 3: Robust Command Parsing, we learned how to turn a raw string of text into a structured tree (AST). We can now read the command safely.
However, reading the command isn't enough. We need to identify the intent.
Imagine you have a rule: "Developers are allowed to run git status."
If a developer runs:
nice -n 10 git status
A dumb system might say: "Access Denied. You are running nice, not git."
This is the problem of Wrapper Commands. Tools like sudo, time, watch, or npm run are just delivery vehicles. The "real" command is inside them. To apply security policies effectively, we need to peel back these layers to find the command's true identity.
The Command Prefix is the canonical identity of a command.
sudo -E git commit -m "WIP"-E (sudo flags), -m "WIP" (git arguments).sudo git commitBy extracting this prefix, we can check our permission rules against a clean, standardized string, ignoring variable arguments like commit messages or file paths.
We use the Command Semantic Registry (from Chapter 1) to guide us.
When the system sees sudo git status:
sudo in the Registry.sudo says: "My first argument is actually another command."sudo and looks at git.git. The Spec says: "I am a command, and status is my subcommand."sudo git status.
The core logic lives in prefix.ts. The main function is getCommandPrefixStatic. It works recursively (a function that calls itself).
First, we parse the command to separate the program name from its arguments. Then, we check our dictionary (Registry) to see if this program is a wrapper.
// From prefix.ts
export async function getCommandPrefixStatic(command: string): Promise<Result | null> {
// 1. Parse the command string (using logic from Chapter 3)
const parsed = await parseCommand(command)
const [cmd, ...args] = extractCommandArguments(parsed.commandNode)
// 2. Check the Registry (from Chapter 1) for the Command Spec
const spec = await getCommandSpec(cmd)
// 3. Determine if this command wraps another command
// (e.g., does it have an argument marked `isCommand: true`?)
const isWrapper = spec?.args && spec.args.some(arg => arg?.isCommand)
// 4. If it is a wrapper, peel it! If not, just build the prefix.
return isWrapper
? await handleWrapper(cmd, args)
: await buildPrefix(cmd, args, spec)
}
Explanation: We act like a detective. We ask the Spec: "Are you hiding another command inside you?" If the answer is yes (isWrapper), we dig deeper.
If we find a wrapper, we need to find where the inner command starts.
// From prefix.ts (Simplified)
async function handleWrapper(command: string, args: string[]): Promise<string | null> {
const spec = await getCommandSpec(command)
// Find which argument index holds the inner command
// e.g. for `sudo`, the inner command is usually the first argument
const cmdIndex = spec.args.findIndex(arg => arg.isCommand)
// Take the rest of the string starting from that argument
const innerCommandString = args.slice(cmdIndex).join(' ')
// RECURSION: Call the main function again on the inner part!
const innerResult = await getCommandPrefixStatic(innerCommandString)
// Combine the wrapper name with the inner result
// Input: "sudo git", Inner Result: "git" -> Output: "sudo git"
return `${command} ${innerResult.commandPrefix}`
}
Explanation: This is the magic loop. If we see time sudo git, the function calls itself three times:
time wraps sudo...sudo wraps git...git wraps nothing. Stop.
What about commands joined by && or ||?
Example: git fetch && git merge
We need to know the identity of both parts to ensure the whole line is safe.
// From prefix.ts (Simplified)
export async function getCompoundCommandPrefixesStatic(command: string): Promise<string[]> {
// 1. Split by operators (&&, ||, ;)
const subcommands = splitCommand_DEPRECATED(command)
// 2. Calculate prefix for every single part
const prefixes = []
for (const sub of subcommands) {
const result = await getCommandPrefixStatic(sub.trim())
if (result) prefixes.push(result.commandPrefix)
}
// 3. Return the unique prefixes found
return prefixes // e.g. ["git fetch", "git merge"]
}
Sometimes the "raw" command isn't what we want to display to the user. We might want to quote paths to ensure they are valid copy-pasteable commands.
We use shellPrefix.ts to make the output pretty.
// From shellPrefix.ts
export function formatShellPrefixCommand(prefix: string, command: string): string {
// Check if there is a wrapper part (like "/bin/bash -c")
const spaceBeforeDash = prefix.lastIndexOf(' -')
if (spaceBeforeDash > 0) {
// Quote the executable path, leave flags alone
const exec = prefix.substring(0, spaceBeforeDash)
return `${quote([exec])} ${prefix.substring(spaceBeforeDash + 1)}`
}
return quote([prefix])
}
Explanation: If the command is /Program Files/Git/bin/git, we ensure it becomes 'Program Files/Git/bin/git' so spaces in the path don't break the shell.
In this chapter, we learned:
sudo to see the payload inside.&&, |) by analyzing each segment individually.Now that we have extracted the Core Identity of the command, we can perform the most critical step: ensuring the arguments passed to that command aren't malicious.
Next Chapter: Security & Tokenization Sanitization
Generated by Code IQ