Developers / DevOps / Security

Enhance Your Salesforce DevOps Security With an AI-Powered User Analysis Report

By Bassem Marji

Updated December 09, 2025

With Salesforce serving as the backbone for sales, service, and marketing operations, safeguarding user accounts has become a top priority for security teams.

This urgency has been underscored throughout 2025 with attackers exploiting Salesforce integrations in the Salesloft Drift supply-chain breach, stealing OAuth tokens and exposing sensitive data from hundreds of companies, including Cloudflare and Palo Alto Networks. There have also been numerous direct breaches of Salesforce customers via social engineering. 

Although Salesforce’s auditing tools – like LoginHistory and SetupAuditTrail – track user logins and administrative changes, they produce dense and technical logs. Turning raw data like IP addresses and failed logins into actionable insights, such as detecting an administrator logging in from two continents within 30 minutes, requires significant manual effort that often slips through traditional monitoring.

This is where automation and AI can help. By combining Salesforce’s native logging with GitHub Actions automation and local large language models (via Ollama) for analysis, we can create a cost-effective pipeline that automatically collects user activity data, detects suspicious patterns, and generates human-readable reports.

This article demonstrates how to build such a pipeline around three core principles:

  1. Automated data collection: No manual exports, always up to date.
  2. AI-powered insights: turning logs into contextual security findings.
  3. Actionable reporting: structured recommendations for admins and auditors.

By the end, you’ll see how a GitHub Actions workflow can act as your 24/7 Salesforce Security Analyst, securing sensitive data without costly integrations.

Objective

This article introduces a fully automated GitHub Actions pipeline, named the AI-Powered User Activity Analyzer, and designed to: 

  • Gather user login and audit data from Salesforce.
  • Leverage local AI model (via Ollama) for behavioral analysis. 
  • Produce clear, comprehensive security reports. 
  • Operate on-demand, securely, and cost-efficiently.

Key Benefits

  • Detects suspicious activities (e.g., privileged account misuse, repeated failed logins).
  • Uncovers policy violations (e.g., unauthorized admin changes or password resets).
  • Streamlines compliance audits and reporting for frameworks like SOC 2 and HIPAA.
  • Saves money & time by leveraging local AI to avoid expensive SIEM integrations and cloud API costs.
  • Democratizes security by delivering AI-driven insights accessible to both technical and business teams.

This solution is perfect for Salesforce Administrators, Security Analysts, and DevOps teams seeking to enhance monitoring with intelligent, secure analytics while keeping sensitive data protected from third-party AI platforms.

READ MORE: Complete Guide to Salesforce DevOps

Use Cases 

  • Periodic Security Audits: Run the pipeline to produce compliance-focused reports. 
  • Breach Investigation: Examine user activity to respond to potential security incidents.
  • SSO Rollout Monitoring: Track login patterns following single sign-on deployment.
  • Administrative Control: Identify unauthorized changes to permissions.
  • User Onboarding Audit: Verify JIT provisioning behavior.

Core Technologies Powering the User Activity Analysis Pipeline

This pipeline leverages a mix of CI/CD orchestration, Salesforce tooling, and local AI inference. Let’s look into its underlying technologies:

CI/CD 

CI/CD (Continuous Integration and Continuous Delivery/Deployment) automates the process of integrating code changes and deploying them reliably. For Salesforce, this automation helps find problems early by running tests and checks before releases.

Ollama

An open-source platform for running large language models (LLMs) like Mistral locally with minimal setup. Its simple command-line and REST API allow it to be hosted on the same runner as the pipeline, eliminating the data privacy risks of cloud-based AI services and offering several advantages:

  • Data Sovereignty: In-memory analysis with no external data transmission.
  • Offline Capability: No internet connection required once the models are downloaded.
  • Developer Flexibility: Easy integration into scripts and custom automation tools.

Meta Llama 3

Designed as a versatile, general-purpose, open-source model that excels at a wide range of tasks, including dialogue, brainstorming, coding, and creative writing.

Available in 8B and 70B parameter sizes, Llama 3 is built on a standard decoder-only transformer architecture and is trained on a massive dataset of over 15 trillion tokens to ensure high performance and improved reasoning capabilities.

Its balanced design and strong performance across industry benchmarks make it an excellent choice for developers looking for a powerful, all-around model to kickstart innovation.

OpenAI GPT-OSS

Engineered with a Mixture-of-Experts (MoE) architecture to excel at complex reasoning, tool use, and agentic workflows.

Models Comparison at a Glance:

FeatureMeta LlamaOpenAI GPT-OSS
DeveloperMetaOpenAI
Key StrengthGeneral-purpose capabilities across dialogue, coding, and reasoning.Advanced reasoning, agentic tasks, and tool use (e.g., API chaining).
Model Sizes8B, 70B, 405B parameters (Llama 3.1 variants).20B, 120B parameters.
Defining FeatureHigh performance per parameter; multilingual support in 8 languages.Configurable reasoning effort (Low, Medium, High) for adaptive compute.
Release DateApril 18, 2024 (initial); July 23, 2024 (3.1 update).August 5, 2025.

The Solution Architecture

Let’s discover how this pipeline operates.

Overview

This workflow is initiated manually via GitHub’s workflow_dispatch event and runs in two sequential jobs:

  1. collect-user-data: Authenticates to your Salesforce org, queries user activity data (login history, user details, and security audit trails) for a configurable time window, and aggregates it into JSON artifacts.
  2. analyze-user-activity: Conditionally downloads the artifacts (only if data exists), sets up Ollama for local AI inference, and generates a comprehensive Markdown security report using the selected LLM model.

By processing data locally, this self-contained pipeline ensures privacy while scanning for security anomalies such as suspicious logins. It then generates actionable insights, enabling efficient DevOps auditing with no external dependencies.

The runtime of this pipeline varies depending on data volume and the selected model size.

The following diagram illustrates the pipeline’s architecture and its jobs workflow:

Prerequisites

Before implementing the pipeline, ensure you have:

  1. GitHub Repository: Sign in to GitHub and create a new private repository for your Salesforce project as shown below:
  1. Generate Salesforce Authentication URL: Using the Salesforce CLI, authenticate to your org and generate an authentication URL by running this command in your terminal:

> sf force:auth:web:login --alias <OrgAlias> --instance-url <OrgURL>  --set-default

Then, follow these steps:

  1. When the browser opens, log in to Salesforce.
  2. Close the tab after successful authentication.
  1. Execute this command in your terminal:

> sf org display --target-org <OrgAlias> --verbose

  1. Copy the generated SFDX Auth URL and store it in a secure location for later use.

Security Note: SFDX Auth URLs must be classified and protected as highly sensitive credentials due to their ability to grant permissions equivalent to the associated Salesforce user. As development orgs frequently employ administrative profiles, exposure could result in a full compromise of the organization.

Secret NameDescription
ORG_SFDX_URLSFDX authentication URL for your Salesforce org
  1. Store the collected authentication URL in GitHub Secrets: Navigate to Repository Settings → Secrets and variables → Actions. Select New repository secret and add the following:
  1. Enable Workflow Permissions: Grant permissions for actions and reusable workflows by selecting the “Allow all actions and reusable workflows” setting:

Implementation

  • Create a new file named “UserActivityAnalyzer.yml” in your repository’s /.github/workflows/ folder.
  • Paste the following YAML code snippet into the new file.
  • Save and commit your changes to enable the workflow.
name: AI Powered User Activity Analyzer

on:
  workflow_dispatch:
    inputs:
      org_alias:
        type: string
        description: "Salesforce org alias"
        required: true
        default: "dev"
      days_back:
        type: number
        description: "Number of days back to retrieve user activity data"
        required: true
        default: 30
      model_name:
        type: choice
        description: "Ollama model to use"
        options:
          - llama3:latest
          - gpt-oss:latest
        default: llama3:latest

env:
  ORG_ALIAS: ${{ github.event.inputs.org_alias }}
  DAYS_BACK: ${{ github.event.inputs.days_back }}
  MODEL_NAME: ${{ github.event.inputs.model_name }}
  NODE_VERSION: "20"
  DATA_DIR: "user-activity-data"

jobs:
  collect-user-data:
    name: Collect User Activity Data
    runs-on: ubuntu-latest
    outputs:
      login_count: ${{ steps.query-logins.outputs.login_count }}
      user_count: ${{ steps.query-users.outputs.user_count }}
      has_data: ${{ steps.check-data.outputs.has_data }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Install dependencies
        run: |
          npm install --global @salesforce/cli
          sf plugins:update
          sudo apt-get update && sudo apt-get install -y jq
      
      - name: 🔐 Authenticate to Salesforce Org
        run: |
          echo "${{ secrets.ORG_SFDX_URL }}" | sf org login sfdx-url --alias $ORG_ALIAS --set-default --sfdx-url-stdin
          sf org list

      - name: Query Login History
        id: query-logins
        run: |
          mkdir -p "$DATA_DIR"
          START_TIME=$(date -u -d "$DAYS_BACK days ago" '+%Y-%m-%dT%H:%M:%S.000Z')
          echo "🔍 Querying login history since: $START_TIME"
          OFFSET=0
          > "$DATA_DIR/login_history_all.json"
          while true; do
            RESULT_FILE="$DATA_DIR/login_history_$OFFSET.json"
            sf data query \
              --query "SELECT Id, UserId, LoginTime, LoginType, SourceIp, Platform, Browser, Status FROM LoginHistory WHERE LoginTime >= $START_TIME ORDER BY LoginTime DESC LIMIT 500 OFFSET $OFFSET" \
              --target-org "$ORG_ALIAS" \
              --result-format json > "$RESULT_FILE"
            COUNT=$(jq -r '.result.records | length' "$RESULT_FILE")
            [ "$COUNT" -eq 0 ] && break
            OFFSET=$((OFFSET + 500))
          done
          # Merge paginated files into one JSON
          jq -s '{result:{records:map(.result.records) | add, totalSize:(map(.result.records)|add|length)}}' "$DATA_DIR"/login_history_*.json > "$DATA_DIR/login_history.json"

      - name: Aggregate Logins
        id: aggregate-logins
        run: |
          login_count=$(jq -r '.result.totalSize // 0' "$DATA_DIR/login_history.json")
          echo "login_count=$login_count" >> $GITHUB_OUTPUT
      
      - name: Query User Information
        id: query-users
        timeout-minutes: 5
        run: |
          # Get unique user IDs from login history
          user_ids=$(jq -r '.result.records[].UserId' "$DATA_DIR/login_history.json" | sort -u | head -100)
          user_count=$(echo "$user_ids" | wc -l)
          
          if [ "$user_count" -gt 0 ]; then
            # Convert user IDs to SOQL IN clause format
            user_ids_formatted=$(echo "$user_ids" | sed "s/^/'/g" | sed "s/$/'/g" | tr '\n' ',' | sed 's/,$//')
            
            # Query user details
            USER_QUERY="SELECT Id, Username, Name, Email, Profile.Name, UserRole.Name, 
                               IsActive, LastLoginDate, CreatedDate, LastModifiedDate,
                               Department, Division, Title, CompanyName, City, State, Country,
                               TimeZoneSidKey, LocaleSidKey, LanguageLocaleKey, UserType,
                               LastPasswordChangeDate, NumberOfFailedLogins, MobilePhone
                          FROM User 
                         WHERE Id IN ($user_ids_formatted)"
            
            echo "Executing User details SOQL query..."
            sf data query \
              --query "$USER_QUERY" \
              --target-org "$ORG_ALIAS" \
              --result-format json > "$DATA_DIR/user_details.json"
              
            echo "📊 Found details for $user_count users"
          fi
          
          echo "user_count=$user_count" >> $GITHUB_OUTPUT
      
      - name: Query Additional Security Data
        timeout-minutes: 10
        run: |
          # Query SetupAuditTrail for security-related changes
          SETUP_START_TIME=$(date -u -d "$DAYS_BACK days ago" '+%Y-%m-%dT%H:%M:%S.000Z')

          SETUP_QUERY="SELECT Id, Action, Section, CreatedDate, CreatedById, CreatedBy.Username,
                              CreatedBy.Name, Display, DelegateUser, ResponsibleNamespacePrefix
                       FROM SetupAuditTrail 
                       WHERE CreatedDate >= $SETUP_START_TIME 
                       AND (Action LIKE '%User%' OR Action LIKE '%Login%' OR Action LIKE '%Password%' 
                            OR Action LIKE '%Profile%' OR Action LIKE '%Permission%' OR Action LIKE '%Security%')
                       ORDER BY CreatedDate DESC"

          echo "Executing SetupAuditTrail SOQL query in batched mode..."
          sf data query \
            --query "$SETUP_QUERY" \
            --target-org "$ORG_ALIAS" \
            --result-format json \
            --all > "$DATA_DIR/setup_audit_trail.json" || echo "⚠️ SetupAuditTrail query failed (may not have access)"
      
      - name: Check Data Availability
        id: check-data
        run: |
          login_count=$(jq -r '.result.totalSize' "$DATA_DIR/login_history.json" 2>/dev/null || echo "0")
          
          if [ "$login_count" -eq 0 ]; then
            echo "⚠️ No login data found in the last $DAYS_BACK days"
            echo "has_data=false" >> $GITHUB_OUTPUT
            echo "No user activity data found in the last $DAYS_BACK days" > "$DATA_DIR/no-data-found.txt"
          else
            echo "✅ Found user activity data to analyze"
            echo "has_data=true" >> $GITHUB_OUTPUT
            
            # Create summary statistics
            jq -r --arg days "$DAYS_BACK" '{
              summary: {
                days_analyzed: ($days | tonumber),
                total_logins: .result.totalSize,
                analysis_period: {
                  start: (.result.records | map(.LoginTime) | min),
                  end: (.result.records | map(.LoginTime) | max)
                },
                unique_users: (.result.records | map(.UserId) | unique | length),
                unique_ips: (.result.records | map(.SourceIp) | unique | length),
                login_types: (.result.records | group_by(.LoginType) | map({type: .[0].LoginType, count: length})),
                platforms: (.result.records | group_by(.Platform) | map({platform: .[0].Platform, count: length}))
              }
            }' "$DATA_DIR/login_history.json" > "$DATA_DIR/data_summary.json"
          fi
      - name: Upload User Activity Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: user-activity-data
          path: ${{ env.DATA_DIR }}
          retention-days: 7
          if-no-files-found: warn

      - name: 📄 Summary of Collected Data
        run: |
          {
            echo "## 📊 Data Collection Summary" 
            echo "- Org Alias: **$ORG_ALIAS**" 
            echo "- Analysis Period: **$DAYS_BACK days**"
            echo "- Login Records: **${{ steps.query-logins.outputs.login_count }}**" 
            echo "- Users Analyzed: **${{ steps.query-users.outputs.user_count }}**"
            if [ "${{ steps.check-data.outputs.has_data }}" = "true" ]; then
              echo "- Data Available: ✅ **Yes**"
              echo "- Artifact: \`user-activity-data\`"
            else
              echo "- Data Available: ❌ **No data found**"
            fi
          } >> $GITHUB_STEP_SUMMARY
         
  analyze-user-activity:
    name: Analyze User Activity with AI
    runs-on: ubuntu-latest
    needs: collect-user-data
    if: needs.collect-user-data.outputs.has_data == 'true'
    steps:
      - name: Download User Activity Artifacts
        uses: actions/download-artifact@v4
        with:
          name: user-activity-data
          path: data

      - name: Install Ollama
        run: |
          curl -fsSL https://ollama.com/install.sh -o install.sh
          chmod +x install.sh && ./install.sh
          echo "$HOME/.ollama/bin" >> $GITHUB_PATH
          export PATH="$HOME/.ollama/bin:$PATH"
          ollama serve &
          for i in {1..30}; do
            curl -s http://localhost:11434/api/version && break
            sleep 2
          done
      - name: Download Model
        timeout-minutes: 30
        run: |
          MODEL="${{ inputs.model_name }}"
          echo "📥 Downloading Ollama model: $MODEL"
          ollama pull "$MODEL"
          
          echo "🔍 Verifying model installation..."
          ollama list
      - name: Analyze User Activity Data
        run: |
          REPORT_FILE="user_activity_analysis.md"
          MODEL="${{ inputs.model_name }}"
      
          # Initialize report
          echo "# AI User Activity Analysis Report" > "$REPORT_FILE"
          echo "**Analysis Date:** $(date)" >> "$REPORT_FILE"
          echo "**Model Used:** $MODEL" >> "$REPORT_FILE"
          echo "**Period:** Last ${{ inputs.days_back }} days" >> "$REPORT_FILE"
          echo "" >> "$REPORT_FILE"
          
          # Add data summary if available
          if [ -f "data/data_summary.json" ]; then
            echo "## 📊 Data Overview" >> "$REPORT_FILE"
            echo '```json' >> "$REPORT_FILE"
            jq '.' data/data_summary.json >> "$REPORT_FILE"
            echo '```' >> "$REPORT_FILE"
            echo "" >> "$REPORT_FILE"
          fi
      
          # Generate comprehensive analysis prompt
          generate_analysis_prompt() {
            local login_data="$1"
            local user_data="$2"
            local audit_data="$3"
            
            cat <<EOF
            You are a Salesforce Security Analyst specializing in user activity and login behavior analysis.
            Analyze the provided Salesforce user activity data and produce a comprehensive security assessment report covering all aspects: suspicious logins, user behavior patterns, and security anomalies.
            
            **Data Provided:**
            1. LoginHistory records with IP addresses, browsers, platforms, geographic data
            2. User account details with profiles, roles, and account status
            3. SetupAuditTrail records for administrative changes (if available)
            
            **Required Output Format (Markdown):**
            
            ## 🚨 Critical Security Findings
            | Severity | Finding | Evidence | Recommendation |
            |----------|---------|----------|----------------|
            | HIGH/MEDIUM/LOW | Description | Supporting data | Action to take |
            
            ## 🌍 Geographic & Network Analysis
            | Category | Details | Risk Level |
            |----------|---------|-----------|
            | Unusual Locations | Countries/cities not typical for org | HIGH/MEDIUM/LOW |
            | Suspicious IPs | IP addresses with multiple user logins | HIGH/MEDIUM/LOW |
            | VPN/Proxy Usage | Potential proxy or VPN indicators | HIGH/MEDIUM/LOW |
            
            ## 👤 User Behavior Patterns
            | User | Pattern Observed | Risk Assessment |
            |------|------------------|-----------------|
            | Username | Behavioral description | Risk level + reasoning |
            
            ## ⏰ Temporal Analysis
            - **Peak login times:** [List unusual login timing patterns]
            - **After-hours activity:** [Suspicious off-hours logins]
            - **Weekend/holiday logins:** [Unexpected login timing]
            
            ## 🔐 Authentication & Access Patterns
            - **Failed login clusters:** [Multiple failed attempts patterns]
            - **Rapid location changes:** [Same user, different locations quickly]
            - **Device/browser anomalies:** [Unusual client patterns]
            
            ## 📋 Administrative Activity Review
            [If SetupAuditTrail data available - review admin changes, user modifications, permission changes]
            
            **Analysis Rules:**
            - Flag logins from new countries/unusual locations
            - Identify users with failed login spikes
            - Detect rapid geographic changes (impossible travel)
            - Highlight privileged user suspicious activity
            - Note unusual login timing patterns
            - Identify shared accounts or credential concerns
            - Be specific with user IDs, IP addresses, and timestamps
            - Provide actionable security recommendations
            - Rate findings as HIGH/MEDIUM/LOW risk
            - Base conclusions ONLY on provided data
            
            **Data:**
            LOGIN HISTORY:
            $login_data
            
            USER DETAILS:
            $user_data
            
            AUDIT TRAIL:
            $audit_data
          EOF
          }
      
          # Prepare data for analysis
          login_data=""
          user_data=""
          audit_data=""
          
          if [ -f "data/login_history.json" ]; then
            login_data=$(cat data/login_history.json)
          fi
          
          if [ -f "data/user_details.json" ]; then
            user_data=$(cat data/user_details.json)
          fi
          
          if [ -f "data/setup_audit_trail.json" ]; then
            audit_data=$(cat data/setup_audit_trail.json)
          fi
      
          # Generate analysis
          echo "🤖 Generating comprehensive AI analysis..."
          generate_analysis_prompt "$login_data" "$user_data" "$audit_data" | ollama run "$MODEL" >> "$REPORT_FILE"
          
          # Add technical appendix
          echo -e "\n---\n" >> "$REPORT_FILE"
          echo "## 📋 Technical Data Summary" >> "$REPORT_FILE"
          echo "- **Total Login Records:** $(jq -r '.result.totalSize // 0' data/login_history.json 2>/dev/null || echo 'N/A')" >> "$REPORT_FILE"
          echo "- **Analysis Period:** ${{ inputs.days_back }} days" >> "$REPORT_FILE"
          echo "- **Report Generated:** $(date)" >> "$REPORT_FILE"
          
      - name: Upload Analysis Report
        uses: actions/upload-artifact@v4
        with:
          name: user-activity-analysis-report
          path: user_activity_analysis.md
          retention-days: 14

      - name: 📄 Analysis Summary
        env:
          MODEL: ${{ inputs.model_name }}
        run: |
          {
            echo "## 🧠 User Activity Analysis Summary"
            login_count=$(jq -r '.result.totalSize // 0' data/login_history.json 2>/dev/null || echo '0')
            user_count=$(jq -r '.result.records | map(.UserId) | unique | length' data/login_history.json 2>/dev/null || echo '0')
            echo "- **Login Records Analyzed:** $login_count" 
            echo "- **Unique Users:** $user_count"
            echo "- **AI Model:** \`$MODEL\`"
            echo "- **Report:** \`user_activity_analysis.md\`"
            echo "- **Artifact:** \`user-activity-analysis-report\`" 
            echo ""
          } >> $GITHUB_STEP_SUMMARY

Running and Monitoring the Pipeline

To execute and keep track of your workflow:

  1. Navigate to your GitHub repository and select the “Actions” tab in the top navigation bar:
  2. Trigger the workflow:
    • In the left sidebar, locate and select the “AI Powered User Activity Analyzer” workflow.
    • Click the “Run workflow” dropdown, verify the input parameters, and click to execute.
  1. Monitor Execution: Expand the job instances to inspect logs and outputs.
  1. Check the Results: To view the analysis results, download the user-activity-analysis-report artifact and open the markdown file it contains.

Final Thoughts

Traditional monitoring tools often fall short, requiring manual log reviews, complex dashboards, or expensive SIEM integrations. 

This pipeline demonstrates how DevOps, open-source AI, and Salesforce can converge to create a powerful, secure, and intelligent monitoring system.

The Author

Bassem Marji

Bassem is a certified Salesforce Administrator, a Project Implementation Manager at BLOM Bank Lebanon, and a Technical Author at Educative. He is a big fan of the Salesforce ecosystem.

Leave a Reply