diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index c5d7f56..d83dce5 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -41,3 +41,73 @@ jobs: VCS_REF=${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max + + audit-api-spec: + name: Audit API Spec + runs-on: ubuntu-latest + + steps: + - name: Checkout PR + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Cache npm dependencies + id: node-modules-cache + uses: actions/cache@v4 + with: + path: '**/node_modules' + key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-modules- + - name: Install Dependencies + if: steps.node-modules-cache.outputs.cache-hit != 'true' + run: npm ci + + - name: Download and install vacuum v0.18.1 + run: | + curl -L \ + --output vacuum.tar.gz \ + --silent \ + --show-error \ + --fail \ + https://github.com/daveshanley/vacuum/releases/download/v0.18.1/vacuum_0.18.1_linux_x86_64.tar.gz + tar -xzf vacuum.tar.gz + chmod u+x vacuum + sudo mv vacuum /usr/local/bin/ + vacuum version + + - name: Generate API spec + run: | + ./node_modules/.bin/openapi-generator \ + src/masterBitgoExpress/routers/index.ts \ + > api-generated.json + + - name: Audit with Vacuum + run: | + vacuum report \ + --no-style \ + --stdout \ + --ruleset ruleset.yaml \ + api-generated.json > vacuum-report.json + + jq '.resultSet.results // []' vacuum-report.json > vacuum-results.json + + ERROR_COUNT=$(jq '[.[] | select(.ruleSeverity == "error")] | length' vacuum-results.json) + WARNING_COUNT=$(jq '[.[] | select(.ruleSeverity == "warn")] | length' vacuum-results.json) + + echo "Found $ERROR_COUNT error(s) and $WARNING_COUNT warning(s)" + + if [ "$ERROR_COUNT" -gt 0 ]; then + echo "API specification audit failed with $ERROR_COUNT error(s)" + echo "" + echo "Errors:" + jq -r '.[] | select(.ruleSeverity == "error") | " - [\(.ruleId)] \(.message) at \(.path)"' vacuum-results.json + exit 1 + else + echo "API specification audit passed!" + fi diff --git a/ruleset.yaml b/ruleset.yaml new file mode 100644 index 0000000..cef2020 --- /dev/null +++ b/ruleset.yaml @@ -0,0 +1,411 @@ +description: Recommended rules for a high quality specification. +documentationUrl: https://quobix.com/vacuum/rulesets/recommended +rules: + duplicated-entry-in-enum: + category: + description: Schemas are how request bodies and response payloads are defined. They define the data going in and the data flowing out of an operation. These rules check for structural validity, checking types, checking required fields and validating correct use of structures. + id: schemas + name: Schemas + description: Enum values can't be the same. + formats: + - oas3 + - oas3_1 + - oas2 + given: $ + howToFix: Make each enum value unique. + id: duplicated-entry-in-enum + recommended: true + severity: error + then: + function: duplicatedEnum + type: validation + no-$ref-siblings: + category: + description: Schemas are how request bodies and response payloads are defined. They define the data going in and the data flowing out of an operation. These rules check for structural validity, checking types, checking required fields and validating correct use of structures. + id: schemas + name: Schemas + description: $ref values can't be next to other properties. + formats: + - oas3 + - oas3_1 + - oas2 + given: $ + howToFix: Remove all sibling nodes, including descriptions. + id: no-$ref-siblings + recommended: true + severity: error + then: + function: refSiblings + type: validation + no-ambiguous-paths: + category: + description: Operations are the core of the contract, they define paths and HTTP methods. These rules check operations have been well constructed, looks for operationId, parameter, schema and return types in depth. + id: operations + name: Operations + description: Paths must resolve unambiguously from one another. For example, /{id}/ambiguous and /ambiguous/{id} are the same thing. + formats: + - oas3 + - oas3_1 + - oas2 + given: $ + howToFix: Make the path, and the variables used, unique. Check the ordering of variables and the naming of path segments. + id: no-ambiguous-paths + recommended: true + resolved: true + severity: error + then: + function: noAmbiguousPaths + type: validation + no-eval-in-markdown: + category: + description: Validation rules make sure that certain characters or patterns have not been used that may cause issues when rendering in different types of applications. + id: validation + name: Validation + description: Markdown descriptions can't have `eval()` statements. Malicious actors can use these to embed code in contracts, executing when a browser reads it. + formats: + - oas3 + - oas3_1 + - oas2 + given: $ + howToFix: Remove all references to 'eval()' from the description. + id: no-eval-in-markdown + recommended: true + resolved: true + severity: error + then: + function: noEvalDescription + functionOptions: + pattern: eval\( + type: validation + no-script-tags-in-markdown: + category: + description: Validation rules make sure that certain characters or patterns have not been used that may cause issues when rendering in different types of applications. + id: validation + name: Validation + description: Markdown descriptions can't have `