From 1bf0065934740ecd978e5d2070c455b918163b6b Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 15 May 2026 21:09:13 -0400 Subject: [PATCH] feat: harden GitHub Action execution --- .github/workflows/ci.yml | 9 ++++++++ README.md | 26 ++++++++++++++++++--- action.yml | 40 ++++++++++++++------------------ scripts/action-install.sh | 46 +++++++++++++++++++++++++++++++++++++ scripts/action-scan.sh | 48 +++++++++++++++++++++++++++++++++++++++ tests/action_smoke.sh | 46 +++++++++++++++++++++++++++++++++++++ 6 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 scripts/action-install.sh create mode 100644 scripts/action-scan.sh create mode 100644 tests/action_smoke.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 109ad90..47c0506 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,15 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo test --all-targets + action-smoke: + name: Action smoke + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: bash tests/action_smoke.sh + clippy: name: Clippy runs-on: ubuntu-latest diff --git a/README.md b/README.md index e480f8c..5f51b6c 100644 --- a/README.md +++ b/README.md @@ -253,7 +253,29 @@ $ agentwise scan . --supply-chain ## CI/CD Integration -### GitHub Actions (manual, available now) +### GitHub Actions + +Use the bundled composite action for release installs, safer argument handling, and source-mode self-tests: + +```yaml +- uses: brandonwise/agentwise@v1 + with: + path: ./Agent Configs/.mcp.json + format: sarif + output: ./reports/agentwise.sarif + fail-on: high + +- uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ./reports/agentwise.sarif +``` + +Notes: +- Paths and output files with spaces are supported. +- `install-mode: source` builds the checked-out action source instead of downloading a release. Useful for testing action changes in pull requests. +- The `--fail-on` threshold still gates the job when findings meet or exceed the selected severity. + +If you prefer the manual path, this still works: ```yaml - name: Install agentwise @@ -267,8 +289,6 @@ $ agentwise scan . --supply-chain sarif_file: agentwise.sarif ``` -The `--fail-on` flag exits with code 1 when findings at or above the specified severity are found, gating your pipeline. - ## Output Formats ```bash diff --git a/action.yml b/action.yml index 39ba5e1..2fbb190 100644 --- a/action.yml +++ b/action.yml @@ -31,37 +31,31 @@ inputs: required: false default: 'false' version: - description: 'agentwise version to install' + description: 'agentwise version to install when install-mode=release' required: false default: 'latest' + install-mode: + description: 'Install from a GitHub release (release) or build the checked-out action source (source)' + required: false + default: 'release' runs: using: 'composite' steps: - name: Install agentwise shell: bash - run: | - if [ "${{ inputs.version }}" = "latest" ]; then - curl -sSf https://raw.githubusercontent.com/brandonwise/agentwise/main/install.sh | sh - else - curl -sSf https://raw.githubusercontent.com/brandonwise/agentwise/main/install.sh | sh -s -- --version ${{ inputs.version }} - fi - echo "$HOME/.local/bin" >> $GITHUB_PATH + env: + INPUT_INSTALL_MODE: ${{ inputs.install-mode }} + INPUT_VERSION: ${{ inputs.version }} + run: bash "$GITHUB_ACTION_PATH/scripts/action-install.sh" - name: Run agentwise scan shell: bash - run: | - ARGS="scan ${{ inputs.path }} --format ${{ inputs.format }}" - if [ -n "${{ inputs.fail-on }}" ]; then - ARGS="$ARGS --fail-on ${{ inputs.fail-on }}" - fi - if [ -n "${{ inputs.output }}" ]; then - ARGS="$ARGS --output ${{ inputs.output }}" - fi - if [ "${{ inputs.live }}" = "true" ]; then - ARGS="$ARGS --live" - fi - if [ "${{ inputs.supply-chain }}" = "true" ]; then - ARGS="$ARGS --supply-chain" - fi - agentwise $ARGS + env: + INPUT_PATH: ${{ inputs.path }} + INPUT_FORMAT: ${{ inputs.format }} + INPUT_FAIL_ON: ${{ inputs.fail-on }} + INPUT_OUTPUT: ${{ inputs.output }} + INPUT_LIVE: ${{ inputs.live }} + INPUT_SUPPLY_CHAIN: ${{ inputs.supply-chain }} + run: bash "$GITHUB_ACTION_PATH/scripts/action-scan.sh" diff --git a/scripts/action-install.sh b/scripts/action-install.sh new file mode 100644 index 0000000..3d830f7 --- /dev/null +++ b/scripts/action-install.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${INPUT_INSTALL_MODE:=release}" +: "${INPUT_VERSION:=latest}" + +install_bin="" + +case "$INPUT_INSTALL_MODE" in + release) + if [[ "$INPUT_VERSION" == "latest" ]]; then + curl -sSf https://raw.githubusercontent.com/brandonwise/agentwise/main/install.sh | sh + else + curl -sSf https://raw.githubusercontent.com/brandonwise/agentwise/main/install.sh | sh -s -- --version "$INPUT_VERSION" + fi + install_bin="$HOME/.local/bin" + ;; + source) + if ! command -v cargo >/dev/null 2>&1; then + echo "cargo is required when INPUT_INSTALL_MODE=source" >&2 + exit 1 + fi + + if [[ -z "${GITHUB_ACTION_PATH:-}" || ! -f "${GITHUB_ACTION_PATH}/Cargo.toml" ]]; then + echo "GITHUB_ACTION_PATH must point to the agentwise checkout when INPUT_INSTALL_MODE=source" >&2 + exit 1 + fi + + install_root="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/agentwise-action-root" + rm -rf "$install_root" + cargo install --locked --path "$GITHUB_ACTION_PATH" --root "$install_root" --force + install_bin="$install_root/bin" + ;; + *) + echo "Unsupported INPUT_INSTALL_MODE: $INPUT_INSTALL_MODE (expected 'release' or 'source')" >&2 + exit 1 + ;; +esac + +if [[ -n "${GITHUB_PATH:-}" ]]; then + printf '%s\n' "$install_bin" >> "$GITHUB_PATH" +else + export PATH="$install_bin:$PATH" +fi + +echo "Installed agentwise via ${INPUT_INSTALL_MODE} mode (${install_bin})" diff --git a/scripts/action-scan.sh b/scripts/action-scan.sh new file mode 100644 index 0000000..c96a397 --- /dev/null +++ b/scripts/action-scan.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${INPUT_PATH:=.}" +: "${INPUT_FORMAT:=terminal}" +: "${INPUT_FAIL_ON:=}" +: "${INPUT_OUTPUT:=}" +: "${INPUT_LIVE:=false}" +: "${INPUT_SUPPLY_CHAIN:=false}" + +cmd=(agentwise scan "$INPUT_PATH" --format "$INPUT_FORMAT") + +if [[ -n "$INPUT_FAIL_ON" ]]; then + cmd+=(--fail-on "$INPUT_FAIL_ON") +fi + +if [[ -n "$INPUT_OUTPUT" ]]; then + cmd+=(--output "$INPUT_OUTPUT") +fi + +case "$INPUT_LIVE" in + true) + cmd+=(--live) + ;; + false|"") + ;; + *) + echo "INPUT_LIVE must be 'true' or 'false'" >&2 + exit 1 + ;; +esac + +case "$INPUT_SUPPLY_CHAIN" in + true) + cmd+=(--supply-chain) + ;; + false|"") + ;; + *) + echo "INPUT_SUPPLY_CHAIN must be 'true' or 'false'" >&2 + exit 1 + ;; +esac + +printf 'Running:' +printf ' %q' "${cmd[@]}" +printf '\n' +"${cmd[@]}" diff --git a/tests/action_smoke.sh b/tests/action_smoke.sh new file mode 100644 index 0000000..0b5afb7 --- /dev/null +++ b/tests/action_smoke.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root=$(cd "$(dirname "$0")/.." && pwd) +tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT + +export GITHUB_ACTION_PATH="$repo_root" +export RUNNER_TEMP="$tmpdir/runner temp" +mkdir -p "$RUNNER_TEMP" +export GITHUB_PATH="$tmpdir/github-path" +: > "$GITHUB_PATH" + +export INPUT_INSTALL_MODE="source" +export INPUT_VERSION="latest" + +bash "$repo_root/scripts/action-install.sh" + +while IFS= read -r path_line; do + if [[ -n "$path_line" ]]; then + export PATH="$path_line:$PATH" + fi +done < "$GITHUB_PATH" + +agentwise --version >/dev/null + +workspace="$tmpdir/workspace with spaces" +mkdir -p "$workspace" +cp "$repo_root/testdata/clean-mcp.json" "$workspace/clean fixture.json" + +output_dir="$tmpdir/output with spaces" +mkdir -p "$output_dir" +report_path="$output_dir/report with spaces.json" + +export INPUT_PATH="$workspace/clean fixture.json" +export INPUT_FORMAT="json" +export INPUT_OUTPUT="$report_path" +export INPUT_FAIL_ON="critical" +export INPUT_LIVE="false" +export INPUT_SUPPLY_CHAIN="false" + +bash "$repo_root/scripts/action-scan.sh" + +python3 -c 'import json,sys; data=json.load(open(sys.argv[1])); assert data["servers_scanned"] == 1, data; assert data["score"] > 50, data' "$report_path" + +echo "action smoke passed"