Devin is now generally available Get started Devin is available now
/blog

Automatically Remediate Security Alerts from SonarQube with Devin

February 10, 2025 by The Cognition Team

    In modern dev environments, security vulnerabilities can pile up if not addressed quickly. Static code analysis tools like SonarQube are great at finding security issues. Still, the sheer volume of alerts can overwhelm the team and lead to delays in remediation and increased security risk.

    Devin, an autonomous AI agent who understands, writes, and modifies code, can solve this problem with SonarQube workflows. This integration creates a system that automatically remediates security alerts without human intervention.

    How It Works

    The automated remediation process works like this:

    1. SonarQube Analysis: The CI/CD pipeline triggers a SonarQube analysis on every Pull Request.
    2. Alert Detection: SonarQube finds security vulnerabilities and generates alerts.
    3. Devin API Call: A custom script calls the Devin API to create a new session for each security alert.
    4. Code Remediation: Devin analyzes the code, understands the context, and fixes the issue .
    5. Verification: The fix is verified against SonarQube rules to ensure the correct resolution of the alert.
    6. Pull Request Creation: If successful, Devin will create a pull request with the fix for human review.

    It's important to be clear and specific when working with AI agents like Devin. The "What, How, Result" framework ensures Devin stays on track and produces high-quality fixes that meet your standards.

    For example:

    • What: Fix SonarQube security alert [Alert ID] in [File Path].
    • How: Analyze the code, understand the vulnerability, and fix it to meet our team’s coding standards and best practices. Don't introduce new vulnerabilities or anything that would break existing functionality.
    • Result: The SonarQube alert should be resolved, and all tests should pass. Create a pull request with a clear description of the changes made.

    We’ll use the intentionally vulnerable OWASP Juice Shop as a testbed, SonarQube Cloud for code analysis, and Devin AI to generate and apply fixes for vulnerabilities found. Triggering these tools with GitHub Actions will make the process as hands off as possible.

    OWASP Juice Shop is designed to be a comprehensive and modern vulnerable web application, incorporating many security flaws commonly found in real-world applications to help teams and aspiring security researchers better understand attack vectors.

    SonarQube Cloud Analysis

    Running SonarQube Cloud against the OWASP Juice Shop is a good example for our automatic remediation workflow, as it will identify a handful of security vulnerabilities like SQL injections, NoSQL injections, and more for Devin to fix.

    If you’d like to follow along with this tutorial, now is a good time to go to the project page, click Fork, then Create a New Fork under your desired GitHub account.

    Next, head over to SonarQube Cloud and sign up for an account if you don't already have one.

    We recommend using Login with GitHub for this tutorial.

    If you're part of an organization, follow the "Find my organization" guide. If not, click "Import an organization."

    Select your organization, and then select the level of access you want SonarCube to have.

    On the next screen, you'll set up your organization in SonarCube; take note of your organization key as you'll need that later.

    Select the free plan, click Create Organization, and select the Juice Shop repository on the next screen.

    Select Previous version and click Create Project.

    SonarCube will import and scan your repository for the first time. When finished, go to `Administration > Analysis Method` and turn off Automatic Analysis; SonarQube will reject the API call if this setting is turned on.

    On the same page, you'll click With GitHub Actions. Take note of your `SONAR_TOKEN` as you'll need it later.

    Clone your forked Juice Shop project, navigate to `.github/workflows`, and create the file `sonar-devin.yml` and add the following to it:

    Despite this GitHub Actions config being a bit long, it's actually a relatively simple workflow. Every time a new Pull Request is opened on our GitHub repo, our Action runner will call the below python script that triggers our SonarQube and Devin activities.

    name: SonarCloud Scan and Devin Remediation
    on:
      workflow_dispatch:
      pull_request:
        branches:
          - 'master'
    jobs:
      analyze:
        name: Analyze and Remediate
        runs-on: ubuntu-latest
        steps:
          - name: Debug Trigger Info
            run: |
              echo "Event name: ${{ github.event_name }}"
              echo "Actor: ${{ github.actor }}"
              echo "Head ref: ${{ github.head_ref }}"
              echo "Sender type: ${{ github.event.sender.type }}"
              echo "PR user login: ${{ github.event.pull_request.user.login }}"
              
          - name: Check Run Conditions
            id: should_run
            run: |
              if [[ "${{ github.event_name }}" == "pull_request" ]]; then
                if [[ "${{ github.event.pull_request.user.login }}" == "devin-ai-integration" ]] || 
                   [[ "${{ github.actor }}" == "devin-ai-integration" ]] ||
                   [[ "${{ github.head_ref }}" == devin/* ]]; then
                  echo "Skipping workflow due to PR from automated user or devin branch"
                  exit 1
                fi
              fi
              
          - uses: actions/checkout@v4
            if: success()
            with:
              fetch-depth: 0
              
          - name: SonarCloud Scan
            if: success()
            uses: SonarSource/sonarqube-scan-action@v4
            with:
              args: >
                -Dsonar.organization=${{ secrets.SONAR_ORG }}
                -Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }}
                -Dsonar.sources=.
            env:
              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
              SONAR_HOST_URL: https://sonarcloud.io
              
          - name: Setup Python
            if: success()
            uses: actions/setup-python@v5
            with:
              python-version: '3.x'
              
          - name: Install Dependencies
            if: success()
            run: pip install aiohttp
            
          - name: Configure Git
            if: success()
            run: |
              git config --global user.name "GitHub Action"
              git config --global user.email "action@github.com"
              
          - name: Run Devin Remediation
            if: success()
            env:
              DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
              SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              SONAR_ORG: ${{ secrets.SONAR_ORG }}
              SONAR_PROJECT_KEY: ${{ secrets.SONAR_PROJECT_KEY }}
            run: |
              echo "Starting Devin Remediation script..."
              python .github/scripts/devin_remediation.py
              echo "Devin Remediation script finished."
              
          - name: Handle Errors
            if: failure()
            run: |
              echo "An error occurred during the workflow!"
    

    To set up that Python script, go back to the `.github` directory, create a new folder named `scripts`, and within that folder create a file named `devin_remediation.py`.

    Copy and paste the following, then commit and push your changes.

    import asyncio
    import aiohttp
    import os
    from datetime import datetime
    
    # Environment variables
    GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")
    SONAR_TOKEN = os.getenv("SONAR_TOKEN")
    DEVIN_API_KEY = os.getenv("DEVIN_API_KEY")
    SONAR_ORG = os.getenv("SONAR_ORG")
    SONAR_PROJECT_KEY = os.getenv("SONAR_PROJECT_KEY")
    DEVIN_API_BASE = "https://api.devin.ai/v1"
    
    async def get_sonarcloud_issues():
        """Fetch open vulnerabilities from SonarCloud."""
        url = "https://sonarcloud.io/api/issues/search"
        headers = {"Authorization": f"Bearer {SONAR_TOKEN}"}
        params = {
            "organization": SONAR_ORG,
            "projectKeys": SONAR_PROJECT_KEY,
            "types": "VULNERABILITY",
            "statuses": "OPEN"
        }
        
        print("Fetching SonarCloud issues with params:", params)
        async with aiohttp.ClientSession() as session:
            async with session.get(url, headers=headers, params=params) as response:
                if response.status != 200:
                    print(f"Error getting SonarCloud issues: {await response.text()}")
                    return []
                result = await response.json()
                print(f"Found {len(result.get('issues', []))} issues")
                return result.get('issues', [])
    
    async def delegate_task_to_devin(issue):
        """Delegate the entire task of fixing, committing, and pushing to Devin AI."""
        async with aiohttp.ClientSession() as session:
            headers = {"Authorization": f"Bearer {DEVIN_API_KEY}"}
            
            # Add timestamp to make branch name unique
            timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
            branch_name = f"devin/fix-{timestamp}-{issue['key']}"
            
            prompt = f"""
            Fix the following vulnerability in {GITHUB_REPOSITORY}: {issue['message']} in file {issue['component']}.
            1. Create a new branch named '{branch_name}'.
            2. Implement the fix.
            3. Write a detailed commit message explaining the changes:
                - Issue Key: {issue['key']}
                - Component: {issue['component']}
                - Fixed by Devin AI at {datetime.now().isoformat()}
                - Include 'Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>'.
            4. Push the branch to the remote repository.
            5. Open a pull request with a description of the fix.
            """
            
            print(f"Creating Devin session with branch: {branch_name}")
            data = {"prompt": prompt, "idempotent": True}
            
            async with session.post(f"{DEVIN_API_BASE}/sessions", json=data, headers=headers) as response:
                if response.status != 200:
                    print(f"Error delegating task to Devin: {await response.text()}")
                    return None
                result = await response.json()
                print(f"Devin session created: {result}")
                return result
    
    async def monitor_devin_session(session_id):
        """Monitor Devin's progress until it completes the task."""
        async with aiohttp.ClientSession() as session:
            headers = {"Authorization": f"Bearer {DEVIN_API_KEY}"}
            
            while True:
                async with session.get(f"{DEVIN_API_BASE}/session/{session_id}", headers=headers) as response:
                    if response.status != 200:
                        print(f"Error monitoring Devin session: {await response.text()}")
                        return None
                    
                    result = await response.json()
                    status = result.get("status_enum")
                    print(f"Devin session status: {status}")
                    
                    if status in ["completed", "stopped"]:
                        print(f"Devin completed the task: {result}")
                        return result
                    elif status == "blocked":
                        print("Devin encountered an issue. Please check manually.")
                        return None
                    
                    await asyncio.sleep(5)
    
    async def main():
        try:
            print("Starting main execution...")
            issues = await get_sonarcloud_issues()
            
            for issue in issues:
                print(f"Processing issue: {issue['key']}")
                
                # Delegate task to Devin AI
                session_data = await delegate_task_to_devin(issue)
                
                if session_data:
                    session_id = session_data["session_id"]
                    print(f"Monitoring session: {session_id}")
                    
                    # Monitor Devin's progress
                    await monitor_devin_session(session_id)
                    
        except Exception as e:
            print(f"Error occurred: {str(e)}")
            raise
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Our Python script is a bit more complex than our GitHub Actions workflow. It makes REST API requests to SonarCloud so that it knows to analyze our repository. It then takes the issues that SonarCloud returns, and makes an API call to Devin for each issue to start the remediation process. This API call to Devin also includes our prompt that tells Devin how to approach its work.

    The script then monitors Devin's progress, and completes our GitHub Action only when Devin has finished its own session. Note that in a production environment, you likely want a different exit condition for your GitHub Action in case the remediation workflow is long-running on Devin's end.

    With our scripts in place, we now need to configure our environment variables (seen in the snippet below) to work with GitHub Actions.

    SONAR_TOKEN = os.getenv("SONAR_TOKEN")
    DEVIN_API_KEY = os.getenv("DEVIN_API_KEY")
    SONAR_ORG = os.getenv("SONAR_ORG")
    SONAR_PROJECT_KEY = os.getenv("SONAR_PROJECT_KEY")
    

    You collected the `SONAR_TOKEN` and `SONAR_ORG` variables earlier during your SonarCloud setup process.

    To get the `SONAR_PROJECT_KEY`, navigate to `Administration > Update Key` in your SonarCube Cloud project and see the key listed there.

    To get your Devin API key, login to your account, navigate to `Settings > Devin's API`, and click View Key.

    Now it's time to let GitHub know about the keys. Go to `Settings > Secrets and variables > Actions` in your GitHub project.

    To add each key, click New repository secret. Ensure they have the same name as the ones in the code above:

    SONAR_TOKEN, DEVIN_API_KEY, SONAR_ORG, SONAR_PROJECT_KEY

    Now that the keys have been added, navigate to `Actions > SonarCloud Scan and Devin Remediation`, click Run workflow, and run the analysis on your master branch.

    It will take some time for SonarQube to scan the project and then for Devin to assess and make its fixes. Now that you’ve delegated these tasks to Devin you can go work on something more important. When the Action is completed, click on Pull Requests to see the PRs Devin has made. Navigating inside one, you'll see the reasoning behind the changes.

    As with all Pull Requests, whether they are from a human or from Devin, It is essential to review changes to see if they align with your team's coding and security standards before merging.

    Taking a Look Inside

    Here’s an example of how Devin decided to tackle one of the security issues: Prevent SQL injection vulnerability in product reviews.

    Devin gives its reasoning in the Pull Request as well as a link to the session:

    SonarCloud also confirms that our fix has worked:

    How to Review and Manage AI-Generated Code Fixes

    You should evaluate Devin's code as you would any engineer's on your team. Ensure that test suites pass, and that Devin knows which CI tools you rely on for evaluating successful PRs. When you have things you would like Devin to do differently, you can either modify your prompt or edit Devin's Knowledge with that new information.

    For security remediation in particular, you should also perform regular audits that analyze fixed effectiveness, false positive rates, and recurring error types. These insights feed back into prompt engineering adjustments for Devin, test suite expansion, and review guideline updates, creating a closed loop where human expertise trains the AI assistant. Teams at IBM who use a similar approach reduced vulnerability reintroduction by 63% while maintaining deployment velocity.

    This tutorial is a good example of how you can easily build workflows around Devin, using your team's existing tools. Since you can configure and customize Devin in in-depth ways using Knowledge and your initial Repo Setup, you can ensure that when you delegate a problem to Devin that it understands not only the code itself but also how your team works and why. This allows you to push beyond a general-purpose code review tool and tackle more complex and context-specific tasks with Devin,

    As teams master Devin.ai's capabilities, they can expand its role beyond security to other aspects of the development lifecycle. Code optimization, feature development, and automated testing are just a few areas where Devin can make an impact on your backlog.


    Join us

    Our team is small and talent-dense. Our founding team has 10 IOI gold medals and includes leaders and builders who have worked at the cutting edge of applied AI at companies like Cursor, Scale AI, Lunchclub, Modal, Google DeepMind, Waymo, and Nuro.‍

    Building Devin is just the first step—our hardest challenges still lie ahead. If you're excited to solve some of the world's biggest problems and build AI that can reason, learn more about our team and apply to one of the roles below.

    Open positions