# How to Add Custom SAST Patterns in MobileAudit: Complete Guide to the Pattern Engine

> Learn how to add custom SAST patterns in the MobileAudit pattern engine by modifying the Pattern database model at runtime. Enhance your security analysis today.

- Repository: [Mónica Pastor/mobileaudit](https://github.com/mpast/mobileaudit)
- Tags: how-to-guide
- Published: 2026-03-07

---

**You can add or modify custom SAST patterns in MobileAudit by creating or updating rows in the `Pattern` database model, which the engine dynamically loads and compiles at runtime without requiring code redeployment.**

MobileAudit's static application security testing (SAST) engine uses a flexible, database-driven architecture that makes extending detection capabilities a data operation rather than a development task. By manipulating the `Pattern` model defined in [`app/models.py`](https://github.com/mpast/mobileaudit/blob/main/app/models.py), security teams can introduce custom regex rules or tune existing ones instantly. This guide covers three methods to manage these rules—Django admin, shell scripting, and version-controlled migrations—while explaining exactly how the pattern engine consumes them during scans.

## Understanding the Pattern Engine Architecture

The core scanning logic resides in [`app/analysis.py`](https://github.com/mpast/mobileaudit/blob/main/app/analysis.py) inside the `find_patterns()` function. According to the MobileAudit source code, this function queries all active patterns from the database, compiles each stored regex with `re.compile(p.pattern, re.MULTILINE)`, and executes the search against every line of decompiled APK source files.

When a match occurs, the engine instantiates a `Finding` object that inherits metadata from the matched `Pattern` row—specifically the `default_name`, `default_description`, `default_severity`, `default_cwe`, and `default_mitigation` fields. Because this lookup happens at scan time, any change to the `Pattern` table is immediately effective for subsequent analyses.

## Method 1: Quick Pattern Addition via Django Admin

For ad-hoc rule creation or rapid prototyping, use the built-in Django administrative interface.

1. Start the development server and navigate to the admin endpoint (e.g., `http://localhost:8000/admin/`).
2. Log in as a superuser (create one with `./manage.py createsuperuser` if necessary).
3. Select **Patterns** under the app section, then click **Add Pattern**.
4. Populate the following fields:

   - **default_cwe** — Create or select a `Cwe` entry (e.g., `798` for "Use of Hard-coded Credentials").
   - **default_risk** — Link to a `Risk` entry (e.g., risk level `3` for High).
   - **default_name** — `Hard-coded API key`.
   - **default_description** — `Detects API keys that are hard-coded in source files.`
   - **default_severity** — `HI` (High) or `CR` (Critical) using the `Severity` enum.
   - **default_mitigation** — `Move the key to a secure vault or environment variable.`
   - **pattern** — `(?i)api[_-]?key\s*=\s*["\'][A-Za-z0-9]{32,}["\']`
   - **active** — Check this box to enable the rule immediately.

Saving the record instantly makes the pattern available to the next scan; no service restart is required.

## Method 2: Programmatic Pattern Management with Django Shell

For scripted automation or bulk updates, use Django's interactive shell to interact directly with the ORM.

Open the shell:

```bash
./manage.py shell

```

Create a new custom SAST pattern:

```python
from app.models import Pattern, Cwe, Risk, Severity

# Ensure related metadata exists

cwe, _ = Cwe.objects.get_or_create(
    cwe=798,
    defaults={'description': 'Use of Hard-coded Credentials'}
)
risk, _ = Risk.objects.get_or_create(
    risk=3,
    defaults={'description': 'High', 'reference': ''}
)

# Insert the new pattern

new_pat = Pattern.objects.create(
    default_cwe=cwe,
    default_risk=risk,
    default_name='Hard-coded API key',
    default_description='Detects API keys that appear as literals.',
    default_severity=Severity.HI,
    default_mitigation='Store keys in a secret manager.',
    pattern=r'(?i)api[_-]?key\s*=\s*["\'][A-Za-z0-9]{32,}["\']',
    active=True,
)
print(f'Created pattern ID {new_pat.id}')

```

Modify an existing pattern by ID:

```python
Pattern.objects.filter(id=12).update(
    pattern=r'(?i)secret\s*=\s*["\'].*["\']',
    default_name='Hard-coded secret',
    default_severity=Severity.CR,
)
print('Pattern updated.')

```

Because `find_patterns()` reads the database at runtime, these changes are effective immediately.

## Method 3: Version-Controlled Pattern Deployment

To persist custom SAST patterns in your repository history and deploy them across environments, create a Django data migration.

Generate an empty migration:

```bash
./manage.py makemigrations app --empty -n add_custom_api_key_pattern

```

Edit the generated file (e.g., [`app/migrations/000X_add_custom_api_key_pattern.py`](https://github.com/mpast/mobileaudit/blob/main/app/migrations/000X_add_custom_api_key_pattern.py)):

```python
from django.db import migrations

def create_pattern(apps, schema_editor):
    Pattern = apps.get_model('app', 'Pattern')
    Cwe = apps.get_model('app', 'Cwe')
    Risk = apps.get_model('app', 'Risk')
    Severity = apps.get_model('app', 'Severity')

    cwe, _ = Cwe.objects.get_or_create(
        cwe=798,
        defaults={'description': 'Use of Hard-coded Credentials'}
    )
    risk, _ = Risk.objects.get_or_create(
        risk=3,
        defaults={'description': 'High', 'reference': ''}
    )

    Pattern.objects.create(
        default_cwe=cwe,
        default_risk=risk,
        default_name='Hard-coded API key',
        default_description='Detects API keys that appear as literals.',
        default_severity=Severity.HI,
        default_mitigation='Store keys in a secret manager.',
        pattern=r'(?i)api[_-]?key\s*=\s*["\'][A-Za-z0-9]{32,}["\']',
        active=True,
    )

class Migration(migrations.Migration):
    dependencies = [
        ('app', 'previous_migration_name'),  # Update this

    ]

    operations = [
        migrations.RunPython(create_pattern),
    ]

```

Commit this migration to version control. Any environment running `./manage.py migrate` will automatically receive the new pattern.

## How the Engine Consumes Patterns at Runtime

The scan execution follows a strict four-phase pipeline implemented in [`app/analysis.py`](https://github.com/mpast/mobileaudit/blob/main/app/analysis.py):

1. **Loading** — `find_patterns()` executes `Pattern.objects.filter(active=True)` to retrieve the current rule set.
2. **Compilation** — Each `p.pattern` string is compiled with `re.compile(p.pattern, re.MULTILINE)`, enabling multiline regex matching.
3. **Matching** — The compiled regex iterates over every line of every decompiled source file in the APK.
4. **Persistence** — Upon match, a `Finding` row is created, copying the pattern's default metadata (name, description, severity, CWE, remediation).

This architecture means that adding, editing, or deactivating patterns is purely a data operation requiring no code redeployment or container rebuilds.

## Bulk Import and Pattern Lifecycle Management

### Importing Patterns from JSON

For bulk migration of rules from other tools, use a shell script to import a JSON definition file:

```python
import json
from app.models import Pattern, Cwe, Risk, Severity

with open('custom_patterns.json') as f:
    data = json.load(f)

for entry in data:
    cwe, _ = Cwe.objects.get_or_create(
        cwe=entry['cwe'],
        defaults={'description': entry.get('cwe_desc', '')}
    )
    risk, _ = Risk.objects.get_or_create(
        risk=entry['risk'],
        defaults={'description': entry.get('risk_desc', ''), 'reference': ''}
    )

    Pattern.objects.update_or_create(
        pattern=entry['regex'],
        defaults={
            'default_cwe': cwe,
            'default_risk': risk,
            'default_name': entry['name'],
            'default_description': entry['description'],
            'default_severity': getattr(Severity, entry['severity']),
            'default_mitigation': entry.get('mitigation', ''),
            'active': entry.get('active', True),
        }
    )
print('Import completed.')

```

Execute the script via:

```bash
./manage.py shell < import_patterns.py

```

### Deactivating Patterns via API

The `patterns` view in [`app/views.py`](https://github.com/mpast/mobileaudit/blob/main/app/views.py) handles activation toggles. To deactivate a pattern programmatically (e.g., ID 12) without deleting it:

```bash
curl -X POST -d "status=inactive&12=on" \
     -b cookies.txt http://localhost:8000/patterns/

```

This sets `active=False` on the specified row, removing it from subsequent scans while preserving the rule for audit history.

## Summary

- **Custom SAST patterns** in MobileAudit are database rows in the `Pattern` model defined in [`app/models.py`](https://github.com/mpast/mobileaudit/blob/main/app/models.py).
- The engine loads and compiles these patterns dynamically in `app/analysis.py → find_patterns()`, requiring no restart when rules change.
- **Three management methods** exist: Django Admin for quick edits, Django Shell for scripting, and data migrations for version-controlled deployments.
- Patterns support full metadata (CWE, Risk, Severity, remediation) and standard Python regex syntax with multiline flags.
- Deactivation preserves historical findings while stopping future matches.

## Frequently Asked Questions

### Do I need to restart MobileAudit after adding a custom pattern?

No. The `find_patterns()` function queries the database at scan time, compiling regexes fresh for each analysis. Changes to active patterns are effective immediately for the next scan without restarting the application server or workers.

### What regex flags does the MobileAudit pattern engine use?

As implemented in `mpast/mobileaudit`, the engine compiles every pattern with `re.MULTILINE` enabled. This allows anchors like `^` and `$` to match the start and end of each line rather than the entire file. You can embed additional flags (e.g., `(?i)` for case-insensitive matching) directly inside your pattern string.

### Can I deactivate a pattern without losing its historical findings?

Yes. Deactivating a pattern sets `active=False` on the row, which excludes it from future scans. However, existing `Finding` records linked to that pattern remain in the database for audit trails and compliance reporting. You can reactivate the pattern later by toggling the flag back to `True`.

### How do I modify an existing pattern's regex without creating a duplicate?

Use the Django shell to update the specific row by ID or by unique pattern string. For example, `Pattern.objects.filter(id=12).update(pattern=r'new-regex-here')` will modify the existing rule in place. If using the Django Admin, simply edit the row and save; the engine will pick up the updated regex on the next scan automatically.