RepoPilotOpen in app →

anordal/shellharden

The corrective bash syntax highlighter

Healthy

Healthy across all four use cases

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 4mo ago
  • 3 active contributors
  • MPL-2.0 licensed
Show all 8 evidence items →
  • CI configured
  • Tests present
  • Slowing — last commit 4mo ago
  • Small team — 3 contributors active in recent commits
  • Single-maintainer risk — top contributor 98% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/anordal/shellharden)](https://repopilot.app/r/anordal/shellharden)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/anordal/shellharden on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: anordal/shellharden

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/anordal/shellharden shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • Last commit 4mo ago
  • 3 active contributors
  • MPL-2.0 licensed
  • CI configured
  • Tests present
  • ⚠ Slowing — last commit 4mo ago
  • ⚠ Small team — 3 contributors active in recent commits
  • ⚠ Single-maintainer risk — top contributor 98% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live anordal/shellharden repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/anordal/shellharden.

What it runs against: a local clone of anordal/shellharden — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in anordal/shellharden | Confirms the artifact applies here, not a fork | | 2 | License is still MPL-2.0 | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 153 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>anordal/shellharden</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of anordal/shellharden. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/anordal/shellharden.git
#   cd shellharden
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of anordal/shellharden and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "anordal/shellharden(\\.git)?\\b" \\
  && ok "origin remote is anordal/shellharden" \\
  || miss "origin remote is not anordal/shellharden (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MPL-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MPL-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is MPL-2.0" \\
  || miss "license drift — was MPL-2.0 at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "src/main.rs" \\
  && ok "src/main.rs" \\
  || miss "missing critical file: src/main.rs"
test -f "src/machine.rs" \\
  && ok "src/machine.rs" \\
  || miss "missing critical file: src/machine.rs"
test -f "src/situation.rs" \\
  && ok "src/situation.rs" \\
  || miss "missing critical file: src/situation.rs"
test -f "src/sitcmd.rs" \\
  && ok "src/sitcmd.rs" \\
  || miss "missing critical file: src/sitcmd.rs"
test -f "src/sitrvalue.rs" \\
  && ok "src/sitrvalue.rs" \\
  || miss "missing critical file: src/sitrvalue.rs"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 153 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~123d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/anordal/shellharden"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Shellharden is a Rust-built syntax highlighter and semi-automated rewriter for bash scripts that enforces ShellCheck conformance, primarily through intelligent quote insertion. It highlights shell syntax in foreground colors while showing suggested corrections (additions/removals) in background colors, and can apply those corrections with the --transform flag to harden scripts against variable expansion vulnerabilities. Single Rust binary (src/ not shown in file list but implied from Cargo.toml). Test-driven architecture: moduletests/original/ contains raw bash scripts, moduletests/expected/ contains the corrected versions. The parser must handle bash syntax nuances (heredocs, arithmetic, command substitution, quoting contexts) evident from test file names like heredoc_complicated.bash, cmdsub.bash, arithmetic.bash.

👥Who it's for

Bash script maintainers and DevOps engineers who need to audit and fix existing shell scripts for safety without manually rewriting them; developers who want to understand bash parsing; ShellCheck users looking for an automated correction tool that goes beyond detection.

🌱Maturity & risk

Production-ready. The project has a stable versioning scheme (v4.3.1), comprehensive test suite in moduletests/expected/ and moduletests/original/ with 40+ test cases, active CI/CD via GitHub Actions (.github/workflows/build-and-tests.yml and release automation), and MPL-2.0 licensing. Actively maintained with recent updates visible in the changelog.

Low risk: zero external dependencies in Cargo.toml, single maintainer (Andreas Nordal), but this also means response time to issues depends on one person. The codebase is primarily Rust (79KB) with shell test fixtures, making it relatively contained. No obvious breaking changes indicated in visible structure, though bash semantics edge cases could always emerge.

Active areas of work

Unable to determine from file list alone (no visible git history, open PRs, or recent timestamps), but the presence of TODO.md and a structured changelog suggests ongoing maintenance. The codebase spans quoting rules, error handling, and bash syntax edge cases based on test coverage.

🚀Get running

git clone https://github.com/anordal/shellharden.git
cd shellharden
cargo build --release
./target/release/shellharden --help

Or install via: cargo install shellharden

Daily commands:

cargo build --release
cargo test
# Run on a bash script:
./target/release/shellharden < script.bash
# With transformations:
./target/release/shellharden --transform < script.bash

Test coverage: env RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE='run-%m.profraw' cargo test && grcov . --binary-path ./target/debug/ -s . -t html -o ./coverage/

🗺️Map of the codebase

  • src/main.rs — Entry point for the shellharden CLI; defines argument parsing and orchestrates the text transformation pipeline
  • src/machine.rs — Core state machine that drives the lexer/parser; processes input byte-by-byte and dispatches to situation handlers
  • src/situation.rs — Abstract situation trait defining the parser's context-aware states; every syntax context (strings, variables, etc.) implements this
  • src/sitcmd.rs — Implements the command-context situation; handles command parsing, redirects, and pipes
  • src/sitrvalue.rs — Parses right-hand-side values (arguments, variable expansions); core logic for quoting transformations
  • Cargo.toml — Project manifest; defines the shellharden package metadata and build configuration
  • moduletests/run — Test harness that validates syntax highlighting and transformations against expected output pairs

🧩Components & responsibilities

  • machine.rs (State Machine) (Rust enums, trait dispatch) — Reads bytes sequentially and routes to appropriate Situation handler; manages state transitions
    • Failure mode: Infinite loop if situation handler never yields control; memory exhaustion if tokens grow unbounded
  • situation.rs & implementations (Context Parsers) (Trait-based polymorphism, byte pattern matching) — Each Situation interprets bytes in its context (command args, quoted strings, etc.) and decides next state
    • Failure mode: Misclassification of bash syntax (e.g., comment vs. string); incorrect quoting suggestions
  • sitrvalue.rs (Quoting Logic) — Analyzes argument tokens to

🛠️How to make changes

Add support for a new bash syntax construct

  1. Create a new situation handler struct in src/sit<construct>.rs implementing the Situation trait (src/situation.rs)
  2. Implement the feed() and is_a_match() methods for your syntax context (src/sit<construct>.rs)
  3. Register the new situation in the main state machine dispatch in src/machine.rs or the parent situation handler (src/machine.rs)
  4. Add test cases in moduletests/original/ and expected/ directories following the naming pattern (moduletests/run)
  5. Run the test harness to validate: bash moduletests/run (moduletests/run)

Add a new quoting transformation rule

  1. Identify the appropriate situation handler (usually src/sitrvalue.rs for argument contexts) (src/sitrvalue.rs)
  2. Add logic to detect when quotes should be added/removed by examining token context (src/sitrvalue.rs)
  3. Use the MRef.replace() API to insert syntax corrections into the output (src/sitextent.rs)
  4. Add test cases in moduletests/ and verify with the test harness (moduletests/run)

Extend the CLI with a new output format or flag

  1. Add the new command-line argument parsing logic to src/main.rs (src/main.rs)
  2. Pass the new flag or mode through the Machine initialization (src/machine.rs)
  3. Modify situation handlers to respect the new mode (e.g., different highlighting or transformation behavior) (src/situation.rs)
  4. Test the new mode end-to-end via shell integration tests (tests/moduletest.rs)

🔧Why these technologies

  • Rust — Memory-safe, zero-cost abstractions for byte-by-byte parsing; no garbage collection needed for a syntax tool
  • Single-pass lexer/parser state machine — Efficient streaming processing; handles arbitrarily large files without buffering entire AST
  • Trait-based Situation handlers — Composable context-aware parsers; each bash syntax construct is a separate implementation

⚖️Trade-offs already made

  • Byte-by-byte streaming instead of full AST

    • Why: Enables real-time syntax highlighting and large-file support without memory bloat
    • Consequence: Cannot perform global optimizations or complex cross-statement transformations; limited to local quoting fixes
  • Output includes both foreground colors (syntax) and background colors (suggestions)

    • Why: Non-invasive preview of changes; user can review before applying --transform
    • Consequence: Requires terminal color support; output is verbose and less suitable for piping to other tools
  • No full bash AST; custom context-aware parsers

    • Why: Simpler, faster, and avoids bash's ambiguous grammar
    • Consequence: Parser may miss or misinterpret exotic edge cases; maintained as 'best-effort' conformance tool

🚫Non-goals (don't propose these)

  • Does not execute bash scripts; pure syntax analysis only
  • Not a bash interpreter or sh-compliant shell validator; focused on ShellCheck-style linting
  • Does not resolve variable references or perform semantic analysis across functions
  • Not a code formatter for arbitrary bash style (only corrects safety-critical quoting)
  • Does not support Windows; assumes POSIX bash environment

🪤Traps & gotchas

Bash version quirks: The parser must handle bash semantics precisely; edge cases in error_unexpected_eof_*.bash tests hint at tricky EOF and escaping scenarios. No external deps is a feature, not a bug: zero stdlib-adjacent libraries means you're implementing bash parsing from scratch—study the test cases carefully to understand what edge cases exist. Test coverage validation: cargo test requires bash to be installed; Docker build available in docker/Dockerfile if running in non-bash environments.

🏗️Architecture

💡Concepts to learn

  • Bash syntax parsing (recursive descent or hand-written lexer/parser) — The entire tool depends on correctly tokenizing and parsing bash grammar; understanding how the parser handles heredocs, command substitution, arithmetic expansion, and quoting contexts is essential to modifying or extending it
  • Quoting contexts and word splitting — Shellharden's core mission is intelligent quote insertion; you must understand when quotes are necessary to prevent word splitting and glob expansion, which is why test cases like quoting_unneeded.bash and var.bash exist
  • Variable expansion safety (parameter expansion, command substitution) — The tool detects and fixes unquoted variables that could trigger word splitting or glob expansion; understanding $var vs "$var" vs ${var} is fundamental to the rewriting logic
  • Here documents (heredocs) and here strings — Test files like heredoc_complicated.bash and heredoc_vs_var.bash show that heredoc handling is a complex edge case; the parser must track delimiter context and quote escaping within heredocs
  • ANSI color codes for syntax highlighting — Shellharden's output mode uses foreground colors (syntax) and background colors (corrections); generating correct ANSI escape sequences is essential for the visual feedback feature
  • Regression testing with expected output files — The test suite compares original/ to expected/ files; understanding how to add and validate new test cases is crucial for contributing correctness improvements
  • Bash error handling and EOF detection — Test files with names like error_unexpected_eof_*.bash show that the parser must gracefully handle incomplete syntax; this is important for real-world scripts that may be malformed
  • koalaman/shellcheck — The detection tool that Shellharden complements; Shellharden applies ShellCheck rules automatically where SC only advises
  • bminor/bash — Official GNU Bash repository; essential reference for bash semantics and behavior that Shellharden must parse correctly
  • scop/bash-completion — Bash completion scripts that Shellharden could improve; real-world examples of shell scripts that benefit from hardening
  • nix-community/nix-init — Uses Rust CLI patterns similar to Shellharden; companion tool in the shell scripting ecosystem for generating Nix packaging metadata
  • adrienverge/yamllint — Similar architectural pattern: a linter with syntax highlighting and optional auto-correction; demonstrates the linter-with-fixer paradigm

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive module tests for heredoc edge cases

The moduletests/original directory has heredoc_vs_var.bash and heredoc_complicated.bash (expected) but no corresponding original for heredoc_complicated.bash. Additionally, there are no tests for heredoc with special quoting variations, parameter expansion within heredocs, or interaction with command substitution. Given the complexity of heredoc parsing visible in src/machine.rs, these edge cases should have explicit test coverage.

  • [ ] Create moduletests/original/heredoc_complicated.bash with test cases for heredoc_no_indent, parameter expansion in heredocs, and nested command substitution
  • [ ] Create moduletests/original/heredoc_quoting.bash for edge cases with <<'EOF', <<\EOF, and <<-EOF variations
  • [ ] Update moduletests/run script if needed to verify the new test files are executed
  • [ ] Verify expected output files match actual shellharden output for each new test

Add integration tests for --transform flag in CI pipeline

The build-and-tests.yml workflow appears to run tests but there's no evidence of testing the --transform feature end-to-end. Given that shellharden's core value proposition is semi-automated rewriting of scripts, the CI should verify that transformations are correctly applied and reversible. The moduletests structure has original and expected directories that seem designed for this but aren't explicitly tested in the visible workflows.

  • [ ] Review .github/workflows/build-and-tests.yml and confirm if moduletests/run is being executed
  • [ ] If not present, add a build step that runs moduletests/run and compares transformed output against moduletests/expected/* files
  • [ ] Add a specific test job that validates --transform produces valid bash syntax by running the output through bash -n
  • [ ] Document the test flow in a TEST.md file explaining how to validate transformations locally

Create unit tests for src/commonstrcmd.rs and src/commonargcmd.rs string/argument handling

The src/ directory has several core modules (commonstrcmd.rs, commonargcmd.rs, machine.rs, errfmt.rs) but no visible unit tests within those files or a dedicated tests/ directory. These command parsing modules are critical to correct syntax highlighting and transformation. Adding inline or integration tests would catch regressions in quote handling, escape sequences, and argument parsing.

  • [ ] Create a tests/ directory at repo root if it doesn't exist
  • [ ] Add tests/common_string_cmd_tests.rs with unit tests for string parsing edge cases (escaped quotes, nested quotes, $'...' syntax)
  • [ ] Add tests/common_arg_cmd_tests.rs with tests for argument splitting and quote removal scenarios
  • [ ] Verify tests reference specific scenarios from moduletests/original files and validate the parsing produces correct ASTs
  • [ ] Ensure cargo test runs these new test suites in CI

🌿Good first issues

  • Review TODO.md (not shown but mentioned in file list) to identify tracked improvements; implement one of the documented parser enhancements for a specific bash construct.
  • Add regression tests for complex bash patterns: create a new file moduletests/original/my_edge_case.bash with a tricky quoting or variable expansion scenario, implement the corrected version in moduletests/expected/, and ensure the parser handles it—this exercises both parser logic and test infrastructure.
  • Expand how_to_do_things_safely_in_bash.md with annotated examples from the test suite: cross-reference failing scripts in moduletests/original/ with their corrected versions to document why certain patterns are unsafe and how Shellharden fixes them.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 1e62972 — Fix GitHub Actions badge in README.md (PeterDaveHello)
  • 6a6ffd4 — Fix highlighting of += assignments (anordal)
  • 20cad82 — Fix lifetime elision consistency warnings, explicit edition (anordal)
  • 29f400b — Variable expansion recursivity: Count ${ instead of { (anordal)
  • 467789e — Tests: Split out var_unchanged (anordal)
  • 9aad5a4 — TODO.md: Touches (anordal)
  • 23da0c6 — Syntax: Highlight lvalue, subtler blue for var expansions to distinguish (anordal)
  • d75c377 — SitFor: Lvalue prework (anordal)
  • 795b168 — Fix misdetection of line continuation as a command also in case arms (anordal)
  • e2280f7 — Add dockerfile by popular request (#41, #58) (anordal)

🔒Security observations

Shellharden demonstrates a strong security posture with no external dependencies, reducing supply chain attack surface. The codebase is a Rust-based syntax highlighter with no network I/O, database interactions, or cryptographic operations evident from the structure. Primary concerns are: (1) use of outdated Rust edition 2015 which should be modernized to 2021, and (2) the absence of dependencies means reliance on internal implementations which require careful code review (not visible in this analysis). No secrets, injection vulnerabilities, or Docker misconfigurations were detected. Recommend updating the Rust edition and maintaining regular security audits of the core parsing logic given the security-sensitive nature of the tool (bash script analysis).

  • Low · Rust Edition 2015 is Outdated — Cargo.toml. The project uses Rust edition '2015' which is significantly outdated. Current stable Rust recommends edition '2021'. Older editions may miss modern safety improvements and best practices. Fix: Update edition to '2021' and verify all code compiles without warnings. Run 'cargo fix --edition' to automatically apply necessary changes.
  • Low · No Dependencies Specified — Cargo.toml [dependencies] section. The project has zero external dependencies in Cargo.toml. While this reduces supply chain risk, it may indicate the project reinvents common utilities which could introduce bugs. This is acceptable for a specialized syntax highlighter but warrants code quality review. Fix: Maintain minimal dependencies as currently done. Ensure any future dependencies are carefully vetted and kept up-to-date.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · anordal/shellharden — RepoPilot