Skip to content

fix(security): Fix DOMPDF RCE and customer email sanitization#4568

Open
jekkos wants to merge 4 commits into
masterfrom
fix/security-vulnerabilities
Open

fix(security): Fix DOMPDF RCE and customer email sanitization#4568
jekkos wants to merge 4 commits into
masterfrom
fix/security-vulnerabilities

Conversation

@jekkos
Copy link
Copy Markdown
Member

@jekkos jekkos commented Jun 3, 2026

Summary

  • Fix DOMPDF Remote Code Execution (RCE) vulnerability by disabling PHP execution and remote file access
  • Fix customer email import sanitization by adding proper email validation and sanitization
  • Fix XSS vulnerability in bootstrap table email rendering by escaping emails before passing to mailto()

Vulnerability Details

1. DOMPDF RCE (Critical)

Location: app/Helpers/dompdf_helper.php

Issue: DOMPDF was configured with isPhpEnabled => true and isRemoteEnabled => true, allowing:

  • Execution of embedded PHP code in HTML content (RCE)
  • Loading arbitrary remote URLs (SSRF)

Attack Vector: Malicious actors could inject PHP code into customer data fields (names, addresses, comments). When PDFs are generated, this code would execute on the server.

Fix: Set both options to false:

  • isPhpEnabled => false - Prevents PHP injection attacks
  • isRemoteEnabled => false - Prevents SSRF attacks

2. Customer Email Import Sanitization (Medium-High)

Location: app/Controllers/Customers.php:postImportCsvFile()

Issue: CSV import only lowercased email addresses without validation or sanitization, allowing:

  • XSS payloads in email field (<script>alert(1)</script>@example.com)
  • Invalid email formats causing downstream issues

Fix: Added proper email handling:

  • filter_var($email, FILTER_SANITIZE_EMAIL) - Removes dangerous characters
  • filter_var($email, FILTER_VALIDATE_EMAIL) - Rejects invalid email formats
  • Import fails gracefully with row-specific error messages

3. XSS in Bootstrap Table Email Rendering (Medium-High)

Location: app/Helpers/tabular_helper.php

Issue: Email columns in bootstrap tables had escaping disabled (line 52), and mailto() function doesn't escape its parameters:

// Line 52 - Email explicitly excluded from escaping
'escape' => !preg_match("/(edit|email|messages|item_pic)/", key($element))

// Line 229/295/366 - mailto() has NO escaping
'email' => empty($person->email) ? '' : mailto($person->email, $person->email),

Attack Vector:

  1. CSV import stores <script>alert(1)</script>@example.com
  2. Customer table renders: <a href="mailto:<script>alert(1)</script>@example.com">...</a>
  3. XSS executes when email is displayed

Fix: Escape email addresses before passing to mailto():

'email' => empty($person->email) ? '' : mailto(esc($person->email), esc($person->email)),

Applies to:

  • get_person_data_row() - Employee emails
  • get_customer_data_row() - Customer emails
  • get_supplier_data_row() - Supplier emails

Testing

  • Manual verification of code changes
  • LSP diagnostics pass (CI4 helper functions not recognized but valid)
  • Changes follow existing codebase patterns

Security Comments

Comments added to DOMPDF configuration to prevent future developers from inadvertently re-enabling these dangerous options.

Summary by CodeRabbit

  • New Features

    • CSV customer import now validates email format and rejects invalid emails
    • Email addresses are sanitized during import
  • Bug Fixes

    • Email addresses in mailto links are now properly escaped
    • PDF generation now disables remote file access and PHP execution

- Disable isPhpEnabled in DOMPDF to prevent RCE via embedded PHP in HTML
- Disable isRemoteEnabled to prevent SSRF attacks
- Add email validation and sanitization in CSV import (FILTER_SANITIZE_EMAIL, FILTER_VALIDATE_EMAIL)
- Reject invalid email formats during customer import
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request introduces three independent security hardening measures: email validation and sanitization for customer CSV imports with full test coverage, disabling of remote resources and PHP execution in PDF generation, and escaping of email addresses in table helper output to prevent XSS injection.

Changes

Security Hardening

Layer / File(s) Summary
Email validation in customer CSV import with test coverage
app/Controllers/Customers.php, tests/Controllers/CustomersCsvImportTest.php
CSV import sanitizes email via filter_var() and validates with FILTER_VALIDATE_EMAIL; invalid emails trigger row-level failure messages and are skipped. Test suite verifies valid email acceptance, invalid email rejection with descriptive error messages, XSS payload sanitization, mixed valid/invalid email handling, and special character escaping in stored data.
PDF generation security hardening
app/Helpers/dompdf_helper.php
Dompdf configuration now disables remote file access (isRemoteEnabled = false) and PHP execution (isPhpEnabled = false) by default, replacing prior permissive settings.
Email escaping in table helpers
app/Helpers/tabular_helper.php
Person, customer, and supplier table row generators now escape email values using esc() in both mailto: link targets and displayed text to prevent XSS injection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • BudsieBuds
  • objecttothis

Poem

🐰 Security updates hop through the fields,
Email filters catch what danger yields,
PDFs locked, and output well escaped,
Three hardened layers, attack-safe shaped!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing DOMPDF RCE vulnerability and customer email sanitization, which are the primary security concerns addressed across the modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/security-vulnerabilities

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Ollama added 2 commits June 3, 2026 20:50
Email columns in bootstrap tables had escaping disabled (line 52) and
mailto() function doesn't escape its parameters. This fix escapes email
addresses before passing to mailto() in:
- get_person_data_row() (employees)
- get_customer_data_row() (customers)
- get_supplier_data_row() (suppliers)

Attack vector: Malicious email via CSV import renders XSS in table view.
Tests cover:
- Valid email acceptance
- Invalid email rejection with row-specific error
- XSS payload sanitization in email field
- Mixed valid/invalid email handling
- Email with special characters sanitization

Verifies fixes for customer email import vulnerability.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/Controllers/CustomersCsvImportTest.php (1)

82-82: 💤 Low value

assertJSONExact couples to the exact lang string.

This asserts the literal 'Customers imported successfully'. If lang('Customers.csv_import_success') differs or changes, the test breaks for non-behavioral reasons. Consider asserting success === true (as the other tests do) rather than the exact message text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Controllers/CustomersCsvImportTest.php` at line 82, The test in
CustomersCsvImportTest is asserting the exact message string via
assertJSONExact(['success' => true, 'message' => 'Customers imported
successfully']), which couples the spec to a literal lang string; change the
assertion to only verify success === true (e.g. use assertJSON or
assertJSONFragment to check ['success' => true]) and remove the exact message
check so the test no longer depends on lang('Customers.csv_import_success')
text.
app/Controllers/Customers.php (1)

425-425: 💤 Low value

Inconsistent failure entries and hardcoded message.

Other failure paths push a bare integer ($failCodes[] = $i; at Lines 468 and 474), while this path pushes a descriptive string. The user-facing message is also hardcoded in English unlike the surrounding lang() usage. Consider routing this through lang() and keeping the failCodes element type consistent for a uniform csv_import_partially_failed message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Controllers/Customers.php` at line 425, The current failure branch pushes
a descriptive string into $failCodes while other branches push the row index
integer and the user-facing text is hardcoded; change the line that currently
does $failCodes[] = 'Row ' . $i . ': Invalid email format' to push the integer
index ($failCodes[] = $i;) so $failCodes stays consistently an array of
integers, and move the human-readable message into a language entry (e.g. add a
key like 'customers.invalid_email' to your lang files) and ensure the code that
composes the final csv_import_partially_failed message uses lang('...') to
format "Row X: {message}" when rendering failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Controllers/Customers.php`:
- Around line 422-428: The current email validation treats empty emails as
invalid because filter_var('' , FILTER_VALIDATE_EMAIL) returns false; update the
check in the import loop around the $email variable so you only validate when an
address is present (e.g. trim and lower-case $email, then if $email is not empty
and !filter_var($email, FILTER_VALIDATE_EMAIL) push the 'Invalid email format'
into $failCodes and continue). Keep the existing increment of $i and the rest of
the flow (postSave() still handles optional emails).

In `@tests/Controllers/CustomersCsvImportTest.php`:
- Around line 17-20: The test class CustomersCsvImportTest is at risk of
cross-test DB contamination because protected $refresh = false prevents
DatabaseTestTrait::regressDatabase() from resetting state; update the test to
set protected $refresh = true in the class declaration so the DB is rolled back
before each test, or alternatively implement a tearDown that truncates the
customers table (or calls DatabaseTestTrait::tearDownDatabase()) or wrap tests
exercising /customers/importCsvFile in a transaction to ensure imported rows
don't persist across methods; reference the CustomersCsvImportTest class, the
$refresh property, DatabaseTestTrait::regressDatabase(),
DatabaseTestTrait::tearDownDatabase(), and hasInDatabase() when making the fix.

---

Nitpick comments:
In `@app/Controllers/Customers.php`:
- Line 425: The current failure branch pushes a descriptive string into
$failCodes while other branches push the row index integer and the user-facing
text is hardcoded; change the line that currently does $failCodes[] = 'Row ' .
$i . ': Invalid email format' to push the integer index ($failCodes[] = $i;) so
$failCodes stays consistently an array of integers, and move the human-readable
message into a language entry (e.g. add a key like 'customers.invalid_email' to
your lang files) and ensure the code that composes the final
csv_import_partially_failed message uses lang('...') to format "Row X:
{message}" when rendering failures.

In `@tests/Controllers/CustomersCsvImportTest.php`:
- Line 82: The test in CustomersCsvImportTest is asserting the exact message
string via assertJSONExact(['success' => true, 'message' => 'Customers imported
successfully']), which couples the spec to a literal lang string; change the
assertion to only verify success === true (e.g. use assertJSON or
assertJSONFragment to check ['success' => true]) and remove the exact message
check so the test no longer depends on lang('Customers.csv_import_success')
text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ccd7be3-87fc-492e-bd95-056b4357b849

📥 Commits

Reviewing files that changed from the base of the PR and between 9509a97 and 66b365a.

📒 Files selected for processing (4)
  • app/Controllers/Customers.php
  • app/Helpers/dompdf_helper.php
  • app/Helpers/tabular_helper.php
  • tests/Controllers/CustomersCsvImportTest.php

Comment on lines +422 to +428
$email = filter_var(strtolower($data[4]), FILTER_SANITIZE_EMAIL);

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$failCodes[] = 'Row ' . $i . ': Invalid email format';
$i++;
continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Empty/optional emails are now rejected, breaking imports of customers without an email.

filter_var('', FILTER_VALIDATE_EMAIL) returns false, so any row with a blank email column will now fail with "Invalid email format" and be skipped. postSave() does not treat email as required, so customers without an email were previously importable. This is a behavioral regression for legitimate rows that omit the email.

Validate only when an email is actually present:

🐛 Proposed fix to allow empty emails while still rejecting malformed ones
-                        $email = filter_var(strtolower($data[4]), FILTER_SANITIZE_EMAIL);
-                        
-                        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
-                            $failCodes[] = 'Row ' . $i . ': Invalid email format';
-                            $i++;
-                            continue;
-                        }
-                        
+                        $email = filter_var(strtolower($data[4]), FILTER_SANITIZE_EMAIL);
+
+                        if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
+                            $failCodes[] = 'Row ' . $i . ': Invalid email format';
+                            $i++;
+                            continue;
+                        }
+

If email is intended to be mandatory for CSV imports, please confirm and ignore this.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$email = filter_var(strtolower($data[4]), FILTER_SANITIZE_EMAIL);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$failCodes[] = 'Row ' . $i . ': Invalid email format';
$i++;
continue;
}
$email = filter_var(strtolower($data[4]), FILTER_SANITIZE_EMAIL);
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$failCodes[] = 'Row ' . $i . ': Invalid email format';
$i++;
continue;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Controllers/Customers.php` around lines 422 - 428, The current email
validation treats empty emails as invalid because filter_var('' ,
FILTER_VALIDATE_EMAIL) returns false; update the check in the import loop around
the $email variable so you only validate when an address is present (e.g. trim
and lower-case $email, then if $email is not empty and !filter_var($email,
FILTER_VALIDATE_EMAIL) push the 'Invalid email format' into $failCodes and
continue). Keep the existing increment of $i and the rest of the flow
(postSave() still handles optional emails).

Comment on lines +17 to +20
protected $migrate = true;
protected $migrateOnce = true;
protected $refresh = false;
protected $namespace = null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

CodeIgniter 4 DatabaseTestTrait $refresh false transaction rollback between tests behavior

💡 Result:

In CodeIgniter 4, the DatabaseTestTrait's $refresh property and the automatic transaction rollback mechanism are distinct features that serve different purposes in your test suite [1][2]. The $refresh property (when set to true) controls whether the database migrations are completely reset (rolled back to version 0 and re-run) before the test class begins [1][2]. Setting $refresh to false simply skips this heavy-duty reset process [1]. It does not, by itself, disable or enable database transactions [1][3]. Regarding transactions, the DatabaseTestTrait does not automatically wrap every test method in a database transaction for the purpose of rollback [4]. Instead, database isolation is primarily managed through either: 1. Manual Test Transactions: You can use the database transaction system in "test mode" by calling $this->db->transStart(true) at the beginning of your test, which ensures that all queries within that block are automatically rolled back, regardless of their success [5]. 2. Cleanup via Assertions: If you use the hasInDatabase method provided by the trait, CodeIgniter tracks these specific insertions and automatically removes them after the test has finished, providing a clean state for that specific data [4]. If you set $refresh to false, your database state will persist across tests within the same class (or across different classes if migrations were already run) [1][6]. If you need a clean database for every test method while $refresh is false, you must manually manage your transactions or explicitly clear the affected tables in your tearDown method [4][5]. In summary, setting $refresh to false does not trigger or bypass automatic transaction rollbacks; it only skips the full migration reset [1][2]. Transactions must still be managed explicitly using the database connection methods if you want to ensure data isolation during your tests [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List file (sanity)
ls -la tests/Controllers/CustomersCsvImportTest.php

# Show relevant parts of the test file (around the properties + setup/teardown + failing assertions)
sed -n '1,120p' tests/Controllers/CustomersCsvImportTest.php
sed -n '120,260p' tests/Controllers/CustomersCsvImportTest.php

# Search within this file for email/assert logic and any hasInDatabase usage
rg -n "refresh|migrateOnce|tearDown|transStart|hasInDatabase|check_email_exists|example\.com|assertNotNull|assertJSONExact|mailchimp|addOrUpdateMember" tests/Controllers/CustomersCsvImportTest.php || true

# Find how this test base class is defined (where DatabaseTestTrait comes from)
rg -n "class CustomersCsvImportTest|extends |use .*DatabaseTestTrait" tests/Controllers/CustomersCsvImportTest.php || true

# Identify parent test case (if local) and whether it uses DatabaseTestTrait defaults
# (Try to find the referenced parent class name(s) and locate their definitions)
rg -n "namespace|use |extends " tests/Controllers/CustomersCsvImportTest.php

Repository: opensourcepos/opensourcepos

Length of output: 10923


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate DatabaseTestTrait source in vendor (or in installed CodeIgniter package)
fd "DatabaseTestTrait.php" -t f . || true
fd "DatabaseTestTrait" -t f vendor . || true

# Show trait file (small enough) and any related transaction/refresh logic
files=$(fd "DatabaseTestTrait.php" -t f vendor . || true)
echo "Found: $files"
for f in $files; do
  echo "==== $f ===="
  wc -l "$f"
  sed -n '1,200p' "$f"
  sed -n '200,400p' "$f" || true
done

# Also locate CIUnitTestCase base class used here
fd "CIUnitTestCase.php" -t f vendor . || true
base=$(fd "CIUnitTestCase.php" -t f vendor . | head -n 1 || true)
if [ -n "$base" ]; then
  echo "==== $base ===="
  wc -l "$base"
  sed -n '1,220p' "$base"
fi

# Search within vendor for $refresh or transStart usage in test traits
rg -n "\\$refresh|transStart|rollback|hasInDatabase|DatabaseTestTrait" vendor/codeigniter* vendor/ | head -n 200 || true

Repository: opensourcepos/opensourcepos

Length of output: 143


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repo top:"
ls -ლა

echo "Searching for DatabaseTestTrait.php + CIUnitTestCase.php in-repo (incl. vendor if present)..."
find . -name 'DatabaseTestTrait.php' 2>/dev/null | head -n 50
find . -name 'CIUnitTestCase.php' 2>/dev/null | head -n 50

echo "Searching for the string 'class DatabaseTestTrait'..."
rg -n "class DatabaseTestTrait" . 2>/dev/null | head -n 20 || true

echo "Searching for '$refresh' usage in test traits..."
rg -n "\\$refresh" . 2>/dev/null | head -n 50 || true

Repository: opensourcepos/opensourcepos

Length of output: 163


🌐 Web query:

CodeIgniter 4 DatabaseTestTrait teardown transaction rollback $refresh migrateOnce implementation

💡 Result:

In CodeIgniter 4’s testing database helpers, $refresh does NOT imply a per-test “teardown transaction rollback” in the DatabaseTestTrait itself. Instead, the trait’s documented behavior is: when $refresh is true, “all migrations are rolled back to version 0” before the test runs, and the trait provides regressDatabase to do that/reset work manually if needed [1][2]. On the other hand, rollback of a database transaction at the end of the test is something CodeIgniter documents as being handled via the CIUnitTestCase/DatabaseTestTrait lifecycle methods (setUp/tearDown) and “special functionality executed during the setUp and tearDown phases” (meaning you should call parent::tearDown to keep the framework cleanup running) [1]. The API docs list tearDownDatabase and other internal helpers on DatabaseTestTrait, but they don’t document a specific “transaction rollback” detail in that public docs snippet [3]. Implementation detail you can rely on from the official docs: 1) Set protected $refresh = true to force the database to be completely refreshed (migrations rolled back to version 0) before each test [1][2]. 2) If you override tearDown in your test class, you must call parent::tearDown or you will “lose much of the functionality described here” [1]. 3) The trait exposes migrateDatabase (called during setUp) and regressDatabase (called during $refresh) as the hooks that affect how migrations/seeding state is prepared/reset [4][1][2]. 4) $migrateOnce controls whether migrations run only once before the first test, otherwise migrations run before each test [4][1]. So, the “$refresh migrateOnce teardown transaction rollback” behavior is effectively: - $refresh: reset migrations/seeds state by rolling back migrations to 0 (via regressDatabase) before the test [4][1][2]. - $migrateOnce: if true, run migrations only once before the first test (rather than every test) [4][1]. - teardown: ensure parent::tearDown is called so the trait’s cleanup (via its tearDown* helpers) still runs [1]. If what you specifically need is the exact line(s) in the CodeIgniter 4 source where DatabaseTestTrait begins/rolls back a transaction during teardown, that isn’t shown in the API snippets available from the docs pages above; you’d need to inspect the DatabaseTestTrait PHP source file in the CodeIgniter repository to confirm the precise rollback call and whether it’s conditional on $refresh/$migrateOnce. The official docs pages only state the migration rollback-to-0 behavior for $refresh and the requirement to call parent::tearDown for the framework cleanup to occur [4][1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ls -la
test -d vendor && echo "vendor exists" || echo "vendor missing"

find . -maxdepth 4 -type d -name vendor 2>/dev/null

# If vendor exists, locate the actual DatabaseTestTrait + CIUnitTestCase implementations
if [ -d vendor ]; then
  fd "DatabaseTestTrait.php" -t f vendor | head -n 20
  fd "CIUnitTestCase.php" -t f vendor | head -n 20

  db_trait=$(fd "DatabaseTestTrait.php" -t f vendor | head -n 1 || true)
  ci_unit=$(fd "CIUnitTestCase.php" -t f vendor | head -n 1 || true)

  echo "DatabaseTestTrait file: $db_trait"
  echo "CIUnitTestCase file: $ci_unit"

  if [ -n "${db_trait:-}" ]; then
    sed -n '1,240p' "$db_trait"
    sed -n '240,520p' "$db_trait"
  fi

  if [ -n "${ci_unit:-}" ]; then
    sed -n '1,260p' "$ci_unit"
  fi

  echo "Search for transaction/rollback/migrateDatabase/regressDatabase in DatabaseTestTrait..."
  rg -n "transStart|transRollback|rollback|revert|migrateDatabase|regressDatabase|hasInDatabase|refresh" "$db_trait" || true
fi

# Also inspect CIUnitTestCase for any per-test transaction wrapping logic
if [ -d vendor ] && [ -n "${ci_unit:-}" ]; then
  rg -n "transStart|transRollback|rollback|DatabaseTestTrait|hasInDatabase" "$ci_unit" || true
fi

Repository: opensourcepos/opensourcepos

Length of output: 3307


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for DatabaseTestTrait definition in vendor..."
rg -n "trait DatabaseTestTrait" vendor | head -n 20 || true
rg -n "class CIUnitTestCase" vendor | head -n 20 || true
rg -n "namespace CodeIgniter\\\\Test" vendor | head -n 20 || true

echo "Searching for filenames containing DatabaseTestTrait/CIUnitTestCase..."
find vendor -type f \( -iname "*databasetesttrait*.php" -o -iname "*ciunittestcase*.php" \) | head -n 50

echo "Searching for regressDatabase/migrateDatabase/hasInDatabase helpers in vendor..."
rg -n "regressDatabase|migrateDatabase|hasInDatabase" vendor | head -n 50 || true

Repository: opensourcepos/opensourcepos

Length of output: 404


🏁 Script executed:

#!/bin/bash
set -euo pipefail

db_trait="vendor/codeigniter4/framework/system/Test/DatabaseTestTrait.php"
ci_unit="vendor/codeigniter4/framework/system/Test/CIUnitTestCase.php"

wc -l "$db_trait" "$ci_unit"
sed -n '1,260p' "$db_trait"
sed -n '260,620p' "$db_trait"

sed -n '1,260p' "$ci_unit"
sed -n '260,620p' "$ci_unit"

echo "Search within DatabaseTestTrait.php for refresh/transactions/teardown hooks:"
rg -n "\\$refresh|transStart|transRollback|transCommit|rollback|regressDatabase|migrateDatabase|hasInDatabase|tearDown" "$db_trait"

Repository: opensourcepos/opensourcepos

Length of output: 24278


Fix potential cross-test DB contamination in CustomersCsvImportTest ($refresh = false)

With protected $refresh = false (lines 17-20), DatabaseTestTrait skips regressDatabase() before each test, and tearDownDatabase() only clears rows added via hasInDatabase() (via $insertCache). Since these tests exercise /customers/importCsvFile directly and don’t use hasInDatabase(), inserted customers can persist across test methods—so any import logic that treats existing emails as duplicates can make later assertions flaky. Set $refresh = true or explicitly clear/transaction-wrap the affected tables in tearDown (or ensure imported emails remain unique after sanitization).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Controllers/CustomersCsvImportTest.php` around lines 17 - 20, The test
class CustomersCsvImportTest is at risk of cross-test DB contamination because
protected $refresh = false prevents DatabaseTestTrait::regressDatabase() from
resetting state; update the test to set protected $refresh = true in the class
declaration so the DB is rolled back before each test, or alternatively
implement a tearDown that truncates the customers table (or calls
DatabaseTestTrait::tearDownDatabase()) or wrap tests exercising
/customers/importCsvFile in a transaction to ensure imported rows don't persist
across methods; reference the CustomersCsvImportTest class, the $refresh
property, DatabaseTestTrait::regressDatabase(),
DatabaseTestTrait::tearDownDatabase(), and hasInDatabase() when making the fix.

@jekkos jekkos requested a review from objecttothis June 4, 2026 15:47
@github-actions github-actions Bot had a problem deploying to staging June 4, 2026 18:51 Failure
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

Staging deployment failed

Check the workflow logs for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants