From 7d6ca589796e38b1d775014b3ae311879edc106d Mon Sep 17 00:00:00 2001 From: Swanny Date: Tue, 27 Jan 2026 15:22:41 -0500 Subject: [PATCH 1/2] feat: add rust security audit workflow Add reusable workflow for running cargo audit on Rust projects with automatic GitHub issue creation for security vulnerabilities. Includes deduplication, customizable issue metadata, and support for private dependencies. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/rust-audit-security.yml | 232 ++++++++++++++++++ docs/rust-audit-security.md | 275 ++++++++++++++++++++++ examples/example-rust-audit-security.yml | 37 +++ 3 files changed, 544 insertions(+) create mode 100644 .github/workflows/rust-audit-security.yml create mode 100644 docs/rust-audit-security.md create mode 100644 examples/example-rust-audit-security.yml diff --git a/.github/workflows/rust-audit-security.yml b/.github/workflows/rust-audit-security.yml new file mode 100644 index 0000000..b8874c1 --- /dev/null +++ b/.github/workflows/rust-audit-security.yml @@ -0,0 +1,232 @@ +name: 'Rust Security Audit' +on: + workflow_call: + inputs: + issue-labels: + description: 'Comma-separated labels to apply to created issues' + required: false + default: 'security,dependencies' + type: string + issue-assignees: + description: 'Comma-separated GitHub usernames to assign to created issues' + required: false + default: '' + type: string + issue-milestone: + description: 'Milestone name or number to assign to created issues' + required: false + default: '' + type: string + deny-warnings: + description: 'Fail workflow on audit warnings (not just errors)' + required: false + default: false + type: boolean + ignore-advisories: + description: 'Comma-separated advisory IDs to ignore (e.g., RUSTSEC-2021-0001,RUSTSEC-2021-0002)' + required: false + default: '' + type: string + requires-private-deps: + description: 'Requires private dependencies to be fetched, sets up ssh-agent' + required: false + default: false + type: boolean + require-lockfile: + description: 'Require a Cargo.lock file to be present and use --locked flag' + required: false + default: false + type: boolean + working-directory: + description: 'Directory containing Cargo.toml' + required: false + default: '.' + type: string + secrets: + SSH_PRIVATE_KEY: + description: 'SSH private key for fetching private dependencies' + required: false + SSH_PRIVATE_KEY_2: + description: 'SSH private key for fetching private dependencies' + required: false + SSH_PRIVATE_KEY_3: + description: 'SSH private key for fetching private dependencies' + required: false + +permissions: + contents: read + issues: write + +jobs: + security-audit: + name: Security Audit + runs-on: + group: init4-runners + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup ssh-agent + if: ${{ inputs.requires-private-deps == true }} + uses: webfactory/ssh-agent@v0.9.1 + with: + ssh-private-key: | + ${{ secrets.SSH_PRIVATE_KEY }} + ${{ secrets.SSH_PRIVATE_KEY_2 }} + ${{ secrets.SSH_PRIVATE_KEY_3 }} + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ inputs.working-directory }} + + - name: Install cargo-audit + env: + CARGO_NET_GIT_FETCH_WITH_CLI: true + run: cargo install cargo-audit --locked + + - name: Run cargo audit + id: audit + working-directory: ${{ inputs.working-directory }} + env: + CARGO_NET_GIT_FETCH_WITH_CLI: true + continue-on-error: true + run: | + # Build audit command with flags + AUDIT_CMD="cargo audit --json --color never" + + # Add deny level + if [ "${{ inputs.deny-warnings }}" = "true" ]; then + AUDIT_CMD="$AUDIT_CMD --deny warnings" + else + AUDIT_CMD="$AUDIT_CMD --deny error" + fi + + # Add locked flag if required + if [ "${{ inputs.require-lockfile }}" = "true" ]; then + AUDIT_CMD="$AUDIT_CMD --locked" + fi + + # Add ignore flags for each advisory + if [ -n "${{ inputs.ignore-advisories }}" ]; then + IFS=',' read -ra ADVISORIES <<< "${{ inputs.ignore-advisories }}" + for advisory in "${ADVISORIES[@]}"; do + # Trim whitespace + advisory=$(echo "$advisory" | xargs) + if [ -n "$advisory" ]; then + AUDIT_CMD="$AUDIT_CMD --ignore $advisory" + fi + done + fi + + echo "Running: $AUDIT_CMD" + eval "$AUDIT_CMD" > audit-results.json || AUDIT_EXIT_CODE=$? + + # Store exit code for later + echo "exit_code=${AUDIT_EXIT_CODE:-0}" >> $GITHUB_OUTPUT + + # Always output results for processing + cat audit-results.json + + - name: Process vulnerabilities and create issues + if: always() + working-directory: ${{ inputs.working-directory }} + env: + GH_TOKEN: ${{ github.token }} + run: | + # Check if audit results file exists + if [ ! -f audit-results.json ]; then + echo "No audit results found" + exit 0 + fi + + # Extract vulnerabilities from JSON output + VULNS=$(jq -r '.vulnerabilities.list[] | @json' audit-results.json 2>/dev/null || echo "") + + if [ -z "$VULNS" ]; then + echo "No vulnerabilities found" + exit 0 + fi + + echo "Processing vulnerabilities..." + + # Process each vulnerability + echo "$VULNS" | while IFS= read -r vuln; do + # Extract vulnerability details + ADVISORY_ID=$(echo "$vuln" | jq -r '.advisory.id') + ADVISORY_TITLE=$(echo "$vuln" | jq -r '.advisory.title') + PACKAGE=$(echo "$vuln" | jq -r '.package.name') + VERSION=$(echo "$vuln" | jq -r '.package.version') + DESCRIPTION=$(echo "$vuln" | jq -r '.advisory.description') + SEVERITY=$(echo "$vuln" | jq -r '.advisory.cvss // "Unknown"') + PATCHED=$(echo "$vuln" | jq -r '.versions.patched | join(", ") // "None specified"') + URL=$(echo "$vuln" | jq -r '.advisory.url') + + echo "Processing $ADVISORY_ID - $ADVISORY_TITLE" + + # Check if issue already exists + EXISTING_ISSUE=$(gh issue list --search "$ADVISORY_ID in:title" --json number --jq '.[0].number' 2>/dev/null || echo "") + + if [ -n "$EXISTING_ISSUE" ] && [ "$EXISTING_ISSUE" != "null" ]; then + echo " Issue already exists for $ADVISORY_ID (#$EXISTING_ISSUE), skipping..." + continue + fi + + # Build issue body + ISSUE_BODY="## Security Advisory: $ADVISORY_ID + +**Package**: \`$PACKAGE\` +**Affected Version**: \`$VERSION\` +**Severity**: $SEVERITY + +### Description +$DESCRIPTION + +### Patched Versions +$PATCHED + +### References +- [Advisory Details]($URL) +- [RustSec Advisory Database](https://rustsec.org/advisories/$ADVISORY_ID) + +### Recommendation +Update the affected package to a patched version or review the advisory for alternative mitigation strategies. + +--- +*This issue was automatically created by the Rust Security Audit workflow.*" + + # Build gh issue create command + ISSUE_CMD="gh issue create --title \"Security: $ADVISORY_ID - $ADVISORY_TITLE\" --body \"$ISSUE_BODY\"" + + # Add labels if specified + if [ -n "${{ inputs.issue-labels }}" ]; then + ISSUE_CMD="$ISSUE_CMD --label \"${{ inputs.issue-labels }}\"" + fi + + # Add assignees if specified + if [ -n "${{ inputs.issue-assignees }}" ]; then + ISSUE_CMD="$ISSUE_CMD --assignee \"${{ inputs.issue-assignees }}\"" + fi + + # Add milestone if specified + if [ -n "${{ inputs.issue-milestone }}" ]; then + ISSUE_CMD="$ISSUE_CMD --milestone \"${{ inputs.issue-milestone }}\"" + fi + + # Create the issue + echo " Creating issue for $ADVISORY_ID..." + eval "$ISSUE_CMD" || echo " Failed to create issue for $ADVISORY_ID" + done + + - name: Check audit result + if: always() + run: | + EXIT_CODE=${{ steps.audit.outputs.exit_code }} + if [ "$EXIT_CODE" != "0" ]; then + echo "Security audit failed with exit code $EXIT_CODE" + exit $EXIT_CODE + fi + echo "Security audit passed" diff --git a/docs/rust-audit-security.md b/docs/rust-audit-security.md new file mode 100644 index 0000000..96ea6cc --- /dev/null +++ b/docs/rust-audit-security.md @@ -0,0 +1,275 @@ +# rust-audit-security.yml + +## Base Usage + +```yml +rust-security: + uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main +``` + +This workflow runs `cargo audit` to check for security vulnerabilities in your Rust dependencies. When vulnerabilities are found, it automatically creates GitHub issues with detailed information about each advisory. + +## Features + +- **Automatic Issue Creation**: Creates GitHub issues for each security advisory +- **Deduplication**: Checks for existing issues to avoid duplicates +- **Customizable Issue Metadata**: Configure labels, assignees, and milestones +- **Advisory Filtering**: Ignore specific advisories when needed +- **Flexible Scheduling**: Run on a schedule, on push, or manually + +## Optional Parameters + +### `issue-labels` + +**Description:** Comma-separated labels to apply to created issues + +**Type**: `string` + +**Default Value:** `security,dependencies` + +**Allowed values:** Any valid GitHub label names, comma-separated + +### `issue-assignees` + +**Description:** Comma-separated GitHub usernames to assign to created issues + +**Type**: `string` + +**Default Value:** `''` (empty string, no assignees) + +**Allowed values:** Valid GitHub usernames, comma-separated (e.g., `user1,user2`) + +### `issue-milestone` + +**Description:** Milestone name or number to assign to created issues + +**Type**: `string` + +**Default Value:** `''` (empty string, no milestone) + +**Allowed values:** Valid milestone name or number in your repository + +### `deny-warnings` + +**Description:** Fail workflow on audit warnings (not just errors). By default, the workflow only fails on security errors. + +**Type**: `boolean` + +**Default Value:** `false` + +**Allowed values:** `false`, `true` + +### `ignore-advisories` + +**Description:** Comma-separated advisory IDs to ignore. Useful for accepting known risks or false positives. + +**Type**: `string` + +**Default Value:** `''` (empty string, no advisories ignored) + +**Allowed values:** Advisory IDs from RustSec database (e.g., `RUSTSEC-2021-0001,RUSTSEC-2021-0002`) + +### `requires-private-deps` + +**Description:** Requires private dependencies to be fetched, sets up ssh-agent + +**Type**: `boolean` + +**Default Value:** `false` + +**Allowed values:** `false`, `true` + +### `require-lockfile` + +**Description:** Require a `Cargo.lock` file to be present and use the `--locked` flag + +**Type**: `boolean` + +**Default Value:** `false` + +**Allowed values:** `false`, `true` + +### `working-directory` + +**Description:** Directory containing `Cargo.toml`. Useful for monorepos or non-root projects. + +**Type**: `string` + +**Default Value:** `.` (repository root) + +**Allowed values:** Any valid directory path relative to repository root + +## Optional Secrets + +### `SSH_PRIVATE_KEY` + +**Description:** The SSH private key to be used for private dependencies, required if `requires-private-deps` is set to `true` + +### `SSH_PRIVATE_KEY_2` + +**Description:** Additional SSH private key for private dependencies (optional) + +### `SSH_PRIVATE_KEY_3` + +**Description:** Additional SSH private key for private dependencies (optional) + +## Scheduling Recommendations + +This workflow is designed to run on a schedule to continuously monitor your dependencies for security vulnerabilities. + +### Daily Check (Recommended for Active Projects) + +```yml +on: + schedule: + - cron: '0 9 * * *' # 9 AM UTC daily + workflow_dispatch: # Allow manual runs +``` + +### Weekly Check (Recommended for Most Projects) + +```yml +on: + schedule: + - cron: '0 9 * * 1' # 9 AM UTC every Monday + workflow_dispatch: # Allow manual runs +``` + +### Monthly Check (Low-Risk Projects) + +```yml +on: + schedule: + - cron: '0 9 1 * *' # 9 AM UTC on the 1st of each month + workflow_dispatch: # Allow manual runs +``` + +## Issue Management + +### Issue Format + +Created issues follow this format: + +**Title**: `Security: RUSTSEC-YYYY-NNNN - [Advisory Title]` + +**Body**: Includes package name, version, severity, description, patched versions, and links to the advisory. + +### Deduplication + +The workflow checks for existing issues with the same advisory ID in the title before creating new issues. This prevents duplicate issues on subsequent runs. + +### Managing False Positives + +If an advisory does not apply to your usage or you accept the risk, you have two options: + +1. **Close the issue**: Simply close the GitHub issue. The workflow will not recreate it as long as it exists (even when closed). + +2. **Ignore the advisory**: Add the advisory ID to the `ignore-advisories` parameter. This prevents the workflow from reporting the advisory at all. + +```yml +with: + ignore-advisories: 'RUSTSEC-2021-0001,RUSTSEC-2023-0042' +``` + +## Example Configurations + +### Basic Configuration with Scheduling + +```yml +name: Security Audit +on: + schedule: + - cron: '0 9 * * 1' # Weekly on Monday + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + security-audit: + uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main +``` + +### With Custom Labels and Assignees + +```yml +jobs: + security-audit: + uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main + with: + issue-labels: 'security,high-priority,dependencies' + issue-assignees: 'security-team,project-lead' + issue-milestone: 'Security Sprint' +``` + +### Strict Mode (Fail on Warnings) + +```yml +jobs: + security-audit: + uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main + with: + deny-warnings: true +``` + +### With Private Dependencies + +```yml +jobs: + security-audit: + uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main + with: + requires-private-deps: true + secrets: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} +``` + +### Monorepo Configuration + +```yml +jobs: + security-audit-api: + uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main + with: + working-directory: './packages/api' + issue-labels: 'security,api' + + security-audit-cli: + uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main + with: + working-directory: './packages/cli' + issue-labels: 'security,cli' +``` + +### Ignoring Known Issues + +```yml +jobs: + security-audit: + uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main + with: + ignore-advisories: 'RUSTSEC-2020-0071,RUSTSEC-2021-0145' +``` + +## Permissions Required + +The workflow requires the following GitHub token permissions: + +- `contents: read` - To checkout the repository +- `issues: write` - To create and check issues + +These permissions must be set in the calling workflow: + +```yml +permissions: + contents: read + issues: write +``` + +## Notes + +- The workflow uses `continue-on-error: true` for the audit step to ensure issues are created even when vulnerabilities are found +- The final step checks the audit exit code and fails the workflow appropriately +- Issues are only created for vulnerabilities, not for warnings or informational messages +- The workflow uses the GitHub CLI (`gh`) to manage issues, which respects your repository's issue templates and automation diff --git a/examples/example-rust-audit-security.yml b/examples/example-rust-audit-security.yml new file mode 100644 index 0000000..a282ab8 --- /dev/null +++ b/examples/example-rust-audit-security.yml @@ -0,0 +1,37 @@ +name: Security Audit +# This workflow runs cargo audit weekly to check for security vulnerabilities +# and automatically creates GitHub issues for any findings + +on: + schedule: + # Run every Monday at 9 AM UTC + - cron: '0 9 * * 1' + workflow_dispatch: # Allow manual runs + +permissions: # these permissions are required for the workflow to run + contents: read # Required to checkout the repository + issues: write # Required to create issues + +jobs: + security-audit: + uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main + with: + # Apply custom labels to created issues + issue-labels: 'security,dependencies,audit' + # Assign issues to security team members + issue-assignees: 'security-lead,dev-lead' + # Optional: assign to a specific milestone + # issue-milestone: 'Security Sprint' + # Optional: fail on warnings (not just errors) + # deny-warnings: true + # Optional: ignore specific advisories + # ignore-advisories: 'RUSTSEC-2020-0071' + # Optional: for projects with private dependencies + # requires-private-deps: true + # Optional: require Cargo.lock and use --locked flag + # require-lockfile: true + # Optional: for monorepos or non-root projects + # working-directory: './packages/api' + # Uncomment if using private dependencies + # secrets: + # SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} From 15f504850a005546e06ff18ac8e9b919f254a2b9 Mon Sep 17 00:00:00 2001 From: Swanny Date: Tue, 27 Jan 2026 15:33:28 -0500 Subject: [PATCH 2/2] refactor: simplify security audit workflow using rustsec/audit-check Refactor the security audit workflow to use the official rustsec/audit-check action instead of a custom implementation. This provides a simpler, more maintainable solution. Changes: - Replace custom cargo audit script with rustsec/audit-check@v2.0.0 - Reduce workflow from 232 lines to 35 lines (~85% reduction) - Remove custom issue creation logic (handled by audit-check) - Remove unsupported parameters (labels, assignees, milestone, SSH keys) - Keep only supported parameters (ignore-advisories, working-directory) - Update documentation to reflect simpler approach and behavior differences - Update example to show both scheduled and push triggers Trade-offs: - Simpler implementation and less maintenance burden - Official RustSec team maintenance and updates - Loss of issue customization (labels, assignees, milestones) - No support for private dependencies via SSH keys - Issue format determined by audit-check action Co-Authored-By: Claude Opus 4.5 --- .github/workflows/rust-audit-security.yml | 209 +--------------------- docs/rust-audit-security.md | 158 +++++----------- examples/example-rust-audit-security.yml | 38 ++-- 3 files changed, 65 insertions(+), 340 deletions(-) diff --git a/.github/workflows/rust-audit-security.yml b/.github/workflows/rust-audit-security.yml index b8874c1..27d77cc 100644 --- a/.github/workflows/rust-audit-security.yml +++ b/.github/workflows/rust-audit-security.yml @@ -2,60 +2,21 @@ name: 'Rust Security Audit' on: workflow_call: inputs: - issue-labels: - description: 'Comma-separated labels to apply to created issues' - required: false - default: 'security,dependencies' - type: string - issue-assignees: - description: 'Comma-separated GitHub usernames to assign to created issues' - required: false - default: '' - type: string - issue-milestone: - description: 'Milestone name or number to assign to created issues' - required: false - default: '' - type: string - deny-warnings: - description: 'Fail workflow on audit warnings (not just errors)' - required: false - default: false - type: boolean ignore-advisories: description: 'Comma-separated advisory IDs to ignore (e.g., RUSTSEC-2021-0001,RUSTSEC-2021-0002)' required: false default: '' type: string - requires-private-deps: - description: 'Requires private dependencies to be fetched, sets up ssh-agent' - required: false - default: false - type: boolean - require-lockfile: - description: 'Require a Cargo.lock file to be present and use --locked flag' - required: false - default: false - type: boolean working-directory: description: 'Directory containing Cargo.toml' required: false default: '.' type: string - secrets: - SSH_PRIVATE_KEY: - description: 'SSH private key for fetching private dependencies' - required: false - SSH_PRIVATE_KEY_2: - description: 'SSH private key for fetching private dependencies' - required: false - SSH_PRIVATE_KEY_3: - description: 'SSH private key for fetching private dependencies' - required: false permissions: contents: read issues: write + checks: write jobs: security-audit: @@ -66,167 +27,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 - - name: Setup ssh-agent - if: ${{ inputs.requires-private-deps == true }} - uses: webfactory/ssh-agent@v0.9.1 - with: - ssh-private-key: | - ${{ secrets.SSH_PRIVATE_KEY }} - ${{ secrets.SSH_PRIVATE_KEY_2 }} - ${{ secrets.SSH_PRIVATE_KEY_3 }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 + - name: Run security audit + uses: rustsec/audit-check@v2.0.0 with: - workspaces: ${{ inputs.working-directory }} - - - name: Install cargo-audit - env: - CARGO_NET_GIT_FETCH_WITH_CLI: true - run: cargo install cargo-audit --locked - - - name: Run cargo audit - id: audit - working-directory: ${{ inputs.working-directory }} - env: - CARGO_NET_GIT_FETCH_WITH_CLI: true - continue-on-error: true - run: | - # Build audit command with flags - AUDIT_CMD="cargo audit --json --color never" - - # Add deny level - if [ "${{ inputs.deny-warnings }}" = "true" ]; then - AUDIT_CMD="$AUDIT_CMD --deny warnings" - else - AUDIT_CMD="$AUDIT_CMD --deny error" - fi - - # Add locked flag if required - if [ "${{ inputs.require-lockfile }}" = "true" ]; then - AUDIT_CMD="$AUDIT_CMD --locked" - fi - - # Add ignore flags for each advisory - if [ -n "${{ inputs.ignore-advisories }}" ]; then - IFS=',' read -ra ADVISORIES <<< "${{ inputs.ignore-advisories }}" - for advisory in "${ADVISORIES[@]}"; do - # Trim whitespace - advisory=$(echo "$advisory" | xargs) - if [ -n "$advisory" ]; then - AUDIT_CMD="$AUDIT_CMD --ignore $advisory" - fi - done - fi - - echo "Running: $AUDIT_CMD" - eval "$AUDIT_CMD" > audit-results.json || AUDIT_EXIT_CODE=$? - - # Store exit code for later - echo "exit_code=${AUDIT_EXIT_CODE:-0}" >> $GITHUB_OUTPUT - - # Always output results for processing - cat audit-results.json - - - name: Process vulnerabilities and create issues - if: always() - working-directory: ${{ inputs.working-directory }} - env: - GH_TOKEN: ${{ github.token }} - run: | - # Check if audit results file exists - if [ ! -f audit-results.json ]; then - echo "No audit results found" - exit 0 - fi - - # Extract vulnerabilities from JSON output - VULNS=$(jq -r '.vulnerabilities.list[] | @json' audit-results.json 2>/dev/null || echo "") - - if [ -z "$VULNS" ]; then - echo "No vulnerabilities found" - exit 0 - fi - - echo "Processing vulnerabilities..." - - # Process each vulnerability - echo "$VULNS" | while IFS= read -r vuln; do - # Extract vulnerability details - ADVISORY_ID=$(echo "$vuln" | jq -r '.advisory.id') - ADVISORY_TITLE=$(echo "$vuln" | jq -r '.advisory.title') - PACKAGE=$(echo "$vuln" | jq -r '.package.name') - VERSION=$(echo "$vuln" | jq -r '.package.version') - DESCRIPTION=$(echo "$vuln" | jq -r '.advisory.description') - SEVERITY=$(echo "$vuln" | jq -r '.advisory.cvss // "Unknown"') - PATCHED=$(echo "$vuln" | jq -r '.versions.patched | join(", ") // "None specified"') - URL=$(echo "$vuln" | jq -r '.advisory.url') - - echo "Processing $ADVISORY_ID - $ADVISORY_TITLE" - - # Check if issue already exists - EXISTING_ISSUE=$(gh issue list --search "$ADVISORY_ID in:title" --json number --jq '.[0].number' 2>/dev/null || echo "") - - if [ -n "$EXISTING_ISSUE" ] && [ "$EXISTING_ISSUE" != "null" ]; then - echo " Issue already exists for $ADVISORY_ID (#$EXISTING_ISSUE), skipping..." - continue - fi - - # Build issue body - ISSUE_BODY="## Security Advisory: $ADVISORY_ID - -**Package**: \`$PACKAGE\` -**Affected Version**: \`$VERSION\` -**Severity**: $SEVERITY - -### Description -$DESCRIPTION - -### Patched Versions -$PATCHED - -### References -- [Advisory Details]($URL) -- [RustSec Advisory Database](https://rustsec.org/advisories/$ADVISORY_ID) - -### Recommendation -Update the affected package to a patched version or review the advisory for alternative mitigation strategies. - ---- -*This issue was automatically created by the Rust Security Audit workflow.*" - - # Build gh issue create command - ISSUE_CMD="gh issue create --title \"Security: $ADVISORY_ID - $ADVISORY_TITLE\" --body \"$ISSUE_BODY\"" - - # Add labels if specified - if [ -n "${{ inputs.issue-labels }}" ]; then - ISSUE_CMD="$ISSUE_CMD --label \"${{ inputs.issue-labels }}\"" - fi - - # Add assignees if specified - if [ -n "${{ inputs.issue-assignees }}" ]; then - ISSUE_CMD="$ISSUE_CMD --assignee \"${{ inputs.issue-assignees }}\"" - fi - - # Add milestone if specified - if [ -n "${{ inputs.issue-milestone }}" ]; then - ISSUE_CMD="$ISSUE_CMD --milestone \"${{ inputs.issue-milestone }}\"" - fi - - # Create the issue - echo " Creating issue for $ADVISORY_ID..." - eval "$ISSUE_CMD" || echo " Failed to create issue for $ADVISORY_ID" - done - - - name: Check audit result - if: always() - run: | - EXIT_CODE=${{ steps.audit.outputs.exit_code }} - if [ "$EXIT_CODE" != "0" ]; then - echo "Security audit failed with exit code $EXIT_CODE" - exit $EXIT_CODE - fi - echo "Security audit passed" + token: ${{ github.token }} + ignore: ${{ inputs.ignore-advisories }} + working-directory: ${{ inputs.working-directory }} diff --git a/docs/rust-audit-security.md b/docs/rust-audit-security.md index 96ea6cc..9345036 100644 --- a/docs/rust-audit-security.md +++ b/docs/rust-audit-security.md @@ -7,58 +7,19 @@ rust-security: uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main ``` -This workflow runs `cargo audit` to check for security vulnerabilities in your Rust dependencies. When vulnerabilities are found, it automatically creates GitHub issues with detailed information about each advisory. +This workflow uses the official [rustsec/audit-check](https://github.com/rustsec/audit-check) action to check for security vulnerabilities in your Rust dependencies. When vulnerabilities are found on scheduled runs, it automatically creates GitHub issues with detailed information about each advisory. ## Features -- **Automatic Issue Creation**: Creates GitHub issues for each security advisory +- **Automatic Issue Creation**: Creates GitHub issues for each security advisory (on scheduled runs only) - **Deduplication**: Checks for existing issues to avoid duplicates -- **Customizable Issue Metadata**: Configure labels, assignees, and milestones +- **Check Creation**: Creates status checks on push/PR events that fail when vulnerabilities are found - **Advisory Filtering**: Ignore specific advisories when needed +- **Official Maintenance**: Maintained by the RustSec team - **Flexible Scheduling**: Run on a schedule, on push, or manually ## Optional Parameters -### `issue-labels` - -**Description:** Comma-separated labels to apply to created issues - -**Type**: `string` - -**Default Value:** `security,dependencies` - -**Allowed values:** Any valid GitHub label names, comma-separated - -### `issue-assignees` - -**Description:** Comma-separated GitHub usernames to assign to created issues - -**Type**: `string` - -**Default Value:** `''` (empty string, no assignees) - -**Allowed values:** Valid GitHub usernames, comma-separated (e.g., `user1,user2`) - -### `issue-milestone` - -**Description:** Milestone name or number to assign to created issues - -**Type**: `string` - -**Default Value:** `''` (empty string, no milestone) - -**Allowed values:** Valid milestone name or number in your repository - -### `deny-warnings` - -**Description:** Fail workflow on audit warnings (not just errors). By default, the workflow only fails on security errors. - -**Type**: `boolean` - -**Default Value:** `false` - -**Allowed values:** `false`, `true` - ### `ignore-advisories` **Description:** Comma-separated advisory IDs to ignore. Useful for accepting known risks or false positives. @@ -69,26 +30,6 @@ This workflow runs `cargo audit` to check for security vulnerabilities in your R **Allowed values:** Advisory IDs from RustSec database (e.g., `RUSTSEC-2021-0001,RUSTSEC-2021-0002`) -### `requires-private-deps` - -**Description:** Requires private dependencies to be fetched, sets up ssh-agent - -**Type**: `boolean` - -**Default Value:** `false` - -**Allowed values:** `false`, `true` - -### `require-lockfile` - -**Description:** Require a `Cargo.lock` file to be present and use the `--locked` flag - -**Type**: `boolean` - -**Default Value:** `false` - -**Allowed values:** `false`, `true` - ### `working-directory` **Description:** Directory containing `Cargo.toml`. Useful for monorepos or non-root projects. @@ -99,20 +40,6 @@ This workflow runs `cargo audit` to check for security vulnerabilities in your R **Allowed values:** Any valid directory path relative to repository root -## Optional Secrets - -### `SSH_PRIVATE_KEY` - -**Description:** The SSH private key to be used for private dependencies, required if `requires-private-deps` is set to `true` - -### `SSH_PRIVATE_KEY_2` - -**Description:** Additional SSH private key for private dependencies (optional) - -### `SSH_PRIVATE_KEY_3` - -**Description:** Additional SSH private key for private dependencies (optional) - ## Scheduling Recommendations This workflow is designed to run on a schedule to continuously monitor your dependencies for security vulnerabilities. @@ -144,19 +71,27 @@ on: workflow_dispatch: # Allow manual runs ``` -## Issue Management +## Behavior + +### On Push/Pull Request + +The workflow creates a status check that fails when vulnerabilities are found. Informational advisories do not affect the check status. + +### On Scheduled Runs + +The workflow automatically creates GitHub issues for each new advisory discovered, including informational ones. ### Issue Format -Created issues follow this format: +Created issues follow this format (generated by rustsec/audit-check): -**Title**: `Security: RUSTSEC-YYYY-NNNN - [Advisory Title]` +**Title**: `RUSTSEC-YYYY-NNNN: Advisory Title` -**Body**: Includes package name, version, severity, description, patched versions, and links to the advisory. +**Body**: A markdown table with package name, version, patched versions, URL, date, and the full advisory description. ### Deduplication -The workflow checks for existing issues with the same advisory ID in the title before creating new issues. This prevents duplicate issues on subsequent runs. +The workflow automatically checks for existing issues/PRs with the same advisory ID in the title before creating new issues. This prevents duplicate issues on subsequent runs. ### Managing False Positives @@ -185,44 +120,31 @@ on: permissions: contents: read issues: write + checks: write jobs: security-audit: uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main ``` -### With Custom Labels and Assignees +### On Push to Main ```yml -jobs: - security-audit: - uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main - with: - issue-labels: 'security,high-priority,dependencies' - issue-assignees: 'security-team,project-lead' - issue-milestone: 'Security Sprint' -``` - -### Strict Mode (Fail on Warnings) - -```yml -jobs: - security-audit: - uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main - with: - deny-warnings: true -``` +name: Security Audit +on: + push: + branches: [main] + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' -### With Private Dependencies +permissions: + contents: read + checks: write -```yml jobs: security-audit: uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main - with: - requires-private-deps: true - secrets: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} ``` ### Monorepo Configuration @@ -233,13 +155,11 @@ jobs: uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main with: working-directory: './packages/api' - issue-labels: 'security,api' security-audit-cli: uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main with: working-directory: './packages/cli' - issue-labels: 'security,cli' ``` ### Ignoring Known Issues @@ -257,19 +177,29 @@ jobs: The workflow requires the following GitHub token permissions: - `contents: read` - To checkout the repository -- `issues: write` - To create and check issues +- `issues: write` - To create and check issues (required for scheduled runs) +- `checks: write` - To create status checks (required for push/PR runs) These permissions must be set in the calling workflow: ```yml +# For scheduled runs (creates issues) permissions: contents: read issues: write + checks: write + +# For push/PR runs (creates checks only) +permissions: + contents: read + checks: write ``` ## Notes -- The workflow uses `continue-on-error: true` for the audit step to ensure issues are created even when vulnerabilities are found -- The final step checks the audit exit code and fails the workflow appropriately -- Issues are only created for vulnerabilities, not for warnings or informational messages -- The workflow uses the GitHub CLI (`gh`) to manage issues, which respects your repository's issue templates and automation +- Issues are **only created on scheduled runs**, not on push or pull request events +- On push/PR events, the workflow creates status checks that fail when vulnerabilities are found +- Informational advisories do not affect check status but do create issues on scheduled runs +- The workflow uses the official [rustsec/audit-check](https://github.com/rustsec/audit-check) action maintained by the RustSec team +- Deduplication is handled automatically by checking for existing issues/PRs with the advisory ID +- The workflow cannot create checks for pull requests from forked repositories due to GitHub token permission restrictions diff --git a/examples/example-rust-audit-security.yml b/examples/example-rust-audit-security.yml index a282ab8..8e5ffd0 100644 --- a/examples/example-rust-audit-security.yml +++ b/examples/example-rust-audit-security.yml @@ -1,37 +1,29 @@ name: Security Audit -# This workflow runs cargo audit weekly to check for security vulnerabilities -# and automatically creates GitHub issues for any findings +# This workflow uses rustsec/audit-check to check for security vulnerabilities +# On scheduled runs: automatically creates GitHub issues for findings +# On push/PR: creates status checks that fail when vulnerabilities are found on: schedule: # Run every Monday at 9 AM UTC - cron: '0 9 * * 1' + push: + branches: [main] + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' workflow_dispatch: # Allow manual runs permissions: # these permissions are required for the workflow to run contents: read # Required to checkout the repository - issues: write # Required to create issues + issues: write # Required to create issues (scheduled runs) + checks: write # Required to create status checks (push/PR) jobs: security-audit: uses: init4tech/actions/.github/workflows/rust-audit-security.yml@main - with: - # Apply custom labels to created issues - issue-labels: 'security,dependencies,audit' - # Assign issues to security team members - issue-assignees: 'security-lead,dev-lead' - # Optional: assign to a specific milestone - # issue-milestone: 'Security Sprint' - # Optional: fail on warnings (not just errors) - # deny-warnings: true - # Optional: ignore specific advisories - # ignore-advisories: 'RUSTSEC-2020-0071' - # Optional: for projects with private dependencies - # requires-private-deps: true - # Optional: require Cargo.lock and use --locked flag - # require-lockfile: true - # Optional: for monorepos or non-root projects - # working-directory: './packages/api' - # Uncomment if using private dependencies - # secrets: - # SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + # Optional: ignore specific advisories + # with: + # ignore-advisories: 'RUSTSEC-2020-0071,RUSTSEC-2021-0001' + # Optional: for monorepos or non-root projects + # working-directory: './packages/api'