Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]
## [0.19.0] — 2026-09-22

Migration: **none required.** The one new surface is an opt-in flag, and nothing existing changes
shape. The change a CI author should expect is **fewer findings**: four false-positive sources are
fixed here, so a `--fail-on` gate or a baseline diff that previously tripped on a
`timing-unsafe-compare` against a length, a `weak-password-hash` over an identifier, a
browser-extension handler whose sender check sat behind an alias, or a PII projection that actually
removed the field will now come back clean. That is a precision gain, not a coverage loss — each fix
ships with the paired true-positive control proving the real case still fires.

**Precision work, with the controls attached.** The theme of this release is that a detector fix is
only accepted alongside a test proving it did not go quiet. Every false-positive fix below names the
near misses that must still report, and one piece of #143 is deliberately left out because it never
fired end-to-end and carried no control. The claimspec `ignore` writer applies the same standard to
an export format: entries whose reason cannot be established honestly are omitted **and counted**,
so a partial policy can never read as a whole one.


### Added — the claimspec `ignore` writer

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "websec-validator"
version = "0.18.0"
version = "0.19.0"
description = "Defensive, local-first security recon that briefs your AI coding agent on your own codebase — read-only by default (code in, artifacts out): facts + tailored probe scripts, no LLM, no server, no running app."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
10 changes: 9 additions & 1 deletion tests/test_explained_brief.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,15 @@ def test_stated_version_shipped_and_is_current_in_masthead(self):
changelog = (REPO / "CHANGELOG.md").read_text(encoding="utf-8")
self.assertIn(f"## [{STATED_VERSION}]", changelog)
self.assertTrue(_has(f"Technical Brief · v{STATED_VERSION}"))
releases = len(re.findall(r"^## \[\d+\.\d+", changelog, flags=re.M))
# The brief is a SNAPSHOT of STATED_VERSION and says so ("asserted ... at the version in
# the masthead"), so it must count the releases that existed at that version -- not the
# running total. Binding it to the total made every future release fail this assertion
# while proving nothing extra: a brief cannot overstate a release that shipped after it.
# Same counting basis as before (a grouped heading like "[0.2.x]" counts once),
# newest first, so slicing from STATED_VERSION keeps only what had shipped by then.
shipped = re.findall(r"^## \[(\d+\.\d+[^\]]*)\]", changelog, flags=re.M)
self.assertIn(STATED_VERSION, shipped)
releases = len(shipped[shipped.index(STATED_VERSION):])
self.assertTrue(_has("twenty-eight releases"), "release count wording drifted")
self.assertEqual(releases, 28)
breaking = changelog.count("### Changed — BREAKING")
Expand Down
Loading