Ghostwriter v7.2.5
Summary
This release is all about tightening security and fixing a few related bugs along the way. We've been doing a review of the codebase to tighten up controls and make some authorization models less ambiguous.
A couple of notable change here:
One is adjustment to template management authorization. We previously allowed anyone to adjust templates, but determined we wanted to limit the impact that any one user could have on reports globally. Now, users can only upload new templates when uploading it for a specific client to which they have access. They cannot edit global templates. They also cannot edit client templates if the protected boolean is set. Only managers can edit protected templates, create new global templates, or edit existing templates. Some teams may delegate this work to a technical writer and you may not want that person to have manager-role access. For those use cases, there is now a template management permission that admins can toggle on for an account with the user role.
The other big change is a scheduled task allowlist. Ghostwriter now uses a customized Django Q scheduler that checks any commands against the allowlist. This greatly simplifies configuration for Ghostwriter's available scheduled tasks while also preventing the execution of arbitrary tasks. The allowlist is configured in the .env file on disk and controls args, kwargs, and hooks. This separates access for an admin who only needs access to the admin panel and admins who also manage the server. This is intentional to prevent application admins from being able to run commands on the system via the scheduler. Teams can now make someone an admin in the application while also controlling which, if any, system tasks or commands they are allowed to execute.
CHANGELOG
[7.2.5] - 1 August 2026
Changed
- Improved logging for skipped signal imports and background task failures
Fixed
- Fixed block-level rich-text content rendering outside table cells in Word reports
- Headings, blockquotes, code blocks, lists, page breaks, evidence, images, and captions now remain in their original cells
- Corrected the AJAX URL for deleting report observations
- This fixed a typo in the path, but had no effect on functionality
- Fixed activity-log sanitization confirmation and scoped its CSRF header to its own request
- Fixed expired API and service token feedback so the appropriate token row and empty state are updated in the UI
- Fixed PowerPoint report generation when rich-text table cells contain block quotes or pre-formatted text
Security
- Restricted report finding reorder operations to findings attached to the authorized report
- Reorder requests containing a finding from another report are rejected without modifying any findings
- Prevented report titles from being interpreted as HTML when activating a report
- Restricted GraphQL local finding-note updates and deletions to the note owner for non-privileged users
- Restricted global and protected report template management and template deletion across Django and Hasura
- Managers, administrators, and users explicitly granted report template management permission may administer global templates
- Report template management permission does not grant access to unrelated clients or projects; client-scoped templates retain their existing client access checks
- Regular users may continue creating and editing unprotected templates for clients they can access
- Required bearer authentication and authenticated-principal authorization for Hasura tag actions
- Direct public access to tag action handlers is blocked, and the shared action secret no longer has an insecure default
- Restricted report filename rendering to detached JSON primitives and applied matching validation to Django and Hasura template uploads
- Report-controlled Jinja templates can no longer reach live serializers, querysets, models, or other application objects
- Hardened Jinja2 report rendering against sandbox escapes while preserving user-authored report templates and previews
- Operation-log values are treated as literal report data, including values containing captured Jinja2 payloads
- Lazy rich-text rendering now rejects templates that were not compiled by Ghostwriter's sandboxed environment
- Report template objects, Python callables, and document-export objects no longer expose unsafe attributes or call paths to Jinja2
- Hardened user-controlled values rendered in JavaScript contexts to prevent stored cross-site scripting
- Autocomplete data is now serialized as inert JSON instead of being interpolated into JavaScript source
- Tag autocomplete suggestions are scoped to objects the current user can access
- Additional inline JavaScript values and activity-log rich-text previews are escaped or sanitized for their output context
- These changes are related to GHSA-5xvc-cm65-jw3p, but go beyond that to further harden sanitization (Thank you to @hippiiee for reporting the original issue!)
- Added matching Django and Hasura validation for domain and static server names while preserving user access to create and manage shared inventory
- Restricted Django Q scheduled tasks to a server-controlled allowlist (Closes #911)
- The admin panel now exposes only approved functions, Slack notification hooks, and validated task arguments
- Optional system commands must be configured as fixed argument vectors on the server and run without a shell
- Schedule saves, queue submissions, result hooks, and worker execution enforce the policy independently
- Added
audit_django_q_policyto identify or pause existing schedules that do not satisfy the configured policy