Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 71 additions & 24 deletions .github/workflows/titanshield-scan.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# TitanShield Security Scanner — Real E2E GitHub Actions workflow
# Scans APK/IPA via TitanShield API, polls for results, downloads SARIF, uploads to GitHub Security tab
# TitanShield Security Scanner — GitHub Actions
# Scans APK/IPA on push/PR, uploads SARIF, comments on PR, security gate
#
# Works with ALL repos (public + private):
# - Public repos: SARIF uploaded to Security tab
# - Private repos: Results in PR comment + downloadable artifacts
#
# Required secrets:
# TITANSHIELD_API_TOKEN — API token from https://titanshield.tech/profile
# TITANSHIELD_API_TOKEN — from https://titanshield.tech/profile

name: TitanShield Security Scan

Expand All @@ -15,7 +19,8 @@ on:

permissions:
contents: read
security-events: write # Required for SARIF upload to GitHub Security tab
security-events: write
pull-requests: write
actions: read

env:
Expand Down Expand Up @@ -87,16 +92,18 @@ jobs:
CRITICAL=$(echo "$STATUS" | jq -r '.severity_counts.Critical // 0')
HIGH=$(echo "$STATUS" | jq -r '.severity_counts.High // 0')
MEDIUM=$(echo "$STATUS" | jq -r '.severity_counts.Medium // 0')
LOW=$(echo "$STATUS" | jq -r '.severity_counts.Low // 0')
VULNS=$(echo "$STATUS" | jq -r '.vulnerabilities_count // 0')

echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
echo "high=$HIGH" >> $GITHUB_OUTPUT
echo "medium=$MEDIUM" >> $GITHUB_OUTPUT
echo "low=$LOW" >> $GITHUB_OUTPUT
echo "total=$VULNS" >> $GITHUB_OUTPUT
echo "completed=true" >> $GITHUB_OUTPUT

echo ""
echo "Scan complete: $VULNS vulnerabilities (C:$CRITICAL H:$HIGH M:$MEDIUM)"
echo "Scan complete: $VULNS vulnerabilities (C:$CRITICAL H:$HIGH M:$MEDIUM L:$LOW)"
break
fi

Expand All @@ -107,33 +114,32 @@ jobs:
fi
done

- name: Download SARIF Report
- name: Download Reports
if: steps.poll.outputs.completed == 'true'
run: |
AID=${{ steps.scan.outputs.analysis_id }}

echo "Downloading SARIF..."
curl -sL "$TITANSHIELD_URL/api/analysis/$AID/download/sarif" \
-H "X-API-Token: ${{ secrets.TITANSHIELD_API_TOKEN }}" \
-o titanshield-results.sarif

echo "SARIF downloaded:"
cat titanshield-results.sarif | jq '{schema: ."$schema", tool: .runs[0].tool.driver.name, results: (.runs[0].results | length)}' 2>/dev/null || echo "SARIF parse check failed"
echo "Downloading PDF..."
curl -sL "$TITANSHIELD_URL/api/analysis/$AID/download/pdf" \
-H "X-API-Token: ${{ secrets.TITANSHIELD_API_TOKEN }}" \
-o titanshield-report.pdf

echo "SARIF: $(wc -c < titanshield-results.sarif) bytes"
echo "PDF: $(wc -c < titanshield-report.pdf) bytes"

- name: Upload SARIF to GitHub Security
if: steps.poll.outputs.completed == 'true'
continue-on-error: true
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: titanshield-results.sarif
category: titanshield-android-sast

- name: Download PDF Report
if: steps.poll.outputs.completed == 'true'
run: |
AID=${{ steps.scan.outputs.analysis_id }}
curl -sL "$TITANSHIELD_URL/api/analysis/$AID/download/pdf" \
-H "X-API-Token: ${{ secrets.TITANSHIELD_API_TOKEN }}" \
-o titanshield-report.pdf
echo "PDF: $(du -h titanshield-report.pdf | cut -f1)"

- name: Upload Artifacts
if: always() && steps.poll.outputs.completed == 'true'
uses: actions/upload-artifact@v4
Expand All @@ -142,29 +148,70 @@ jobs:
path: |
titanshield-results.sarif
titanshield-report.pdf
retention-days: 30
retention-days: 90

- name: Comment on PR
if: github.event_name == 'pull_request' && steps.poll.outputs.completed == 'true'
uses: actions/github-script@v7
with:
script: |
const critical = parseInt('${{ steps.poll.outputs.critical }}') || 0;
const high = parseInt('${{ steps.poll.outputs.high }}') || 0;
const medium = parseInt('${{ steps.poll.outputs.medium }}') || 0;
const low = parseInt('${{ steps.poll.outputs.low }}') || 0;
const total = parseInt('${{ steps.poll.outputs.total }}') || 0;
const aid = '${{ steps.scan.outputs.analysis_id }}';

const status = critical > 0 ? ':red_circle: FAILED' : ':green_circle: PASSED';
const runUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;

const body = `## TitanShield Security Scan ${status}

| Severity | Count |
|----------|-------|
| Critical | ${critical} |
| High | ${high} |
| Medium | ${medium} |
| Low | ${low} |
| **Total** | **${total}** |

**Reports:** [Download from workflow artifacts](${runUrl})
**Dashboard:** [View in TitanShield](https://titanshield.tech/analysis/s/${aid})

---
<sub>Powered by [TitanShield](https://titanshield.tech) — Mobile & Web Security Testing</sub>`;

github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});

- name: Security Gate
if: steps.poll.outputs.completed == 'true'
run: |
CRITICAL=${{ steps.poll.outputs.critical }}
HIGH=${{ steps.poll.outputs.high }}
TOTAL=${{ steps.poll.outputs.total }}

echo "## Security Gate" >> $GITHUB_STEP_SUMMARY
echo "## TitanShield Security Gate" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Critical | $CRITICAL |" >> $GITHUB_STEP_SUMMARY
echo "| High | $HIGH |" >> $GITHUB_STEP_SUMMARY
echo "| Medium | ${{ steps.poll.outputs.medium }} |" >> $GITHUB_STEP_SUMMARY
echo "| **Total** | **${{ steps.poll.outputs.total }}** |" >> $GITHUB_STEP_SUMMARY
echo "| Low | ${{ steps.poll.outputs.low }} |" >> $GITHUB_STEP_SUMMARY
echo "| **Total** | **$TOTAL** |" >> $GITHUB_STEP_SUMMARY

if [ "$CRITICAL" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "> **FAILED** — $CRITICAL critical vulnerabilities found" >> $GITHUB_STEP_SUMMARY
echo "::error::Security gate failed: $CRITICAL critical vulnerabilities"
echo "> :red_circle: **FAILED** — $CRITICAL critical vulnerabilities found" >> $GITHUB_STEP_SUMMARY
echo "::error::Security gate FAILED: $CRITICAL critical vulnerabilities"
exit 1
fi

echo "" >> $GITHUB_STEP_SUMMARY
echo "> **PASSED** — No critical vulnerabilities" >> $GITHUB_STEP_SUMMARY
echo "Security gate passed"
echo "> :green_circle: **PASSED** — No critical vulnerabilities" >> $GITHUB_STEP_SUMMARY
echo "Security gate PASSED ($TOTAL findings, 0 critical)"
Loading