---
title: "GHSA-mrv2-8596-cx34"
description: "DeadlineGuard certifies directional and unlisted-event terms against the signing date (false VERIFIED). Affected and fixed versions of qwed-legal, @qwed-ai/legal, @qwed-ai/legal."
url: https://qwedai.com/security/advisories/ghsa-mrv2-8596-cx34
---

# GHSA-mrv2-8596-cx34

DeadlineGuard certifies directional and unlisted-event terms against the signing date (false VERIFIED)

### Summary

`DeadlineGuard` computes every deadline as `signing_date + N <unit>`. A prior fix added a denylist of event nouns (receipt, notice, service, delivery, occurrence, demand, invoice, breach, termination, payment, acceptance, approval) so that terms anchored to an event, rather than to the signing date, fail closed. Two gaps remain, both of which allow a wrong deadline to be certified `VERIFIED` with DETERMINISTIC evidence.

### Impact

An attacker- or LLM-supplied term can obtain a `VERIFIED` result for a deadline that is not actually derivable from the inputs (chosen-date forgery). A downstream consumer that trusts the verdict may accept an incorrect legal deadline as proven.

1.  **Directional terms are ignored.** `"30 days before signing"`, `"prior to signing"`, `"ahead of signing"`, and `"preceding signing"` are all computed as `signing + 30` and certified against a claim of `signing + 30`. The direction is dropped, so the guard certifies the opposite of what the term states.

2.  **The event-anchor denylist is incomplete.** Common anchor nouns outside the list — e.g. closing, completion, notification, "effective date", request, filing, judgment, discovery, death, "closing date", arrival, shipment — are not recognised as anchors, so the term is computed from the signing date and certified. A gap of more than two words also slips a listed noun through (`"30 days after the said written receipt"` certifies, while `"30 days after receipt"` correctly fails closed).

All three surfaces are affected: the Python `DeadlineGuard.verify`, the npm `DeadlineVerifier.verify`, and the GitHub Action (`mode: deadline` / `all`), since each forwards the raw term to the same computation.

### Proof of concept

```python
from qwed_legal import DeadlineGuard
d = DeadlineGuard()

# Directional term computed in the wrong direction, certified VERIFIED
r = d.verify("2026-01-15", "30 days before signing", "2026-02-14")  # claim = signing+30
assert r.verified and r.to_diagnostic().status.value == "VERIFIED"  # expected: UNVERIFIABLE

# Unlisted event anchor certified at signing+N
for term in ["30 days after closing", "30 days after completion",
             "30 days after notification", "30 days after the Effective Date",
             "30 days after filing", "30 days after discovery",
             "30 days after the said written receipt"]:
    assert d.verify("2026-01-15", term, "2026-02-14").verified  # all VERIFIED

# Control: a listed noun with a short gap correctly fails closed
assert not d.verify("2026-01-15", "30 days after receipt", "2026-02-14").verified
```

### Affected versions

-   `qwed-legal` (PyPI) `<= 0.5.0`.
-   `@qwed-ai/legal` (npm) `<= 0.5.0`, and the stray `1.0.0` publish, which was built from pre-0.2.0 sources and is not a newer release.

The npm package forwards to whichever Python `qwed_legal` is importable, so npm users are exposed whenever that Python package is `<= 0.5.0`.

### Patches

Fixed in `qwed-legal` `0.5.1` and `@qwed-ai/legal` `0.5.1`. Upgrade both: `pip install "qwed-legal>=0.5.1"` and `npm install @qwed-ai/legal@0.5.1`. The npm `0.5.1` `DeadlineVerifier` also refuses to run against a Python `qwed-legal` older than 0.5.1. npm users on `1.0.0` should move to `0.5.1`; there is no patched `1.x`.

### Workarounds

Until upgrading, do not treat a `DeadlineGuard` `VERIFIED` result as authoritative for terms that are relative to an event or that use directional wording (before / prior to / ahead of / preceding). Require that the term is explicitly anchored to the signing date.

### Root cause

In `qwed_legal/guards/deadline_guard.py`, the event-anchor regex is a closed noun denylist (anchors outside the alternation are invisible to the anchor-rejection path), its adjacent-word gap is bounded at two words, and the term parser extracts only a quantity and unit — directional wording is never parsed, so the sign is always positive. The robust fix is to invert the denylist to an allowlist: compute from the signing date only when the term is affirmatively signing-anchored (or unanchored with a supported direction) and fail closed otherwise.
