From 9eaa2f34f305fe1c2415ca112844b0dc2be53816 Mon Sep 17 00:00:00 2001 From: jekkos Date: Wed, 30 Sep 2026 15:49:18 +0200 Subject: [PATCH 1/5] chore: strip advisory IDs from code comments and changelog (#4720) Per the project's policy of treating security advisory IDs as secret-like, remove the identifiers embedded in source/test comments and CHANGELOG entries. Each keeps its human-readable description (and PR number where present), so traceability is preserved. No logic changes. --- CHANGELOG.md | 12 ++++++------ app/Config/App.php | 2 +- app/Models/Item.php | 2 +- tests/Controllers/HomeTest.php | 10 +++++----- tests/Controllers/ItemKitsControllerTest.php | 2 +- tests/Controllers/ItemsControllerTest.php | 4 ++-- tests/Controllers/LoginTest.php | 2 +- tests/Controllers/ReportsControllerTest.php | 2 +- tests/Controllers/SalesControllerTest.php | 2 +- tests/Models/CustomerRewardPointsTest.php | 2 +- tests/Models/GiftcardTest.php | 2 +- tests/Models/ItemBulkUpdateTest.php | 2 +- tests/Models/ItemQuantityTest.php | 2 +- tests/Models/ReceivingTest.php | 2 +- tests/Models/SaleTest.php | 2 +- 15 files changed, 25 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aef9932b9..b0ee0efac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,7 +101,7 @@ All notable changes to this project will be documented in this file. - Fix: Preserve CHECKBOX attribute state when adding attributes (#4385) by @jekkos - Fix payment type becoming null when editing sales by @Ollama - Fix broken SQL injection fix - use havingLike() instead of having() with named params by @Ollama -- Fix mass assignment vulnerability in bulk edit (GHSA-49mq-h2g4-grr9) by @Ollama +- Fix mass assignment vulnerability in bulk edit by @Ollama - Sync language files (#3468) by @Ollama - Add workflow to auto-update issue templates with releases by @Ollama - Update SECURITY.md with published security advisories by @Ollama @@ -109,15 +109,15 @@ All notable changes to this project will be documented in this file. - Add filter persistence for table views via URL query string (#4400) by @jekkos - Fix filter persistence javascript issues (#4400) by @jekkos - Fix PHPUnit test configuration for database connectivity (#4430) by @jekkos -- Fix IDOR vulnerability in password change (GHSA-mcc2-8rp2-q6ch) (#4427) by @jekkos +- Fix IDOR vulnerability in password change (#4427) by @jekkos - Fix XSS vulnerability in tax invoice view (#4432) by @jekkos - Fix permission bypass in Sales.getManage() access control (#4428) by @jekkos - Update SECURITY.md with published security advisories (#4431) by @jekkos - Fix SQL injection in suggestions column configuration (#4421) by @jekkos - Fix PHPUnit environment variables not being set (#4434) by @jekkos - Fix DECIMAL attribute not respecting locale format (#4422) by @jekkos -- Fix stored XSS vulnerability in Attribute Definitions (GHSA-rvfg-ww4r-rwqf) (#4429) by @jekkos -- Fix: Host Header Injection vulnerability (GHSA-jchf-7hr6-h4f3) by @Ollama +- Fix stored XSS vulnerability in Attribute Definitions (#4429) by @jekkos +- Fix: Host Header Injection vulnerability by @Ollama - Fix stored XSS in gcaptcha_site_key on login page by @Ollama - Fix stored XSS via stock location name by @Ollama - Fix Token_lib::render() for PHP 8.4 compatibility by @Ollama @@ -149,7 +149,7 @@ All notable changes to this project will be documented in this file. - fix: Use file-based session until database is migrated by @Ollama - feat: Improve migration UX on login page by @Ollama - Disable opencode workflow + run docker build by @jekkos -- Fix negative price/quantity/discount validation (GHSA-wv3j-pp8r-7q43) (#4450) by @Nozomu Sasaki (Paul) +- Fix negative price/quantity/discount validation (#4450) by @Nozomu Sasaki (Paul) - fix(ci): replace / with _ in branch names for Docker tags by @Ollama - fix(security): prevent command injection in sendmail path configuration by @Ollama - fix(security): prevent SQL injection in tax controller sort columns by @Ollama @@ -254,7 +254,7 @@ All notable changes to this project will be documented in this file. - fix(validation): broaden sendmail path regex, expand i18n, strip advisory IDs by @objecttothis - fix(security): handle special characters in `.env` key values and improve insertion logic (#4656) by @objecttothis - fix(locale): validate language_code against known locales to block path traversal (#4704) by @jekkos -- Fix GHSA-frx7-c5vv-m3mr: recompute cashup total server-side and force owner identity (#4706) by @jekkos +- Fix: recompute cashup total server-side and force owner identity (#4706) by @jekkos - feat(security): add THROTTLE_KEY env-var fallback for throttle.key (#4707) by @jekkos - fix(i18n): translate remaining English labels in Swiss German Items.php (#4701) by @Rayan Abdul Cader - fix(ci): stop stamping app version onto master and branch Docker tags (#4709) by @jekkos diff --git a/app/Config/App.php b/app/Config/App.php index 69fabf65d..de204b036 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -307,7 +307,7 @@ class App extends BaseConfig /** * Validates and returns a trusted hostname. * - * Security: Prevents Host Header Injection attacks (GHSA-jchf-7hr6-h4f3) + * Security: Prevents Host Header Injection attacks * by validating the HTTP_HOST against a whitelist of allowed hostnames. * * In production: Fails fast if allowedHostnames is not configured. diff --git a/app/Models/Item.php b/app/Models/Item.php index 1268839a3..f8cd99175 100644 --- a/app/Models/Item.php +++ b/app/Models/Item.php @@ -529,7 +529,7 @@ class Item extends Model */ public function updateMultiple(array $itemData, string $itemIds): bool { - // Query Builder bypasses $allowedFields, so the whitelist is enforced here (GHSA-49mq-h2g4-grr9) + // Query Builder bypasses $allowedFields, so the whitelist is enforced here $itemData = array_intersect_key($itemData, array_flip(self::ALLOWED_BULK_EDIT_FIELDS)); if (empty($itemData)) { diff --git a/tests/Controllers/HomeTest.php b/tests/Controllers/HomeTest.php index d8821c37a..d10c8c650 100644 --- a/tests/Controllers/HomeTest.php +++ b/tests/Controllers/HomeTest.php @@ -303,7 +303,7 @@ class HomeTest extends CIUnitTestCase /** * Test non-admin cannot view admin password change form - * BOLA vulnerability fix: GHSA-q58g-gg7v-f9rf + * BOLA vulnerability fix. * * @return void */ @@ -319,7 +319,7 @@ class HomeTest extends CIUnitTestCase /** * Test non-admin cannot change admin password - * BOLA vulnerability fix: GHSA-q58g-gg7v-f9rf + * BOLA vulnerability fix. * * @return void */ @@ -448,7 +448,7 @@ class HomeTest extends CIUnitTestCase /** * Test non-admin cannot view another non-admin's password form - * IDOR vulnerability fix: GHSA-mcc2-8rp2-q6ch + * IDOR vulnerability fix. * * @return void */ @@ -470,7 +470,7 @@ class HomeTest extends CIUnitTestCase /** * Test non-admin cannot change another non-admin's password - * IDOR vulnerability fix: GHSA-mcc2-8rp2-q6ch + * IDOR vulnerability fix. * * @return void */ @@ -503,7 +503,7 @@ class HomeTest extends CIUnitTestCase } /** - * Regression test for GHSA-9gr6-4mm4-4wrq: Home::__construct() previously + * Regression test: Home::__construct() previously * read the raw (single-decoded) URI segment to decide whether to skip * Secure_Controller's module-grant check for 'logout'. A route whose * double-decoded method name resolves to 'logout' must still be treated diff --git a/tests/Controllers/ItemKitsControllerTest.php b/tests/Controllers/ItemKitsControllerTest.php index 566516c40..cfdfca983 100644 --- a/tests/Controllers/ItemKitsControllerTest.php +++ b/tests/Controllers/ItemKitsControllerTest.php @@ -111,7 +111,7 @@ class ItemKitsControllerTest extends CIUnitTestCase $itemKitId = $this->createItemKit(); $this->loginAsAdmin(); - // URL-encoded three times (GHSA-3vpv-jqr3-7256 PoC). + // URL-encoded three times. // The framework's router decodes this twice before routing; the controller used to apply // a third urldecode(), turning the remaining %3C.../%3E into a live tag. // With that urldecode() removed, the value must stay percent-encoded text and never diff --git a/tests/Controllers/ItemsControllerTest.php b/tests/Controllers/ItemsControllerTest.php index 895f87868..1f73c73e5 100644 --- a/tests/Controllers/ItemsControllerTest.php +++ b/tests/Controllers/ItemsControllerTest.php @@ -103,7 +103,7 @@ class ItemsControllerTest extends CIUnitTestCase } /** - * Regression test for GHSA-92cx-fc8x-7wmm: `tax_names[]` containing `<`/`>` + * Regression test: `tax_names[]` containing `<`/`>` * (the stored-XSS vector) must be rejected by postSave. */ public function testPostSaveRejectsMaliciousTaxName(): void @@ -190,7 +190,7 @@ class ItemsControllerTest extends CIUnitTestCase } /** - * Regression test for GHSA-cm7j-957q-8pgg: an attribute definition whose + * Regression test: an attribute definition whose * `definition_name` contains HTML must be entity-escaped when rendered in the * items attributes dropdown, not emitted as a live (executable) tag. */ diff --git a/tests/Controllers/LoginTest.php b/tests/Controllers/LoginTest.php index 8378f98e5..8d75c3134 100644 --- a/tests/Controllers/LoginTest.php +++ b/tests/Controllers/LoginTest.php @@ -9,7 +9,7 @@ use CodeIgniter\Test\FeatureTestTrait; /** * Test suite for the Login controller, including the CI Throttler - * mitigation for brute-force/credential-stuffing (GHSA-hm9c-xchj-xgcp). + * mitigation for brute-force/credential-stuffing. */ class LoginTest extends CIUnitTestCase { diff --git a/tests/Controllers/ReportsControllerTest.php b/tests/Controllers/ReportsControllerTest.php index 7960f508d..c2f59979c 100644 --- a/tests/Controllers/ReportsControllerTest.php +++ b/tests/Controllers/ReportsControllerTest.php @@ -10,7 +10,7 @@ use App\Models\Employee; use Config\OSPOS; /** - * Regression tests for GHSA-9gr6-4mm4-4wrq + * Regression tests for the reports permission bypass * * Reports::__construct() previously derived the report method name from * $request->getUri()->getSegment(2), which CodeIgniter decodes once, while diff --git a/tests/Controllers/SalesControllerTest.php b/tests/Controllers/SalesControllerTest.php index d0b5dec6e..08e643c1a 100644 --- a/tests/Controllers/SalesControllerTest.php +++ b/tests/Controllers/SalesControllerTest.php @@ -14,7 +14,7 @@ use Tests\Support\EmployeeFixtureTrait; use Tests\Support\SaleFixtureTrait; /** - * Regression tests for GHSA-3xf6-8fmq-44wg. + * Regression tests for the Sales per-endpoint access-control bypass. * * A cashier holding only the base "sales" grant (no "reports_sales") must * not be able to reach the per-sale endpoints that getManage() gates diff --git a/tests/Models/CustomerRewardPointsTest.php b/tests/Models/CustomerRewardPointsTest.php index 7671a1d8d..7e89a29d4 100644 --- a/tests/Models/CustomerRewardPointsTest.php +++ b/tests/Models/CustomerRewardPointsTest.php @@ -9,7 +9,7 @@ use Config\Database; use Tests\Support\ConcurrentDbRaceTrait; /** - * Regression tests for GHSA-995p-52qw-5hh2: adjustRewardPoints() must apply + * Regression tests: adjustRewardPoints() must apply * its balance check and its write in a single atomic UPDATE, so that two * concurrent reward-point spends against the same customer can never both * read the same stale balance and double-spend it. diff --git a/tests/Models/GiftcardTest.php b/tests/Models/GiftcardTest.php index e57b88cd9..d097cd673 100644 --- a/tests/Models/GiftcardTest.php +++ b/tests/Models/GiftcardTest.php @@ -9,7 +9,7 @@ use Config\Database; use Tests\Support\ConcurrentDbRaceTrait; /** - * Regression tests for GHSA-995p-52qw-5hh2: decrementGiftcardValue() must + * Regression tests: decrementGiftcardValue() must * apply its balance check and its write in a single atomic UPDATE, so that * two concurrent decrements against the same gift card can never both read * the same stale balance and double-spend it. diff --git a/tests/Models/ItemBulkUpdateTest.php b/tests/Models/ItemBulkUpdateTest.php index a48e2d547..4e1cdcb5a 100644 --- a/tests/Models/ItemBulkUpdateTest.php +++ b/tests/Models/ItemBulkUpdateTest.php @@ -7,7 +7,7 @@ use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; /** - * Regression coverage for GHSA-49mq-h2g4-grr9 (mass assignment in bulk edit). + * Regression coverage for mass assignment in bulk edit. * * Item::update_multiple() writes through the Query Builder, which bypasses the * model's $allowedFields, so these assertions go straight to the items table. diff --git a/tests/Models/ItemQuantityTest.php b/tests/Models/ItemQuantityTest.php index b564fb097..8ca38a267 100644 --- a/tests/Models/ItemQuantityTest.php +++ b/tests/Models/ItemQuantityTest.php @@ -10,7 +10,7 @@ use Tests\Support\ConcurrentDbRaceTrait; use Tests\Support\ItemFixtureTrait; /** - * Regression tests for GHSA-995p-52qw-5hh2: changeQuantity() must apply + * Regression tests: changeQuantity() must apply * its write in a single atomic upsert, so that two concurrent sales of the * same item/location can never both read the same stale quantity and * oversell stock. Unlike the gift card and reward point spends, there is diff --git a/tests/Models/ReceivingTest.php b/tests/Models/ReceivingTest.php index 106d9e3a3..318727b09 100644 --- a/tests/Models/ReceivingTest.php +++ b/tests/Models/ReceivingTest.php @@ -9,7 +9,7 @@ use Tests\Support\EmployeeFixtureTrait; use Tests\Support\ItemFixtureTrait; /** - * Regression tests for GHSA-995p-52qw-5hh2: Receiving::delete_value() must + * Regression tests: Receiving::delete_value() must * correctly reverse the stock quantity change it applied via * Item_quantity::changeQuantity(), using the same atomic upsert as the * sale-checkout and sale-cancel paths. diff --git a/tests/Models/SaleTest.php b/tests/Models/SaleTest.php index 4dffad438..aff3b79c5 100644 --- a/tests/Models/SaleTest.php +++ b/tests/Models/SaleTest.php @@ -10,7 +10,7 @@ use Tests\Support\EmployeeFixtureTrait; use Tests\Support\ItemFixtureTrait; /** - * Regression tests for GHSA-995p-52qw-5hh2: Sale::save_value() must reject + * Regression tests: Sale::save_value() must reject * (and roll back) a payment that would overdraw a gift card or a customer's * reward points, instead of silently applying a stale/negative balance. */ From b9ad77ba07e3304133b040615d07c5284f24e426 Mon Sep 17 00:00:00 2001 From: jekkos Date: Wed, 30 Sep 2026 15:50:11 +0200 Subject: [PATCH 2/5] fix(security): report unwritable .env.lock, make throttle limits configurable (#4714) * fix(security): report unwritable .env.lock, make throttle limits configurable envFileIsWritable() previously checked is_writable(.env) (the file), which passes in Docker even when the mutex file is root-owned by a prior env:provision run. It now checks the real write path: the directory (to create .env.tmp.* + .env.lock) and any existing .env.lock must be writable, so the app throws the clear 'run env:provision' error instead of crashing on 'Unable to open .env.lock'. The Throttle filter now reads throttle.capacity / throttle.seconds from env (default 5 per 60s); capacity <= 0 disables throttling, so operators serving sequential HTTP clients (e.g. Zabbix) are not caught by the lockout. * fix(security): address CodeRabbit review findings - Throttle: validate throttle.capacity as an integer before treating a non-positive value as 'disabled', so a non-numeric value (e.g. 'five') falls back to the default instead of silently bypassing the lockout. Apply the same validation to throttle.seconds. - envFileIsWritable(): also reject an existing non-writable .env on Windows (where rename() cannot replace a read-only destination); keep the check Windows-only since POSIX rename() replaces a read-only dest when the directory is writable. - Tests: restore the prior throttle.capacity env state in ThrottleTest (capture/restore instead of delete); add an invalid-capacity fallback case; skip the not-writable fixtures when running as root (where is_writable() is bypassed). * fix(migration): guard ConvertToCI4 key-write branches with envFileIsWritable() The migration's 'no key' and 'CI3 key' branches called rotateEncryptionKey()/rotateEncryptionKeyTransaction() directly, bypassing the envFileIsWritable() guard that checkEncryption() uses. On a fresh Docker/Compose install where the web runtime cannot write /app/.env.lock, this produced a raw 'fopen(/app/.env.lock): Permission denied' error instead of the actionable 'run php spark env:provision' message. A valid CI4 key now short-circuits to checkEncryption() (no write); every write branch is gated on envFileIsWritable() first. * fix: read provisioned encryption.key so config sees it env:provision persists the key as 'encryption.key' in .env (matching the throttle path and .env.example), but Config\Encryption only read the ENCRYPTION_KEY env var. On a fresh Docker instance the provisioned key was invisible to config('Encryption')->key, so the app believed no key existed and tried to write one -- hitting the .env.lock permission wall. Read encryption.key (via $_SERVER/$_ENV/getenv) first, then fall back to ENCRYPTION_KEY for Docker '-e' usage. Mirrors checkThrottleEncryption(). * fix: cascade encryption.key lookup past empty-string sources * refactor: extract Encryption::resolveKey() and test it without global env mutation * fix(security): decode fallback-selected encryption key like BaseConfig A key picked in the constructor fallback (notably ENCRYPTION_KEY, which BaseConfig never inspects) was assigned verbatim, bypassing the hex2bin:/base64: decode the parent applies to `encryption.key`. Route the selected key through a parseKey() helper mirroring BaseConfig's parseEncryptionKey() so prefixed values decrypt consistently, and add a pure regression test. Co-Authored-By: Claude Opus 4.8 (1M context) * chore: remove advisory ID from comments and tighten verbose comments Per review: treat GHSA advisory IDs like secrets (drop from code) and replace the multi-paragraph comments with concise one-liners that keep the non-obvious "why". No logic changes. --------- Co-authored-by: objecttothis <17935339+objecttothis@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) --- app/Config/Encryption.php | 43 +++++++- .../20220127000000_convertToCI4.php | 22 ++++- app/Filters/Throttle.php | 33 +++++-- app/Helpers/security_helper.php | 29 +++++- tests/Config/EncryptionTest.php | 54 ++++++++++ tests/Filters/ThrottleTest.php | 98 +++++++++++++++++++ tests/helpers/security_helperTest.php | 63 +++++++++--- 7 files changed, 310 insertions(+), 32 deletions(-) create mode 100644 tests/Config/EncryptionTest.php diff --git a/app/Config/Encryption.php b/app/Config/Encryption.php index 9147e1031..310a5a174 100644 --- a/app/Config/Encryption.php +++ b/app/Config/Encryption.php @@ -112,8 +112,47 @@ class Encryption extends BaseConfig parent::__construct(); if ($this->key === '') { - $envKey = getenv('ENCRYPTION_KEY'); - $this->key = $envKey === false ? '' : $envKey; + // Fallback sources (notably the ENCRYPTION_KEY Docker var, which the + // parent never reads) were not decode-parsed, so run them through + // the same parser to keep hex2bin:/base64: keys consistent. + $this->key = self::parseKey(self::resolveKey( + (string) ($_SERVER['encryption.key'] ?? ''), + (string) ($_ENV['encryption.key'] ?? ''), + (string) getenv('encryption.key'), + (string) getenv('ENCRYPTION_KEY'), + )); } } + + /** + * Decode a key's `hex2bin:`/`base64:` prefix, mirroring + * BaseConfig::parseEncryptionKey(); kept static so it is unit-testable. + */ + public static function parseKey(string $key): string + { + if (str_starts_with($key, 'hex2bin:')) { + return (string) hex2bin(substr($key, 8)); + } + + if (str_starts_with($key, 'base64:')) { + return (string) base64_decode(substr($key, 7), true); + } + + return $key; + } + + /** + * Return the first non-empty source (highest precedence first). Cascading + * past empty strings (vs `??`) avoids a blank value shadowing the real key. + */ + public static function resolveKey(string ...$sources): string + { + foreach ($sources as $source) { + if ($source !== '') { + return $source; + } + } + + return ''; + } } diff --git a/app/Database/Migrations/20220127000000_convertToCI4.php b/app/Database/Migrations/20220127000000_convertToCI4.php index 1f005cfe1..f860c7aaa 100644 --- a/app/Database/Migrations/20220127000000_convertToCI4.php +++ b/app/Database/Migrations/20220127000000_convertToCI4.php @@ -7,6 +7,7 @@ use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\Forge; use CodeIgniter\Database\Migration; use CodeIgniter\HTTP\Exceptions\RedirectException; +use RuntimeException; class ConvertToCI4 extends Migration { @@ -32,18 +33,31 @@ class ConvertToCI4 extends Migration $existingKey = (string) config('Encryption')->key; + // A valid CI4 key requires no write — just confirm it is usable. + if ($existingKey !== '' && strlen($existingKey) >= 64) { + checkEncryption(); + + return; + } + + // Every branch below writes to .env. If the runtime user cannot write + // it (e.g. Docker/Compose with a read-only .env mount), fail with an + // actionable message instead of a raw fopen() error deep in the writer. + if (!envFileIsWritable()) { + log_message('critical', 'Encryption key not provisioned and .env is not writable. Run `php spark env:provision` to generate one.'); + + throw new RuntimeException(lang('Error.encryption_key_not_provisioned')); + } + if ($existingKey !== '' && strlen($existingKey) < 64) { // Old CI3-era key: decrypt, rotate, re-encrypt, persist — all under // a single .env lock (see convertCI3EncryptedData). $this->convertCI3EncryptedData($existingKey); - } elseif ($existingKey === '') { + } else { // No key at all: provision a fresh one (single atomic write), then // drop the incidental pre-write backup left behind by the rotation. rotateEncryptionKey(null); removeBackup(); - } else { - // Key already present and a valid CI4 key: confirm it is usable. - checkEncryption(); } } diff --git a/app/Filters/Throttle.php b/app/Filters/Throttle.php index 75ca63a79..ca4a558ab 100644 --- a/app/Filters/Throttle.php +++ b/app/Filters/Throttle.php @@ -8,22 +8,37 @@ use CodeIgniter\HTTP\ResponseInterface; use Config\Services; /** - * Rate limits login/migrate POST attempts, keyed by IP and by submitted - * username, to mitigate brute-force and credential-stuffing attacks - * (GHSA-hm9c-xchj-xgcp). Backed by CodeIgniter's cache-based Throttler, - * so limits are per-server (not shared across nodes on file cache). + * Rate limits login/migrate POST attempts by IP and submitted username to + * mitigate brute-force/credential-stuffing. Backed by CodeIgniter's + * cache-based Throttler, so limits are per-server (not shared on file cache). + * + * Tunable via `throttle.capacity` (default 5; 0 disables) and + * `throttle.seconds` (window, default 60) in .env. */ class Throttle implements FilterInterface { - private const CAPACITY = 5; - private const SECONDS = 60; - public function before(RequestInterface $request, $arguments = null) { if ($request->getMethod() !== 'POST') { return null; } + // Non-positive integer = explicit disable; missing/non-numeric falls + // back to the default so a typo (e.g. "five") cannot bypass lockout. + $capacity = filter_var(env('throttle.capacity'), FILTER_VALIDATE_INT); + if ($capacity === false) { + $capacity = 5; + } + if ($capacity <= 0) { + return null; + } + + $seconds = filter_var(env('throttle.seconds'), FILTER_VALIDATE_INT); + if ($seconds === false) { + $seconds = 60; + } + $seconds = max(1, $seconds); + helper('security'); $throttler = Services::throttler(); @@ -34,8 +49,8 @@ class Throttle implements FilterInterface $username = is_scalar($rawUsername) ? strtolower((string) $rawUsername) : ''; $usernameKey = $username !== '' ? 'login-user-' . hash_hmac('sha256', $username, $secret) : null; - $ipOk = $throttler->check($ipKey, self::CAPACITY, self::SECONDS); - $usernameOk = $usernameKey === null || $throttler->check($usernameKey, self::CAPACITY, self::SECONDS); + $ipOk = $throttler->check($ipKey, $capacity, $seconds); + $usernameOk = $usernameKey === null || $throttler->check($usernameKey, $capacity, $seconds); if (!$ipOk || !$usernameOk) { log_message('warning', 'Login throttled for IP {ip} (username: {username})', [ diff --git a/app/Helpers/security_helper.php b/app/Helpers/security_helper.php index 32635d2c8..3c384ce75 100644 --- a/app/Helpers/security_helper.php +++ b/app/Helpers/security_helper.php @@ -236,17 +236,38 @@ function writeNewEncryptionKey(string $configFile, string $key, string $oldKey): /** * Returns true when the current process can write to (or create) .env. * - * A missing .env file is considered writable when the directory is writable. + * The write path (temp file + lock, then rename) needs write permission on + * the DIRECTORY, not on .env itself — a bind-mounted .env can be writable + * while the dir (or a root-owned .env.lock) is not, so .env's own mode is not + * a reliable signal. * * @return bool */ function envFileIsWritable(): bool { $configPath = config('SecurityEnv')->envPath; + $lockPath = config('SecurityEnv')->lockPath; + $dir = dirname($configPath); - return file_exists($configPath) - ? is_writable($configPath) - : is_writable(dirname($configPath)); + if (!is_writable($dir)) { + return false; + } + + // Windows-only: rename() can't replace a read-only destination, so an + // existing .env must itself be writable (POSIX rename() can, if the dir is). + if (PHP_OS_FAMILY === 'Windows' + && file_exists($configPath) + && !is_writable($configPath) + ) { + return false; + } + + // An existing mutex file (e.g. left by a prior root env:provision) must be writable. + if (file_exists($lockPath) && !is_writable($lockPath)) { + return false; + } + + return true; } /** diff --git a/tests/Config/EncryptionTest.php b/tests/Config/EncryptionTest.php new file mode 100644 index 000000000..071e04519 --- /dev/null +++ b/tests/Config/EncryptionTest.php @@ -0,0 +1,54 @@ +assertSame( + 'server-key', + Encryption::resolveKey('server-key', 'env-key', 'getenv-key', 'docker-key') + ); + } + + public function testEmptyStringDoesNotShadowLaterSource(): void + { + // A `??`-based lookup would stop at a higher-precedence empty string; + // the cascade must skip empties and reach the real key. + $this->assertSame( + 'getenv-key', + Encryption::resolveKey('', '', 'getenv-key', 'docker-key') + ); + + $this->assertSame( + 'docker-key', + Encryption::resolveKey('', '', '', 'docker-key') + ); + } + + public function testAllEmptySourcesReturnEmptyString(): void + { + $this->assertSame('', Encryption::resolveKey('', '', '', '')); + } + + public function testPrefixedFallbackKeyIsDecoded(): void + { + // Fallback-selected keys (notably ENCRYPTION_KEY, which BaseConfig + // never reads) must be decoded like BaseConfig does, so a + // hex2bin:/base64:-prefixed value is not stored verbatim. + $this->assertSame("\xab\xcd", Encryption::parseKey('hex2bin:abcd')); + $this->assertSame("\x68\x65\x6c\x6c\x6f", Encryption::parseKey('base64:aGVsbG8=')); + + // No prefix / empty value passes through unchanged. + $this->assertSame('plain-key', Encryption::parseKey('plain-key')); + $this->assertSame('', Encryption::parseKey('')); + } +} diff --git a/tests/Filters/ThrottleTest.php b/tests/Filters/ThrottleTest.php index 997804430..0edf51926 100644 --- a/tests/Filters/ThrottleTest.php +++ b/tests/Filters/ThrottleTest.php @@ -161,4 +161,102 @@ class ThrottleTest extends CIUnitTestCase $this->assertNotNull($result); $this->assertSame(429, $result->getStatusCode()); } + + public function testCustomCapacityIsHonored(): void + { + $ip = '203.0.113.7'; + $prev = $this->captureEnv('throttle.capacity'); + + $this->putEnv('throttle.capacity', '2'); + + try { + $this->assertNull($this->filter->before($this->makeRequest('POST', $ip, 'c1'))); + $this->assertNull($this->filter->before($this->makeRequest('POST', $ip, 'c2'))); + + $result = $this->filter->before($this->makeRequest('POST', $ip, 'c3')); + + $this->assertNotNull($result); + $this->assertSame(429, $result->getStatusCode()); + } finally { + $this->restoreEnv('throttle.capacity', $prev); + } + } + + public function testZeroCapacityDisablesThrottling(): void + { + $ip = '203.0.113.8'; + $prev = $this->captureEnv('throttle.capacity'); + + $this->putEnv('throttle.capacity', '0'); + + try { + for ($i = 0; $i < 10; $i++) { + $result = $this->filter->before($this->makeRequest('POST', $ip, "z{$i}")); + $this->assertNull($result, "Attempt {$i} should not be throttled when disabled"); + } + } finally { + $this->restoreEnv('throttle.capacity', $prev); + } + } + + public function testInvalidCapacityFallsBackToDefault(): void + { + // A non-numeric value must fall back to the default (5), not disable. + $ip = '203.0.113.9'; + $prev = $this->captureEnv('throttle.capacity'); + + $this->putEnv('throttle.capacity', 'five'); + + try { + for ($i = 0; $i < 5; $i++) { + $this->assertNull($this->filter->before($this->makeRequest('POST', $ip, "v{$i}"))); + } + + // 6th attempt exceeds the default capacity of 5. + $result = $this->filter->before($this->makeRequest('POST', $ip, 'v6')); + $this->assertNotNull($result); + $this->assertSame(429, $result->getStatusCode()); + } finally { + $this->restoreEnv('throttle.capacity', $prev); + } + } + + private function captureEnv(string $key): array + { + return [ + 'putenv' => getenv($key), + 'hasENV' => array_key_exists($key, $_ENV), + 'ENV' => $_ENV[$key] ?? null, + 'hasSRV' => array_key_exists($key, $_SERVER), + 'SERVER' => $_SERVER[$key] ?? null, + ]; + } + + private function putEnv(string $key, string $value): void + { + putenv("{$key}={$value}"); + $_ENV[$key] = $value; + $_SERVER[$key] = $value; + } + + private function restoreEnv(string $key, array $prev): void + { + if ($prev['putenv'] === false) { + putenv($key); + } else { + putenv("{$key}={$prev['putenv']}"); + } + + if ($prev['hasENV']) { + $_ENV[$key] = $prev['ENV']; + } else { + unset($_ENV[$key]); + } + + if ($prev['hasSRV']) { + $_SERVER[$key] = $prev['SERVER']; + } else { + unset($_SERVER[$key]); + } + } } diff --git a/tests/helpers/security_helperTest.php b/tests/helpers/security_helperTest.php index 7d18282e7..42a9d7699 100644 --- a/tests/helpers/security_helperTest.php +++ b/tests/helpers/security_helperTest.php @@ -301,14 +301,31 @@ class security_helperTest extends CIUnitTestCase $this->assertTrue(envFileIsWritable()); } - public function testEnvFileIsWritableReturnsFalseWhenFileIsReadonly(): void + public function testEnvFileIsWritableReturnsFalseWhenLockFileIsReadOnly(): void { + $this->skipIfRoot(); file_put_contents($this->envPath, "# tmp\n"); - chmod($this->envPath, 0444); + file_put_contents($this->lockPath, ""); + chmod($this->lockPath, 0444); - $this->assertFalse(envFileIsWritable()); + try { + $this->assertFalse(envFileIsWritable()); + } finally { + @unlink($this->lockPath); + } + } - chmod($this->envPath, 0644); + public function testEnvFileIsWritableReturnsFalseWhenDirectoryIsNotWritable(): void + { + $this->skipIfRoot(); + file_put_contents($this->envPath, "# tmp\n"); + chmod($this->sandbox, 0500); + + try { + $this->assertFalse(envFileIsWritable()); + } finally { + chmod($this->sandbox, 0700); + } } // -- checkEncryption() -- @@ -340,16 +357,18 @@ class security_helperTest extends CIUnitTestCase public function testCheckEncryptionThrowsWhenKeyEmptyAndEnvNotWritable(): void { + $this->skipIfRoot(); config('Encryption')->key = ''; file_put_contents($this->envPath, "encryption.key=''\n"); - chmod($this->envPath, 0444); + file_put_contents($this->lockPath, ""); + chmod($this->lockPath, 0444); try { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('provisioned'); checkEncryption(); } finally { - chmod($this->envPath, 0644); + @unlink($this->lockPath); } } @@ -392,11 +411,9 @@ class security_helperTest extends CIUnitTestCase public function testCheckEncryptionRollsBackWhenSaveAllFails(): void { - // Regression guard (thread #2): if the post-rotation saveAll() fails, - // the freshly rotated .env key must be restored from the backup so the - // original CI3 ciphertext stays decryptable. A failing fake Appconfig - // (injected via CI3SecretConverter) forces saveAll() to throw without - // a real database. + // If the post-rotation saveAll() fails, the rotated key must be rolled + // back so the original CI3 ciphertext stays decryptable. A failing fake + // Appconfig (injected via CI3SecretConverter) makes saveAll() throw. $oldKey = bin2hex(random_bytes(16)); // < 64 chars -> CI3 era $plaintext = ['smtp_pass' => 'keep-me-safe']; $ciphertext = array_map(fn ($v) => $this->ci3Encrypt($v, $oldKey), $plaintext); @@ -453,17 +470,19 @@ class security_helperTest extends CIUnitTestCase public function testCheckThrottleEncryptionThrowsWhenKeyMissingAndEnvNotWritable(): void { + $this->skipIfRoot(); putenv('throttle.key'); unset($_ENV['throttle.key'], $_SERVER['throttle.key']); file_put_contents($this->envPath, "encryption.key='abc'\n"); - chmod($this->envPath, 0444); + file_put_contents($this->lockPath, ""); + chmod($this->lockPath, 0444); try { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('provisioned'); checkThrottleEncryption(); } finally { - chmod($this->envPath, 0644); + @unlink($this->lockPath); } } @@ -657,4 +676,22 @@ class security_helperTest extends CIUnitTestCase $this->assertFileDoesNotExist($this->backupPath); } + + /** + * When PHPUnit runs as root, is_writable() reports 0444 files as writable + * (and root bypasses directory permission bits), so the "not writable" + * fixtures cannot be faked reliably. Skip those tests under root. + */ + private function skipIfRoot(): void + { + $isRoot = false; + if (function_exists('posix_geteuid')) { + $isRoot = posix_geteuid() === 0; + } elseif (function_exists('get_current_user')) { + $isRoot = in_array(get_current_user(), ['root', '0'], true); + } + if ($isRoot) { + $this->markTestSkipped('is_writable() is bypassed when running as root; cannot fake a non-writable fixture'); + } + } } From 7aa624c8ea020f0412a3efb873f8197aa4335ba5 Mon Sep 17 00:00:00 2001 From: jekkos Date: Wed, 30 Sep 2026 16:23:17 +0000 Subject: [PATCH 3/5] chore: reset 3.4.2 (undo premature 3.4.3 bump + stale changelog) for re-cut --- CHANGELOG.md | 232 +-------------------------------------------- app/Config/App.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 234 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0ee0efac..407c8b711 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,4 @@ -[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.4.2...HEAD -[3.4.2]: https://github.com/opensourcepos/opensourcepos/compare/3.4.1...3.4.2 +[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.4.1...HEAD [3.4.1]: https://github.com/opensourcepos/opensourcepos/compare/3.4.0...3.4.1 [3.4.0]: https://github.com/opensourcepos/opensourcepos/compare/3.3.9...3.4.0 [3.3.9]: https://github.com/opensourcepos/opensourcepos/compare/3.3.8...3.3.9 @@ -34,235 +33,6 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -## [3.4.2] - 2026-09-24 -- Fix writable folder permission check (#4270) (#4273) by @jekkos -- Extended payment delete fix (#4274) by @jekkos -- Upgrade github workflow (#3708) (#4280) by @jekkos -- Fix typo in writeable (#4270) by @jekkos -- Fix migration 20250522000000 (#4284) by @jekkos -- Upgrade to ci 4.6.2 (#4296) (#4298) by @jekkos -- Fix barcode generation in items (#4270) by @jekkos -- Allow empty tax category id (#4285) (#4288) by @jekkos -- Fix security incident email address (#4298) by @jekkos -- Fix item kits update (#4294) by @jekkos -- Revert toast message sanitization (#4302) by @jekkos -- Fix for suspended sales (#4283) (#4303) by @jekkos -- Fix reference to uploads folder (#4270) (#4286) by @jekkos -- Add generic try/catch in import (#4302) by @jekkos -- Bump jspdf from 3.0.1 to 3.0.2 (#4309) by @dependabot[bot] -- Fix mount path for uploads (#4308) by @jekkos -- Add transactions to missing config keys migration. (#4318) by @Joe Williams -- [Feature] Add logging to migrations (#4327) by @Joe Williams -- Clean up docker compose setup (#4308) by @jekkos -- Fix tax configuration pages (#4331) by @jekkos -- Update SECURITY.md contact (#4335) by @jekkos -- Add server side validation for password (#4335) by @jekkos -- Add env variable to disallow pwd change (#4325) by @jekkos -- Add recent releases to issue template (#4317) by @jekkos -- Add DOMpurify + fix XSS (#4341) by @jekkos -- Fix attachment cid (#4314) by @jekkos -- Add DOMPurify to JS includes (#4341) by @jekkos -- Allow anonymous giftcard creation (#4278) by @jekkos -- Fix toast notifications in config (#4341) (#4343) by @jekkos -- Fix creation of date attribute value (#4310) (#4344) by @jekkos -- Fix wrong migration script location (#4285) by @jekkos -- Escape return_policy in receipt + invoice (#4349) by @jekkos -- Fix for detailed suppliers report (#4351) by @jekkos -- Add show/hide cost price & profit feature - in reports #4130 (#4350) by @BhojKamal -- Fix travis build after merge (#4130) by @jekkos -- Add equals as permitted URI character (#4329) by @Chathura Dilushanka -- Fix multiple XSS vulnerabilities (#3965) (#4356) by @jekkos -- Bump lodash from 4.17.21 to 4.17.23 (#4369) by @dependabot[bot] -- Bump jspdf and jspdf-autotable (#4373) by @dependabot[bot] -- Fix XSS vulnerabilities in invoices + receipts (#3965) (#4363) by @jekkos -- Fix XSS vulnerability in attributes (#3965) by @jekkos -- Fix XSS vulnerability in register (#3965) by @jekkos -- Fix XSS vulnerability in register (#3965) by @jekkos -- Fix XSS vulnerabilities in invoice_email.php view by @jekkos -- Fix permission bypass in Reports submodule access control (#4389) by @jekkos -- Use Content-Type application/json for AJAX responses (#4357) by @jekkos -- Language Array Key Typo Fix (#4371) by @Lucas Lyimo -- Fix: Refresh session language for employee after update. (#4245) by @jekkos -- Fix Docker image upload by replacing slashes in TAG by @jekkos -- Fix broken object-level authorization in Employees controller (CVE-worthy) (#4391) by @jekkos -- Bump dompurify from 3.3.1 to 3.3.2 (#4402) by @dependabot[bot] -- Fix incorrect argument types in migration round_number() methods (#4403) by @jekkos -- dd validation for invalid stock locations in CSV import (#4399) by @jekkos -- fix(security): whitelist and validate invoice template types (#4393) by @jekkos -- Fix second-order SQL injection in currency_symbol config (#4390) by @jekkos -- Add row-level authorization to password change endpoints (#4401) by @jekkos -- Fix: Handle image filenames with spaces in thumbnails by @jekkos -- Fix: Sanitize image filenames to prevent thumbnail display issues (#4372) by @jekkos -- Add migration to fix existing image filenames with spaces (#4372) by @jekkos -- Refactor: Move ADMIN_MODULES to constants, rename methods to camelCase by @jekkos -- Fix SQL injection in custom attribute search by @Ollama -- Fix stored XSS vulnerability in item descriptions by @Ollama -- Fix stored XSS vulnerabilities in employee permissions and customer data by @Ollama -- Fix: Preserve CHECKBOX attribute state when adding attributes (#4385) by @jekkos -- Fix payment type becoming null when editing sales by @Ollama -- Fix broken SQL injection fix - use havingLike() instead of having() with named params by @Ollama -- Fix mass assignment vulnerability in bulk edit by @Ollama -- Sync language files (#3468) by @Ollama -- Add workflow to auto-update issue templates with releases by @Ollama -- Update SECURITY.md with published security advisories by @Ollama -- Bump jspdf from 4.1.0 to 4.2.0 (#4383) by @dependabot[bot] -- Add filter persistence for table views via URL query string (#4400) by @jekkos -- Fix filter persistence javascript issues (#4400) by @jekkos -- Fix PHPUnit test configuration for database connectivity (#4430) by @jekkos -- Fix IDOR vulnerability in password change (#4427) by @jekkos -- Fix XSS vulnerability in tax invoice view (#4432) by @jekkos -- Fix permission bypass in Sales.getManage() access control (#4428) by @jekkos -- Update SECURITY.md with published security advisories (#4431) by @jekkos -- Fix SQL injection in suggestions column configuration (#4421) by @jekkos -- Fix PHPUnit environment variables not being set (#4434) by @jekkos -- Fix DECIMAL attribute not respecting locale format (#4422) by @jekkos -- Fix stored XSS vulnerability in Attribute Definitions (#4429) by @jekkos -- Fix: Host Header Injection vulnerability by @Ollama -- Fix stored XSS in gcaptcha_site_key on login page by @Ollama -- Fix stored XSS via stock location name by @Ollama -- Fix Token_lib::render() for PHP 8.4 compatibility by @Ollama -- Use CIUnitTestCase for consistency with other tests by @Ollama -- Fix: Pass parameter to generate() and add composite format tests by @Ollama -- Fix strftime directives handling and tighten test assertions by @Ollama -- Add AGENTS.md with coding guidelines for AI agents by @Ollama -- Fix: Add Debit Card filter to Daily Sales and Takings by @Ollama -- Fix Taxes Summary Report totals not matching row values by @Ollama -- Add unit tests for Taxes Summary Report calculations by @Ollama -- Fix rounding consistency and update tests per review feedback by @Ollama -- Rewrite tests to use database integration testing by @Ollama -- Add seed data to tests for proper integration testing by @Ollama -- Fix: Restrict employee selection in expenses and receivings forms by @Ollama -- Fix review comments: remove redundant loop and add XSS escaping by @Ollama -- Bump jspdf from 4.2.0 to 4.2.1 by @dependabot[bot] -- Bump picomatch from 2.3.1 to 2.3.2 (#4451) by @dependabot[bot] -- fix: Clear sale session after completing sale by @Ollama -- fix: Remove redundant clear_mode() calls by @Ollama -- Translate missing strings in multiple languages by @Ollama -- Fix translation issues from code review by @Ollama -- Remove English fallbacks from non-English translations by @Ollama -- Add Calendar.php translations for missing languages by @Ollama -- feat: migrate CI from Travis to GitHub Actions with enhancements by @Ollama -- refactor: remove tables.sql and constraints.sql (#4447) by @Ollama -- refactor: remove build-database gulp task (#4447) by @Ollama -- refactor: optimize Docker image size by @Ollama -- fix: remove duplicate phpunit.xml that prevented tests from running by @Ollama -- fix: Use file-based session until database is migrated by @Ollama -- feat: Improve migration UX on login page by @Ollama -- Disable opencode workflow + run docker build by @jekkos -- Fix negative price/quantity/discount validation (#4450) by @Nozomu Sasaki (Paul) -- fix(ci): replace / with _ in branch names for Docker tags by @Ollama -- fix(security): prevent command injection in sendmail path configuration by @Ollama -- fix(security): prevent SQL injection in tax controller sort columns by @Ollama -- feat: add release workflow with automated version bumping by @Ollama -- refactor: simplify release workflow to version bump only by @Ollama -- fix: address review comments by @Ollama -- fix: address all review comments and restore issue template version update by @Ollama -- fix: Tax Rate form not loading due to router service failure (#4479) by @jekkos -- fix: Handle empty database on fresh install (#4467) by @jekkos -- Fix: Improve allowedHostnames .env configuration and fail-fast in production (#4482) by @jekkos -- [Feature]: Case-sensitive attribute updates and CSV Import attribute deletion capability (#4384) by @objecttothis -- fix: change docker image tag to master by @jekkos -- Update to CodeIgniter 4.7.2 (#4485) by @objecttothis -- Bump lodash from 4.17.23 to 4.18.1 (#4462) by @dependabot[bot] -- [Fix]: Add missing return statements to Sales Controller functions by @Ollama -- Encourage users to star the project by @objecttothis -- Bump dompurify from 3.3.2 to 3.4.0 (#4512) by @dependabot[bot] -- fix: propagate attribute definition failures in postSaveGeneral() (#4509) by @jekkos -- fix: Escape dynamic output and fix CSS property in barcode_sheet.php (#4501) by @jekkos -- Fix CRC currency reverting to EUR/LAK in locale config (#4511) by @jekkos -- fix: Add missing $img_tag variable in Sales::getSendPdf() (#4515) by @jekkos -- fix: Language dropdown not displaying saved language correctly (#4518) by @jekkos -- fix: Scope orWhere clauses in Item::exists() and Item::get_item_id() (#4520) by @jekkos -- fix: Update calendar translations (#4498) by @jekkos -- fix: Catch mysqli_sql_exception in DB fallback handlers for fresh Docker installs (#4525) by @jekkos -- fix(home): improve internal data type handling for user identification in auth process by @enricodelarosa -- Assignable Keyboard Shortcuts Updates (#4532) by @WShells -- chore: miscellaneous updates and improvements (#4530) by @BudsieBuds -- chore(deps): bump minimatch from 3.1.2 to 3.1.5 (#4536) by @dependabot[bot] -- chore: sync project files to match upstream templates (#4537) by @BudsieBuds -- fix(ci): include hidden files in Docker build context (#4543) by @jekkos -- feat: add ALLOWED_HOSTNAMES environment variable support for Docker/Compose (#4544) by @jekkos -- fix(docker): correct permissions and fix migration barcode_type error (#4546) by @jekkos -- docs: Update SECURITY.md with disclosure process (#4549) by @jekkos -- feat: Bank transfer and wallet payment option added #4540 (#4547) by @BhojKamal -- fix(security): Path traversal vulnerability in getPicThumb (#4545) by @jekkos -- fix(security): SQL injection and path traversal vulnerabilities (#4539) by @jekkos -- fix: Capture CSV import failures in save_tax_data and save_inventory_quantities (#4507) by @jekkos -- fix: validate attributeId > 0 in saveAttributeLink() (#4508) by @jekkos -- feat: Add deployment workflow with approval gates (#4522) by @jekkos -- Bugfixes to get Migration working on MySQL and MariaDB (#4551) by @objecttothis -- Bugfix: Sale search in register not handling trailing space properly (#4557) by @objecttothis -- fix: cast string returns to int in MY_Migration (#4560) by @jekkos -- Add fallback for allowedHostnames environment variable (#4565) by @objecttothis -- fix: Allow searching by Sale ID in Takings/Daily Sales view (#4569) by @jekkos -- Add Guards to Database Migration (#4571) by @objecttothis -- fix: tax rate inputs blank with comma-decimal locales (#4555) by @jekkos -- fix(security): Fix DOMPDF RCE and customer email sanitization (#4568) by @jekkos -- Fix overly lenient date validation (#4574) by @objecttothis -- fix(security): Escape attribute value in register by @jekkos -- chore(deps): bump dompurify from 3.4.0 to 3.4.11 (#4578) by @dependabot[bot] -- Bugfix: Fix problems with migration UI in login (#4589) by @objecttothis -- Forgotten commit from login migration branch (#4592) by @objecttothis -- Feature: Payment reference code (#4587) by @objecttothis -- fix(giftcard): correct return type and rename getGiftcardId method (#4600) by @objecttothis -- chore(deps): bump dompurify from 3.4.11 to 3.4.12 (#4602) by @dependabot[bot] -- bugfix(reports): crash on detailed sales report when sale has multiple payments with reference codes (#4599) by @objecttothis -- style(models): normalize quote style in SQL GROUP_CONCAT expression (#4608) by @objecttothis -- chore(deps): upgrade dompdf from v2.0.8 to v3.1.6 (#4610) by @objecttothis -- chore(deps): bump brace-expansion (#4614) by @dependabot[bot] -- chore(deps): add xlsx via SheetJS CDN and upgrade tableexport plugin (#4615) by @objecttothis -- chore(deps): bump lodash.template from 4.5.0 to 4.18.1 (#4616) by @objecttothis -- fix(login): skip auth validation on new install to allow migration (#4609) by @objecttothis -- fix: Wrap postSave() in single transaction for atomicity (#4506) by @jekkos -- refactor: Replace var with let/const in JavaScript files (#4503) by @jekkos -- fix: get_definition_by_name() returns single row instead of multi-dimensional array (#4452) (#4464) by @Jonathan Chang -- fix(config): validate theme param to prevent XSS via invalid theme (#4620) by @objecttothis -- fix(sales): enforce server-side authorization for price changes (#4631) by @objecttothis -- fix(sales): escape quote number in email template to prevent XSS (#4625) by @objecttothis -- refactor(migrations): rename execute_script to executeScript across all migrations (#4611) by @objecttothis -- fix(auth): validate gcaptcha before password to prevent bypass (#4618) by @objecttothis -- refactor: apply PSR-12 naming to Attribute definition methods (#4624) by @Rayan Abdul Cader -- fix(security): sanitize filenames and escape logo path in config (#4630) by @objecttothis -- fix: use db_connect() for item save transactions (#4636) by @richardmilles -- fix(xss): remove redundant escaping that double-encoded item attribute values (#4628) by @objecttothis -- chore(deps): bump codeigniter4/framework from 4.7.2 to 4.7.4 (#4638) by @dependabot[bot] -- chore(deps): bump dompurify from 3.4.12 to 3.4.13 (#4639) by @dependabot[bot] -- Reject item CSV imports whose header row is missing required columns (#4597) by @Sai Asish Y -- fix(items): validate item_number and skip receiving quantity default for temp items (#4621) by @objecttothis -- Feature: CodeIgniter Throttler (#4619) by @objecttothis -- Bugfix: Resolve Race Condition in Rewards and Gift Card Spending (#4640) by @objecttothis -- hotfix(auth): hash throttler keys to improve security (#4646) by @objecttothis -- fix(items): add explicit sentinel value for clearing supplier in bulk edit (#4617) by @objecttothis -- Codeigniter changes between 4.7.2 and 4.7.4 (#4650) by @objecttothis -- Hotfix: Fix CI3 database migration caused by regression (#4649) by @objecttothis -- fix(sales): gate per-record endpoints behind reports_sales grant (#4627) by @objecttothis -- fix(email): update method call to camelCase for PSR-12 compliance (#4659) by @objecttothis -- Feature admin account safeguards (#4657) by @objecttothis -- Ensure payload data is escaped to prevent XSS (#4664) by @objecttothis -- fix(sales): enforce reports_sales grant on search endpoint (#4673) by @objecttothis -- fix(reports, home): resolve double-URL-decoding bypass for method grants (#4660) (#4666) by @objecttothis -- Bugfix tax names (#4677) by @objecttothis -- feat(validation, tests): add `valid_path_strict` rule and integrate into mailpath validation (#4684) by @objecttothis -- fix(sales): harden payment validation and gift card handling by @objecttothis -- fix: prevent duplicate items when editing imported rows (#4634) by @richardmilles -- fix(barcode): resolve string interpolation issue in barcode display html (#4692) by @Vighnesh Nilajakar -- fix(sales): gate getSearch behind reports_sales grant by @jekkos -- bugfix(sales): reject non-negative gift-card amount_tendered (#4674) by @jekkos -- fix(sales): harden unsuspend with auth, status gating, and null safety by @objecttothis -- fix(tests): resolve all phpunit failures — clean-DB suite green (#4626) (#4691) by @jekkos -- fix(licenses): guard malformed data, parallelize gulp tasks, require Node 20 by @objecttothis -- fix(validation): broaden sendmail path regex, expand i18n, strip advisory IDs by @objecttothis -- fix(security): handle special characters in `.env` key values and improve insertion logic (#4656) by @objecttothis -- fix(locale): validate language_code against known locales to block path traversal (#4704) by @jekkos -- Fix: recompute cashup total server-side and force owner identity (#4706) by @jekkos -- feat(security): add THROTTLE_KEY env-var fallback for throttle.key (#4707) by @jekkos -- fix(i18n): translate remaining English labels in Swiss German Items.php (#4701) by @Rayan Abdul Cader -- fix(ci): stop stamping app version onto master and branch Docker tags (#4709) by @jekkos -- chore(deps): bump fflate from 0.8.2 to 0.8.3 (#4690) by @dependabot[bot] -- fix(i18n): swap print_delay_autoreturn number/required messages in 5 locales (#4699) by @Rayan Abdul Cader -- chore(release): unified git-cliff release workflow (changelog + tag + optional bump) (#4711) by @jekkos -- fix(release): push changelog/bump to master via admin PAT (GITHUB_TOKEN blocked by branch protection) by @jekkos - ## [3.4.1] - 2025-06-05 - Feature: PSR-12 Compliant Indentation by @objecttothis in ([#4196](https://github.com/opensourcepos/opensourcepos/pull/4196)) - Add .env to dist zip by @jekkos in ([#4199](https://github.com/opensourcepos/opensourcepos/pull/4199)) diff --git a/app/Config/App.php b/app/Config/App.php index de204b036..13b132b0d 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -12,7 +12,7 @@ class App extends BaseConfig * * @var string */ - public string $application_version = '3.4.3'; + public string $application_version = '3.4.2'; /** * This is the commit hash for the version you are currently using. diff --git a/package-lock.json b/package-lock.json index 13a032102..eb1011d1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@opensourcepos/opensourcepos", - "version": "3.4.3", + "version": "3.4.2", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index 13e735c21..f2546ba4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@opensourcepos/opensourcepos", - "version": "3.4.3", + "version": "3.4.2", "description": "Open Source Point of Sale is a web based point of sale system written in the PHP language. It uses MySQL as the data storage back-end and has a simple user interface.", "keywords": [ "point-of-sale", From cb5e2a16a9ac49e0d582eaa4d198633ce9d71efd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Sep 2026 16:27:22 +0000 Subject: [PATCH 4/5] docs: add 3.4.2 changelog --- CHANGELOG.md | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 239 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 407c8b711..890a137b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ -[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.4.1...HEAD +[unreleased]: https://github.com/opensourcepos/opensourcepos/compare/3.4.2...HEAD +[3.4.2]: https://github.com/opensourcepos/opensourcepos/compare/3.4.1...3.4.2 [3.4.1]: https://github.com/opensourcepos/opensourcepos/compare/3.4.0...3.4.1 [3.4.0]: https://github.com/opensourcepos/opensourcepos/compare/3.3.9...3.4.0 [3.3.9]: https://github.com/opensourcepos/opensourcepos/compare/3.3.8...3.3.9 @@ -33,6 +34,243 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [3.4.2] - 2026-09-30 +- Fix writable folder permission check (#4270) (#4273) by @jekkos +- Extended payment delete fix (#4274) by @jekkos +- Upgrade github workflow (#3708) (#4280) by @jekkos +- Fix typo in writeable (#4270) by @jekkos +- Fix migration 20250522000000 (#4284) by @jekkos +- Upgrade to ci 4.6.2 (#4296) (#4298) by @jekkos +- Fix barcode generation in items (#4270) by @jekkos +- Allow empty tax category id (#4285) (#4288) by @jekkos +- Fix security incident email address (#4298) by @jekkos +- Fix item kits update (#4294) by @jekkos +- Revert toast message sanitization (#4302) by @jekkos +- Fix for suspended sales (#4283) (#4303) by @jekkos +- Fix reference to uploads folder (#4270) (#4286) by @jekkos +- Add generic try/catch in import (#4302) by @jekkos +- Bump jspdf from 3.0.1 to 3.0.2 (#4309) by @dependabot[bot] +- Fix mount path for uploads (#4308) by @jekkos +- Add transactions to missing config keys migration. (#4318) by @Joe Williams +- [Feature] Add logging to migrations (#4327) by @Joe Williams +- Clean up docker compose setup (#4308) by @jekkos +- Fix tax configuration pages (#4331) by @jekkos +- Update SECURITY.md contact (#4335) by @jekkos +- Add server side validation for password (#4335) by @jekkos +- Add env variable to disallow pwd change (#4325) by @jekkos +- Add recent releases to issue template (#4317) by @jekkos +- Add DOMpurify + fix XSS (#4341) by @jekkos +- Fix attachment cid (#4314) by @jekkos +- Add DOMPurify to JS includes (#4341) by @jekkos +- Allow anonymous giftcard creation (#4278) by @jekkos +- Fix toast notifications in config (#4341) (#4343) by @jekkos +- Fix creation of date attribute value (#4310) (#4344) by @jekkos +- Fix wrong migration script location (#4285) by @jekkos +- Escape return_policy in receipt + invoice (#4349) by @jekkos +- Fix for detailed suppliers report (#4351) by @jekkos +- Add show/hide cost price & profit feature - in reports #4130 (#4350) by @BhojKamal +- Fix travis build after merge (#4130) by @jekkos +- Add equals as permitted URI character (#4329) by @Chathura Dilushanka +- Fix multiple XSS vulnerabilities (#3965) (#4356) by @jekkos +- Bump lodash from 4.17.21 to 4.17.23 (#4369) by @dependabot[bot] +- Bump jspdf and jspdf-autotable (#4373) by @dependabot[bot] +- Fix XSS vulnerabilities in invoices + receipts (#3965) (#4363) by @jekkos +- Fix XSS vulnerability in attributes (#3965) by @jekkos +- Fix XSS vulnerability in register (#3965) by @jekkos +- Fix XSS vulnerability in register (#3965) by @jekkos +- Fix XSS vulnerabilities in invoice_email.php view by @jekkos +- Fix permission bypass in Reports submodule access control (#4389) by @jekkos +- Use Content-Type application/json for AJAX responses (#4357) by @jekkos +- Language Array Key Typo Fix (#4371) by @Lucas Lyimo +- Fix: Refresh session language for employee after update. (#4245) by @jekkos +- Fix Docker image upload by replacing slashes in TAG by @jekkos +- Fix broken object-level authorization in Employees controller (CVE-worthy) (#4391) by @jekkos +- Bump dompurify from 3.3.1 to 3.3.2 (#4402) by @dependabot[bot] +- Fix incorrect argument types in migration round_number() methods (#4403) by @jekkos +- dd validation for invalid stock locations in CSV import (#4399) by @jekkos +- fix(security): whitelist and validate invoice template types (#4393) by @jekkos +- Fix second-order SQL injection in currency_symbol config (#4390) by @jekkos +- Add row-level authorization to password change endpoints (#4401) by @jekkos +- Fix: Handle image filenames with spaces in thumbnails by @jekkos +- Fix: Sanitize image filenames to prevent thumbnail display issues (#4372) by @jekkos +- Add migration to fix existing image filenames with spaces (#4372) by @jekkos +- Refactor: Move ADMIN_MODULES to constants, rename methods to camelCase by @jekkos +- Fix SQL injection in custom attribute search by @Ollama +- Fix stored XSS vulnerability in item descriptions by @Ollama +- Fix stored XSS vulnerabilities in employee permissions and customer data by @Ollama +- Fix: Preserve CHECKBOX attribute state when adding attributes (#4385) by @jekkos +- Fix payment type becoming null when editing sales by @Ollama +- Fix broken SQL injection fix - use havingLike() instead of having() with named params by @Ollama +- Fix mass assignment vulnerability in bulk edit (GHSA-49mq-h2g4-grr9) by @Ollama +- Sync language files (#3468) by @Ollama +- Add workflow to auto-update issue templates with releases by @Ollama +- Update SECURITY.md with published security advisories by @Ollama +- Bump jspdf from 4.1.0 to 4.2.0 (#4383) by @dependabot[bot] +- Add filter persistence for table views via URL query string (#4400) by @jekkos +- Fix filter persistence javascript issues (#4400) by @jekkos +- Fix PHPUnit test configuration for database connectivity (#4430) by @jekkos +- Fix IDOR vulnerability in password change (GHSA-mcc2-8rp2-q6ch) (#4427) by @jekkos +- Fix XSS vulnerability in tax invoice view (#4432) by @jekkos +- Fix permission bypass in Sales.getManage() access control (#4428) by @jekkos +- Update SECURITY.md with published security advisories (#4431) by @jekkos +- Fix SQL injection in suggestions column configuration (#4421) by @jekkos +- Fix PHPUnit environment variables not being set (#4434) by @jekkos +- Fix DECIMAL attribute not respecting locale format (#4422) by @jekkos +- Fix stored XSS vulnerability in Attribute Definitions (GHSA-rvfg-ww4r-rwqf) (#4429) by @jekkos +- Fix: Host Header Injection vulnerability (GHSA-jchf-7hr6-h4f3) by @Ollama +- Fix stored XSS in gcaptcha_site_key on login page by @Ollama +- Fix stored XSS via stock location name by @Ollama +- Fix Token_lib::render() for PHP 8.4 compatibility by @Ollama +- Use CIUnitTestCase for consistency with other tests by @Ollama +- Fix: Pass parameter to generate() and add composite format tests by @Ollama +- Fix strftime directives handling and tighten test assertions by @Ollama +- Add AGENTS.md with coding guidelines for AI agents by @Ollama +- Fix: Add Debit Card filter to Daily Sales and Takings by @Ollama +- Fix Taxes Summary Report totals not matching row values by @Ollama +- Add unit tests for Taxes Summary Report calculations by @Ollama +- Fix rounding consistency and update tests per review feedback by @Ollama +- Rewrite tests to use database integration testing by @Ollama +- Add seed data to tests for proper integration testing by @Ollama +- Fix: Restrict employee selection in expenses and receivings forms by @Ollama +- Fix review comments: remove redundant loop and add XSS escaping by @Ollama +- Bump jspdf from 4.2.0 to 4.2.1 by @dependabot[bot] +- Bump picomatch from 2.3.1 to 2.3.2 (#4451) by @dependabot[bot] +- fix: Clear sale session after completing sale by @Ollama +- fix: Remove redundant clear_mode() calls by @Ollama +- Translate missing strings in multiple languages by @Ollama +- Fix translation issues from code review by @Ollama +- Remove English fallbacks from non-English translations by @Ollama +- Add Calendar.php translations for missing languages by @Ollama +- feat: migrate CI from Travis to GitHub Actions with enhancements by @Ollama +- refactor: remove tables.sql and constraints.sql (#4447) by @Ollama +- refactor: remove build-database gulp task (#4447) by @Ollama +- refactor: optimize Docker image size by @Ollama +- fix: remove duplicate phpunit.xml that prevented tests from running by @Ollama +- fix: Use file-based session until database is migrated by @Ollama +- feat: Improve migration UX on login page by @Ollama +- Disable opencode workflow + run docker build by @jekkos +- Fix negative price/quantity/discount validation (GHSA-wv3j-pp8r-7q43) (#4450) by @Nozomu Sasaki (Paul) +- fix(ci): replace / with _ in branch names for Docker tags by @Ollama +- fix(security): prevent command injection in sendmail path configuration by @Ollama +- fix(security): prevent SQL injection in tax controller sort columns by @Ollama +- feat: add release workflow with automated version bumping by @Ollama +- refactor: simplify release workflow to version bump only by @Ollama +- fix: address review comments by @Ollama +- fix: address all review comments and restore issue template version update by @Ollama +- fix: Tax Rate form not loading due to router service failure (#4479) by @jekkos +- fix: Handle empty database on fresh install (#4467) by @jekkos +- Fix: Improve allowedHostnames .env configuration and fail-fast in production (#4482) by @jekkos +- [Feature]: Case-sensitive attribute updates and CSV Import attribute deletion capability (#4384) by @objecttothis +- fix: change docker image tag to master by @jekkos +- Update to CodeIgniter 4.7.2 (#4485) by @objecttothis +- Bump lodash from 4.17.23 to 4.18.1 (#4462) by @dependabot[bot] +- [Fix]: Add missing return statements to Sales Controller functions by @Ollama +- Encourage users to star the project by @objecttothis +- Bump dompurify from 3.3.2 to 3.4.0 (#4512) by @dependabot[bot] +- fix: propagate attribute definition failures in postSaveGeneral() (#4509) by @jekkos +- fix: Escape dynamic output and fix CSS property in barcode_sheet.php (#4501) by @jekkos +- Fix CRC currency reverting to EUR/LAK in locale config (#4511) by @jekkos +- fix: Add missing $img_tag variable in Sales::getSendPdf() (#4515) by @jekkos +- fix: Language dropdown not displaying saved language correctly (#4518) by @jekkos +- fix: Scope orWhere clauses in Item::exists() and Item::get_item_id() (#4520) by @jekkos +- fix: Update calendar translations (#4498) by @jekkos +- fix: Catch mysqli_sql_exception in DB fallback handlers for fresh Docker installs (#4525) by @jekkos +- fix(home): improve internal data type handling for user identification in auth process by @enricodelarosa +- Assignable Keyboard Shortcuts Updates (#4532) by @WShells +- chore: miscellaneous updates and improvements (#4530) by @BudsieBuds +- chore(deps): bump minimatch from 3.1.2 to 3.1.5 (#4536) by @dependabot[bot] +- chore: sync project files to match upstream templates (#4537) by @BudsieBuds +- fix(ci): include hidden files in Docker build context (#4543) by @jekkos +- feat: add ALLOWED_HOSTNAMES environment variable support for Docker/Compose (#4544) by @jekkos +- fix(docker): correct permissions and fix migration barcode_type error (#4546) by @jekkos +- docs: Update SECURITY.md with disclosure process (#4549) by @jekkos +- feat: Bank transfer and wallet payment option added #4540 (#4547) by @BhojKamal +- fix(security): Path traversal vulnerability in getPicThumb (#4545) by @jekkos +- fix(security): SQL injection and path traversal vulnerabilities (#4539) by @jekkos +- fix: Capture CSV import failures in save_tax_data and save_inventory_quantities (#4507) by @jekkos +- fix: validate attributeId > 0 in saveAttributeLink() (#4508) by @jekkos +- feat: Add deployment workflow with approval gates (#4522) by @jekkos +- Bugfixes to get Migration working on MySQL and MariaDB (#4551) by @objecttothis +- Bugfix: Sale search in register not handling trailing space properly (#4557) by @objecttothis +- fix: cast string returns to int in MY_Migration (#4560) by @jekkos +- Add fallback for allowedHostnames environment variable (#4565) by @objecttothis +- fix: Allow searching by Sale ID in Takings/Daily Sales view (#4569) by @jekkos +- Add Guards to Database Migration (#4571) by @objecttothis +- fix: tax rate inputs blank with comma-decimal locales (#4555) by @jekkos +- fix(security): Fix DOMPDF RCE and customer email sanitization (#4568) by @jekkos +- Fix overly lenient date validation (#4574) by @objecttothis +- fix(security): Escape attribute value in register by @jekkos +- chore(deps): bump dompurify from 3.4.0 to 3.4.11 (#4578) by @dependabot[bot] +- Bugfix: Fix problems with migration UI in login (#4589) by @objecttothis +- Forgotten commit from login migration branch (#4592) by @objecttothis +- Feature: Payment reference code (#4587) by @objecttothis +- fix(giftcard): correct return type and rename getGiftcardId method (#4600) by @objecttothis +- chore(deps): bump dompurify from 3.4.11 to 3.4.12 (#4602) by @dependabot[bot] +- bugfix(reports): crash on detailed sales report when sale has multiple payments with reference codes (#4599) by @objecttothis +- style(models): normalize quote style in SQL GROUP_CONCAT expression (#4608) by @objecttothis +- chore(deps): upgrade dompdf from v2.0.8 to v3.1.6 (#4610) by @objecttothis +- chore(deps): bump brace-expansion (#4614) by @dependabot[bot] +- chore(deps): add xlsx via SheetJS CDN and upgrade tableexport plugin (#4615) by @objecttothis +- chore(deps): bump lodash.template from 4.5.0 to 4.18.1 (#4616) by @objecttothis +- fix(login): skip auth validation on new install to allow migration (#4609) by @objecttothis +- fix: Wrap postSave() in single transaction for atomicity (#4506) by @jekkos +- refactor: Replace var with let/const in JavaScript files (#4503) by @jekkos +- fix: get_definition_by_name() returns single row instead of multi-dimensional array (#4452) (#4464) by @Jonathan Chang +- fix(config): validate theme param to prevent XSS via invalid theme (#4620) by @objecttothis +- fix(sales): enforce server-side authorization for price changes (#4631) by @objecttothis +- fix(sales): escape quote number in email template to prevent XSS (#4625) by @objecttothis +- refactor(migrations): rename execute_script to executeScript across all migrations (#4611) by @objecttothis +- fix(auth): validate gcaptcha before password to prevent bypass (#4618) by @objecttothis +- refactor: apply PSR-12 naming to Attribute definition methods (#4624) by @Rayan Abdul Cader +- fix(security): sanitize filenames and escape logo path in config (#4630) by @objecttothis +- fix: use db_connect() for item save transactions (#4636) by @richardmilles +- fix(xss): remove redundant escaping that double-encoded item attribute values (#4628) by @objecttothis +- chore(deps): bump codeigniter4/framework from 4.7.2 to 4.7.4 (#4638) by @dependabot[bot] +- chore(deps): bump dompurify from 3.4.12 to 3.4.13 (#4639) by @dependabot[bot] +- Reject item CSV imports whose header row is missing required columns (#4597) by @Sai Asish Y +- fix(items): validate item_number and skip receiving quantity default for temp items (#4621) by @objecttothis +- Feature: CodeIgniter Throttler (#4619) by @objecttothis +- Bugfix: Resolve Race Condition in Rewards and Gift Card Spending (#4640) by @objecttothis +- hotfix(auth): hash throttler keys to improve security (#4646) by @objecttothis +- fix(items): add explicit sentinel value for clearing supplier in bulk edit (#4617) by @objecttothis +- Codeigniter changes between 4.7.2 and 4.7.4 (#4650) by @objecttothis +- Hotfix: Fix CI3 database migration caused by regression (#4649) by @objecttothis +- fix(sales): gate per-record endpoints behind reports_sales grant (#4627) by @objecttothis +- fix(email): update method call to camelCase for PSR-12 compliance (#4659) by @objecttothis +- Feature admin account safeguards (#4657) by @objecttothis +- Ensure payload data is escaped to prevent XSS (#4664) by @objecttothis +- fix(sales): enforce reports_sales grant on search endpoint (#4673) by @objecttothis +- fix(reports, home): resolve double-URL-decoding bypass for method grants (#4660) (#4666) by @objecttothis +- Bugfix tax names (#4677) by @objecttothis +- feat(validation, tests): add `valid_path_strict` rule and integrate into mailpath validation (#4684) by @objecttothis +- fix(sales): harden payment validation and gift card handling by @objecttothis +- fix: prevent duplicate items when editing imported rows (#4634) by @richardmilles +- fix(barcode): resolve string interpolation issue in barcode display html (#4692) by @Vighnesh Nilajakar +- fix(sales): gate getSearch behind reports_sales grant by @jekkos +- bugfix(sales): reject non-negative gift-card amount_tendered (#4674) by @jekkos +- fix(sales): harden unsuspend with auth, status gating, and null safety by @objecttothis +- fix(tests): resolve all phpunit failures — clean-DB suite green (#4626) (#4691) by @jekkos +- fix(licenses): guard malformed data, parallelize gulp tasks, require Node 20 by @objecttothis +- fix(validation): broaden sendmail path regex, expand i18n, strip advisory IDs by @objecttothis +- fix(security): handle special characters in `.env` key values and improve insertion logic (#4656) by @objecttothis +- fix(locale): validate language_code against known locales to block path traversal (#4704) by @jekkos +- Fix GHSA-frx7-c5vv-m3mr: recompute cashup total server-side and force owner identity (#4706) by @jekkos +- feat(security): add THROTTLE_KEY env-var fallback for throttle.key (#4707) by @jekkos +- fix(i18n): translate remaining English labels in Swiss German Items.php (#4701) by @Rayan Abdul Cader +- fix(ci): stop stamping app version onto master and branch Docker tags (#4709) by @jekkos +- chore(deps): bump fflate from 0.8.2 to 0.8.3 (#4690) by @dependabot[bot] +- fix(i18n): swap print_delay_autoreturn number/required messages in 5 locales (#4699) by @Rayan Abdul Cader +- chore(release): unified git-cliff release workflow (changelog + tag + optional bump) (#4711) by @jekkos +- fix(release): push changelog/bump to master via admin PAT (GITHUB_TOKEN blocked by branch protection) by @jekkos +- docs: add 3.4.2 changelog by @github-actions[bot] +- chore: bump version to 3.4.3 by @github-actions[bot] +- fix(security): HTML-escape attribute dropdown option labels in items attributes view (#4715) by @jekkos +- fix(release): keep package-lock.json version in sync on bump (#4718) by @jekkos +- fix(security): strip all HTML tags from $.notify alert messages (#4716) by @jekkos +- chore: strip advisory IDs from code comments and changelog (#4720) by @jekkos +- fix(security): report unwritable .env.lock, make throttle limits configurable (#4714) by @jekkos +- chore: reset 3.4.2 (undo premature 3.4.3 bump + stale changelog) for re-cut by @jekkos + ## [3.4.1] - 2025-06-05 - Feature: PSR-12 Compliant Indentation by @objecttothis in ([#4196](https://github.com/opensourcepos/opensourcepos/pull/4196)) - Add .env to dist zip by @jekkos in ([#4199](https://github.com/opensourcepos/opensourcepos/pull/4199)) From 89c6d5a3e95d31a463dc607ab6d688da0d6d9710 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Sep 2026 16:27:25 +0000 Subject: [PATCH 5/5] chore: bump version to 3.4.3 --- app/Config/App.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Config/App.php b/app/Config/App.php index 13b132b0d..de204b036 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -12,7 +12,7 @@ class App extends BaseConfig * * @var string */ - public string $application_version = '3.4.2'; + public string $application_version = '3.4.3'; /** * This is the commit hash for the version you are currently using. diff --git a/package-lock.json b/package-lock.json index eb1011d1f..13a032102 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@opensourcepos/opensourcepos", - "version": "3.4.2", + "version": "3.4.3", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index f2546ba4a..13e735c21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@opensourcepos/opensourcepos", - "version": "3.4.2", + "version": "3.4.3", "description": "Open Source Point of Sale is a web based point of sale system written in the PHP language. It uses MySQL as the data storage back-end and has a simple user interface.", "keywords": [ "point-of-sale",