GitHub Actions Integration Guide¶
Run automated cost checks on pull requests, export SARIF findings, and block budget-exceeding deployments.
Quick Start (5-Minute Setup)¶
Prerequisites¶
- GitHub account with admin access
- GitHub Actions enabled on repository
- CloudSlash API key (from dashboard)
- (Optional) Personal Access Token for private repositories
Step 1: Install CloudSlash Action¶
Add CloudSlash to your .github/workflows/ directory:
# .github/workflows/cloudslash.yml
name: CloudSlash Security & Cost Scan
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main, develop ]
schedule:
# Run daily at 2 AM
- cron: '0 2 * * *'
jobs:
scan:
name: CloudSlash Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup CloudSlash
uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.CLOUDSLASH_API_KEY }}
config: cloudslash.yml
- name: Run PR/MR Scan
if: github.event_name == 'pull_request'
run: cloudslash scan --config cloudslash.yml --mode pr --commit-ref ${{ github.event.pull_request.head.sha }}
- name: Run Scheduled Scan
if: github.event_name == 'schedule'
run: cloudslash scan --config cloudslash.yml --mode scheduled --scan-id daily-scan
- name: Publish Report
if: always()
run: |
cloudslash report --format html --output report.html
echo "Report saved to report.html"
Step 2: Add Secrets¶
Go to your repository → Settings → Secrets → New repository secret:
- Add
CLOUDSLASH_API_KEYwith your CloudSlash API key - (Optional) For private repos: Add
GITHUB_TOKEN(already available)
Step 3: Configure CloudSlash¶
Create cloudslash.yml in your repository root:
version: 1.0
accounts:
- provider: aws
name: production
accounts:
- id: "123456789012"
role_arn: "arn:aws:iam::123456789012:role/CloudSlashAccess"
security:
compliance:
enabled: true
benchmark: cis
notifications:
slack:
webhook_url: "${{ secrets.SLACK_WEBHOOK }}"
Done! Your first scan will run on the next PR or push.
Full Configuration Reference¶
Common Workflow Patterns¶
Pattern 1: PR/MR Cost Scanning¶
Prevent costly changes from being merged:
# .github/workflows/cost-scan.yml
name: Infrastructure Cost Scan
on:
pull_request:
types:
- opened
- synchronize
- reopened
permissions:
pull-requests: write
jobs:
cost-scan:
name: Cost Analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: CloudSlash Scan
uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.CLOUDSLASH_API_KEY }}
config: cloudslash-config.yml
- name: Upload Cost Report
uses: actions/upload-artifact@v3
with:
name: cost-scan-report
path: cost-report.json
- name: Comment on PR
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('cost-report.json', 'utf8'));
const message = `### 📊 Cost Analysis
Findings: ${report.summary.findings}
Estimated Monthly Cost: $${report.summary.estimated_cost}
\`\`\`
${report.findings.map(f => `- ${f.severity}: ${f.resource} - $${f.cost}/month`).join('\n')}
\`\`\`
[View full report](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});
Pattern 2: Scheduled Drift Detection¶
Monitor production infrastructure changes automatically:
# .github/workflows/drift-detection.yml
name: Infrastructure Drift Detection
on:
schedule:
# Run every day at 2 AM EST
- cron: '0 2 * * *'
workflow_dispatch: # Allow manual trigger
inputs:
scan-id:
description: 'Scan ID for identification'
required: true
type: text
permissions:
issues: write
jobs:
drift-scan:
name: Check for Drift
runs-on: ubuntu-latest
outputs:
scan-results: ${{ steps.scan.outputs.results }}
steps:
- uses: actions/checkout@v4
- name: CloudSlash Drift Scan
id: scan
uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.CLOUDSLASH_API_KEY }}
config: cloudslash-config.yml
mode: scheduled
scan-id: ${{ github.event.inputs.scan-id || 'daily-drift' }}
- name: Check for Drift
run: |
if [ "${{ steps.scan.outputs.results-drift-count }}" -gt 0 ]; then
echo "⚠️ Drift detected: ${{ steps.scan.outputs.results-drift-count }} changes"
exit 1
fi
echo "✅ No infrastructure drift detected"
- name: Create Daily Report
if: always()
uses: actions/github-script@v6
with:
script: |
const scanResult = '${{ steps.scan.outputs.results }}';
const issueTitle = `📅 Daily Drift Report - ${new Date().toISOString().split('T')[0]}`;
let body = `### 📅 Infrastructure Drift Report\n\n`;
try {
const data = JSON.parse(scanResult);
body += `**Status**: ${data.summary.status}\n`;
body += `**Drift Changes**: ${data.drift.changes.length}\n`;
body += `**Cost Impact**: $${data.drift.costImpact}\n\n`;
if (data.drift.changes.length > 0) {
body += `#### Modified Resources:\n\n`;
data.drift.changes.forEach(change => {
body += `- \`${change.resource}\` (${change.provider})\n`;
body += ` - Previous: ${JSON.stringify(change.previous)}\n`;
body += ` - Current: ${JSON.stringify(change.current)}\n\n`;
});
body += `🔗 [View full report](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
} else {
body += `✅ **No drift detected** - Infrastructure is in sync with your IaC.`;
}
} catch (e) {
body += `Error parsing scan results: ${e.message}`;
}
const result = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: issueTitle,
labels: ['ci', 'drift-detection', 'weekly'],
body: body
});
console.log(`Created issue: ${result.data.number}`);
Pattern 3: Deployment Gates¶
Block deployments that fail security checks:
# .github/workflows/deployment-gates.yml
name: Deployment Security Gates
on:
workflow_call:
inputs:
environment:
required: true
type: string
outputs:
scan-result:
value: ${{ jobs.security-gate.outputs.result }}
secrets:
api-key:
required: true
permissions:
contents: read
pull-requests: write
jobs:
security-gate:
name: Security Gate Check
runs-on: ubuntu-latest
outputs:
result: ${{ steps.scan.outputs.passed }}
steps:
- uses: actions/checkout@v4
- name: CloudSlash Security Check
uses: cloudslash/scan@v1
id: scan
with:
api-key: ${{ secrets.api-key }}
config: cloudslash-config.yml
mode: deployment-gate
fail-on-errors: true
fail-on-warnings: true
- name: Verify Security
run: |
if [ "${{ steps.scan.outputs.passed }}" != "true" ]; then
echo "❌ Security violations found"
cat cloudslash-result.json
exit 1
fi
echo "✅ Security checks passed"
Usage:
# In your deployment workflow
jobs:
deploy:
uses: ./.github/workflows/deployment-gates.yml
secrets:
api-key: ${{ secrets.CLOUDSLASH_API_KEY }}
with:
environment: production
Pattern 4: Monorepo Setup¶
Centralized scanning for multiple repositories:
# Root monorepo pipeline
# .github/workflows/multi-repo-scan.yml
name: Monorepo Infrastructure Scan
on:
push:
branches: [ main, develop ]
pull_request:
types: [opened, synchronize, reopened]
jobs:
scan-all:
name: Scan All Subrepos
runs-on: ubuntu-latest
strategy:
matrix:
repository: [frontend-api, backend-api, infrastructure, data-pipeline]
steps:
- uses: actions/checkout@v4
- name: Checkout Subrepo
uses: actions/checkout@v4
with:
repository: ${{ matrix.repository }}
path: ${{ matrix.repository }}
- name: CloudSlash Scan
working-directory: ${{ matrix.repository }}
uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.CLOUDSLASH_API_KEY }}
config: cloudslash.yml
- name: Upload Results
uses: actions/upload-artifact@v3
with:
name: scan-results-${{ matrix.repository }}
path: ${{ matrix.repository }}/cloudslash-results.json
Pattern 5: Multi-Cloud Setup¶
Unified scanning across AWS, Azure, and GCP:
# .github/workflows/multi-cloud-scan.yml
name: Multi-Cloud Infrastructure Scan
on:
push:
branches: [ main ]
pull_request:
types: [opened, synchronize, reopened]
jobs:
multi-cloud:
name: Multi-Cloud Integration Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to AWS
run: |
echo "Deploying to AWS..."
# Your deployment scripts
- name: CloudSlash AWS Scan
uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.AWS_CLOUDSLASH_API_KEY }}
config: aws-cloudslash.yml
- name: Deploy to Azure
run: |
echo "Deploying to Azure..."
# Your deployment scripts
- name: CloudSlash Azure Scan
uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.AZURE_CLOUDSLASH_API_KEY }}
config: azure-cloudslash.yml
- name: Deploy to GCP
run: |
echo "Deploying to GCP..."
# Your deployment scripts
- name: CloudSlash GCP Scan
uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.GCP_CLOUDSLASH_API_KEY }}
config: gcp-cloudslash.yml
- name: Aggregate Results
run: |
echo "Aggregating multi-cloud reports..."
# Combine results from all three providers
Security Best Practices¶
1. Use Workflow Secrets¶
Never hardcode credentials. Always use GitHub Secrets:
# ✅ DO THIS: Use secrets
- name: CloudSlash Scan
uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.CLOUDSLASH_API_KEY }}
# ❌ DON'T DO THIS: Hardcode credentials
- name: CloudSlash Scan
uses: cloudslash/scan@v1
with:
api-key: "sk-1234567890"
2. Restrict Permissions¶
Limit workflow permissions to only what's necessary:
permissions:
pull-requests: write # Only required for PR comments
contents: read # Required for fetching code
issues: write # Required for creating reports
discussions: write # Required for posting updates
3. Use Dependabot for Updates¶
Update the CloudSlash action automatically:
OR manually using dependabot.yml:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
reviewers:
- "your-team"
labels:
- "dependencies"
- "github-actions"
4. Validate Inputs¶
Validate workflow inputs to prevent injection attacks:
jobs:
validate:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
outputs:
is-safe: ${{ steps.is-safe.outputs.result }}
steps:
- name: Validate Name
id: is-safe
run: |
# Whitelist allowed environments
if [[ "${{ github.event.inputs.environment }}" =~ ^(prod|staging|dev)$ ]]; then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
echo "Invalid environment: ${{ github.event.inputs.environment }}"
exit 1
fi
5. Rate Limit Protection¶
Prevent GitHub rate limit issues:
jobs:
scan:
runs-on: ubuntu-latest
outputs:
results-qr: ${{ steps.qr.outputs.count }}
results-pr: ${{ steps.pr.outputs.count }}
steps:
- uses: actions/github-script@v6
id: check-rate
with:
script: |
const {
data: { remaining, limit }
} = await github.rest.rateLimit.get();
console.log(`Rate limit: ${remaining}/${limit}`);
if (remaining < 50) {
throw new Error('Rate limit approaching');
}
Multi-Account Setup¶
Using AWS Organizations¶
jobs:
org-scan:
runs-on: ubuntu-latest
steps:
- uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.API_KEY }}
mode: pr
config: org-config.yml
# Use org-scan.yml for AWS Organizations scanning
org-config.yml:
version: 1.0
accounts:
- provider: aws
name: all-org-accounts
accounts:
- role_arn: "arn:aws:iam::111111111111:role/CloudSlashScanRole"
- role_arn: "arn:aws:iam::222222222222:role/CloudSlashScanRole"
# Scan all child accounts recursively
recursive: true
security:
compliance:
enabled: true
benchmark: cis
Using Cross-Account Roles¶
For complex AWS setups with multiple trust relationships:
# .github/workflows/cross-account-pr.yml
name: Cross-Account PR Scan
on:
pull_request:
types: [opened, synchronize, reopened]
env:
# Shared assume-role policy for cross-account scanning
AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
jobs:
scan:
name: Cross-Account Security Scan
runs-on: ubuntu-latest
outputs:
scan-timeout: ${{ steps.aws.config.outputs.timeout }}
scan-duration: ${{ steps.aws.config.outputs.duration }}
steps:
- uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.CLOUDSLASH_API_KEY }}
config: config.yml
assume-role-arn: ${{ env.AWS_ASSUME_ROLE }}
- name: Configure AWS for Assume Role
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ env.AWS_ASSUME_ROLE }}
aws-region: us-east-1
Notification Integration¶
Slack Integration¶
# .github/workflows/slack-notifications.yml
name: CloudSlash Slack Notifications
on:
workflow_run:
workflows: ["CloudSlash Scan"]
types: [completed]
branches: [ main, develop ]
jobs:
notify:
name: Post Results to Slack
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'failure'
steps:
- uses: slackapi/slack-github-action@v1
id: slack
with:
payload: |
{
"text": "CloudSlash Scan Failed",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "❌ <${{ github.event.workflow_run.html_url }}|CloudSlash Security Scan Failed>"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Repository:*\n${{ github.repository }}"
},
{
"type": "mrkdwn",
"text": "*Actor:*\n${{ github.event.sender.login }}"
},
{
"type": "mrkdwn",
"text": "*Time:*\n${{ github.event.workflow_run.created_at }}"
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Email Notifications¶
# .github/workflows/email-notifications.yml
name: Email Summary Report
on:
schedule:
- cron: '0 9 * * 1' # Mondays at 9 AM
env:
REPORT_EMAIL: ${{ secrets.REPORT_EMAIL_RECIPIENTS }}
jobs:
send-report:
name: Send Weekly Report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate Report
run: |
cloudslash report --format html --output report.html
cloudslash report --format json --output report.json
- name: Send Email Report
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.EMAIL_USERNAME }}
password: ${{ secrets.EMAIL_PASSWORD }}
subject: "Weekly Infrastructure Report - ${{ github.run_id }}"
to: ${{ secrets.REPORT_EMAIL_RECIPIENTS }}
from: "CloudSlash Bot"
body: "See attached HTML report for infrastructure security and cost analysis."
attachments: |
report.html
report.json
Webhook Integration¶
# .github/workflows/webhook-notifications.yml
name: Webhook Notifications
on:
workflow_run:
workflows: ["CloudSlash Scan"]
types: [completed]
jobs:
webhook:
name: Send to Webhook Endpoint
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'failure'
steps:
- name: Fetch Scan Results
id: results
run: |
# Fetch results from previous workflow run
gh api repos/${{ github.repository }}/actions/artifacts's name:'scan-results'&ref=${{ github.event.workflow_run.head_commit.id }}
# Process and send to webhook
Troubleshooting¶
Common Issues¶
Issue 1: "API Key Invalid" Error¶
Symptom: Scan fails with authentication error
Solution:
# Verify your API key in CloudSlash dashboard
# Check that you're using the correct secret name
# Ensure the API key is not expired
# Test locally first
cloudslash scan --api-key "$CLOUDSLASH_API_KEY" --config cloudslash.yml
Issue 2: Rate Limit Exceeded¶
Symptom: "GitHub rate limit exceeded" error
Solution:
# Add retry logic
- name: Retry on Rate Limit
uses: nick-fields/retry-action@v2
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 120
command: |
cloudslash scan --config cloudslash.yml
Issue 3: Scan Takes Too Long (> 30 minutes)¶
Symptom: Workflow times out before scan completes
Solutions:
-
Parallel scanning: Split by region/account
-
Use differential scanning: Only scan changed resources
Issue 4: PR Comments Not Appearing¶
Symptom: Expected PR comment doesn't show up
Steps:
# 1. Verify write permissions
permissions:
pull-requests: write
# 2. Add debug output
- name: Debug PR Info
run: |
echo "PR Number: ${{ github.event.pull_request.number }}"
echo "Repository: ${{ github.repository }}"
echo "Viewer: ${{ github.actor }}"
# 3. Verify script runs
- name: Create Comment
uses: actions/github-script@v6
with:
script: |
console.log("Attempting to create comment...");
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Test comment"
});
Issue 5: Secrets Not Available¶
Symptom: "undefined environment variable" error
Solution:
# 1. Check secret name matches exactly (case-sensitive)
# 2. Verify you're on a fork (forks have secret limitations)
# 3. Re-create the secret
# Settings → Secrets and variables → Actions → New repository secret
Debug Mode¶
Enable debug logging:
- name: CloudSlash Scan (Debug)
env:
CLOUDSLASH_DEBUG: "true"
uses: cloudslash/scan@v1
with:
api-key: ${{ secrets.API_KEY }}
config: cloudslash.yml
Verbose Output¶
Get detailed scan information:
# Run with verbose flags
cloudslash scan \
--config cloudslash.yml \
--mode pr \
--verbose \
--debug
# Output goes to terminal and GitHub Actions logs
Feature Comparison Matrix¶
GitHub Actions vs Other Platforms¶
| Feature | GitHub Actions | GitLab CI | Azure DevOps | CircleCI | Jenkins |
|---|---|---|---|---|---|
| Setup Time (min) | ⚡ 2-3 | ⚡ 2-3 | ⭐ 3-5 | ⚡ 2-4 | ⭐ 5-10 |
| PR/MR Comments | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| Free Plan | ⭐⭐⭐⭐⭐ (unlimited) | ⭐⭐⭐⭐⭐ (unlimited) | ⭐⭐⭐⭐ (limited) | ⭐⭐⭐ (limited) | ⭐⭐ (limited) |
| Scheduled Jobs | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Monorepo Support | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Multi-Cloud Support | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Docker Execution | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Custom Actions | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Cost of Build Minutes | ⚡ Free (public repos) | ⚡ Free (public repos) | ⭐ Free (small repos) | ⭐ Limited | ⚡ Free (self-hosted) |
| Integrations | ⭐⭐⭐⭐⭐ (AWS, Azure, GCP) | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
Best Overall: GitHub Actions (Ease of use, free for public repos)
Click for detailed feature descriptions
### GitHub Actions Strengths - ✅ **GitHub-native**: Seamless integration with GitHub UI - ✅ **Docker containers**: Built-in container support - ✅ **Reusable workflows**: Share workflows across repositories - ✅ **Matrix jobs**: Parallel execution for multi-region/scenario scanning - ✅ **Marketplace**: Thousands of community actions - ✅ **Free for public** and brand-name private repos ### GitHub Actions Weaknesses - ❌ **CI minutes limits**: Paid plans have build minutes - ❌ **No self-hosted runners** for free (requires premium) - ❌ **Complex artifacts**: Managing large reports can be challengingCommon Patterns¶
Pattern 1: DAG-based Scanning¶
Sequence of dependent scans (security → cost → compliance):
# .github/workflows/scan-chain.yml
name: Infrastructure Scan Chain
on:
pull_request: [opened, synchronize]
jobs:
security-baseline:
name: Security Baseline
runs-on: ubuntu-latest
steps:
- uses: cloudslash/scan@v1
with:
config: config.yml
mode: pr
id: security
cost-analysis:
name: Cost Analysis
runs-on: ubuntu-latest
needs: security-baseline
if: always()
steps:
- uses: cloudslash/scan@v1
with:
config: config.yml
mode: cost
compliance-check:
name: Compliance Check
runs-on: ubuntu-latest
needs: [security-baseline]
if: always() && steps.security.outputs.passed == 'true'
steps:
- uses: cloudslash/scan@v1
with:
config: config.yml
mode: compliance
Pattern 2: Tag-based Scanning¶
Conditional scanning based on Git tags:
# .github/workflows/tag-scan.yml
name: Tag-based Infrastructure Scan
on:
push:
tags:
- 'v*'
jobs:
production-scan:
name: Production Scan
if: contains(github.ref, 'prod')
runs-on: ubuntu-latest
steps:
- uses: cloudslash/scan@v1
with:
config: config.yml
mode: deployment-gate
staging-scan:
name: Staging Scan
if: contains(github.ref, 'stage')
runs-on: ubuntu-latest
steps:
- uses: cloudslash/scan@v1
with:
config: config.yml
mode: pr
Pattern 3: Environment-specific Scanning¶
Different scanning depth for production vs development:
jobs:
dev-scan:
name: Developer Scan
if: github.ref == 'refs/heads/develop'
steps:
- uses: cloudslash/scan@v1
with:
config: minimal-config.yml # Limited account scanning
prod-scan:
name: Production Scan
if: github.ref == 'refs/heads/main'
steps:
- uses: cloudslash/scan@v1
with:
config: full-config.yml # Full account scanning
Best Practices¶
1. Use Artifact Storage¶
Store large scan results:
- name: Upload Scan Results
uses: actions/upload-artifact@v3
with:
name: scan-results-${{ github.run_id }}
path: cloudslash-results.json
retention-days: 30
- name: Download Report for Comment
uses: dawidd6/action-download-artifact@v2
with:
workflow: cloudslash.yml
name: scan-results-${{ github.run_id }}
path: artifacts
2. Monitor Job Duration¶
Alert if scans take too long:
- name: Check Scan Duration
id: time
run: |
START=$(date +%s)
echo "Starting at $(date -u -d @$START)"
# CloudSlash scan runs here...
END=$(date +%s)
DURATION=$((END - START))
echo "Completed at $(date -u -d @$END)"
echo "Duration: ${DURATION}s"
# Alert if scan exceeds 30 minutes
if [ $DURATION -gt 1800 ]; then
echo "⚠️ Scan exceeded 30 minutes"
exit 1
fi
echo "duration=${DURATION}" >> $GITHUB_OUTPUT
3. Use Composite Actions¶
Create reusable composite actions for repeated patterns:
# .github/actions/cloudslash-scan/action.yml
name: CloudSlash Scan
description: Run CloudSlash security and cost scanning
inputs:
api-key:
description: CloudSlash API key
required: true
config:
description: CloudSlash config file
required: true
default: cloudslash.yml
mode:
description: Scan mode (pr, scheduled, deployment-gate)
required: true
outputs:
passed:
description: Whether scan passed
value: ${{ steps.scan.outputs.passed }}
runs:
using: composite
steps:
- name: CloudSlash Scan
run: |
cloudslash scan \
--api-key "${{ inputs.api-key }}" \
--config "${{ inputs.config }}" \
--mode "${{ inputs.mode }}"
shell: bash
Usage:
4. Cache Configuration¶
Cache CloudSlash config and dependencies:
- name: Cache CloudSlash
uses: actions/cache@v3
with:
path: ~/.cache/cloudslash
key: ${{ runner.os }}-cloudslash-${{ hashFiles('**/cloudslash.yml') }}
FAQ¶
Can I use CloudSlash with forked repositories?
Yes, but with some limitations: - Secrets in forks are not automatically inherited - You'll need to re-enter secrets in fork - Fork runs use GitHub's extended permissions - Scheduled workflows don't work on forks See: [Fork Documentation](https://docs.github.com/en/actions/using-workflows/authenticating-with-the-github_token)How do I handle multi-region deployments?
Use matrix jobs to scan multiple regions in parallel:Can I limit the number of accounts scanned?
Yes, use filters in your config: Or use environment-based configuration for different branches/commits.Documentation Version: 2.0.0 Last Updated: September 10, 2026 Related: CI/CD Integration Guide | Best Practices