django-octo-security 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_octo_security/__init__.py +3 -0
- django_octo_security/admin.py +131 -0
- django_octo_security/apps.py +7 -0
- django_octo_security/migrations/0001_initial.py +70 -0
- django_octo_security/migrations/__init__.py +0 -0
- django_octo_security/models.py +34 -0
- django_octo_security/scanner.py +139 -0
- django_octo_security/templates/admin/django_octo_security/cveauditscan/change_list.html +11 -0
- django_octo_security/templates/security_audit.html +58 -0
- django_octo_security/tests.py +1 -0
- django_octo_security/urls.py +8 -0
- django_octo_security/views.py +15 -0
- django_octo_security-0.1.0.dist-info/METADATA +129 -0
- django_octo_security-0.1.0.dist-info/RECORD +16 -0
- django_octo_security-0.1.0.dist-info/WHEEL +4 -0
- django_octo_security-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from django.contrib import admin, messages
|
|
2
|
+
from django.http import HttpResponseRedirect
|
|
3
|
+
from django.urls import path, reverse
|
|
4
|
+
from django.utils.html import format_html, format_html_join
|
|
5
|
+
from .models import CveAuditScan
|
|
6
|
+
from .scanner import run_full_cve_audit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@admin.register(CveAuditScan)
|
|
10
|
+
class CveAuditScanAdmin(admin.ModelAdmin):
|
|
11
|
+
list_display = (
|
|
12
|
+
"id",
|
|
13
|
+
"executed_at",
|
|
14
|
+
"total_packages",
|
|
15
|
+
"vulnerabilities_found",
|
|
16
|
+
"status_badge",
|
|
17
|
+
)
|
|
18
|
+
readonly_fields = (
|
|
19
|
+
"executed_at",
|
|
20
|
+
"total_packages",
|
|
21
|
+
"vulnerabilities_found",
|
|
22
|
+
"status",
|
|
23
|
+
"error_message",
|
|
24
|
+
"rendered_report",
|
|
25
|
+
)
|
|
26
|
+
exclude = ("report_data",)
|
|
27
|
+
actions = ["execute_cve_scan_action"]
|
|
28
|
+
change_list_template = "admin/django_octo_security/cveauditscan/change_list.html"
|
|
29
|
+
|
|
30
|
+
def get_urls(self):
|
|
31
|
+
urls = super().get_urls()
|
|
32
|
+
custom_urls = [
|
|
33
|
+
path(
|
|
34
|
+
"run-scan/",
|
|
35
|
+
self.admin_site.admin_view(self.run_scan_view),
|
|
36
|
+
name="django_octo_security_run_scan",
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
return custom_urls + urls
|
|
40
|
+
|
|
41
|
+
def run_scan_view(self, request):
|
|
42
|
+
result = run_full_cve_audit()
|
|
43
|
+
scan = CveAuditScan.objects.create(
|
|
44
|
+
total_packages=result["total_packages"],
|
|
45
|
+
vulnerabilities_found=len(result["vulnerabilities"]),
|
|
46
|
+
status=result["status"],
|
|
47
|
+
error_message=result["error"],
|
|
48
|
+
report_data=result["vulnerabilities"],
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if scan.status == "ERROR":
|
|
52
|
+
self.message_user(
|
|
53
|
+
request,
|
|
54
|
+
f"Audit #{scan.id} failed: {scan.error_message}",
|
|
55
|
+
level=messages.ERROR,
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
msg = f"Audit #{scan.id} completed: {scan.total_packages} packages checked, {scan.vulnerabilities_found} vulnerabilities detected."
|
|
59
|
+
self.message_user(
|
|
60
|
+
request,
|
|
61
|
+
msg,
|
|
62
|
+
level=messages.WARNING
|
|
63
|
+
if scan.vulnerabilities_found
|
|
64
|
+
else messages.SUCCESS,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
changelist_url = reverse("admin:django_octo_security_cveauditscan_changelist")
|
|
68
|
+
return HttpResponseRedirect(changelist_url)
|
|
69
|
+
|
|
70
|
+
@admin.display(description="Status")
|
|
71
|
+
def status_badge(self, obj):
|
|
72
|
+
palette = {
|
|
73
|
+
"CLEAN": ("#065f46", "#d1fae5", "CLEAN"),
|
|
74
|
+
"VULNERABLE": ("#991b1b", "#fee2e2", "VULNERABLE"),
|
|
75
|
+
"ERROR": ("#92400e", "#fef3c7", "API ERROR"),
|
|
76
|
+
}
|
|
77
|
+
fg, bg, text = palette.get(obj.status, ("#374151", "#f3f4f6", obj.status))
|
|
78
|
+
return format_html(
|
|
79
|
+
'<span style="color:{}; background:{}; padding:4px 10px; border-radius:4px; font-weight:700; font-size:11px;">{}</span>',
|
|
80
|
+
fg,
|
|
81
|
+
bg,
|
|
82
|
+
text,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@admin.display(description="Detailed CVE & Source Diagnostics")
|
|
86
|
+
def rendered_report(self, obj):
|
|
87
|
+
if obj.status == "ERROR":
|
|
88
|
+
return format_html(
|
|
89
|
+
'<p style="color:#b91c1c;"><strong>Query failure:</strong> {}</p>',
|
|
90
|
+
obj.error_message,
|
|
91
|
+
)
|
|
92
|
+
if not obj.report_data:
|
|
93
|
+
return format_html(
|
|
94
|
+
'<p style="color:#059669;"><strong>Clean environment:</strong> No known vulnerabilities found across {} packages.</p>',
|
|
95
|
+
obj.total_packages,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
cards = []
|
|
99
|
+
for item in obj.report_data:
|
|
100
|
+
aliases = ", ".join(item.get("aliases", [])) or "No CVE alias"
|
|
101
|
+
fixed = ", ".join(item.get("fixed_versions", [])) or "Check references"
|
|
102
|
+
refs_html = format_html_join(
|
|
103
|
+
"",
|
|
104
|
+
'<li><a href="{}" target="_blank" rel="noopener">{}</a></li>',
|
|
105
|
+
((url, url) for url in item.get("references", [])),
|
|
106
|
+
)
|
|
107
|
+
cards.append(
|
|
108
|
+
format_html(
|
|
109
|
+
'<div style="border:1px solid #e5e7eb; border-left:4px solid #dc2626; padding:12px; margin-bottom:12px; border-radius:4px; background:#fafafa;">'
|
|
110
|
+
'<h4 style="margin:0 0 6px 0; color:#111827;">{} (installed: <code>{}</code>) — <span style="color:#dc2626;">{}</span> <small>({})</small></h4>'
|
|
111
|
+
'<p style="margin:4px 0;"><strong>Summary:</strong> {}</p>'
|
|
112
|
+
'<p style="margin:4px 0;"><strong>Fixed Version:</strong> <code style="background:#dcfce7; color:#166534; padding:2px 6px;">{}</code></p>'
|
|
113
|
+
'<p style="margin:6px 0 2px 0;"><strong>Official References:</strong></p><ul style="margin:0; padding-left:18px;">{}</ul>'
|
|
114
|
+
"</div>",
|
|
115
|
+
item.get("package"),
|
|
116
|
+
item.get("installed_version"),
|
|
117
|
+
item.get("id"),
|
|
118
|
+
aliases,
|
|
119
|
+
item.get("summary"),
|
|
120
|
+
fixed,
|
|
121
|
+
refs_html,
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
return format_html("{}", format_html_join("", "{}", ((c,) for c in cards)))
|
|
125
|
+
|
|
126
|
+
@admin.action(description="Run new CVE environment audit")
|
|
127
|
+
def execute_cve_scan_action(self, request, queryset):
|
|
128
|
+
return self.run_scan_view(request)
|
|
129
|
+
|
|
130
|
+
def has_add_permission(self, request):
|
|
131
|
+
return False
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Generated by Django 6.1.2 on 2026-11-14 13:01
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
initial = True
|
|
8
|
+
|
|
9
|
+
dependencies = []
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.CreateModel(
|
|
13
|
+
name="CveAuditScan",
|
|
14
|
+
fields=[
|
|
15
|
+
(
|
|
16
|
+
"id",
|
|
17
|
+
models.BigAutoField(
|
|
18
|
+
auto_created=True,
|
|
19
|
+
primary_key=True,
|
|
20
|
+
serialize=False,
|
|
21
|
+
verbose_name="ID",
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
(
|
|
25
|
+
"executed_at",
|
|
26
|
+
models.DateTimeField(auto_now_add=True, verbose_name="Executed At"),
|
|
27
|
+
),
|
|
28
|
+
(
|
|
29
|
+
"total_packages",
|
|
30
|
+
models.PositiveIntegerField(
|
|
31
|
+
default=0, verbose_name="Packages Checked"
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
(
|
|
35
|
+
"vulnerabilities_found",
|
|
36
|
+
models.PositiveIntegerField(
|
|
37
|
+
default=0, verbose_name="Vulnerabilities Found"
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
(
|
|
41
|
+
"status",
|
|
42
|
+
models.CharField(
|
|
43
|
+
choices=[
|
|
44
|
+
("CLEAN", "Clean"),
|
|
45
|
+
("VULNERABLE", "Vulnerable"),
|
|
46
|
+
("ERROR", "Network Error"),
|
|
47
|
+
],
|
|
48
|
+
default="CLEAN",
|
|
49
|
+
max_length=20,
|
|
50
|
+
verbose_name="Status",
|
|
51
|
+
),
|
|
52
|
+
),
|
|
53
|
+
(
|
|
54
|
+
"error_message",
|
|
55
|
+
models.TextField(blank=True, default="", verbose_name="Error Log"),
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
"report_data",
|
|
59
|
+
models.JSONField(
|
|
60
|
+
blank=True, default=list, verbose_name="Technical Report (JSON)"
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
],
|
|
64
|
+
options={
|
|
65
|
+
"verbose_name": "CVE Checker",
|
|
66
|
+
"verbose_name_plural": "CVE Checker",
|
|
67
|
+
"ordering": ["-executed_at"],
|
|
68
|
+
},
|
|
69
|
+
),
|
|
70
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CveAuditScan(models.Model):
|
|
5
|
+
executed_at = models.DateTimeField(auto_now_add=True, verbose_name="Executed At")
|
|
6
|
+
total_packages = models.PositiveIntegerField(
|
|
7
|
+
default=0, verbose_name="Packages Checked"
|
|
8
|
+
)
|
|
9
|
+
vulnerabilities_found = models.PositiveIntegerField(
|
|
10
|
+
default=0, verbose_name="Vulnerabilities Found"
|
|
11
|
+
)
|
|
12
|
+
status = models.CharField(
|
|
13
|
+
max_length=20,
|
|
14
|
+
choices=[
|
|
15
|
+
("CLEAN", "Clean"),
|
|
16
|
+
("VULNERABLE", "Vulnerable"),
|
|
17
|
+
("ERROR", "Network Error"),
|
|
18
|
+
],
|
|
19
|
+
default="CLEAN",
|
|
20
|
+
verbose_name="Status",
|
|
21
|
+
)
|
|
22
|
+
error_message = models.TextField(blank=True, default="", verbose_name="Error Log")
|
|
23
|
+
report_data = models.JSONField(
|
|
24
|
+
default=list, blank=True, verbose_name="Technical Report (JSON)"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
class Meta:
|
|
28
|
+
app_label = "django_octo_security"
|
|
29
|
+
verbose_name = "CVE Checker"
|
|
30
|
+
verbose_name_plural = "CVE Checker"
|
|
31
|
+
ordering = ["-executed_at"]
|
|
32
|
+
|
|
33
|
+
def __str__(self):
|
|
34
|
+
return f"Audit #{self.id} — {self.get_status_display()} ({self.vulnerabilities_found} CVEs)"
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
import json
|
|
3
|
+
import ssl
|
|
4
|
+
import urllib.error
|
|
5
|
+
import urllib.request
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
OSV_API_HOST = "api.osv.dev"
|
|
9
|
+
OSV_BATCH_PATH = "/v1/querybatch"
|
|
10
|
+
OSV_VULN_PATH = "/v1/vulns/"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_ssl_context():
|
|
14
|
+
try:
|
|
15
|
+
import certifi
|
|
16
|
+
|
|
17
|
+
return ssl.create_default_context(cafile=certifi.where())
|
|
18
|
+
except ImportError:
|
|
19
|
+
return ssl.create_default_context()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
DEFAULT_HEADERS = {
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
"Accept": "application/json",
|
|
25
|
+
"User-Agent": "django-octo-security/0.1.0",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_installed_packages() -> list[dict]:
|
|
30
|
+
packages = []
|
|
31
|
+
for dist in importlib.metadata.distributions():
|
|
32
|
+
name = dist.metadata.get("Name")
|
|
33
|
+
version = dist.version
|
|
34
|
+
if name and version:
|
|
35
|
+
packages.append(
|
|
36
|
+
{"package": {"name": name, "ecosystem": "PyPI"}, "version": version}
|
|
37
|
+
)
|
|
38
|
+
return packages
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def fetch_vuln_full_details(vuln_id: str) -> dict:
|
|
42
|
+
url = f"https://{OSV_API_HOST}{OSV_VULN_PATH}{vuln_id}"
|
|
43
|
+
req = urllib.request.Request(
|
|
44
|
+
url,
|
|
45
|
+
headers={
|
|
46
|
+
"Accept": "application/json",
|
|
47
|
+
"User-Agent": "django-octo-security/0.1.0",
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
try:
|
|
51
|
+
with urllib.request.urlopen(
|
|
52
|
+
req, timeout=8, context=_get_ssl_context()
|
|
53
|
+
) as response:
|
|
54
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
55
|
+
import_source = data.get("database_specific", {}).get("import_source", "")
|
|
56
|
+
references = [
|
|
57
|
+
ref.get("url") for ref in data.get("references", []) if ref.get("url")
|
|
58
|
+
]
|
|
59
|
+
aliases = data.get("aliases", [])
|
|
60
|
+
fixed_versions = []
|
|
61
|
+
for affected in data.get("affected", []):
|
|
62
|
+
for rng in affected.get("ranges", []):
|
|
63
|
+
for event in rng.get("events", []):
|
|
64
|
+
if "fixed" in event:
|
|
65
|
+
fixed_versions.append(event["fixed"])
|
|
66
|
+
return {
|
|
67
|
+
"id": data.get("id", vuln_id),
|
|
68
|
+
"aliases": aliases,
|
|
69
|
+
"summary": data.get("summary") or "No summary available.",
|
|
70
|
+
"import_source": import_source,
|
|
71
|
+
"references": references[:5],
|
|
72
|
+
"fixed_versions": list(set(fixed_versions)),
|
|
73
|
+
}
|
|
74
|
+
except Exception as exc:
|
|
75
|
+
return {
|
|
76
|
+
"id": vuln_id,
|
|
77
|
+
"aliases": [],
|
|
78
|
+
"summary": f"Failed to retrieve details: {exc}",
|
|
79
|
+
"import_source": "",
|
|
80
|
+
"references": [],
|
|
81
|
+
"fixed_versions": [],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def run_full_cve_audit() -> dict:
|
|
86
|
+
packages = get_installed_packages()
|
|
87
|
+
if not packages:
|
|
88
|
+
return {
|
|
89
|
+
"total_packages": 0,
|
|
90
|
+
"vulnerabilities": [],
|
|
91
|
+
"status": "CLEAN",
|
|
92
|
+
"error": "",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
batch_url = f"https://{OSV_API_HOST}{OSV_BATCH_PATH}"
|
|
96
|
+
payload = json.dumps({"queries": packages}).encode("utf-8")
|
|
97
|
+
req = urllib.request.Request(
|
|
98
|
+
batch_url,
|
|
99
|
+
data=payload,
|
|
100
|
+
headers=DEFAULT_HEADERS,
|
|
101
|
+
method="POST",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
enriched_findings = []
|
|
105
|
+
try:
|
|
106
|
+
with urllib.request.urlopen(
|
|
107
|
+
req, timeout=15, context=_get_ssl_context()
|
|
108
|
+
) as response:
|
|
109
|
+
batch_data = json.loads(response.read().decode("utf-8"))
|
|
110
|
+
results = batch_data.get("results", [])
|
|
111
|
+
for pkg_info, res in zip(packages, results):
|
|
112
|
+
for v in res.get("vulns", []):
|
|
113
|
+
vuln_id = v.get("id")
|
|
114
|
+
if vuln_id:
|
|
115
|
+
details = fetch_vuln_full_details(vuln_id)
|
|
116
|
+
details["package"] = pkg_info["package"]["name"]
|
|
117
|
+
details["installed_version"] = pkg_info["version"]
|
|
118
|
+
enriched_findings.append(details)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
"total_packages": len(packages),
|
|
122
|
+
"vulnerabilities": enriched_findings,
|
|
123
|
+
"status": "VULNERABLE" if enriched_findings else "CLEAN",
|
|
124
|
+
"error": "",
|
|
125
|
+
}
|
|
126
|
+
except urllib.error.HTTPError as exc:
|
|
127
|
+
return {
|
|
128
|
+
"total_packages": len(packages),
|
|
129
|
+
"vulnerabilities": [],
|
|
130
|
+
"status": "ERROR",
|
|
131
|
+
"error": f"OSV API HTTP {exc.code} ({exc.reason})",
|
|
132
|
+
}
|
|
133
|
+
except Exception as exc:
|
|
134
|
+
return {
|
|
135
|
+
"total_packages": len(packages),
|
|
136
|
+
"vulnerabilities": [],
|
|
137
|
+
"status": "ERROR",
|
|
138
|
+
"error": str(exc),
|
|
139
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{% extends "admin/change_list.html" %}
|
|
2
|
+
{% load i18n admin_urls %}
|
|
3
|
+
|
|
4
|
+
{% block object-tools-items %}
|
|
5
|
+
<li>
|
|
6
|
+
<a href="{% url 'admin:django_octo_security_run_scan' %}" class="addlink" style="background: #0284c7; color: #ffffff; font-weight: 600;">
|
|
7
|
+
Run CVE Environment Audit
|
|
8
|
+
</a>
|
|
9
|
+
</li>
|
|
10
|
+
{{ block.super }}
|
|
11
|
+
{% endblock %}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Django-Octo Security — Runtime & CVE Audit</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #f8fafc; margin: 0; padding: 2rem; }
|
|
9
|
+
.container { max-width: 820px; margin: 0 auto; }
|
|
10
|
+
.card { background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1.5rem; margin-bottom: 1.5rem; }
|
|
11
|
+
h1 { font-size: 1.5rem; margin-top: 0; color: #38bdf8; }
|
|
12
|
+
h2 { font-size: 1.1rem; color: #94a3b8; border-bottom: 1px solid #334155; padding-bottom: 0.5rem; }
|
|
13
|
+
.badge { display: inline-block; padding: 0.25rem 0.6rem; border-radius: 4px; font-weight: 700; font-size: 0.8rem; }
|
|
14
|
+
.clean { background: #065f46; color: #6ee7b7; }
|
|
15
|
+
.vuln { background: #7f1d1d; color: #fca5a5; }
|
|
16
|
+
.pending { background: #334155; color: #cbd5e1; }
|
|
17
|
+
table { width: 100%; border-collapse: collapse; margin-top: 1rem; font-size: 0.9rem; }
|
|
18
|
+
td, th { text-align: left; padding: 0.6rem; border-bottom: 1px solid #334155; }
|
|
19
|
+
code { background: #0f172a; padding: 0.2rem 0.4rem; border-radius: 4px; color: #38bdf8; }
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<div class="container">
|
|
24
|
+
<div class="card">
|
|
25
|
+
<h1>Django-Octo Security Engine</h1>
|
|
26
|
+
<p>Decoupled runtime hardening and environment CVE audit module.</p>
|
|
27
|
+
{% if latest_scan %}
|
|
28
|
+
<p>
|
|
29
|
+
<strong>Latest Audit (#{{ latest_scan.id }}):</strong>
|
|
30
|
+
{% if latest_scan.status == "CLEAN" %}
|
|
31
|
+
<span class="badge clean">CLEAN ({{ latest_scan.total_packages }} packages)</span>
|
|
32
|
+
{% elif latest_scan.status == "VULNERABLE" %}
|
|
33
|
+
<span class="badge vuln">VULNERABLE ({{ latest_scan.vulnerabilities_found }} CVEs)</span>
|
|
34
|
+
{% else %}
|
|
35
|
+
<span class="badge pending">{{ latest_scan.get_status_display }}</span>
|
|
36
|
+
{% endif %}
|
|
37
|
+
</p>
|
|
38
|
+
{% else %}
|
|
39
|
+
<p><span class="badge pending">No audit executed yet — access /admin/ to run a scan</span></p>
|
|
40
|
+
{% endif %}
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="card">
|
|
44
|
+
<h2>Active Security Headers</h2>
|
|
45
|
+
<table>
|
|
46
|
+
<thead>
|
|
47
|
+
<tr><th>HTTP Header</th><th>Enforced Policy</th></tr>
|
|
48
|
+
</thead>
|
|
49
|
+
<tbody>
|
|
50
|
+
{% for header, value in security_headers.items %}
|
|
51
|
+
<tr><td><code>{{ header }}</code></td><td><code>{{ value }}</code></td></tr>
|
|
52
|
+
{% endfor %}
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</body>
|
|
58
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Create your tests here.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from django.shortcuts import render
|
|
2
|
+
from .models import CveAuditScan
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def security_audit_view(request):
|
|
6
|
+
latest_scan = CveAuditScan.objects.first()
|
|
7
|
+
context = {
|
|
8
|
+
"latest_scan": latest_scan,
|
|
9
|
+
"security_headers": {
|
|
10
|
+
"X-Frame-Options": "DENY",
|
|
11
|
+
"X-Content-Type-Options": "nosniff",
|
|
12
|
+
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
return render(request, "security_audit.html", context)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: django-octo-security
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Enterprise-ready Django runtime security and zero-dependency environment CVE audit module under the Django-Octo architecture.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jeanpmodev/django_octo_security
|
|
6
|
+
Project-URL: Repository, https://github.com/jeanpmodev/django_octo_security
|
|
7
|
+
Author-email: jeanpmodev <jeanpmodev@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: audit,cve,django,hardening,osv,security,vulnerability-scanner
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Security
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Requires-Dist: django>=6.1.2
|
|
18
|
+
Requires-Dist: python-dotenv
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# django_octo_security
|
|
22
|
+
|
|
23
|
+
[](LICENSE)
|
|
24
|
+
[](https://python.org)
|
|
25
|
+
[](https://djangoproject.com)
|
|
26
|
+
[](https://astral.sh)
|
|
27
|
+

|
|
28
|
+
[](https://djangopackages.org/packages/p/django_octo_security/)
|
|
29
|
+
[](https://github.com/jeanpmodev/django_octo_security/blob/main/TESTING.md)
|
|
30
|
+
[](https://github.com/jeanpmodev/django_octo_security/blob/main/TESTING.md)
|
|
31
|
+
[](https://github.com/jeanpmodev/django_octo_security/blob/main/performance/locust_load_report.pdf)
|
|
32
|
+
[](https://github.com/jeanpmodev/django_octo_security/actions/workflows/ci.yml)
|
|
33
|
+
[](https://peps.python.org/pep-0008/)
|
|
34
|
+
[](https://pypi.org/project/django-octo-security/#provenance)
|
|
35
|
+
|
|
36
|
+
**Enterprise-ready, non-intrusive Django runtime security posture and zero-dependency environment CVE audit module under the Django-Octo architecture.**
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Core Philosophy: Non-Intrusive Dual Access
|
|
41
|
+
|
|
42
|
+
`django-octo-security` is engineered to plug into any existing Django application **without altering your host project's native logic, templates, or root routes**:
|
|
43
|
+
|
|
44
|
+
1. **Public/Internal Posture Endpoint (`/security-audit/`):** Single-query database lookup (`CveAuditScan.objects.first()`) rendering enforced HTTP security headers and the latest environment vulnerability audit status.
|
|
45
|
+
2. **Native Django Admin Integration (`Security > CVE Checker`):** Automatically registers a dedicated administrative module in the left sidebar of `/admin/` with one-click environment scanning (`Run CVE Environment Audit`), querying the official **OSV.dev** vulnerability database via Python standard library (`importlib.metadata` + `urllib.request`) with **zero external runtime dependencies**.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Security & Audit Architecture
|
|
50
|
+
|
|
51
|
+
| Layer | Mechanism / Target | Engineering Purpose |
|
|
52
|
+
| :--- | :--- | :--- |
|
|
53
|
+
| **01** | **Clickjacking Shield** | Enforces `X-Frame-Options: DENY` to prevent unauthorized iframe embedding. |
|
|
54
|
+
| **02** | **MIME Sniffing Guard** | Enforces `X-Content-Type-Options: nosniff` against content-type confusion attacks. |
|
|
55
|
+
| **03** | **Referrer Isolation** | Applies `Referrer-Policy: strict-origin-when-cross-origin` to prevent token leakage. |
|
|
56
|
+
| **04** | **Single-Query Posture View** | Renders `/security-audit/` in a single ORM query for high-throughput scalability. |
|
|
57
|
+
| **05** | **Runtime Introspection** | Reads active virtual environment packages via native `importlib.metadata`. |
|
|
58
|
+
| **06** | **Batch OSV Vulnerability Query** | Cross-references all installed PyPI packages against `api.osv.dev/v1/querybatch`. |
|
|
59
|
+
| **07** | **Advisory Enrichment & Fixes** | Resolves CVE aliases, patched versions, and official references per vulnerability. |
|
|
60
|
+
| **08** | **Immutable Audit Persistence** | Stores historical snapshots (`CveAuditScan`) directly in the host database. |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Plug-and-Play Installation
|
|
65
|
+
|
|
66
|
+
Install the official package directly into any modern Django (`>=6.1.2`) and Python (`>=3.12`) infrastructure using **pip** or **Rye**:
|
|
67
|
+
|
|
68
|
+
### 1. Clone & Synchronize Environment
|
|
69
|
+
Clone the repository and trigger the Rust-backed Rye environment compiler to lock and synchronize dependencies:
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/jeanpmodev/django_octo_security.git
|
|
72
|
+
cd django_octo_security
|
|
73
|
+
rye sync
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### 2. Register in `settings.py`
|
|
78
|
+
Add `"django_octo_security"` to your `INSTALLED_APPS`:
|
|
79
|
+
```python
|
|
80
|
+
INSTALLED_APPS = [
|
|
81
|
+
# ... your existing apps
|
|
82
|
+
"django_octo_security",
|
|
83
|
+
]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3. Include the Audit Route in `urls.py`
|
|
87
|
+
Including the module at the root preserves your host application's homepage (`/`) and mounts the posture report at `/security-audit/`:
|
|
88
|
+
```python
|
|
89
|
+
from django.contrib import admin
|
|
90
|
+
from django.urls import include, path
|
|
91
|
+
|
|
92
|
+
urlpatterns = [
|
|
93
|
+
path("admin/", admin.site.urls),
|
|
94
|
+
path("", include("django_octo_security.urls")),
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 4. Apply Migrations
|
|
99
|
+
```bash
|
|
100
|
+
python3 manage.py migrate
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
### 5. Initialize Runtime Service
|
|
105
|
+
Fire up the local development engine instance:
|
|
106
|
+
```bash
|
|
107
|
+
python3 manage.py runserver
|
|
108
|
+
```
|
|
109
|
+
Navigate to `127.0.0.1:8000/security-audit/` to inspect live.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Usage
|
|
114
|
+
|
|
115
|
+
* **Security Posture Screen:** Navigate to `http://127.0.0.1:8000/security-audit/` to inspect active HTTP security policies and the latest environment CVE audit badge.
|
|
116
|
+
* **Admin CVE Checker:** Log in to `http://127.0.0.1:8000/admin/`, open **Security > CVE Checker** in the left navigation bar, and click **Run CVE Environment Audit** at the top right to scan all installed Python packages in real time.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Quality Assurance & Testing
|
|
122
|
+
|
|
123
|
+
This module is backed by a dedicated QA suite covering **Unit & Query-Count Tests (`pytest`)**, **BDD Specifications (`behave`)**, and **Load Testing (`locust`)**. To keep the core package minimal, test dependencies are installed on demand—see [TESTING.md](TESTING.md) for instructions and benchmark reports.
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
## đŸ“„ License & Ecosystem Blueprint
|
|
127
|
+
|
|
128
|
+
Distributed under the permissive terms of the **MIT License**.
|
|
129
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
django_octo_security/__init__.py,sha256=6IDSSveaGiOUyL_8cXbuMVsaSgzvhKp7WGIviatml0s,92
|
|
2
|
+
django_octo_security/admin.py,sha256=WhExEN-C_tiAOFTzyI3N6XTyd0v08oQCVqCU1w5ZZfk,5214
|
|
3
|
+
django_octo_security/apps.py,sha256=fbPYVJPmpiEvWKe14t750MONDsoxafebiHa0nD7ulmQ,200
|
|
4
|
+
django_octo_security/models.py,sha256=UIjsUkcwN1LKbtdjZL4cWfgCOzdVAy7turI6N2ZUswQ,1142
|
|
5
|
+
django_octo_security/scanner.py,sha256=8HFqH5XU5hOe0-b516em6qN4YGLu6k2Uz7rErpslWbg,4447
|
|
6
|
+
django_octo_security/tests.py,sha256=qWDvA9ZhVCQ1rPbkoFify7o_fDirXMUdYMxF12q3WIM,26
|
|
7
|
+
django_octo_security/urls.py,sha256=g3WO6ybXdTxVx2edDkinZAiW8D-xIuIfegUWHE3JMk8,186
|
|
8
|
+
django_octo_security/views.py,sha256=0-eO6rnyxbM-XxT4oYjxz_SNt9Msv-hqs1wOY3VENGA,464
|
|
9
|
+
django_octo_security/migrations/0001_initial.py,sha256=JmROQkumAZdJUXSRLcLlA7-Sx0kN-8BR_G15T0v7qF0,2254
|
|
10
|
+
django_octo_security/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
django_octo_security/templates/security_audit.html,sha256=AkoX-MR1NP8uhbjYjAgWXFxkqnWk1sZPjebMgRjL3sY,2848
|
|
12
|
+
django_octo_security/templates/admin/django_octo_security/cveauditscan/change_list.html,sha256=ZwbpE4oVEEnXFWzNhYTBZtcKe3qEIJhtw06T8g_r3r8,351
|
|
13
|
+
django_octo_security-0.1.0.dist-info/METADATA,sha256=reWRMU09m1Bkm-ibE0TEliAyiYeaGLCbykjtBxgs2Z8,6657
|
|
14
|
+
django_octo_security-0.1.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
15
|
+
django_octo_security-0.1.0.dist-info/licenses/LICENSE,sha256=E7iyIHGFXOmOjqnNkyF1f846sz1z1VLj9qeCC_EeDTQ,1067
|
|
16
|
+
django_octo_security-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jeanpmodev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|