Skip to content

Commit d22fa58

Browse files
committed
Make the Security Scanning as a blocking validator for critical bandit and secrects issues
1 parent ffb3aea commit d22fa58

16 files changed

+1526
-217
lines changed

qgis-app/plugins/api.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from django.utils.translation import gettext_lazy as _
1616
from plugins.models import *
1717
from plugins.validator import validator
18-
from plugins.views import plugin_notify
18+
from plugins.views import plugin_notify, send_upload_confirmation_email
1919
from rpc4django import rpcmethod
2020
from taggit.models import Tag
2121

@@ -120,7 +120,10 @@ def plugin_upload(package, **kwargs):
120120
package.len,
121121
"UTF-8",
122122
),
123-
"approved": request.user.has_perm("plugins.can_approve") or plugin.approved,
123+
# Always start unapproved; async security checks will auto-approve
124+
# trusted users after validation completes.
125+
"approved": False,
126+
"validation_status": VALIDATION_STATUS_VALIDATING,
124127
}
125128

126129
# Optional version metadata
@@ -133,6 +136,13 @@ def plugin_upload(package, **kwargs):
133136
new_version = PluginVersion(**version_data)
134137
new_version.clean()
135138
new_version.save()
139+
140+
# Send Stage 1 upload confirmation email
141+
send_upload_confirmation_email(new_version)
142+
143+
# Queue async security scan task
144+
from plugins.tasks.run_security_scan import run_security_scan_task
145+
run_security_scan_task.delay(new_version.pk)
136146
except IntegrityError as e:
137147
# Avoids error: current transaction is aborted, commands ignored until
138148
# end of transaction block
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.28 on 2026-03-12 02:16
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('plugins', '0019_remove_pluginversion_supports_qt6'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='pluginversion',
15+
name='validation_status',
16+
field=models.CharField(choices=[('pending', 'Pending'), ('validating', 'Validating'), ('validated', 'Validated'), ('blocked', 'Blocked')], db_index=True, default='pending', help_text="Validation status based on security and quality checks. New uploads start as 'validating' until checks complete. 'blocked' means critical issues were found and the version is not available for approval or download.", max_length=20, verbose_name='Validation status'),
17+
),
18+
]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import django.utils.timezone
2+
from django.db import migrations, models
3+
4+
5+
class Migration(migrations.Migration):
6+
7+
dependencies = [
8+
("plugins", "0020_pluginversion_validation_status"),
9+
]
10+
11+
operations = [
12+
migrations.AlterField(
13+
model_name="pluginversionsecurityscan",
14+
name="scanned_on",
15+
field=models.DateTimeField(
16+
default=django.utils.timezone.now,
17+
verbose_name="Scanned on",
18+
),
19+
),
20+
]

qgis-app/plugins/models.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,19 @@ def get_queryset(self):
786786
)
787787

788788

789+
VALIDATION_STATUS_PENDING = "pending"
790+
VALIDATION_STATUS_VALIDATING = "validating"
791+
VALIDATION_STATUS_VALIDATED = "validated"
792+
VALIDATION_STATUS_BLOCKED = "blocked"
793+
794+
VALIDATION_STATUS_CHOICES = [
795+
(VALIDATION_STATUS_PENDING, _("Pending")),
796+
(VALIDATION_STATUS_VALIDATING, _("Validating")),
797+
(VALIDATION_STATUS_VALIDATED, _("Validated")),
798+
(VALIDATION_STATUS_BLOCKED, _("Blocked")),
799+
]
800+
801+
789802
def vjust(str, level=3, delim=".", bitsize=3, fillchar=" ", force_zero=False):
790803
"""
791804
Normalize a dotted version string.
@@ -934,6 +947,22 @@ class PluginVersion(models.Model):
934947
null=True,
935948
)
936949
is_from_token = models.BooleanField(_("Is uploaded using token"), default=False)
950+
951+
# Validation status for security and QA checks
952+
validation_status = models.CharField(
953+
_("Validation status"),
954+
max_length=20,
955+
choices=VALIDATION_STATUS_CHOICES,
956+
default=VALIDATION_STATUS_PENDING,
957+
db_index=True,
958+
help_text=_(
959+
"Validation status based on security and quality checks. "
960+
"New uploads start as 'validating' until checks complete. "
961+
"'blocked' means critical issues were found and the version "
962+
"is not available for approval or download."
963+
),
964+
)
965+
937966
# Link to the token if upload is using token
938967
token = models.ForeignKey(
939968
PluginOutstandingToken,
@@ -949,6 +978,17 @@ class PluginVersion(models.Model):
949978
stable_objects = StablePluginVersions()
950979
experimental_objects = ExperimentalPluginVersions()
951980

981+
@property
982+
def is_available(self):
983+
"""
984+
Returns True if the version is available for download/approval
985+
(not blocked by security checks).
986+
"""
987+
return self.validation_status not in [
988+
VALIDATION_STATUS_VALIDATING,
989+
VALIDATION_STATUS_BLOCKED,
990+
]
991+
952992
@property
953993
def file_name(self):
954994
return os.path.basename(self.package.file.name)
@@ -1185,7 +1225,7 @@ class PluginVersionSecurityScan(models.Model):
11851225
PluginVersion, on_delete=models.CASCADE, related_name="security_scan"
11861226
)
11871227
scanned_on = models.DateTimeField(
1188-
_("Scanned on"), auto_now_add=True, editable=False
1228+
_("Scanned on"), default=timezone.now, editable=False
11891229
)
11901230

11911231
# Summary statistics

qgis-app/plugins/security_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def run_security_scan(plugin_version):
2121
PluginVersionSecurityScan instance or None if scan fails
2222
"""
2323
try:
24+
from django.utils import timezone
25+
2426
# Get the package file path
2527
package_path = plugin_version.package.path
2628

@@ -32,6 +34,7 @@ def run_security_scan(plugin_version):
3234
security_scan, created = PluginVersionSecurityScan.objects.update_or_create(
3335
plugin_version=plugin_version,
3436
defaults={
37+
"scanned_on": timezone.now(),
3538
"total_checks": report["summary"]["total_checks"],
3639
"passed_checks": report["summary"]["passed"],
3740
"warning_count": report["summary"]["warnings"],

qgis-app/plugins/tasks/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
from plugins.tasks.update_qgis_versions import * # noqa
33
from plugins.tasks.rebuild_search_index import * # noqa
44
from plugins.tasks.get_sustaining_members import * # noqa
5+
from plugins.tasks.run_security_scan import * # noqa

0 commit comments

Comments
 (0)