Cascade
Cascade is a lightweight, terminal-based interactive menu that lets administrators expose a curated set of shell commands to users without giving them a full shell. It is written in Go with no external dependencies.
Table of Contents
- How It Works
- Building and Installing
- Configuration Files
- Configuration Resolution Order
- Menu File Format
- Environment Variables
- Use Case: SSH User Isolation
- Security Notes
How It Works
When Cascade starts it:
- Reads the application config to know which shell to use and where the menu file lives.
- Reads the menu file and builds an in-memory tree from dot-separated keys.
- Switches stdin to raw mode and presents the current menu level.
- The user navigates and selects an item using arrow keys or by typing a number — both methods work simultaneously.
0/ selecting the Back/Exit item goes one level up (or exits at the root).- The selected command is executed by spawning
<shell> -c "<command>"with stdin/stdout/stderr inherited from the current terminal, so fully interactive programs (shells,less,psql, etc.) work correctly. - After the command exits, the menu is redrawn. The menu file is reloaded on every redraw, so changes take effect without restarting Cascade.
Building and Installing
# Build binary into ./build/cascade
make build
# Build and run locally
make run
# Install to /opt/cascade/ (copies binary + config files)
sudo make install
# Remove /opt/cascade/
sudo make delete
# Remove ./build/
make clean
make install backs up existing /opt/cascade/config.ini and /opt/cascade/menu.ini before overwriting them (timestamped .old.* copies).
Configuration Files
Application Config (config.ini)
A simple key=value file. Blank lines and lines starting with # are ignored.
| Key | Default | Description |
|---|---|---|
shell |
/bin/sh |
The shell used to execute menu commands (<shell> -c "<cmd>"). |
menu |
(none) | Explicit path to the menu file. Overridden by CASCADE_MENU. |
Example (config.ini.example):
shell=/bin/bash
menu=/etc/cascade/menu.ini
Menu Config (menu.ini)
Defines the tree of commands shown to the user. See Menu File Format below.
Configuration Resolution Order
Application config
Cascade tries each candidate in order and stops at the first one it can read:
- Path in
$CASCADE_CONFenvironment variable. $HOME/.cascade/config.ini/opt/cascade/config.ini- Built-in defaults (
shell=/bin/sh, no menu path).
Menu file
- Path in
$CASCADE_MENUenvironment variable. menu=value from the application config.$HOME/.cascade/menu.ini(only if the file exists)./opt/cascade/menu.ini(only if the file exists).- Fatal error — Cascade exits with a descriptive message.
Menu File Format
Comments and Section Headers
Lines starting with # are comments and are ignored entirely.
# This is a top-level comment
## This is a sub-section comment
Key=command
Dot-Separated Keys
Each non-comment, non-blank line must be Key=command.
The key uses dots (.) as level separators to express a hierarchy:
Level1.Level2.Level3=shell command here
This creates a three-level menu: entering Level1 shows Level2; entering Level2 shows Level3; selecting Level3 runs the command.
Example:
Nginx.Restart=docker restart portal-nginx
Nginx.Logs.Access=docker exec -it portal-nginx less /var/log/nginx/access.log
Nginx.Logs.Error=docker exec -it portal-nginx less /var/log/nginx/error.log
Results in this navigation tree:
[ Cascade v0.01 ]
Select Option:
1) Nginx <-
0) Exit
Enter number:
After pressing Enter (or ↓ then Enter):
[ Nginx ]
Select Option:
1) Logs
2) Restart <-
0) Back
Enter number:
[ Nginx > Logs ]
Select Option:
1) Access
2) Error
0) Back <-
Enter number:
Keys at the same level are displayed sorted alphabetically.
Navigation Controls
| Input | Action |
|---|---|
| ↑ / ↓ | Move the cursor one item up or down. Wraps around at both ends. Moving clears any typed number. |
Digit keys (0–9) |
Type a number directly. The cursor jumps to the matching item as you type. |
| Backspace | Delete the last typed digit. |
| Enter | Confirm the selection — uses the typed number if one is being entered, otherwise uses the cursor position. |
Both input methods (arrows and number typing) can be mixed freely at any time.
Mixed Nodes (Branch + Leaf)
A node can have both a direct command and child items. When this happens, an extra option (run directly) appears at the top of the submenu, allowing the user to execute the node's own command without navigating further.
Hrm.Shell=docker exec -it portal-hrm-php sh
Hrm.Service.PhpFpm.Restart=docker restart portal-hrm-php
Hrm has its own command (Shell) and also has children (Service). Selecting Hrm opens a submenu that includes (run directly) as option 1.
Environment Variables
| Variable | Description |
|---|---|
CASCADE_CONF |
Override the path to the application config file. |
CASCADE_MENU |
Override the path to the menu config file. |
These variables take highest priority in their respective resolution chains.
Use Case: SSH User Isolation
Cascade is designed to be used as a restricted ForceCommand in OpenSSH, so that certain SSH users see only the allowed menu instead of a real shell.
/etc/ssh/sshd_config (or a drop-in file):
Match User dev
SetEnv CASCADE_MENU=/opt/cascade/dev.ini
SetEnv CASCADE_CONF=/opt/cascade/config.ini
ForceCommand /usr/bin/cascade
What this does:
ForceCommand /usr/bin/cascade— every time the userdevconnects, Cascade is launched instead of their login shell, regardless of what command they pass tossh.SetEnv CASCADE_CONF— points to the application config (defines the shell used to run commands).SetEnv CASCADE_MENU— points to a menu file specific to this user, so different users can have different sets of allowed commands.
Different users (or Match blocks) can each have their own CASCADE_MENU pointing to different .ini files, giving fine-grained control over which commands each user can run.
Deployment checklist:
- Copy the
cascadebinary to/usr/bin/cascade(or/opt/cascade/cascade). - Create
/opt/cascade/config.iniwith at minimumshell=/bin/bash. - Create per-user (or shared) menu files, e.g.
/opt/cascade/dev.ini. - Add the
Matchblock(s) tosshd_configand reload sshd (systemctl reload sshd).
Security Notes
- Raw terminal mode — stdin is switched to raw mode only while waiting for a key press and is restored before running any command, so executed programs see a normal terminal.
- Input validation — typed number input is limited to 16 characters and must be all digits. Only arrow keys, digits, Backspace, and Enter are acted upon; all other key sequences are ignored.
- Line length — config lines longer than 4096 bytes are rejected by the scanner.
- No shell access — commands are defined entirely by the administrator in the menu file. The user can only pick a number; they cannot inject arbitrary commands.
- ForceCommand — when used with SSH
ForceCommand, the user cannot bypass Cascade by passing a command tosshdirectly. - Menu hot-reload — the menu file is re-read on every screen redraw. Ensure the file is writable only by root/admin to prevent privilege escalation.