diff --git a/.github/scripts/fortify-wait-fpr.js b/.github/scripts/fortify-wait-fpr.js
new file mode 100644
index 00000000..7fbc5845
--- /dev/null
+++ b/.github/scripts/fortify-wait-fpr.js
@@ -0,0 +1,126 @@
+const fs = require("node:fs");
+const timers = require("node:timers/promises");
+const { exec } = require("node:child_process");
+const { promisify } = require("node:util");
+
+const scanId = process.argv[2];
+const FORTIFY_USER = process.env.FORTIFY_USER;
+const FORTIFY_TOKEN = process.env.FORTIFY_API_TOKEN;
+const FORTIFY_TENANT = process.env.FORTIFY_TENANT;
+
+const execAsync = promisify(exec);
+
+async function grepCount(pattern, filePath) {
+ try {
+ const { stdout } = await execAsync(`grep -c "${pattern}" ${filePath}`);
+ const count = parseInt(stdout.trim(), 10);
+ return count;
+ } catch (error) {
+ return 0;
+ }
+}
+
+async function unzip(filePath) {
+ try {
+ const { stdout } = await execAsync(`unzip ${filePath}`);
+ return stdout;
+ } catch (error) {
+ console.error(`Error executing command: ${error}`);
+ return "";
+ }
+}
+
+async function main() {
+ const tokenData = await fetch("https://api.ams.fortify.com/oauth/token", {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" },
+ body: `grant_type=password&scope=api-tenant&username=${FORTIFY_TENANT}\\${FORTIFY_USER}&password=${FORTIFY_TOKEN}&security_code=`,
+ }).then((r) => r.json());
+
+ if (!tokenData || !tokenData.access_token) {
+ throw new Error("Can't authenticate, check credentials.");
+ }
+
+ const token = tokenData.access_token;
+
+ while (true) {
+ const summaryResponse = await fetch(
+ `https://api.ams.fortify.com/api/v3/scans/${scanId}/summary`,
+ {
+ headers: { Authorization: `Bearer ${token}` },
+ }
+ );
+
+ if (summaryResponse.status === 200) {
+ const summaryData = await summaryResponse.json();
+
+ if (summaryData.analysisStatusType === "Completed") {
+ break;
+ }
+ console.log(`Scan status: ${summaryData.analysisStatusType}...`);
+ } else {
+ console.log(`Scan API status: ${summaryResponse.status}...`);
+ }
+ await timers.setTimeout(5000);
+ }
+
+ let buffer;
+
+ while (true) {
+ const fileResponse = await fetch(
+ `https://api.ams.fortify.com/api/v3/scans/${scanId}/fpr`,
+ {
+ headers: { Authorization: `Bearer ${token}` },
+ }
+ );
+
+ if (fileResponse.status === 200) {
+ buffer = await fileResponse.arrayBuffer();
+ break;
+ }
+
+ if ([202, 429].includes(fileResponse.status)) {
+ console.log(`Waiting FPR file...`);
+ await timers.setTimeout(5000);
+ } else {
+ throw new Error(
+ `Unexpected status code from fpr endpoint: ${fileResponse.status}.`
+ );
+ }
+ }
+
+ fs.writeFileSync("./scandata.fpr", Buffer.from(buffer));
+
+ await unzip("./scandata.fpr");
+
+ const numberOfInfoSevIssues = await grepCount(
+ "1.0",
+ "audit.fvdl"
+ );
+ const numberOfLowSevIssues = await grepCount(
+ "2.0",
+ "audit.fvdl"
+ );
+ const numberOfMediumSevIssues = await grepCount(
+ "3.0",
+ "audit.fvdl"
+ );
+ const numberOfHighSevIssues = await grepCount(
+ "4.0",
+ "audit.fvdl"
+ );
+ const numberOfCriticalSevIssues = await grepCount(
+ "5.0",
+ "audit.fvdl"
+ );
+ const hasBlockingIssues =
+ numberOfCriticalSevIssues > 0 || numberOfHighSevIssues > 0;
+ console.log(
+ `Scan complete, number of info severity issues: ${numberOfInfoSevIssues}, number of low severity issues: ${numberOfLowSevIssues}, number of medium severity issues: ${numberOfMediumSevIssues}, number of high severity issues: ${numberOfHighSevIssues}, number of critical severity issues: ${numberOfCriticalSevIssues}`
+ );
+ if (hasBlockingIssues) {
+ process.exit(1);
+ }
+}
+
+main().catch(console.error);
diff --git a/.github/workflows/mobb-fortify.yaml b/.github/workflows/mobb-fortify.yaml
new file mode 100644
index 00000000..f9e965d4
--- /dev/null
+++ b/.github/workflows/mobb-fortify.yaml
@@ -0,0 +1,84 @@
+# Mobb/Fortify Fixer on pull requests
+# This workflow defines the needed steps to run Fortify on every pull request and pass the results to Mobb Fixer.
+#
+# Secrets in use (add your missing ones):
+# FORTIFY_USER - your Fortify username
+# FORTIFY_TENANT - your Fortify tenant name
+# FORTIFY_API_TOKEN - your Fortify access token
+# FORTIFY_RELEASE_ID - your Fortify app's release-id (number)
+# MOBB_API_TOKEN - your mobb user credentials (autumatially set if you forked this repo via the Mobb app)
+# GITHUB_TOKEN - automatically set by GitHub
+
+name: "Mobb/Fortify"
+
+on:
+ pull_request:
+ branches: ["*"]
+
+jobs:
+ scan-and-fix:
+ name: Scan with Fortify and fix with Mobb
+ runs-on: 'ubuntu-latest'
+ timeout-minutes: 360
+ permissions:
+ pull-requests: write
+ statuses: write
+ contents: read
+ actions: read
+
+ steps:
+ - name: Setup Java on this machine
+ uses: actions/setup-java@v3
+ with:
+ distribution: "oracle"
+ java-version: "19"
+ - name: Setup Maven on this machine
+ uses: stCarolas/setup-maven@v4.5
+ with:
+ maven-version: 3.8.6
+ - name: Setup Node on this machine
+ uses: actions/setup-node@v3.6.0
+ with:
+ node-version: 18
+ - name: Install sed
+ run: sudo apt-get update && sudo apt-get install -y sed
+
+ - name: Checkout repo to get code
+ uses: actions/checkout@v3
+
+ - name: Download Fortify uploader CLI
+ run: |
+ wget https://tools.fortify.com/scancentral/Fortify_ScanCentral_Client_21.2.0_x64.zip -O fcs.zip
+ unzip fcs.zip
+ chmod +x bin/scancentral
+ wget https://github.com/fod-dev/fod-uploader-java/releases/download/v5.4.0/FodUpload.jar -O FodUpload.jar
+
+ - name: Run Fortify SAST scan
+ run: |
+ ./bin/scancentral package -bt mvn -o fortify_package.zip
+ UPLOAD_OUTPUT=$(java -jar FodUpload.jar \
+ -z fortify_package.zip \
+ -ep SingleScanOnly \
+ -portalurl https://ams.fortify.com/ \
+ -apiurl https://api.ams.fortify.com/ \
+ -userCredentials ${{ secrets.FORTIFY_USER }} ${{ secrets.FORTIFY_API_TOKEN }} \
+ -tenantCode ${{ secrets.FORTIFY_TENANT }} \
+ -releaseId ${{ secrets.FORTIFY_RELEASE_ID }} \
+ -pp Queue)
+ SCAN_ID=$(echo "$UPLOAD_OUTPUT" | sed -n 's/Scan \([0-9]*\).*$/\1/p')
+ FORTIFY_USER=${{ secrets.FORTIFY_USER }} FORTIFY_API_TOKEN=${{ secrets.FORTIFY_API_TOKEN }} FORTIFY_TENANT=${{ secrets.FORTIFY_TENANT }} node .github/scripts/fortify-wait-fpr.js "$SCAN_ID"
+ - name: Archive fpr
+ if: always()
+ uses: actions/upload-artifact@v3
+ with:
+ name: fpr
+ path: scandata.fpr
+
+ - name: Run Mobb on the findings and get fixes
+ if: always()
+ uses: mobb-dev/action/review@v1.1
+ with:
+ report-file: "scandata.fpr"
+ api-key: ${{ secrets.MOBB_API_TOKEN }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ scanner: fortify