Step 2 said publishing before the identifier arrives was fine because it gets
attached when GitHub's review completes. That turned out to be wrong, and we
found out the expensive way.
GitHub declined our request for GHSA-q46c-8w98-fq2g as a duplicate: VulnCheck
had already assigned CVE-2026-73531 five days after we published, deriving it
from our own advisory, which is listed as the first reference on the NVD record.
So the identifier exists, but with their severity (6.1) rather than ours (8.1),
and the wording is theirs too.
Publishing early is still often the right call, since step 5 wants the gap
between merge and an installable release measured in minutes. The point is that
it is a trade-off to make deliberately, not a step with no consequence.
Saving the edit form built a fresh FollowUp from the posted fields, moved
the attachments across and deleted the original. Everything the form does
not carry went with it: the message ID, so a reply received by e-mail
stopped being recognisable as one, and the TicketChange rows, which cascade
away with the row they point at, so a ticket lost the record of the status
and owner changes that follow-up carried. The new row also took a new
primary key, breaking any link to the follow-up.
Assign the posted fields to the existing follow-up and save it.
The three dots menu on each follow-up rendered unconditionally, but both of
its entries are gated: Edit needs the follow-up to have an author and the
viewer to be that author or a superuser, and Delete needs a superuser plus
HELPDESK_SHOW_DELETE_BUTTON_SUPERUSER_FOLLOW_UP, which is off by default.
Staff therefore opened an empty menu on every follow-up written by a
colleague, and on every follow-up with no author at all: those received by
e-mail, those submitted from the public form and those raised by the
helpdesk itself.
Work out both permissions in template filters so the menu and its entries
cannot drift apart, and skip the menu when neither applies.
docs: auto pull in license to docs/license.rst
Both the documentation and the 3rd party.license file were falling out of date.
Now the documentation will auto pull in the data from the license file. We will still need to update the license file, especially as yarn and other tools accelerate our ability to update libraries
Instead of all accordion elements being closed we preopen the accordion element if its pk is part of the url; say for example /kb/orders/?kbitem=23 opens the respective accordion. This url is actually a detail url of a kb item
The 3rd party licenses had drifted from what we actually ship. Every
version label was stale and with the versions the attribution had gone stale too.
Three entries named the wrong copyright holder outright: jQuery still
credited John Resig personally rather than the OpenJS Foundation, Bootstrap
still credited Twitter, Inc., and Font Awesome carried no holder at all.
jQuery and jQuery UI become separate entries. They are separate packages at
separate versions, and their license texts genuinely differ -- jQuery UI's
adds a CC0 waiver for sample code -- so a single merged entry was always
wrong about one of them. The 'Smoothness' theme entry now states that it is
covered by the jQuery UI license
docs/license.rst then includes LICENSE.3RDPARTY rather than restating it,
the same treatment the BSD license got in the previous commit. That is what
caused this: the docs page was a hand-maintained third copy, and it had
drifted further than the file it copied. The per-library subsections
disappear from the page's table of contents, which is a real loss, but the
file carries its own index and a license page's first duty is to be
accurate.
Closes#204.
A ticket's follow-up list mixes three kinds of entry: private staff notes,
replies sent out to the submitter, and messages received from the submitter,
and rendered all of them identically apart from a small 'Private' caption.
Staff scanning a long ticket had to read each entry to work out who could see
it, and the failure mode runs in the expensive direction: a note written as if
it were internal that the submitter can in fact read.
@uhurusurfa enabled private vulnerability reporting on the repository after
#1376, so the policy should send people there rather than to a personal email
address. The form opens a draft advisory only maintainers can see, with the
reporter already a collaborator on it, which is what step 1 of the handling
section asks us to set up by hand. The email address stays as a fallback for
anyone without a GitHub account.
Step 1 is reworded accordingly: a report that arrives through the form has
already created the draft, so there is nothing to open, only to take over.
Also settles the file on one sentence per line, agreed in #1376. The section
added there was hard wrapped near 80 columns while the older paragraphs each ran
as one long line. One sentence per line keeps lines short, touches exactly one
line in a diff when a sentence changes, and never needs rewrapping. Markdown
joins consecutive lines into a paragraph, so the rendered output is unchanged.
The button was a bare <input type="submit"> carrying no class and no gettext
call, so it rendered as the browser default next to fields the rest of this
branch now renders with Bootstrap 5. It follows edit_ticket.html instead:
btn btn-primary btn-sm, with the label translated.
form-control, form-select and form-check-input set width differently, so a
widget carrying two of them renders wrong. apply_classes kept every class
already declared in forms.py, and forms.py declared form-control on both the
checkbox and the select it builds for custom fields. A boolean custom field
therefore reached the browser as "form-check-input form-control" and rendered
26px wide instead of 16px, a rectangle rather than a checkbox, on the edit
ticket form.
apply_classes now treats the three as one family and replaces rather than
accumulates, so a wrong class declared in a form cannot reach the markup.
Classes that have nothing to do with the family, such as date-field, are still
preserved.
forms.py is corrected at the source too: form-check-input on the boolean custom
field, form-select on the list one.
FormControlDeleteFormSet.deletion_widget still declares form-control on a
checkbox. It is rendered by the checklist templates rather than through
bootstrap5_form, so it is left for a separate change.
Closes#1375.
The UI ships Bootstrap 5.3.8, but form fields were rendered by
django-bootstrap4-form, whose markup is largely Bootstrap 3: form-group,
control-label, has-error, <div class="checkbox">. None of those classes exists
in the stylesheet we load, so the affected fields lost their bottom margin and
checkboxes and radios were unstyled.
The org's own django-bootstrap5-form was the obvious replacement but pins
django<4.0 while we require django>=4.2, so it cannot be installed alongside us.
Given the filter was used in exactly three templates, owning the rendering is
cheaper than maintaining a published package or adopting a larger one.
Adds a bootstrap5_form template tag and two templates. Widget classes are
assigned in Python rather than the template, because a widget rendered through
{{ field }} has already resolved its attributes: selects get form-select,
checkboxes and radios get form-check-input, everything else form-control, and
hidden inputs are left alone. Classes already declared in forms.py are kept,
including the ones that have nothing to do with Bootstrap.
Also removes four {% load bootstrap4form %} lines from templates that never
called the filter, and drops packaging, which was only declared because
django-bootstrap4-form imports it without declaring it.
Errors use `invalid-feedback d-block` rather than plain `invalid-feedback`: the
latter is only revealed by an adjacent `.is-invalid`, and that relationship does
not hold for radio groups and multiple checkboxes.
SECURITY.md covered how a reporter contacts us and stopped there. Everything
after that point was improvised, and the last two advisories showed the cost:
GHSA-mxcg-7m22-x9pj needed a second release because the first patch was
incomplete, and GHSA-q46c-8w98-fq2g was published a full day before 2.3.3
existed on PyPI, leaving the issue fully disclosed with no installable fix and
Dependabot pointing people at a version that did not exist.
Neither was any individual's mistake, which is the point: nobody owned the whole
chain. So the section starts by assigning an owner, then lists the seven steps
in the order that keeps the disclosure window short.
Per @uhurusurfa's review. Unlike celery and akismet, django-cleanup activates as
soon as it is listed in INSTALLED_APPS, which is exactly what our own
installation instructions tell people to do. Making it optional would stop a
documented configuration from starting at all, which is a different class of
breakage from the other two: `helpdesk/urls.py` already imports `helpdesk.tasks`
inside a try/except ImportError, and `text_is_spam()` already treats a missing
akismet as "not spam".
It also buys the least: django-cleanup pulls nothing else in, where celery alone
accounts for about twenty packages.
Docs and the upgrade note updated to match, and the upgrade note now says
explicitly why django-cleanup is not on the list, so this does not get proposed
again in six months.