Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b3fc907
Allowlist the options rails and rake may be given
grantcox Sep 1, 2026
dfd55d0
Deny the `=` spellings of --sandbox in the sandbox rule itself
grantcox Sep 2, 2026
fcf3ee4
Record a denial, instead of only printing one
grantcox Aug 28, 2026
32de65e
Send the app name, so the cross-checks can see a denial record
grantcox Aug 28, 2026
2e2dbfd
Fixing shellcheck warning on backslashes in single quotes
grantcox Aug 31, 2026
7ecaa79
Strip non-ascii chars from logged denials, rather than trying to esca…
grantcox Aug 31, 2026
5ac84f0
Send DD_SERVICE, so `@app_service` means the same on both record kinds
grantcox Sep 1, 2026
28d2961
Update denial log message for the new "arguments not permitted" denial
grantcox Sep 2, 2026
3da6889
Move the console guard's policy from bash to Ruby
grantcox Sep 2, 2026
4ad2d07
Pin that an operator cannot choose the guard's interpreter
grantcox Sep 2, 2026
dee20c6
Lint the Ruby in CI
grantcox Sep 2, 2026
50cfa2c
Document the Ruby guard
grantcox Sep 2, 2026
19a3c28
Move the guard's policy tests into minitest
grantcox Sep 2, 2026
34f513a
Trust the build to resolve the guard's interpreter
grantcox Sep 3, 2026
fb9ae25
Define what the gate's exit statuses actually mean
grantcox Sep 3, 2026
121ba0a
Tweaking language
grantcox Sep 3, 2026
35cd78d
Drop the nonexistant return code 99
grantcox Sep 3, 2026
7aaefb1
Better comments about which part actually runs the command
grantcox Sep 3, 2026
3284bf0
Adding tests, more explicit comment, about why the exec() line is str…
grantcox Sep 3, 2026
85b8b28
Using standardrb rules with rubocop
grantcox Sep 3, 2026
72d4f6d
Require rbconfig before net/http
grantcox Sep 4, 2026
9a66499
Refuse a spoofed dyno name through deny, like every other refusal
grantcox Sep 4, 2026
a48d698
Refuse a runner path that exists but is not a regular file
grantcox Sep 4, 2026
5a3b5f2
Refuse the sandbox flag bundled with another short option
grantcox Sep 4, 2026
882d0e4
Run the policy suite against every supported Ruby
grantcox Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# editorconfig.org
root = true

[*]
max_line_length = 120
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.rb]
indent_style = space
indent_size = 2

[*.sh]
indent_style = space
indent_size = 2

[Makefile]
indent_style = tab
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,43 @@ permissions:
contents: read

jobs:
ruby-lint:
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- uses: useblacksmith/checkout@2d63ce5ba61677748c4e92f6eb578a8694226225 # v1.2.0

- uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
# The Ruby that runs the linter, which is not the Ruby the guard runs
# on -- that is the app's, and .rubocop.yml sets TargetRubyVersion to
# the oldest supported stack so the rules stay honest about it.
ruby-version: "3.4"
bundler-cache: true

- name: rubocop
run: bundle exec rubocop --format github

# The lint above loads the guard's files but never runs them, and it runs
# on one Ruby. This is the check that the sources still *parse* the way a
# dyno will parse them: `--disable=gems`, which is how bin/compile checks
# them at build time and how the guard is invoked at run time.
#
# Files are discovered rather than listed, so adding a policy file cannot
# silently drop it from the check.
- name: ruby -c --disable=gems
run: |
files=$(git ls-files '*.rb')

if [ -z "$files" ]; then
echo "ruby-lint: no ruby files discovered, so this job proves nothing" >&2
exit 1
fi

for f in $files; do
echo "checking $f"
ruby --disable=gems -c "$f" > /dev/null
done

shellcheck:
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
Expand Down
48 changes: 46 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,51 @@ jobs:
options: --user root
steps:
- uses: useblacksmith/checkout@2d63ce5ba61677748c4e92f6eb578a8694226225 # v1.2.0
- name: bash version
run: bash --version
# The guard's policy is Ruby. On a real dyno it runs the app's own
# interpreter, installed by heroku/ruby; the stack images carry none, so
# the suite stands the distribution's in for it. The version spread across
# the matrix is the point -- it is what pins the guard to stdlib only.
- name: Install Ruby
run: |
apt-get update -qq
apt-get install -y -qq --no-install-recommends ruby

- name: versions
run: |
bash --version
ruby --version
# Policy. Runs anywhere Ruby does; it is here for the stack, not for the
# Ruby version -- tests-ruby-versions below is what covers those.
- name: Policy tests
run: ruby test/run_ruby_tests.rb

# Everything the shell does around the policy, which needs a real login
# shell and procfs.
- name: End-to-end tests
run: ./test/run_tests.sh

# The guard runs the *app's* interpreter, not the stack's, so the version it
# has to survive is whichever Ruby the app's buildpack installed -- anywhere
# from the 3.0 floor that heroku-22 implies to the current release.
# Every supported version is therefore named here. Only the policy suite runs:
# it is the half that is Ruby, and the other half needs a stack image.
tests-ruby-versions:
runs-on: blacksmith-2vcpu-ubuntu-2404
strategy:
fail-fast: false
matrix:
ruby: ["3.0", "3.1", "3.2", "3.3", "3.4", "4.0"]
steps:
- uses: useblacksmith/checkout@2d63ce5ba61677748c4e92f6eb578a8694226225 # v1.2.0

# No bundler-cache: the guard depends on no gem, and the suite must not
# start depending on one either. Only the linter has a Gemfile.
- uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
with:
ruby-version: ${{ matrix.ruby }}

- name: versions
run: ruby --version

- name: Policy tests
run: ruby test/run_ruby_tests.rb
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@
*.swp
*.swo
*~
.vscode/
.idea/

# .vscode is committed: it carries the shared formatter and extension setup.
# Personal overrides go in the user or workspace settings, not here.
.vscode/*
!.vscode/settings.json
!.vscode/extensions.json

# Claude Code
.claude/settings.local.json

Expand Down
27 changes: 27 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require:
- standard

plugins:
- standard-custom
- standard-performance
- rubocop-performance

inherit_gem:
standard: config/base.yml
standard-performance: config/base.yml
standard-custom: config/base.yml

# standard rewrites `rescue StandardError` to a bare `rescue`. Kept explicit
# here: every one of these rescues is a deliberate "whatever goes wrong, degrade
# this way" -- unparseable dyno metadata, an unreachable reporting endpoint, an
# unreadable /proc entry -- and the bare form is the least obvious way to say
# that in code whose failure modes are the point.
Style/RescueStandardError:
Enabled: false

AllCops:
SuggestExtensions: false
NewCops: enable
TargetRubyVersion: 3.0
Exclude:
- "vendor/**/*"
10 changes: 10 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"recommendations": [
"editorconfig.editorconfig",
"Shopify.ruby-lsp", // Language support for Ruby files (highlighting, go to definition)
"rubocop.vscode-rubocop", // Linting and formatting for Ruby files, using standard's rules
"timonwong.shellcheck", // The same lint CI runs over the two shell files
"esbenp.prettier-vscode" // Formats .json and .yaml files
],
"unwantedRecommendations": ["rebornix.ruby", "castwide.solargraph"]
}
18 changes: 18 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// Applied to files in this project when the folder is opened directly.
"editor.formatOnPaste": false,
"[ruby]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "rubocop.vscode-rubocop"
},
"[json][jsonc][yaml]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// The rubocop extension owns diagnostics and formatting for Ruby, so ruby-lsp
// must not also offer them -- two formatters on save fight each other.
"rubyLsp.enabledFeatures": {
"diagnostics": false,
"formatting": false
}
}
9 changes: 9 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

# Development only. Nothing here reaches a dyno: bin/compile installs the files
# under guard/ and nothing else, and the guard runs on stdlib with
# `--disable=gems`, so it cannot load a gem even if one were present.
source "https://rubygems.org"

gem "rubocop", "~> 1.88.2", require: false # Must satisfy standard's rubocop pin
gem "standard", require: false
82 changes: 82 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
GEM
remote: https://rubygems.org/
specs:
ast (2.4.3)
json (2.21.2)
language_server-protocol (3.17.0.6)
lint_roller (1.1.0)
parallel (2.1.0)
parser (3.3.12.0)
ast (~> 2.4.1)
racc
prism (1.9.0)
racc (1.8.1)
rainbow (3.1.1)
regexp_parser (2.12.0)
rubocop (1.88.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (>= 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.49.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.50.0)
parser (>= 3.3.7.2)
prism (~> 1.7)
rubocop-performance (1.26.1)
lint_roller (~> 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
ruby-progressbar (1.13.0)
standard (1.56.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.88.0)
standard-custom (~> 1.0.0)
standard-performance (~> 1.8)
standard-custom (1.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.50)
standard-performance (1.9.0)
lint_roller (~> 1.1)
rubocop-performance (~> 1.26.0)
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.2.0)

PLATFORMS
arm64-darwin-24
ruby

DEPENDENCIES
rubocop (~> 1.88.2)
standard

CHECKSUMS
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
bundler (4.0.14) sha256=d09a0a965cf772266a7e49e83610be7c2f4e49e61134c42a56804bb383cc24b8
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
rubocop (1.88.2) sha256=8def251c90cd955feb4daa3edc0ab56893250c4ce90ef81e6c80c03f9a939bbf
rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
standard (1.56.0) sha256=ae2af4d9669589162ac69ed5ef59dcf9f346d4afc81f7e62b84339310dfcb787
standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b
standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f

BUNDLED WITH
4.0.14
Loading
Loading