Class: Rubino::Security::CommandAllowlist
- Inherits:
-
Object
- Object
- Rubino::Security::CommandAllowlist
- Defined in:
- lib/rubino/security/command_allowlist.rb
Overview
Manages a whitelist of shell commands that can be executed without confirmation.
An allowlist entry pre-approves an EXACT single command, never a prefix of a larger compound line. A naive ‘start_with?` (the old behaviour) let any line whose head matched an entry auto-resolve to :allow — INCLUDING the chained tail: with `git status` allowlisted, `git status; echo k >> ~/.ssh/authorized_keys` resolved to :allow, turning a read-only pre-approval into headless RCE/exfil. So this matcher is chain-aware, mirroring ReadonlyCommands:
- DangerousPatterns runs FIRST on the whole line, so a dangerous tail
(curl|sh, recursive rm, write into ~/.ssh, ...) can never be beaten
by an allowlisted head;
- the line is split into chain segments (|, ||, &&, ;, newline) with the
same quote-aware splitter as ReadonlyCommands, which REJECTS the line
outright on redirection (>), backgrounding (&), command substitution
($(...) / backticks) or process substitution (<(...) / >()) — the
constructs that smuggle a write or an execution past a head check;
- EVERY segment must match an allowlist entry, and a match is on a TOKEN
boundary (a prefix of token tokens), never a bare substring: `git`
allowlisted does NOT pre-approve `git-secret-leak`, and `git status`
does NOT pre-approve `git statusxyz`;
- a matched head is FLAG-VETTED via ReadonlyCommands: an allowlisted
read-capable head can not smuggle a write/exec flag past the prefix
match. With `git diff` allowlisted, `git diff --output /tmp/PWN`
(an arbitrary write) is REJECTED — same for `git diff -O...`,
`find -exec/-delete/-fprintf`, `date -s`, `tree -o` (SEC-1).
Instance Method Summary collapse
-
#allowed?(command) ⇒ Boolean
Returns true ONLY when the ENTIRE command line is covered by the allowlist: not dangerous, splits cleanly into chain segments, and every segment’s head matches an allowlist entry on a token boundary.
-
#initialize(config: nil) ⇒ CommandAllowlist
constructor
A new instance of CommandAllowlist.
Constructor Details
#initialize(config: nil) ⇒ CommandAllowlist
Returns a new instance of CommandAllowlist.
36 37 38 39 |
# File 'lib/rubino/security/command_allowlist.rb', line 36 def initialize(config: nil) @config = config || Rubino.configuration @allowlist = @config.security_command_allowlist end |
Instance Method Details
#allowed?(command) ⇒ Boolean
Returns true ONLY when the ENTIRE command line is covered by the allowlist: not dangerous, splits cleanly into chain segments, and every segment’s head matches an allowlist entry on a token boundary.
An EMPTY allowlist matches NOTHING — pre-approval is opt-in, so an unconfigured allowlist must never auto-approve everything.
47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/rubino/security/command_allowlist.rb', line 47 def allowed?(command) return false if @allowlist.empty? return false if DangerousPatterns.dangerous?(command) entries = allowlist_token_lists return false if entries.empty? segments = ReadonlyCommands.split_segments(command.to_s) return false if segments.nil? || segments.empty? segments.all? { |segment| segment_allowed?(segment, entries) } end |