Repository navigation
Vulnerability Score rule type rebuilt on the library's scoring model; lat/long equals against a database (#52, #49) - #59
Merged
Conversation
…del (#52) Withdrawn from 1.0 because it compiled {threshold, weights}, which the library's VulnerabilityScore plugin never reads. The rebuilt type stores exactly what the plugin reads, in its own shape: risk_levels (threshold, block, and a status code and ban length per matching level) and scoring.methods, patterns, user_agents, countries, asn and asn_patterns, with country_reader and asn_reader opened only for a signal that uses them. Defaults are a working subset of the library's malicious-requests preset. Validation refuses what the library would reject or ignore: undelimited or uncompilable regexes, unknown locations and match types, a matching level at or below zero, duplicate names or thresholds, a pass level above a matching one, and country or network scores with no database. Upgrade routine 12 (schema 12) translates a stored rule with a threshold and no weights into one matching level at that threshold with the default scores. A rule with weights has no sound mapping, so it is kept as it was, switched off, and named by the compiler on the Status screen and in Site Health, enabled or not. The withdrawn-type registry machinery is gone; the old shape is the safety net everywhere a rule is handled. The rule screen reads fields a type marks verbatim as typed: the textarea sanitiser stripped < and %xx out of patterns on every save. Tests: honoured-settings coverage for every field against the real library (above and below a matching level, per-level status and ban, first-match user agent and organisation, config read back off the plugin), refusals, the upgrade routine, and a screen round trip.
…base (fixes #49) A MaxMind record holds the coordinates as floats, a compiled value is text, and the library still compares equals, not_equals and in strictly in 2.35.1 (only the ASN is coerced), so latitude equals never matched a database and not_equals matched every visitor. A database-backed rule now compiles those values as floats, case-sensitive so the library does not lowercase an in list back to text. A CDN-backed rule is left alone: its headers are text, which already compares correctly. Cast rather than withdrawing equals from the screen, so rules that use it start working as written instead of needing rewriting. Tests run equals, not_equals and in on both coordinates against the fake City record and CloudFront's headers, through a YAML round trip.
…#49) README: the type back in the rule types table (ten), a section on risk levels, scores, what the form refuses and why, the defaults, and how rules saved before 1.1 are translated; coordinates compared as numbers against a database and as text from a CDN. readme.txt feature list and CHANGELOG Unreleased (upgrade notes, Added, Fixed). The weight hint on the rule screen names vulnerability scoring again.
Merging main brought in the rc.5 release, which renamed the previous [Unreleased] to [1.0.0-rc.5]; this branch's entries landed under it. They move to a fresh [Unreleased], and the rc.5 ASN entry's note that latitude/longitude equals is not part of that release is restored.
sean-e-dietrich
marked this pull request as ready for review
September 30, 2026 23:09
This was referenced Oct 1, 2026
Merged
sean-e-dietrich
added a commit
that referenced
this pull request
Oct 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #52
Fixes #49
Builds on #58. #58 was squash-merged to main before this branch was cut, so the branch is based on main and shows only its own commits. main (with #40, rc.5) is merged in.
Summary
#52: Vulnerability Score, rebuilt on what the library reads
Kanopi\Firewall\Plugins\VulnerabilityScorereads only these:config.risk_levels: name →{threshold, block, status_code?, expiration_time?}. A request's level is the one with the highest threshold its total reaches. The plugin matches only if that level hasblock: true. The level's status code and ban length override the rule's throughgetStatusCode()/getExpirationTime().config.scoring.methods(method → score),patterns(list of{pattern, score, type, locations}, each counted once),user_agents(list of{pattern, score, type}, first match only).config.scoring.countries, only withmetadata.country_reader(it callscountry(), so it needs a Country database; a City one is refused).config.scoring.asnandasn_patterns(first matching substring), only withmetadata.asn_reader(GeoLite2-ASN).safeRegexMatch(), which needs the same delimiter at both ends.The form has these fields, each stored in the library's own shape and typed as lines:
name threshold match [status] [ban]orname threshold pass.fieldsgroup: method, attack patterns (score type where pattern), user agent (score type pattern), country, network, network organisation.fieldsgroup: the Country and ASN paths.Compiling only re-keys: empty signals are left out, and a reader is written only when a signal needs it. The defaults are a working subset of the library's
malicious-requestspreset.Validation refuses:
Missing, unreadable or wrong-type databases are reported by
check_requirements().Stored
{threshold, weights}rules: upgrade routine 12 (schema 12):The
WITHDRAWNregistry machinery is gone. The old shape is the safety net everywhere a rule is handled:check_requirements()Rule screen: the textarea sanitiser stripped
<and%xxfrom patterns on every save. Fields a type marksverbatimare now read as typed, and the type's validator still checks them.#49: latitude/longitude equals
Checked against 2.35.1:
EvaluateTrait::evaluateComparison()still comparesequals,not_equalsandinstrictly, and #423 coerces only the ASN. So against a MaxMind database (floats),equalsnever matched andnot_equalsmatched everyone.The fix casts the value to a float at compile time, for those operators on
location.latitude/location.longitude, when the rule reads a database. It also setscase_sensitive: true, because the library otherwise lowercases aninlist back to strings. Header sources are left alone: CDN headers are strings, and string against string already works.I chose this over withdrawing
equals: existing rules start working as written, and there's nothing to migrate. Coordinates in a referenced list are filled in after compile and stay text; the README says so.Testing
Unit: 326 OK.
composer analyse: no errors.composer lint: clean.Integration (DDEV): 628 tests OK. The 2 warnings and 19 skips are pre-existing, from the environment.
RuleTypesHonouredTest:vulnerability_scoreis in the coverage enumeration, which now reads the settings each form offers (identical for every other type). The withdrawn exception is removed.test_vulnerability_score: a request above a matching level matches and one below doesn't, evaluated through the real library, with the per-level status and ban, first-match user agent, and a pass level. The effectiverisk_levels/scoringare read back off the plugin object.test_vulnerability_score_databases: the compiled readers, and country + ASN + organisation scoring throughFake_Geo_Reader(which gainscountry()).UpgradeRoutinesTest: routine 12 translates or switches off, and is idempotent.ScreenRoundTripTest: a full vulnerability score rule (fractional threshold, integer ASN keys, negative and fractional scores, encoded patterns) survives render → post → store twice.GeoVariableTest:equals,not_equals,inandgton both coordinates against the database record and CloudFront headers, through a YAML round trip. The database cases fail without the cast.E2E: 13 OK. CLI (
tests/cli/commands.sh): 36 passed.check-versions: OK (rc.5).build-zip: OK, verify-scope OK.DDEV
basic_firewall_*options: unchanged apart from the schema version, 11 → 12, and compile/refresh timestamps.Was AI used in this pull request?