Printed from CheckTick DSPT Compliance Documentation
Vulnerability & Patch Log (Evidence 8.2.1)
Owner: Dr Simon Chapman (CTO) Reviewed and Approved By: Dr Serena Haywood (SIRO) Last SIRO Review: March 2026 Next Review: June 2026 Last Updated: 1 September 2026 (djangorestframework 3.16.1 -> 3.18.0 โ CVE-2026-73228 and CVE-2026-73229 resolved)
Threat intelligence is sourced from NCSC Early Warning, GitHub Security Advisories, and OWASP and is reviewed as a standing item in our quarterly security meeting. Significant threat intelligence events are recorded in this log.
Monitoring Tools: pip-audit, ggshield (via pre-commit), GitHub Dependabot, CodeQL.
1. Active Vulnerability Exceptions (Open & Ignored)
Current Status: 4 active exceptions. All are dev-only transitive dependencies of isolated pre-commit tooling (ggshield/pip-audit); none ship to production. Monitoring for upstream fixes.
| Dependency | CVE/PYSEC | Affected Version | Justification | Date Added | Review Date |
|---|---|---|---|---|---|
markdown |
PYSEC-2026-89 | 3.10.2 (latest; OSV database issue) | OSV database has not cleared this advisory against latest release. Package was updated from 3.10 โ 3.10.2 (the fix was acknowledged by the vendor for 3.8.1+). pip-audit continues to flag 3.10.2 with no fix version listed, indicating the OSV record is either stale or incorrectly scoped. We are on the latest available release. Exception will be removed once the OSV/PYSEC record is corrected upstream or a further patched release is confirmed clear by pip-audit. |
20/05/2026 | 20/06/2026 |
msgpack |
GHSA-6v7p-g79w-8964 | 1.1.2 (transitive via pip-audit โ cachecontrol) |
Dev-only dependency of pip-audit's HTTP cache. No production exposure. cachecontrol caps msgpack<2.0.0; fix (1.2.1) is within range but pip-audit has not refreshed its lock. Exception will be removed when pip-audit/cachecontrol ships a patched msgpack. |
13/07/2026 | 13/08/2026 |
python-dotenv |
PYSEC-2026-2270 | 0.21.1 (transitive via ggshield) |
Local symlink-following in set_key()/unset_key() when rewriting .env files. Dev-only transitive dep of ggshield (isolated pre-commit tool, not shipped to production). ggshield 1.43.0 pins python-dotenv and ggshield 1.53.0 caps cryptography<49, conflicting with our required cryptography 50.0.0 security fix (CVE-2026-69247/8/9). Cannot bump until GitGuardian releases a version supporting cryptography 50. No code path in CheckTick calls set_key()/unset_key(). |
04/08/2026 | 04/09/2026 |
setuptools |
PYSEC-2026-3447 | 80.9.0 (transitive via pygitguardian) |
macOS-only NFC/NFD MANIFEST.in exclusion bypass in sdist building. Dev-only transitive dep of pygitguardian (via ggshield, isolated pre-commit tool). Exploitable only on macOS APFS/HFS+ when building an sdist with non-ASCII exclusion rules โ not part of CheckTick's release pipeline (Poetry-based, no MANIFEST.in). Cannot bump independently; pygitguardian pins setuptools. |
04/08/2026 | 04/09/2026 |
Our security scanning pipeline otherwise operates with zero vulnerability exceptions. All dependencies are monitored in real-time and any new vulnerabilities will cause immediate CI/CD pipeline failure.
2. Application-Layer Security Findings (August 2026 Deep-Dive)
A full static security review of the CheckTick codebase was performed on 01/08/2026 (see security-review-august-2026.md). These are application-layer findings (not dependency CVEs) identified by source review. They are tracked here so they appear alongside the dependency vulnerability record. Remediation is via atomic PRs with regression tests.
| Ref | Severity | Title | Status |
|---|---|---|---|
| F6 | High | Web recovery console bypasses Shamir custodian-share control | Resolved 01/08/2026 โ NM-02 closed |
| F12 | High | SVG upload โ stored XSS via direct /media/ access |
Resolved 01/08/2026 โ NM-03 closed |
| F1 | Medium | Open redirect via protocol-relative next URLs |
Resolved 01/08/2026 โ Django host/scheme validation and regression tests |
| F2 | Medium | HTML injection into team/org invitation emails | Resolved 01/08/2026 โ autoescaped templates, escaped fallbacks, and regression tests |
| F7 | Medium | LLM debug dump writes full payloads to world-readable /tmp |
Resolved 02/08/2026 โ private, retention-bounded dump dir and regression tests |
| F8 | Medium | DataSetViewSet permission class inconsistent with anonymous access |
Resolved 02/08/2026 โ datasets API now authenticated-only; SSR options for respondents |
| F13 | Medium | Survey icon_url accepts javascript: and data: URIs |
Resolved 02/08/2026 โ protocol allowlist at write time + _sanitise_brand_overrides at read time |
| F14 | Medium | Billing webhook has no replay protection (no timestamp/idempotency) | Resolved 02/08/2026 โ WebhookEvent idempotency table + per-event get_or_create in transaction.atomic() |
| F3 | Low | Silent SECRET_KEY fallback in production |
Resolved 02/08/2026 โ ImproperlyConfigured raised at startup when ENVIRONMENT=production and SECRET_KEY unset/empty; random fallback retained for dev only |
| F4 | Low | Weak DRF default permission class | Resolved 02/08/2026 โ global DRF default changed to IsAuthenticated (fail-closed); anonymous-access endpoints already declare AllowAny |
| F9 | Low | CSP style-src 'unsafe-inline' weakens style-injection defence |
Resolved 02/08/2026 โ documented accepted risk; CSS-injection surface mitigated server-side via strengthened sanitize_css_block (F16) |
| F10 | Low | OIDC next parameter inherits F1 open-redirect class |
Resolved 02/08/2026 โ dead HealthcareLoginView removed; live /accounts/login/ page inherits LoginView.get_redirect_url() validation |
| F11 | Low | API-key last_used_at write on every request |
Resolved 02/08/2026 โ cache-based throttle limits last_used_at writes to once per 60s per key |
| F15 | Low | LLM prompt-injection defence overclaimed in docs | Resolved 02/08/2026 โ llm-security.md ยง4 reframed; instruction-based enforcement is a deterrent, output validation is the boundary |
| F16 | Low | sanitize_css_block only strips <>, allowing } breakout |
Resolved 02/08/2026 โ sanitize_css_block now strips { } and url() references |
| F17 | Low | Runtime mutation of global settings in OIDC callback view (thread-safety) | Resolved 02/08/2026 โ per-request get_settings override; global-settings mutation block removed |
| F18 | Low | SNOMED snapshot view bypasses dataset-creation permission | Resolved 02/08/2026 โ snapshot enforces require_can_create_datasets; org assignment restricted to ADMIN/CREATOR |
| F5 | Info | F-string email builders bypass template autoescaping | Resolved 02/08/2026 โ email_utils module docstring documents the .md-template / f-string convention |
No Critical findings. No patient data exposure at rest. The two High findings (F6, F12) are recorded as resolved near-misses NM-02 and NM-03 in the Incident & Near-Miss Log. F12 remediation removed SVG upload support, added regression coverage, and confirmed through a production audit that no legacy SVG media existed. F1 remediation replaced slash-prefix checks with Django's url_has_allowed_host_and_scheme, constrained redirects to the current host, and requires HTTPS when the request is secure. Regression tests confirm protocol-relative, backslash-normalised, and external URLs are rejected while valid local redirects are preserved; successful signup still marks the email unconfirmed, redirects to the home page, and displays the confirmation notice. F2 remediation added the missing team and organisation invitation Markdown templates, restoring Django autoescaping before Markdown-to-HTML conversion, and explicitly escapes all dynamic values in the template-missing fallbacks. Regression tests cover both invitation types and both normal and fallback rendering paths. F7 remediation replaced the three inline /tmp/llm_response_*.json dump sites in checktick_app/surveys/llm_client.py with a single _write_llm_debug_dump helper that writes to settings.BASE_DIR / "logs" / "llm" with mode 0o600 (directory 0o700), prunes files older than 24h on each call, omits the outgoing messages payload (so user prompts are never written to disk), and is blocked in production unless LLM_DEBUG_DUMP_INSECURE=1 is also set. Regression tests in tests/test_llm_debug_dump_security.py cover the disabled case, the private directory and file modes, the messages-omission invariant, the production gate, the insecure-flag override, the 24h retention pruning, and an end-to-end check that ConversationalSurveyLLM.chat routes dumps through the helper. F8 remediation removed all anonymous access from the datasets API: the only anonymous consumer (professional-fields.js, which fetched dataset options into professional-field dropdowns on the survey respondent page) was replaced by server-side rendering via _get_professional_dataset_options() in checktick_app/surveys/views.py, the misleading IsOrgAdminOrCreator permission class was replaced by DataSetAccess (authentication required for every action, non-safe methods rejected), and the AllowAny override on the available-tags action was removed. Queryset scoping (global + own org + own individual datasets) is retained as defence in depth. Regression tests in checktick_app/api/tests/test_dataset_api.py assert anonymous list/retrieve/available-tags requests are denied, and checktick_app/surveys/tests/test_anonymous_access.py asserts anonymous respondents on public surveys receive server-rendered professional-field options with no client-side /api/datasets/ call.
F18 remediation (identified during the follow-up audit of the dataset web views performed as part of the F8 work): the SNOMED snapshot view dataset_snomed_snapshot created new datasets without enforcing the dataset-creation permission, allowing read-only org roles (VIEWER, DATA_CUSTODIAN) to create org-visible datasets; it also assigned snapshots to the user's first organisation regardless of role. The view now calls require_can_create_datasets before any work and assigns the snapshot only to an organisation where the user is ADMIN or CREATOR (personal dataset otherwise). The dataset detail template gates the snapshot/clone buttons on the real can_create_datasets permission, and the misleadingly-named context-processor flag was renamed can_view_datasets. Regression tests in checktick_app/surveys/tests/test_dataset_views.py cover denied anonymous/VIEWER/DATA_CUSTODIAN snapshot attempts, permitted CREATOR and individual-user snapshots with correct org assignment, and hidden buttons for VIEWERs.
F13 remediation: survey_style_update in checktick_app/surveys/views.py now validates icon_url at write time, accepting only http://, https://, and root-relative / paths and rejecting javascript:, data:, file:, and vbscript: schemes. The same validation was added to the platform-level SiteBranding.icon_url / icon_url_dark fields in checktick_app/core/views.py. As defence in depth, a new _sanitise_brand_overrides helper (using is_safe_url from checktick_app/core/theme_utils.py) re-checks icon_url and font_css_url at read time across all five survey views that build brand_overrides (survey_detail, survey_preview, survey_dashboard, survey_groups, group_builder), so values written via admin, import, or legacy data that bypass the write-time validator are still neutralised before reaching respondent-facing templates. Regression tests in checktick_app/surveys/tests/test_xss_creation_forms.py cover javascript:/data:/file:/vbscript: rejection, safe http(s):// and relative-path acceptance, and a defence-in-depth read-time guard asserting a javascript: URI stored directly in survey.style is not rendered on the public survey take page.
F16 remediation: sanitize_css_block in checktick_app/core/theme_utils.py was strengthened to strip { and } (preventing CSS rule breakout from the wrapping [data-theme="..."] { ... } rule) and url() references plus bare http(s):// URLs (preventing CSS-based data exfiltration via background-image: url(attacker/?...)), in addition to the existing < / > strip that prevents </style> breakout. The fallback in checktick_app/context_processors.py was updated to match. The existing usage renders theme_css_light/theme_css_dark as --var: value; declarations inside a fixed [data-theme] rule, so the input only ever needs values โ never rule blocks or external URL loads โ so stripping {} and url() is safe for the intended input. Regression tests in checktick_app/core/tests/test_theme_utils.py cover angle-bracket breakout, curly-brace breakout, the combined brace + url() exfiltration scenario, and preservation of safe --var: value; declarations. End-to-end tests in checktick_app/surveys/tests/test_xss_creation_forms.py assert a } [data-theme='custom'] { background: url(...) } payload stored in survey.style.theme_css_light does not produce a usable CSS rule on the dashboard or group-builder pages.
F9 remediation: per the review's recommended fix #3, style-src 'unsafe-inline' is retained because hCaptcha and DaisyUI genuinely require inline styles, and the relaxation is documented in checktick_app/settings.py (comment on the style-src directive) and docs/security-overview.md (ยงA03 XSS Prevention โ Content Security Policy). The CSS-injection surface that 'unsafe-inline' would otherwise expose is mitigated server-side by the strengthened sanitize_css_block (F16), which strips {, }, and url() references so injected CSS cannot form new rules or exfiltrate via background-image: url(...). Regression tests in checktick_app/core/tests/test_csp.py assert the CSP header is emitted, style-src does not allow a bare wildcard origin, script-src does not carry 'unsafe-inline', and the 'unsafe-inline' relaxation is documented in both settings.py and docs/security-overview.md so the accepted risk is auditable.
F14 remediation: the GoCardless webhook handler in checktick_app/core/views_billing.py verified the HMAC-SHA256 signature correctly but had no replay protection โ a captured body+signature could be replayed indefinitely to re-run payments.confirmed, subscriptions.created, and refund handlers, duplicating Payment rows, re-sending welcome emails, and re-triggering other side effects. A new WebhookEvent model (checktick_app/core/models.py, migration 0026_webhookevent) stores each processed GoCardless event id with a unique=True constraint on event_id. The payment_webhook handler now wraps each event in a transaction.atomic() block that calls WebhookEvent.objects.get_or_create(event_id=event_id) before dispatching; if the row already exists the event is skipped (logged at INFO) and no handler runs. Event routing was extracted into a single _dispatch_gocardless_event helper so the idempotency guard is the one chokepoint for every resource type (subscriptions, payments, refunds, mandates). The idempotency row is created before the handler runs, so a crash mid-handler rolls back both the side effects and the idempotency row (GoCardless retries the event on the next webhook). Events missing an id are skipped with a warning. A read-only WebhookEventAdmin provides an audit view of processed events. The review's recommended fix #2 (timestamp freshness) was investigated and confirmed not applicable: GoCardless's Webhook-Signature header is a bare HMAC-SHA256 hex digest of the body with no timestamp, so event-id idempotency is the primary defence. Recommended fix #3 (per-handler idempotency as defence in depth) is retained and now backed by a database constraint: handle_gocardless_payment_confirmed still guards duplicate Payment rows via payment_id, refund handlers still deduplicate via _refund_event_already_logged against the audit trail, and a partial unique constraint payment_provider_payment_id_unique on Payment.payment_id (migration 0027_payment_provider_payment_id_unique, condition payment_id != '') enforces at the database level that a replayed payments.confirmed cannot create a duplicate Payment row even if the WebhookEvent idempotency record is missing. The constraint is partial so manual/offline payments with a blank payment_id remain allowed (the platform admin refund view and refundable-payments query both anticipate blank payment_id records). These are now a second layer behind the event-id guard. Regression tests in tests/test_billing.py::TestWebhookReplayProtection cover: replaying a subscriptions.created event does not re-send the welcome email; replaying a payments.confirmed event does not re-activate a PAST_DUE subscription or duplicate the Payment row; distinct event ids in the same webhook are all processed (the guard does not over-block); and the partial unique constraint rejects duplicate non-empty provider payment ids while allowing multiple blank payment_id manual payments.
F3 remediation: checktick_app/settings.py now fails fast when ENVIRONMENT=production and SECRET_KEY is unset or empty, raising ImproperlyConfigured at startup with a message directing the operator to set the SECRET_KEY environment variable (e.g. openssl rand -base64 50). Previously SECRET_KEY = env("SECRET_KEY") or os.urandom(32) silently fell back to a per-worker random key, so each gunicorn worker (and each process restart) derived a different key, intermittently breaking sessions, CSRF tokens, signed cookies, and password-reset / email-confirmation tokens across workers with no log entry or exception. The random fallback is retained for development only and now uses os.urandom(32).hex() so the value is a serialisable string. Regression tests in checktick_app/core/tests/test_settings_hardening.py assert that an unset/empty SECRET_KEY in production raises ImproperlyConfigured on settings import, that a correctly-set production SECRET_KEY imports cleanly, and that the random fallback is still permitted in development.
F4 remediation: the global DRF default permission class in checktick_app/settings.py (REST_FRAMEWORK.DEFAULT_PERMISSION_CLASSES) was changed from rest_framework.permissions.IsAuthenticatedOrReadOnly to rest_framework.permissions.IsAuthenticated, so any future viewset that forgets to declare permission_classes ships fail-closed (no anonymous read access) rather than silently exposing anonymous read. Endpoints that genuinely require anonymous access (/api/health, /api/docs, /api/redoc, /api/schema, and the available-tags action) already declare AllowAny explicitly, so no behavioural change is expected. An audit of grep -r "permission_classes" checktick_app/api/ confirmed every existing viewset declares its intent. Regression tests in checktick_app/api/tests/test_api_hardening.py assert the global default is IsAuthenticated and that an anonymous GET against a deliberately-undeclared-permission viewset is denied (401/403). The full API test suite (38 tests across tests/test_api_*.py and checktick_app/api/tests/) passes with no regressions.
F11 remediation: APIKeyAuthentication.authenticate in checktick_app/api/authentication.py now throttles the last_used_at write to at most once per 60 seconds per key using a Django cache marker (apikey_lastused:{api_key.id}). On each authenticated request, the cache is consulted; only if the marker is absent is last_used_at updated and the marker set with a 60s timeout. This removes the synchronous per-request UPDATE on UserAPIKey (which under concurrent load serialised on the row lock for that key and added avoidable write traffic to the primary) while preserving the documented "last seen" purpose of the field โ last_used_at is now at most ~60s stale, which is acceptable. Regression tests in checktick_app/api/tests/test_api_hardening.py assert that three requests within the throttle window produce only one last_used_at write, and that after the cache marker is cleared the next request refreshes the timestamp.
F5 remediation: the email_utils module docstring (checktick_app/core/email_utils.py) now documents the convention that all outbound email bodies must be rendered through .md Markdown templates via render_to_string so Django's autoescaping applies before Markdown-to-HTML conversion, and explicitly warns that f-string interpolation of cross-user controlled values (inviter names, team names, survey titles) bypasses autoescaping and reintroduces the F2 HTML-injection class. Self-only builders (where the only interpolated user content is the recipient's own data) are not exploitable, but the f-string pattern is flagged as fragile so any future builder that interpolates a cross-user value into an f-string will be caught at review. Regression tests in checktick_app/core/tests/test_security_review_docs.py assert the module docstring references .md templates, autoescaping, and the f-string risk.
F10 remediation: the finding was located in HealthcareLoginView (checktick_app/core/oidc_views.py), which passed request.GET.get("next") straight into the template context without validation. On audit, HealthcareLoginView was dead code โ never wired into the URL config and its reverse("oidc:oidc_authentication_init") call used a namespace that does not exist (oidc_urls.py explicitly removed app_name), so the view would have raised NoReverseMatch if called. The live login page is /accounts/login/, served by TwoFactorLoginView (a subclass of Django's LoginView), whose get_context_data only exposes next via get_redirect_url() โ url_has_allowed_host_and_scheme, so unsafe next values never reach the SSO/traditional-login hrefs on the live page. Remediation removed the dead HealthcareLoginView class and its orphaned registration/healthcare_login.html template, and added regression tests in checktick_app/core/tests/test_oidc_auth.py asserting the dead view stays removed, the live login page does not leak an unsafe next into its rendered SSO links, and legitimate relative next URLs still propagate.
F15 remediation: docs/llm-security.md ยง4 was rewritten to clarify that the security boundary is output validation + manual review + no tool access, not the system prompt. Instruction-based role enforcement is now explicitly framed as a deterrent, not a security control, and the docs no longer claim "strict role enforcement" as a protection mechanism or imply the model "reliably" refuses injection. The revised section states that modern LLMs are susceptible to prompt-injection techniques that bypass instruction-based defences, that the system prompt is published by design (so extraction is a non-issue), and that any future LLM feature must rely on output validation + manual review for its security boundary. Regression tests in checktick_app/core/tests/test_security_review_docs.py assert the docs no longer contain "strict role enforcement", do contain "output validation", and frame instruction-based enforcement as a "deterrent".
F17 remediation: HealthcareOIDCCallbackView (checktick_app/core/oidc_views.py) no longer mutates the process-global django.conf.settings object to switch between Google and Azure providers. The previous try/finally block that stored originals, overwrote settings.OIDC_RP_CLIENT_ID / OIDC_RP_CLIENT_SECRET / OIDC_OP_TOKEN_ENDPOINT / OIDC_OP_USER_ENDPOINT / OIDC_OP_JWKS_ENDPOINT / OIDC_RP_SCOPES with Azure values, called super().get(), and restored in finally has been removed entirely. Provider-specific config is now resolved per-request in two places, neither of which touches global settings: (1) HealthcareOIDCAuthView continues to set provider-specific instance attributes (via _configure_google_settings / _configure_azure_settings) for the authorization-request URL; (2) CustomOIDCAuthenticationBackend.authenticate (checktick_app/core/auth.py) re-resolves the provider endpoints/credentials from the OIDC_PROVIDERS dict onto the backend instance before the token exchange runs โ this is the critical fix, because the backend is instantiated fresh by Django's authenticate() and its __init__ reads Google defaults from global settings. Without this, Azure callbacks would hit Google's token endpoint with Azure credentials. Regression tests in checktick_app/core/tests/test_oidc_auth.py assert (a) the Azure callback path does not mutate any of the six global OIDC settings attributes, (b) the backend uses Azure endpoints and credentials for an Azure session (verified by capturing the token-exchange payload), (c) the backend uses Google endpoints for a Google session, and (d) the callback view no longer carries a get_settings override (provider resolution belongs in the backend).
Previously Active Exceptions (Now Resolved - January 2025)
The following vulnerabilities were previously silenced but have been fully remediated through dependency architecture improvements:
| Dependency | CVE / GHSA | Resolution Method | Date Resolved |
|---|---|---|---|
Transitive (via ggshield) |
GHSA-79v4-65xg-pq4g | Isolated ggshield to pre-commit environment |
18/01/2026 |
Transitive (via ggshield) |
GHSA-48p4-8xcf-vxj5 | Isolated ggshield to pre-commit environment |
18/01/2026 |
Transitive (via ggshield) |
GHSA-pq67-6m6q-mj2v | Isolated ggshield to pre-commit environment |
18/01/2026 |
Transitive (via ggshield) |
GHSA-gm62-xv2j-4w53 | Isolated ggshield to pre-commit environment |
18/01/2026 |
Transitive (via ggshield) |
GHSA-2xpw-w6gg-jr37 | Isolated ggshield to pre-commit environment |
18/01/2026 |
Transitive (via ggshield) |
GHSA-wj6h-64fc-37mp | Isolated ggshield to pre-commit environment |
18/01/2026 |
Transitive (via ggshield) |
PYSEC-2024-187 | Isolated ggshield to pre-commit environment |
18/01/2026 |
Transitive (via ggshield) |
GHSA-428g-f7cq-pgp5 | Isolated ggshield to pre-commit environment |
18/01/2026 |
Transitive (via ggshield) |
GHSA-38jv-5279-wg99 | Isolated ggshield to pre-commit environment |
18/01/2026 |
cryptography |
CVE-2024-12797 | Removed dependency pin, updated to latest | 18/01/2026 |
urllib3 |
CVE-2025-50182 | Removed dependency pin, updated to latest | 18/01/2026 |
urllib3 |
CVE-2025-50181 | Removed dependency pin, updated to latest | 18/01/2026 |
urllib3 |
CVE-2025-66418 | Removed dependency pin, updated to latest | 18/01/2026 |
urllib3 |
CVE-2025-66471 | Removed dependency pin, updated to latest | 18/01/2026 |
urllib3 |
CVE-2024-3766 | Removed dependency pin, updated to latest | 18/01/2026 |
ecdsa |
CVE-2024-23342 | Removed python-jose dependency entirely |
18/01/2026 |
2. Recently Remediated Patches (Closed)
Verified history of security updates applied. Real-time evidence is available in the CheckTick GitHub repository under the dependencies label and closed issues.
| Date | Dependency | Version Change | Reason / Security Fix | Verified By |
|---|---|---|---|---|
| 01/09/2026 | djangorestframework |
3.16.1 -> 3.18.0 | Security Fix (2 CVEs): CVE-2026-73228 โ DRF's request.data parsing bypasses Django's DATA_UPLOAD_MAX_MEMORY_SIZE protection for application/json and application/x-www-form-urlencoded bodies: DRF hands the raw HttpRequest stream to JSONParser/FormParser, which consume it via the unguarded HttpRequest.read() streaming path instead of the guarded request.body/request.POST interfaces, allowing oversized request bodies to be fully materialised and parsed (memory/CPU resource-exhaustion risk; multipart/form-data unaffected as it delegates to Django's multipart parser). CVE-2026-73229 โ AdminRenderer renders a 400 Bad Response for an invalid write request by temporarily overriding the method to GET and invoking the view's GET handler without permission checks, so a view that allows POST but denies GET could disclose GET-protected data in the HTML error response (JSON rendering unaffected). Both fixed in 3.17.2. Updated pyproject.toml constraint from ^3.15.2 to ^3.17.2 so the lockfile cannot drift back to vulnerable 3.16.x (following the sqlparse precedent); poetry.lock updated and resolved to 3.18.0. Full test suite (s/test --no-a11y, 2136 tests) passes against 3.18.0. |
CTO |
| 22/08/2026 | pip |
26.1.2 -> 26.2 | Security Fix: PYSEC-2026-3721 โ pip would incorrectly handle doubly-encoded package URLs from indexes, allowing files to be installed to arbitrary locations on disk even when installing wheels. The vulnerability requires downloading or installing a package from a malicious package index to succeed (malicious packages alone are not able to exploit it); it only materially impacts users running pip download with the --only-binary option, as installing source distributions from an untrusted index is already an unsafe operation that executes code at install time. Updated pyproject.toml constraint from >=26.1.2,<27 to >=26.2,<27; poetry.lock updated. |
CTO |
| 20/08/2026 | django |
5.2.16 -> 5.2.17 | Security Fix: PYSEC-2026-3717 โ GeoDjango's django.contrib.gis.geos.GEOSGeometry is subject to potential denial-of-service when parsing deeply nested GEOMETRYCOLLECTION objects supplied as well-known text (WKT), well-known binary (WKB), or hex-encoded WKB, which triggers unbounded recursion and a segmentation fault in the underlying GEOS library. Spatial field lookups and the django.contrib.gis.forms.GeometryField form field are also affected. poetry.lock updated; pyproject.toml constraint ~5.2.15 already permitted the patch release. |
CTO |
| 20/08/2026 | sqlparse |
0.5.5 -> 0.6.0 | Security Fix (4 PYSECs): PYSEC-2026-3696 โ source-code injection in the opt-in output_format='python'/'php' snippet-generation modes (incomplete backslash escaping before quote escaping allows attacker-controlled SQL to break out of the generated string and inject language code). PYSEC-2026-3697 โ group_comments is O(nยฒ) on comment-only statements ('-- c\n' * n), reachable via sqlparse.parse() and sqlparse.format(sql, strip_comments=True); the MAX_GROUPING_TOKENS cap fires only after the quadratic work is done. PYSEC-2026-3698 โ Regular Expression Denial of Service (ReDoS) in the dollar-quoted SQL literal lexer (sqlparse/keywords.py:33 backreference \1 causes O(nยฒ) CPU on inputs with many unique unmatched dollar-quote openers); the same lazy dot-all shape in the multiline-comment patterns is also affected. PYSEC-2026-3699 โ TokenList.__init__ calls str(self) which flattens the entire subtree on every group construction, making the path that reaches the MAX_GROUPING_DEPTH=100 / MAX_GROUPING_TOKENS=10000 caps itself O(n*depth); a ~2 KB nested-parenthesis payload consumes ~10 s of CPU per request (~5000x CPU-to-input amplification). sqlparse is a transitive dependency of Django (used by django.db.backends for SQL formatting); added explicit sqlparse = ">=0.6.0" constraint to pyproject.toml so the lockfile cannot drift back to 0.5.x. poetry.lock updated. |
CTO |
| 04/08/2026 | pygments |
2.19.2 -> 2.20.0 | Security Fix: PYSEC-2026-2987 โ inefficient regular expression complexity (ReDoS) in AdlLexer (pygments/lexers/archetype.py), local-access-only. The same release also clears the previously-open CVE-2026-4539 exception (no fix was available when the exception was logged on 27/03/2026). Updated pyproject.toml constraint from ^2.18.0 to ^2.20.0; poetry.lock updated. Active exception for CVE-2026-4539 removed from ยง1. |
CTO |
| 04/08/2026 | cryptography |
48.0.1 -> 50.0.0 | Security Fix (3 CVEs): CVE-2026-69248 โ x509 path-validation accepted a leaf certificate with a wildcard DNS SAN (*.example.com) when an intermediate constrained CA permitted only a specific name (foo.example.com), allowing the leaf to be trusted for sibling names outside the permitted subtree (RFC 5280 ยง4.2.1.10 scope escape). CVE-2026-69247 โ pkcs7_decrypt_der/pkcs7_decrypt_pem/pkcs7_decrypt_smime reported RSA RecipientInfo decryption failures in distinguishable ways (including disclosing the recovered key length and via timing), giving a Bleichenbacher oracle against the content-encryption key to any service that auto-decrypts untrusted EnvelopedData (e.g. S/MIME gateways). Introduced in 44.0.0. CVE-2026-69249 โ recursive chain building in build_chain_inner did not de-duplicate candidate issuers, so attacker-controlled chains with duplicate self-signed certificates caused exponential blowup and >5s rejection times, enabling denial of service via resource exhaustion (correctness unaffected). Updated pyproject.toml constraint from ^48.0.1 to ^50.0.0; poetry.lock updated. |
CTO |
| 24/07/2026 | pymdown-extensions |
10.21.3 -> 11.0.1 | Security Fix: CVE-2026-61632 โ Path traversal in the b64 extension (pymdownx/b64.py). When inlining images referenced by <img src="..."> as base64 data URIs, the repl_path function joined src onto base_path with os.path.normpath and opened the result directly, with no check that the resolved path stayed inside base_path. A src containing ../ sequences, or an absolute path, could therefore read any file outside base_path (bounded by allowed image extensions .png/.jpg/.jpeg/.gif/.svg) and embed its base64 in the rendered output, disclosing file contents. This was a separate code path from the snippets traversal issues (GHSA-jh85-wwv9-24hv, GHSA-62q4-447f-wv8h). Fixed in 11.0 by introducing a restrict_path option (default True) that resolves the absolute path with os.path.abspath and rejects any path not under base_path (with os.sep boundary enforcement), plus a new root_path option. Updated pyproject.toml constraint from >=10.21.3 to >=11.0.1; poetry.lock updated. Note: 11.0 is a breaking release (drops Python 3.9; b64 now restricts relative links to base_path by default) but CheckTick only uses pymdownx.tasklist (no b64/snippets usage), so no application behaviour change. |
CTO |
| 13/07/2026 | pip |
26.0.1 -> 26.1.2 | Security Fix (4 PYSECs): PYSEC-2026-1703 (path traversal in console_scripts/gui_scripts entry-point installation), PYSEC-2026-196 (entry points installed outside the installation directory due to unsanitized absolute paths), PYSEC-2026-2875 (concatenated tar/ZIP files treated as ZIP regardless of filename, causing confusing installation behaviour), and PYSEC-2026-2876 (self-update check imported well-known module names after wheel installation, allowing newly-installed packages to be imported). Added explicit pip = ">=26.1.2,<27" constraint to pyproject.toml so the lockfile is the single source of truth for the pip installer version across dev, CI, and prod. Removed ad-hoc pip install --upgrade pip>=26.0 lines from all four Dockerfiles and both CI workflows (security.yml, ci.yml); poetry install now installs the locked pip into the venv. poetry.lock updated. |
CTO |
| 13/07/2026 | click |
8.3.1 -> 8.4.2 | Security Fix: PYSEC-2026-2132 โ Pallets Click versions 8.3.2 and below contain a command injection vulnerability in click.edit(), allowing attackers to pass arbitrary OS commands from an unprivileged account. click is a transitive dev dependency (via black); added explicit click = ">=8.3.3" constraint to pyproject.toml to force a patched resolution. poetry.lock updated to 8.4.2. |
CTO |
| 13/07/2026 | pillow |
12.2.0 -> 12.3.0 | Security Fix (5 PYSECs): PYSEC-2026-2253 (PcfFontFile._load_bitmaps() passed glyph dimensions to Image.frombytes() without _decompression_bomb_check()), PYSEC-2026-2255 (BdfFontFile.bdf_char() passed BBX width/height to Image.new() without _decompression_bomb_check()), PYSEC-2026-2257 (WindowsViewer.get_command() embedded a file path in an f-string passed to subprocess.Popen(..., shell=True), allowing cmd.exe metacharacter injection), PYSEC-2026-2256 (GdImageFile._open() stored GD 2.x header dimensions without _decompression_bomb_check()), and PYSEC-2026-2254 (FontFile.compile() assembled per-glyph images with Image.new("1", ...) without _decompression_bomb_check()). Updated pyproject.toml constraint from ^12.2.0 to ^12.3.0; poetry.lock updated. |
CTO |
| 12/07/2026 | django |
5.2.15 -> 5.2.16 | Security Fix (3 PYSECs): PYSEC-2026-2090 (UpdateCacheMiddleware/cache_page() cache responses that vary on cookies when the incoming request carries unrelated cookies, allowing remote attackers to read private data from the shared cache), PYSEC-2026-2091 (django.contrib.gis.gdal.GDALRaster over-reads its in-memory buffer when constructed from a bytes object, disclosing adjacent memory or causing service degradation via segmentation fault when vsi_buffer is accessed), and PYSEC-2026-2092 (DomainNameValidator does not prohibit newlines in domain names, enabling header injection if applications use such values in HTTP responses). poetry.lock updated; pyproject.toml constraint ~5.2.15 already permitted the patch release. |
CTO |
| 12/07/2026 | soupsieve |
2.8.2 -> 2.8.4 | Security Fix (2 CVEs): CVE-2026-49477 โ CSS selector parser regex vulnerable to catastrophic backtracking (ReDoS) when processing an attribute selector with an unterminated quoted value; a 300-byte payload could hang the regex engine for over 3 seconds. CVE-2026-49476 โ unbounded memory allocation when compiling large comma-separated selector lists (500 KB input triggers ~244 MB heap allocation, a 488x amplification). Transitive dependency via beautifulsoup4; poetry.lock updated. |
CTO |
| 17/06/2026 | cryptography |
46.0.7 -> 48.0.1 | Security Fix: GHSA-537c-gmf6-5ccf โ pyca/cryptography wheels include a statically linked copy of OpenSSL. Versions prior to 48.0.1 are vulnerable to issues disclosed in the OpenSSL advisory of 2026-06-09 (https://openssl-library.org/news/secadv/20260609.txt). Updated pyproject.toml constraint from ^46.0.7 to ^48.0.1; poetry.lock updated. |
CTO |
| 09/06/2026 | django |
5.2.14 -> 5.2.15 | Security Fix (5 PYSECs): PYSEC-2026-197 (cache middleware missing Authorization in Vary handling), PYSEC-2026-198 (Vary whitespace normalization issue), PYSEC-2026-199 (non-injective salt derivation in get_signed_cookie), PYSEC-2026-200 (SMTP backend STARTTLS failure + fail_silently=True connection reuse risk), and PYSEC-2026-201 (case-sensitive Cache-Control directive matching in cache middleware). Updated pyproject.toml Django constraint to ~5.2.15 and regenerated poetry.lock. |
CTO |
| 03/06/2026 | pyjwt |
2.12.1 -> 2.13.0 | Security Fix (4 PYSECs): PYSEC-2026-175 (non-HTTP(S) URI handling in PyJWKClient), PYSEC-2026-177 (unknown-kid JWKS refresh amplification), PYSEC-2026-178 (detached JWS b64=false payload amplification), and PYSEC-2026-179 (algorithm confusion risk when both asymmetric and HMAC algorithms are accepted). Added explicit pyjwt = ">=2.13.0" constraint in pyproject.toml and updated poetry.lock to resolve to 2.13.0. |
CTO |
| 20/05/2026 | markdown |
3.10 -> 3.10.2 | Security Fix: PYSEC-2026-89 โ malformed HTML-like sequences could cause html.parser.HTMLParser to raise an unhandled AssertionError during Markdown parsing. Python-Markdown did not catch this exception, enabling remote unauthenticated Denial of Service in any application rendering attacker-controlled Markdown. Updated pyproject.toml constraint from ^3.6 to >=3.10.1; poetry.lock updated. |
CTO |
| 20/05/2026 | idna |
3.11 -> 3.15 | Security Fix: CVE-2026-45409 โ Incomplete fix for CVE-2024-3651. The valid_contexto function was invoked prior to input-length rejection, allowing payloads such as "\u0660" * N to consume significant CPU resources before being rejected, enabling denial-of-service via specially crafted arguments to idna.encode(). Fixed in 3.14 (fast-path rejection for long inputs) and extended in 3.15 (per-label conversions and codec support). Added explicit idna = ">=3.15" constraint to pyproject.toml; poetry.lock updated. |
CTO |
| 20/05/2026 | pymdown-extensions |
10.20.1 -> 10.21.3 | Security Fix: CVE-2026-46338 โ Regression of CVE-2023-32309 (GHSA-jh85-wwv9-24hv) fix in pymdownx.snippets. The restrict_base_path: True containment check used a plain str.startswith(base) comparison which does not enforce a directory boundary. A markdown snippet directive such as --8<-- "../docs_secret/leak.txt" could read files from sibling directories sharing the same path prefix (e.g. /x/docs_secret bypasses a base of /x/docs). Fixed in 10.21.3 by appending os.sep to the base before the prefix check. Updated pyproject.toml constraint from ^10.0 to >=10.21.3; poetry.lock updated. |
CTO |
| 14/05/2026 | urllib3 |
2.6.3 -> 2.7.0 | Security Fix (2 CVEs): CVE-2026-44431 โ sensitive headers (Authorization, Cookie, Proxy-Authorization) were not stripped on cross-origin redirects when using the low-level ProxyManager.connection_from_url().urlopen(..., assert_same_host=False) API. CVE-2026-44432 โ streaming API could decompress the full response body instead of only the requested bytes when using the Brotli library or calling HTTPResponse.drain_conn() after partial decompression, enabling potential denial of service via decompression bombs. Added explicit urllib3 = ">=2.7.0" constraint to pyproject.toml; poetry.lock updated. |
CTO |
| 09/05/2026 | django |
5.2.13 -> 5.2.14 | Security Fix (3 CVEs): CVE-2026-35192 โ with SESSION_SAVE_EVERY_REQUEST=True, response cache variation on cookies could be insufficient when the session is unmodified, enabling session theft after visits to cached public pages. CVE-2026-6907 โ django.middleware.cache.UpdateCacheMiddleware could cache responses where Vary: * is present, potentially storing and serving private data. CVE-2026-5766 โ ASGI requests with missing/understated Content-Length could bypass FILE_UPLOAD_MAX_MEMORY_SIZE, enabling large in-memory uploads and service degradation. Updated pyproject.toml constraint and poetry.lock. |
CTO |
| 09/05/2026 | axe-core |
4.11.3 -> 4.11.4 | Security/maintenance patch: Updated self-hosted checktick_app/static/js/axe-core.min.js using npm pack axe-core@4.11.4; regenerated SHA-384 SRI to sha384-JPn8kKVo7BLn9/zcbvarZHaq40amEwymg7J3Uhc7Lb4ds5KZ1kKLagxlEZX5iqWj; updated template integrity and refreshed docs/cdn-libraries.md. |
CTO |
| 30/04/2026 | axe-core |
4.11.2 -> 4.11.3 | Security/maintenance patch: Updated self-hosted checktick_app/static/js/axe-core.min.js using npm pack axe-core@4.11.3; regenerated SHA-384 SRI to sha384-ZCC+CzYtmcQl5Kc3P96iEgc7ws4aLd064TkQUd85k5wACc0i4CLl7+O5YLV+R9fq; updated workflow pin AXE_CORE_VERSION and refreshed docs/cdn-libraries.md. |
CTO |
| 30/04/2026 | HTMX |
2.0.8 -> 2.0.10 | Security/maintenance patch: Updated self-hosted checktick_app/static/js/htmx.min.js using npm pack htmx.org@2.0.10 and regenerated SHA-384 SRI to sha384-H5SrcfygHmAuTDZphMHqBJLc3FhssKjG7w/CeCpFReSfwBWDTKpkzPP8c+cLsK+V. Updated template integrity in checktick_app/templates/base.html, updated version pin in .github/workflows/update-cdn-libraries.yml, and refreshed HTMX entries in docs/cdn-libraries.md. |
CTO |
| 14/04/2026 | pillow |
12.1.1 -> 12.2.0 | Security Fix: CVE-2026-40192 โ Pillow did not limit the amount of GZIP-compressed data read when decoding a FITS image, making it vulnerable to decompression bomb attacks. A specially crafted FITS file could cause unbounded memory consumption, leading to denial of service (OOM crash or severe performance degradation). Fixed in 12.2.0. pyproject.toml constraint updated from ^12.1.1 to ^12.2.0; poetry.lock updated. |
CTO |
| 14/04/2026 | pytest |
8.4.2 -> 9.0.3 | Security Fix: CVE-2025-71176 โ pytest through 9.0.2 on UNIX relies on directories with a predictable /tmp/pytest-of-{user} name pattern, allowing local users to cause denial of service or possibly gain privileges. Fixed in 9.0.3. pyproject.toml constraint updated from ^8.3.2 to ^9.0.3; pytest-playwright co-updated from ^0.6.2 to ^0.7.0 (minimum version supporting pytest 9); poetry.lock updated. |
CTO |
| 14/04/2026 | axe-core |
4.11.1 -> 4.11.2 | pip-audit remediation: Updated self-hosted axe-core to 4.11.2. Asset sourced via npm pack axe-core@4.11.2; SHA-384 hash updated to sha384-zRVu3r+67m6UWo2ljwGVKxuMOzmMfp2lcxhNVGw0fk2xsbIpJ1mGyY7GV4jPiKu8. 4.11.1 โ 4.11.2 is a patch release containing only bug fixes (no breaking changes). GitHub Actions workflow AXE_CORE_VERSION pin updated from 4.11.1 to 4.11.2. |
CTO |
| 14/04/2026 | ReDoc |
2.1.5 -> 2.5.2 (static file) | pip-audit remediation: The self-hosted static JS file checktick_app/static/js/redoc.standalone.min.js was found to still contain the 2.1.5 bundle after an earlier dependency-only refresh. The file has now been replaced with the correct 2.5.2 bundle sourced via npm pack redoc@2.5.2; SHA-384 SRI hash verified as sha384-70P5pmIdaQdVbxvjhrcTDv1uKcKqalZ3OHi7S2J+uzDl0PW8dO6L+pHOpm9EEjGJ (consistent with template and docs). The GitHub Actions workflow REDOC_VERSION pin updated from 2.1.5 to 2.5.2. |
CTO |
| 10/04/2026 | cryptography |
46.0.6 -> 46.0.7 | Security Fix: CVE-2026-39892 โ Non-contiguous buffer passed to APIs accepting Python buffers (e.g. Hash.update()) could lead to buffer overflows on Python >3.11. Upgraded to 46.0.7 which validates buffer contiguity before processing. |
CTO |
| 10/04/2026 | django |
5.2.12 -> 5.2.13 | Security Fix (5 CVEs): CVE-2026-33033 โ MultiPartParser allows remote attackers to degrade performance via Content-Transfer-Encoding: base64 uploads with excessive whitespace. CVE-2026-33034 โ ASGI requests with missing/understated Content-Length could bypass DATA_UPLOAD_MAX_MEMORY_SIZE, allowing unbounded request body loading. CVE-2026-4292 โ Admin changelist forms using ModelAdmin.list_editable incorrectly allowed new instances to be created via forged POST data. CVE-2026-4277 โ Add permissions on inline model instances were not validated on submission of forged POST data in GenericInlineModelAdmin. CVE-2026-3902 โ ASGIRequest allowed header spoofing by exploiting an ambiguous mapping of headers with hyphens vs underscores. |
CTO |
| 27/03/2026 | cryptography |
46.0.5 -> 46.0.6 | Security Fix: CVE-2026-34073 โ DNS name constraints were only validated against SANs in child certificates and not against the peer name presented during validation. A peer named bar.example.com could validate against *.example.com even if an excluded subtree constraint for bar.example.com existed in a parent certificate. Upgraded to 46.0.6 which now rejects any validation where the peer name would be rejected by a name constraint if it were a SAN. |
CTO |
| 27/03/2026 | requests |
2.32.5 -> 2.33.0 | Security Fix: CVE-2026-25645 โ extract_zipped_paths() used a predictable temp filename allowing a local attacker to pre-create a malicious file. Upgraded to 2.33.0 where extraction uses a non-deterministic location. |
CTO |
| 25/03/2026 | CSP base-uri |
โ | Security Hardening (AD10): Added base-uri: 'self' directive to CONTENT_SECURITY_POLICY in checktick_app/settings.py. Prevents base-tag injection attacks where an attacker could redirect relative URL resolution to an attacker-controlled origin. |
CTO |
| 25/03/2026 | Admin URL path | /admin/ โ /ct-admin/ |
Security Hardening (AD11): Moved Django admin from default /admin/ path to /ct-admin/ in checktick_app/urls.py. Automated scanners probing the default path receive 404s. Primary protection remains CheckTickAdminSite returning 404 to all non-superusers. |
CTO |
| 20/03/2026 | swagger-ui-dist |
5.17.14 -> Removed | Security โ Bootstrap 3.x bundled dependency. Swagger UI bundles Bootstrap 3.x (via SwaggerUIStandalonePreset) which contains multiple known XSS and prototype-pollution CVEs. Swagger UI was removed entirely and replaced with self-hosted ReDoc 2.1.5 (Bootstrap-free, no external CDN calls, no Google Fonts). The /api/docs route has been removed; /api/redoc remains the single interactive docs endpoint. |
CTO |
| 20/03/2026 | ReDoc |
CDN (v2.1.5) -> self-hosted (v2.1.5) | Served from checktick_app/static/js/redoc.standalone.min.js with SHA-384 SRI verification. Google Fonts CDN call (fonts.googleapis.com) removed from the ReDoc template, eliminating third-party data exfiltration of visitor IPs. Asset sourced via npm pack redoc@2.1.5 for reproducibility. |
CTO |
| 14/03/2026 | black |
24.10.0 -> 26.3.1 | CVE-2026-32274 | CTO |
| 14/03/2026 | pyjwt |
2.10.1 -> 2.12.1 | CVE-2026-32597 | CTO |
| 05/03/2026 | django |
5.2.11 -> 5.2.12 | CVE-2026-25673 and CVE-2026-25674 fixes | CTO |
| 22/02/2026 | SortableJS |
1.15.6 -> 1.15.7 | Updated to patched upstream release; regenerated SRI and self-hosted artifact. | CTO |
| 22/02/2026 | django-csp |
4.0 | CSP_CONFIG replaces CONTENT_SECURITY_POLICY | CTO |
| 12/02/2026 | pillow |
11.3.0 -> 12.1.1 | Security Fix: Fixed CVE-2026-25990 (out-of-bounds write in PSD image loader). Specially crafted PSD images could trigger memory corruption. All Pillow >= 10.3.0 users affected. | CTO |
| 12/02/2026 | marshmallow |
3.18.0 -> 3.26.2 | Security Fix: Fixed CVE-2025-68480 (transitive dependency). Updated to latest secure version. | CTO |
| 12/02/2026 | pip |
25.3 -> 26.0.1 | Security Fix: Fixed CVE-2026-1703. Updated to latest secure version. | CTO |
| 10/02/2026 | cryptography |
46.0.3 -> 46.0.5 | Security Fix: Fixed CVE-2026-26007 (SECT curve public key validation bypass). Functions public_key_from_numbers, load_der_public_key, and load_pem_public_key now verify points belong to expected prime-order subgroup, preventing ECDSA signature forgery and ECDH key leakage. |
CTO |
| 04/02/2026 | django |
5.1.x -> 5.2.11 | Critical Security Update: Fixed CVE-2025-13473 (timing attack in mod_wsgi auth), CVE-2026-1207 (SQL injection in RasterField), CVE-2026-1312 (SQL injection in QuerySet.order_by), CVE-2026-1287 (SQL injection in FilteredRelation) | CTO |
| 04/02/2026 | django-axes |
6.5.2 -> 8.1.0 | Updated for Django 6.0 compatibility. Adds enhanced brute-force protection features. | CTO |
| 04/02/2026 | django-csp |
3.8 -> 4.0 | Breaking Change: Migrated to new CONTENT_SECURITY_POLICY configuration format. Updated @csp_exempt decorator syntax. | CTO |
| 04/02/2026 | pip-audit |
2.7.3 -> 2.10.0 | Updated vulnerability scanning tool to latest version | CTO |
| 21/01/2026 | axe-core |
4.11.0 -> 4.11.1 | Patch version upgrade to latest stable | CTO |
| 18/01/2026 | python-jose |
3.5.0 -> Removed | Eliminated vulnerable ecdsa transitive dependency. JWT functionality provided by djangorestframework-simplejwt |
CTO |
| 18/01/2026 | ggshield |
Moved to pre-commit | Architecture Change: Isolated security scanning tool from production dependencies. Prevents dependency pins from blocking security updates. | CTO |
| 18/01/2026 | cryptography |
Unpinned -> Latest | Removed pin previously required by ggshield. Now free to update immediately when patches released. |
CTO |
| 18/01/2026 | urllib3 |
Unpinned -> Latest | Removed pin previously required by ggshield. Resolved 5 CVEs (CVE-2025-50182, CVE-2025-50181, CVE-2025-66418, CVE-2025-66471, CVE-2024-3766). |
CTO |
| 02/01/2026 | requests |
2.31.0 -> 2.32.0 | Fixed CVE-2024-3651 (Header parsing) | CTO |
| 28/12/2025 | daisyui |
4.x -> 5.4.7 | Dependency refresh & security hardening | CTO |
| 05/12/2025 | jinja2 |
3.1.2 -> 3.1.4 | Fixed GHSA-h75v-3vv6-5qhc (XSS risk) | CTO |
| 12/11/2025 | django |
5.0.x -> 5.1.0 | Minor version upgrade to latest stable | CTO |
3. Automation & Triage Process
- Continuous Auditing: Our
Security ScanGitHub Action runs on every Push, Pull Request, and daily at 06:00 UTC. - Hard Block on Production: If
pip-auditdetects any vulnerability (Critical, High, Medium, or Low), the build fails and deployment is automatically blocked. No exceptions are configured. - Isolated Security Tooling: Security scanning tools (
ggshield,pre-commit) run in isolated environments managed by pre-commit, preventing their dependencies from constraining production packages. - Zero-Exception Policy: Since January 2026, we maintain a zero-exception policy for vulnerability scanning. This ensures immediate visibility and remediation of any new vulnerabilities.
- Endpoint Sync: Developers are required to run
poetry installlocally to synchronize their development environment with the latest patched versions inpoetry.lock, preventing 'version drift' between local and production environments.
4. Architecture Improvements (January 2026)
Key Change: Separated security scanning tool dependencies from application runtime dependencies.
Before:
- Security tools (
ggshield) were installed via Poetry alongside application dependencies - Security tools pinned critical dependencies (
cryptography,urllib3) to outdated versions - Required maintaining an exception list of 10+ vulnerabilities
- Could not update vulnerable dependencies without breaking security tools
After:
- Security tools run in isolated pre-commit environments
- Production dependencies are free to update immediately when patches are released
- Zero vulnerabilities in production dependency tree
- Zero exceptions required in CI/CD pipeline
- Improved security posture and maintainability
This architectural change represents a significant improvement in our security monitoring capability while simultaneously reducing our vulnerability exposure.
5. CDN Artifact Handling & Integrity (January 2026)
Change: Updated automated CDN library refresh workflow to use npm pack as the canonical source for packaged JavaScript assets and to compute SRI from the package contents.
- Reason: Downloading files directly into the repository root from third-party CDNs caused temporary artifacts to remain after workflow runs (triggering security scanners such as CodeQL). Using
npm packensures the registry is the authoritative source and the artifact bytes are reproducible. - What changed: The
update-cdn-librariesGitHub Action now: - Runs
npm pack <pkg>@<version>in a temporary directory - Extracts the tarball, finds the minified asset, computes SHA-384 SRI from the file bytes, and atomically moves the asset into
checktick_app/static/js/ - Cleans up temporary files and directories using traps to avoid leaving files in the repository
- Outcome: SRI values are computed from the exact npm package bytes; temporary files no longer appear in the repo root; CodeQL false-positives reduced.
Evidence: PR #155 updates the workflow and documentation to reflect this change and records the refreshed SRI evidence.