How to Add Custom SAST Patterns in MobileAudit: Complete Guide to the Pattern Engine
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, 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 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.
-
Start the development server and navigate to the admin endpoint (e.g.,
http://localhost:8000/admin/). -
Log in as a superuser (create one with
./manage.py createsuperuserif necessary). -
Select Patterns under the app section, then click Add Pattern.
-
Populate the following fields:
- default_cwe — Create or select a
Cweentry (e.g.,798for "Use of Hard-coded Credentials"). - default_risk — Link to a
Riskentry (e.g., risk level3for High). - default_name —
Hard-coded API key. - default_description —
Detects API keys that are hard-coded in source files. - default_severity —
HI(High) orCR(Critical) using theSeverityenum. - 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.
- default_cwe — Create or select a
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:
./manage.py shell
Create a new custom SAST pattern:
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:
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:
./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):
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:
- Loading —
find_patterns()executesPattern.objects.filter(active=True)to retrieve the current rule set. - Compilation — Each
p.patternstring is compiled withre.compile(p.pattern, re.MULTILINE), enabling multiline regex matching. - Matching — The compiled regex iterates over every line of every decompiled source file in the APK.
- Persistence — Upon match, a
Findingrow 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:
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:
./manage.py shell < import_patterns.py
Deactivating Patterns via API
The patterns view in app/views.py handles activation toggles. To deactivate a pattern programmatically (e.g., ID 12) without deleting it:
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
Patternmodel defined inapp/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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →