diff --git a/CHANGELOG.md b/CHANGELOG.md index b1a8546..efe5387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.4.1] - 2026-10-16 + +The per-request CPU patch: the scan time budget and the performance monitor stop spending CPU on bookkeeping for every pattern. Lockstep release with the core engines at 4.4.1. + +### Changed + +- **Behavior to know before upgrading.** `detectionMinSamplesForAnomaly` is now validated as an integer from 10 to 1000 with a default of 30, as in the reference engine. It used to accept any positive integer, defaulted to 10 and had no effect, so a config that sets it below 10 now fails validation. The per-pattern recent-times window is `max(min samples, 100)`. + +### Fixed + +- The scan time budget charges CPU once per pass instead of once per pattern (issue #151, PR #152). `ScanTimeBudget.measure` called `process.cpuUsage()` twice for every pattern check. A pass now reads the clock once when it opens, charges at each `yieldIfDue` slice boundary (before the yield, restarting after it, so another task's CPU is never charged) and around the opt-in worker pool's awaits, and makes a final charge when it closes. A budget without a cap never reads the clock. Detect CPU on a ~1 KB JSON-like value plus 10 short headers went from 5.11 ms to 4.05 ms. +- The performance monitor now matches the reference engine. It flags only slower-than-average samples (it used `Math.abs` on the z-score, so faster-than-average samples also fired statistical anomalies), honours `detectionMinSamplesForAnomaly` (it was ignored and a fixed 10 was used), computes the mean once and the deviation in one allocation-free loop, and skips the sanitized anomaly copy when no callback is registered. + ## [4.4.0] - 2026-10-15 The scan-budget release, with client IP, IP list and dynamic rule parity with the reference engine and adapters that reach the real handler. Lockstep release with the core engines at 4.4.0. diff --git a/package.json b/package.json index 724fd41..cf4a968 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "guardcore-ts", - "version": "4.4.0", + "version": "4.4.1", "private": true, "description": "TypeScript port of guard-core — framework-agnostic security middleware engine", "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 21dee83..330b623 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@guardcore/core", - "version": "4.4.0", + "version": "4.4.1", "description": "Framework-agnostic security middleware engine for Node.js and edge runtimes", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/core/tests/test-detection-engine/validation-cache.test.ts b/packages/core/tests/test-detection-engine/validation-cache.test.ts index 0d77335..7b5c1d9 100644 --- a/packages/core/tests/test-detection-engine/validation-cache.test.ts +++ b/packages/core/tests/test-detection-engine/validation-cache.test.ts @@ -97,7 +97,7 @@ describe('PatternValidationCache', () => { const values = Object.values(stored); expect(values.length).toBe(1); expect(values[0]['version']).toBe(ENGINE_VERSION); - expect(engineVersion()).toBe('4.4.0'); + expect(engineVersion()).toBe('4.4.1'); }); it('a corrupt cache starts empty and recovers', async () => { diff --git a/packages/core/tests/test-models/agent-config.test.ts b/packages/core/tests/test-models/agent-config.test.ts index c2b5a34..6f03ef1 100644 --- a/packages/core/tests/test-models/agent-config.test.ts +++ b/packages/core/tests/test-models/agent-config.test.ts @@ -93,7 +93,7 @@ describe('toAgentConfig (the reference to_agent_config seam)', () => { const result = toAgentConfig(config({ onError })); expect(result?.onError).toBe(onError); expect(result?.guardCoreVersion).toBe(ENGINE_VERSION); - expect(result?.guardCoreVersion).toBe('4.4.0'); + expect(result?.guardCoreVersion).toBe('4.4.1'); }); it('rejects an out-of-range status interval and overflow policy at the schema', () => { diff --git a/packages/express/package.json b/packages/express/package.json index f6f7661..c283d16 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -1,6 +1,6 @@ { "name": "@guardcore/express", - "version": "4.4.0", + "version": "4.4.1", "description": "Express adapter for @guardcore/core security middleware", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/fastify/package.json b/packages/fastify/package.json index c197933..59f8d84 100644 --- a/packages/fastify/package.json +++ b/packages/fastify/package.json @@ -1,6 +1,6 @@ { "name": "@guardcore/fastify", - "version": "4.4.0", + "version": "4.4.1", "description": "Fastify adapter for @guardcore/core security middleware", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/hono/package.json b/packages/hono/package.json index c33b0a4..4d16b94 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -1,6 +1,6 @@ { "name": "@guardcore/hono", - "version": "4.4.0", + "version": "4.4.1", "description": "Hono adapter for @guardcore/core security middleware (edge-safe)", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 07f49d5..a95d63b 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -1,6 +1,6 @@ { "name": "@guardcore/nestjs", - "version": "4.4.0", + "version": "4.4.1", "description": "NestJS adapter for @guardcore/core security middleware", "type": "module", "main": "./dist/index.cjs",