diff --git a/.husky/pre-push b/.husky/pre-push index 5d3cc53411be..44cf5e8c1e1d 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,5 +1,11 @@ #!/bin/sh set -e + +# Run prek hooks (if installed) +if command -v prek >/dev/null 2>&1; then + prek run --stage pre-push +fi + # Check if bun version matches package.json # keep in sync with packages/script/src/index.ts semver qualifier bun -e ' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000000..72535793a9bd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,44 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + # Standard pre-commit hooks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + exclude: 'tsconfig\.json$|\.oxlintrc\.json$' + - id: check-toml + - id: check-added-large-files + args: [ "--maxkb=1000" ] + - id: detect-aws-credentials + args: [ '--allow-missing-credentials' ] + + # Detect secrets with GitLeaks + - repo: https://github.com/zricethezav/gitleaks + rev: v8.30.1 + hooks: + - id: gitleaks-docker + + # Lint GitHub Actions + - repo: https://github.com/rhysd/actionlint + rev: v1.7.10 + hooks: + - id: actionlint-docker + + - repo: https://github.com/lalten/check-gha-pinning + rev: v1.3.1 + hooks: + - id: check-gha-pinning + + # Block forbidden files (.env, etc.) + - repo: local + hooks: + - id: block-env-files + name: Block .env files + entry: scripts/block-env-files.sh + language: script + pass_filenames: false + always_run: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2ae3fc6f2fb5..fb5383184d71 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,6 +39,46 @@ https://github.com/anomalyco/models.dev bun dev ``` +### Setting Up Pre-Commit Hooks + +Pre-commit hooks automatically validate code before pushing. This includes: +- Secret detection (GitLeaks) +- GitHub Actions linting (actionlint, GHA pinning) +- Standard checks (trailing whitespace, JSON/YAML validation, etc.) +- `.env` file protection + +We use [prek](https://prek.j178.dev/), a fast Rust-based drop-in replacement for pre-commit. + +To install prek: + +```bash +# Using uv (recommended) +uv tool install prek + +# Using pip +pip install prek + +# Using Homebrew +brew install prek + +# Or via standalone installer +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/latest/download/prek-installer.sh | sh +``` + +Then install the git hooks: + +```bash +prek install +``` + +This integrates prek into the `pre-push` Husky hook. Hooks will run automatically when you push. To manually run: + +```bash +prek run --stage pre-push # Run all hooks +prek run --stage pre-push --files # Run on specific file +prek run gitleaks-docker --stage pre-push # Run single hook +``` + ### Running against a different directory By default, `bun dev` runs OpenCode in the `packages/opencode` directory. To run it against a different directory or repository: diff --git a/install-flex b/install-flex index af77cade1a28..e0a207f765ef 100755 --- a/install-flex +++ b/install-flex @@ -46,7 +46,12 @@ check_prereqs() { local missing=() command -v git >/dev/null 2>&1 || missing+=("git") command -v bun >/dev/null 2>&1 || missing+=("bun (https://bun.sh)") - command -v aws >/dev/null 2>&1 || missing+=("aws-cli v2 (https://aws.amazon.com/cli/)") + if command -v aws >/dev/null 2>&1; then + aws --version 2>&1 | grep -q '^aws-cli/2\.' \ + || missing+=("aws-cli v2 (found v1 — upgrade: https://aws.amazon.com/cli/)") + else + missing+=("aws-cli v2 (https://aws.amazon.com/cli/)") + fi if [ ${#missing[@]} -gt 0 ]; then die "Missing prerequisites:$(printf '\n • %s' "${missing[@]}")" fi @@ -62,17 +67,33 @@ gather_inputs() { printf 'Clone directory [%s]: ' "$HOME/opencode" >/dev/tty IFS= read -r CLONE_DIR (){}!^*?'\\]*) + die "Invalid clone directory — shell metacharacters are not allowed" ;; + esac + # Reject control characters (especially newlines) + if [[ "$CLONE_DIR" =~ [[:cntrl:]] ]]; then + die "Invalid clone directory — control characters are not allowed" + fi while true; do printf 'AWS account ID: ' >/dev/tty IFS= read -r ACCOUNT_ID /dev/tty IFS= read -r AWS_REGION /dev/null; then - warn "AWS profile [$AWS_PROFILE] already in $config — skipping" + # Use aws configure set for atomic, structured writes — avoids raw cat >> which can + # corrupt ~/.aws/config if a concurrent write leaves an unterminated section header. + # aws configure get doubles as an idempotency check without relying on fragile grep. + if aws configure get sso_start_url --profile "$AWS_PROFILE" >/dev/null 2>&1; then + warn "AWS profile [$AWS_PROFILE] already configured — skipping" return fi - info "Writing AWS SSO profile to $config..." - cat >>"$config" </dev/null; then + # Sentinel comment is the idempotency guard — more reliable than matching the + # function signature, which a user might remove while leaving a stale comment. + if grep -qF "── Flexion opencode launcher ──" "$rc_file" 2>/dev/null; then warn "opencode-work() already defined in $rc_file — skipping" return fi info "Appending opencode-work() to $rc_file..." - # Single-quoted heredoc prevents variable expansion inside the function body. - # CLONE_DIR_PLACEHOLDER is substituted after writing via perl. - cat >>"$rc_file" <<'SHELL_EOF' + # Use printf %q to shell-quote the clone path at install time. + # This eliminates the previous Perl s|...|..| substitution which was vulnerable + # to injection if CLONE_DIR contained a | character. + local clone_dir_q + clone_dir_q=$(printf '%q' "$CLONE_DIR") + + # Double-quoted heredoc: $clone_dir_q expands now (install time); + # all other $ references are \-escaped so they expand at shell run time. + cat >>"$rc_file" <