Skip to content

V2.8.42#911

Merged
jmrenouard merged 9 commits into
major:masterfrom
jmrenouard:v2.8.42
May 25, 2026
Merged

V2.8.42#911
jmrenouard merged 9 commits into
major:masterfrom
jmrenouard:v2.8.42

Conversation

@jmrenouard
Copy link
Copy Markdown
Collaborator

@jmrenouard jmrenouard commented May 21, 2026

2.8.42 2026-05-25

  • chore: bump version to 2.8.42.
  • fix: resolve invalid login credentials error by defaulting to root when only --pass is set and escaping single quotes in passwords (Attempted to use login credentials, but they were invalid #781).
  • fix: resolve fake aborted connections count increase during password strength checks (2.8.41 aborted connections number (fake) increase while testing the pass lsit #900).
  • fix: resolve EOF metadata corruption and duplicated configurations in Emacs block (2.8.41 Corrupt #904).
  • fix: prevent plaintext password leakage in weak password diagnostic messages.
  • fix: implement symlink verification and atomic writes for aborted connects state file protection.
  • fix: append transport-specific host and container identifiers in state file path to prevent collisions.
  • test: strengthen authentication plugin checks and add verification suite for state file protections.
  • ci: optimize release notes generation to isolate branch changes.
  • perf: optimize --dumpdir performance by excluding heavy RDS/Aurora and internal metrics.
  • docs: improve authentication plugins algorithm labels and resolve absolute documentation links.
  • refactor: catch explicit exception classes in build/release_gen.py to prevent masking system signals.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Description check ✅ Passed The pull request description comprehensively details version 2.8.42 release with multiple bug fixes, performance improvements, documentation updates, and test enhancements that align well with the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR prepares the 2.8.42 release by bumping version references across the project, adding the v2.8.42 release notes, and optimizing --dumpdir CSV export by skipping particularly expensive sys/information/performance schema tables (including some AWS RDS-related tables).

Changes:

  • Bump version to 2.8.42 in core script and documentation/version markers.
  • Optimize dump_csv_files() to skip heavy tables/views during --dumpdir exports and log what gets skipped.
  • Add release notes for v2.8.42 and update the changelog entry for the new version (currently incomplete).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
USAGE.md Updates displayed version to 2.8.42.
releases/v2.8.42.md Adds release notes for v2.8.42 (note: contains an accuracy issue about perltidy).
README.md Updates the version badge/link to v2.8.42.
POTENTIAL_ISSUES Renames an audit section to v2.8.42 (but content appears to correspond to v2.8.41).
mysqltuner.pl Bumps tunerversion and updates --dumpdir logic to skip expensive tables/views (minor formatting cleanup needed).
MEMORY_DB.md Updates “Current Version” and relabels “Recent Audits” to v2.8.42 (but the listed items appear to be v2.8.41 content).
CURRENT_VERSION.txt Updates current version to 2.8.42.
Changelog Adds a 2.8.42 header, but the entry doesn’t yet reflect the actual changes in this PR.
Comments suppressed due to low confidence (1)

mysqltuner.pl:11250

  • This block has the same trailing whitespace after or and also inconsistent indentation inside the if (the infoprint/next are indented further than the surrounding code). Please run perltidy or normalize the indentation so the dumpdir skip logic matches the project’s formatting.
        if ( $info_s_table =~ /INNODB_BUFFER_PAGE/ or 
             $info_s_table =~ /RDS_CONTROL_PERFORMANCE_INSIGHTS_STATUS/ or
             $info_s_table =~ /RDS_METRICS_COUNTER/ or
             $info_s_table =~ /RDS_METRICS_GAUGE/) {
             infoprint("SKIPPING $info_s_table");
             next;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mysqltuner.pl Outdated
Comment on lines +11228 to +11230
for my $sys_view ( select_array('use sys;show tables;') ) {
if ( $sys_view =~ /innodb_buffer_stats/ ) {
if ( $sys_view =~ /innodb_buffer_stats/ or
$sys_view =~ /schema_table_statistics_with_buffer/ ) {
Comment thread POTENTIAL_ISSUES Outdated
Comment on lines 151 to 154
## [2026-04-13 Audit] Development v2.8.42

### [v2.8.41] Laboratory Verification
### [v2.8.42] Laboratory Verification

Comment thread MEMORY_DB.md

### Recent Audits
- **v2.8.41**:
- **v2.8.42**:
Comment thread Changelog Outdated

2.8.42 2026-05-17

- chore: bump version to 2.8.42
Comment thread releases/v2.8.42.md Outdated
- **CVE Database Refresh**: Massive update to `vulnerabilities.csv`, adding 69 new vulnerability definitions and updating existing mappings for better security diagnostics.
- **Feature Inventory Sync**: Updated `FEATURES.md` to accurately reflect the full set of implemented features after running automated discovery.
- **Dumpdir Performance Boost**: Significantly accelerated `--dumpdir` analysis by excluding resource-intensive internal tables (e.g., `INNODB_BUFFER_PAGE`) and AWS-specific metrics (`RDS_METRICS_*`, `RDS_CONTROL_*`) that often cause timeouts in managed environments.
- **Code Maintenance**: Applied `perltidy` to `mysqltuner.pl` to ensure consistent formatting and adhere to project style guidelines.
@jmrenouard jmrenouard added bug enhancement dependencies Pull requests that update a dependency file labels May 25, 2026
Copy link
Copy Markdown

@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: 9

🧹 Nitpick comments (2)
tests/test_issue_900.t (1)

55-56: ⚡ Quick win

Fixed temp filenames can make tests race each other.

Using deterministic filenames under tests/ is great for nostalgia, less great for parallel runs and flaky cleanup. Prefer File::Temp for unique, auto-cleaned files.

Also applies to: 104-107

🤖 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/test_issue_900.t` around lines 55 - 56, Replace the fixed test-state
filename with a unique temporary file created via File::Temp: instead of setting
$temp_state_file = File::Spec->catfile('tests', '.mysqltuner_test_state'), use
File::Temp (e.g., File::Temp->new or tempfile()) to create a temp file and
assign its filename to $temp_state_file so each test gets a unique path and the
file is auto-removed; update other occurrences that reference $temp_state_file
(the same spots around the earlier and later uses) to use the temp file handle
or its filename and ensure proper cleanup by letting File::Temp scope/UNLINK
handle removal.
build/release_gen.py (1)

68-75: ⚡ Quick win

Bare except: clauses mask all exceptions.

The naked except: blocks at lines 68, 74, 121, and 128 will catch everything - including KeyboardInterrupt, SystemExit, and typos in your variable names. I've debugged enough "why isn't my script responding to Ctrl-C" mysteries to know this pattern.

Since you're clearly catching subprocess.CalledProcessError from the git commands, make it explicit:

-        except:
+        except subprocess.CalledProcessError:

That said, for a build script that needs to degrade gracefully, this is more of a code hygiene suggestion than a blocker. The fallback messages are reasonable.

Also applies to: 119-129

🤖 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 `@build/release_gen.py` around lines 68 - 75, Replace the naked "except:"
handlers around the git subprocess calls with explicit exception classes so you
don't swallow system-level exceptions; specifically catch
subprocess.CalledProcessError and FileNotFoundError (and optionally OSError) for
the blocks that call subprocess.check_output (the code working with prev_tag and
commits via git describe/log), and apply the same explicit catches to the other
similar try/except blocks later in the file (the blocks around
subprocess.check_output that produce the "Initial release or no previous tag
found." and related fallback messages). Keep the existing fallback return
strings but only return them when the expected subprocess/file errors are
raised, re-raising or letting through KeyboardInterrupt/SystemExit and truly
unexpected exceptions.
🤖 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 `@Changelog`:
- Around line 3-10: The Changelog entry for version 2.8.42 shows date 2026-05-25
which conflicts with the PR description that says 2026-05-17; confirm the
authoritative release date and make a consistent change: if the official release
date is 2026-05-25, update the PR description to 2026-05-25; if the official
date is 2026-05-17, update the Changelog entry in "Changelog" (version header
for 2.8.42) to 2026-05-17 and also correct the date inside releases/v2.8.42.md
so both the Changelog and releases/v2.8.42.md match the confirmed release date
for version 2.8.42.

In `@documentation/AUTHENTICATION_PLUGINS.md`:
- Line 10: Update the documentation row for the `mysql_old_password` entry in
AUTHENTICATION_PLUGINS.md: change the algorithm label from "SHA-1 (Old)" to
"legacy pre-4.1 hash" and ensure `mysql_native_password` keeps the
"double-SHA-1" wording so the table accurately distinguishes the legacy pre-4.1
hashing from the double-SHA-1 native method.

In `@documentation/REFERENCES.md`:
- Line 35: Replace the filesystem URI in the REFERENCES.md entry for
"**MySQLTuner-perl Specification**" (the link currently using
"file:///documentation/AUTHENTICATION_PLUGINS.md") with a repo-relative Markdown
link to documentation/AUTHENTICATION_PLUGINS.md (e.g. [Naming Conventions &
Style Compatibility](documentation/AUTHENTICATION_PLUGINS.md)) so the link
resolves correctly in rendered docs; update the link target text if needed but
keep the same visible label.

In `@mysqltuner.pl`:
- Around line 3048-3050: The current open( my $fh, '>', $state_file ) call
writes a predictable /tmp path and can follow symlinks; fix by writing to a
private temporary file and atomically renaming it into place (or at minimum
reject symlinks) before writing: check that $state_file is not a symlink (using
Perl's -l or lstat) and then create a temp file in the same directory (e.g.
using File::Spec->catfile(dirname($state_file), ...) or File::Temp), set its
mode to 0600, write the "$uptime:$total_attempts\n" to that temp file via the
$fh used in the diff, close it and then rename() the temp file to $state_file to
replace it atomically; ensure any intermediate failures unlink the temp file.
- Around line 4539-4557: The badprint call in the password-check loop (inside
the block using $target_hash, `@passwords`, `@variants` and computing $computed)
prints the matched dictionary password ($v), leaking credentials; change the
message passed to badprint (and similar calls around the other matches) to
indicate a dictionary/weak password was matched for User '$user'\@'$host'
without including $v, and keep the push to `@generalrec` but ensure no variable
containing the plaintext candidate (e.g., $v or any element from
`@passwords/`@variants) is interpolated into logs, reports, or dump artifacts;
apply the same change to the other occurrences noted (the other badprint calls
around the 4601-4613 and 4687-4698 regions).
- Around line 2835-2836: The current adjust_aborted_connects() call mutates
%mystat before assigning it to $result{'Status'}, which changes upstream counter
names/values; instead, preserve raw upstream counters by removing the in-place
mutation: ensure %mystat is copied unchanged into $result{'Status'} and have
adjust_aborted_connects() return a computed/derived value (or write its output
into a separate key such as $result{'Derived'}->{'Aborted_connects_adjusted'})
so callers can use the normalized metric without altering the original
Aborted_connects or Connections fields; apply the same change to the other call
site that invokes adjust_aborted_connects() (the block around the second
invocation).
- Around line 2998-3011: get_state_file_path currently builds the state filename
only from $opt{host}, $opt{port}, and $opt{socket} which causes different
SSH/container targets that proxy to 127.0.0.1:3306 to collide; update
get_state_file_path to incorporate transport-specific identity (e.g., append an
SSH identifier like $opt{ssh_host} or a container identifier when running in
container mode) into $filename before returning it so state files are unique per
target/transport while still sanitizing any added values the same way as
$host/$port/$socket.

In `@tests/auth_plugin_checks.t`:
- Around line 48-49: The current tests use the postfix "ok(... ) for
`@main`::secrec" which performs zero assertions when `@main`::secrec is empty;
change them to first assert the array is non-empty (e.g. ok(scalar `@main`::secrec
> 0, "... has entries")) and then iterate explicitly (foreach my $s
(`@main`::secrec) { ok($s =~ /Migrate to 'caching_sha2_password'/, 'Recommendation
for caching_sha2_password') }) so each element is checked and at least one
assertion runs; apply the same pattern to the other similar check(s) referencing
`@main`::secrec and ensure the consolidated check against `@main`::generalrec still
uses is(scalar(grep { ... } `@main`::generalrec), 1, ...) after confirming the
source arrays are non-empty.

In `@tests/test_issue_900.t`:
- Line 19: Replace the hard-coded require './mysqltuner.pl' in
tests/test_issue_900.t with a path-stable require that builds an absolute path
relative to the test file (e.g., use FindBin or File::Spec to compute the test
file's directory and then require the mysqltuner.pl from that computed path);
update the require call (the one containing './mysqltuner.pl') so the test no
longer depends on being run from the repo root.

---

Nitpick comments:
In `@build/release_gen.py`:
- Around line 68-75: Replace the naked "except:" handlers around the git
subprocess calls with explicit exception classes so you don't swallow
system-level exceptions; specifically catch subprocess.CalledProcessError and
FileNotFoundError (and optionally OSError) for the blocks that call
subprocess.check_output (the code working with prev_tag and commits via git
describe/log), and apply the same explicit catches to the other similar
try/except blocks later in the file (the blocks around subprocess.check_output
that produce the "Initial release or no previous tag found." and related
fallback messages). Keep the existing fallback return strings but only return
them when the expected subprocess/file errors are raised, re-raising or letting
through KeyboardInterrupt/SystemExit and truly unexpected exceptions.

In `@tests/test_issue_900.t`:
- Around line 55-56: Replace the fixed test-state filename with a unique
temporary file created via File::Temp: instead of setting $temp_state_file =
File::Spec->catfile('tests', '.mysqltuner_test_state'), use File::Temp (e.g.,
File::Temp->new or tempfile()) to create a temp file and assign its filename to
$temp_state_file so each test gets a unique path and the file is auto-removed;
update other occurrences that reference $temp_state_file (the same spots around
the earlier and later uses) to use the temp file handle or its filename and
ensure proper cleanup by letting File::Temp scope/UNLINK handle removal.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eb3974a2-4ae5-4602-9279-6a0baac4861c

📥 Commits

Reviewing files that changed from the base of the PR and between 2dfa5f9 and a9bfc42.

📒 Files selected for processing (17)
  • COMMIT_AND_RELEASE.md
  • CURRENT_VERSION.txt
  • Changelog
  • FEATURES.md
  • MEMORY_DB.md
  • POTENTIAL_ISSUES
  • README.md
  • ROADMAP.md
  • USAGE.md
  • build/release_gen.py
  • documentation/AUTHENTICATION_PLUGINS.md
  • documentation/REFERENCES.md
  • mysqltuner.pl
  • releases/v2.8.42.md
  • tests/auth_plugin_checks.t
  • tests/sql_modeling.t
  • tests/test_issue_900.t

Comment thread Changelog
Comment on lines +3 to +10
2.8.42 2026-05-25

- chore: bump version to 2.8.42.
- fix: resolve fake aborted connections count increase during password strength checks (#900).
- fix: resolve EOF metadata corruption and duplicated configurations in Emacs block (#904).
- ci: optimize release notes generation to isolate branch changes.
- perf: optimize --dumpdir performance by excluding heavy RDS/Aurora and internal metrics.

Copy link
Copy Markdown

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

Date discrepancy in release metadata.

The Changelog entry is dated 2026-05-25, but the PR description states "Release 2.8.42 dated 2026-05-17". I've seen enough version-bump commits to know this usually means someone copy-pasted the wrong date somewhere.

Which date is the official release date? If it's 2026-05-25, the PR description should be updated. If it's 2026-05-17, this Changelog date and the corresponding releases/v2.8.42.md date need correction.

🤖 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 `@Changelog` around lines 3 - 10, The Changelog entry for version 2.8.42 shows
date 2026-05-25 which conflicts with the PR description that says 2026-05-17;
confirm the authoritative release date and make a consistent change: if the
official release date is 2026-05-25, update the PR description to 2026-05-25; if
the official date is 2026-05-17, update the Changelog entry in "Changelog"
(version header for 2.8.42) to 2026-05-17 and also correct the date inside
releases/v2.8.42.md so both the Changelog and releases/v2.8.42.md match the
confirmed release date for version 2.8.42.

| Plugin Name | Description | Algorithm | Security Level | Deprecated / Obsolete | Present in MySQL | Present in MariaDB |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| `mysql_native_password` | Historical default authentication method. | SHA-1 | Low | Yes (Removed from MySQL 8.4+ / Deprecated in MariaDB) | ✅ (Obsolete) | ✅ (Historical default) |
| `mysql_old_password` | Ancient pre-4.1 authentication method. | SHA-1 (Old) | Very Low | Yes (Removed) | ❌ | ✅ (Obsolete) |
Copy link
Copy Markdown

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:

What hashing algorithm does the MySQL/MariaDB mysql_old_password plugin use, and is it SHA-1 or a legacy pre-4.1 hash format?

💡 Result:

The mysql_old_password plugin uses a legacy, insecure pre-4.1 password hashing algorithm, not SHA-1 [1][2][3]. This algorithm produces a 16-byte hash value [1][4][5]. In contrast, the newer 4.1+ authentication method (mysql_native_password) utilizes a double-SHA-1 hashing process and produces a 41-byte hash value [1][4][6]. The mysql_old_password plugin and its associated pre-4.1 hashing method are considered obsolete and insecure [2][3][7]. Support for this plugin and the pre-4.1 hash format was removed in MySQL 5.7.5 [1][5]. While it remains available in some environments for backward compatibility, it is strongly recommended to migrate to more secure authentication plugins such as caching_sha2_password or ed25519 [2][8][9].

Citations:


Fix documentation: mysql_old_password is not SHA-1 (it’s pre-4.1 legacy hashing).
Line 10 labels mysql_old_password as SHA-1 (Old), but that plugin uses the legacy pre-4.1 password hashing algorithm (not SHA-1). Adjust the table wording to “legacy pre-4.1 hash” (and reserve “double-SHA-1” for mysql_native_password) so the security story isn’t told with the wrong algorithm.

🤖 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 `@documentation/AUTHENTICATION_PLUGINS.md` at line 10, Update the documentation
row for the `mysql_old_password` entry in AUTHENTICATION_PLUGINS.md: change the
algorithm label from "SHA-1 (Old)" to "legacy pre-4.1 hash" and ensure
`mysql_native_password` keeps the "double-SHA-1" wording so the table accurately
distinguishes the legacy pre-4.1 hashing from the double-SHA-1 native method.

- **MySQL**: [Primary Key Optimization](https://dev.mysql.com/doc/refman/8.4/en/optimizing-primary-keys.html) - Surrogate keys, UUID indexing, and index traversal efficiency.
- **MySQL**: [JSON Datatype Indexing](https://dev.mysql.com/doc/refman/8.4/en/create-table-secondary-indexes.html#json-column-indirect-index) - Secondary indexing via virtual generated columns.
- **MariaDB**: [Invisible Indexes KB](https://mariadb.com/kb/en/invisible-indexes/) - Hiding indexes to test query planner changes.
- **MySQLTuner-perl Specification**: [Naming Conventions & Style Compatibility](file:///documentation/AUTHENTICATION_PLUGINS.md) - Summary of naming styles.
Copy link
Copy Markdown

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

file:/// link will 404 in rendered docs

Line 35 uses a local filesystem URI, so readers get a broken jump instead of your carefully curated wisdom. Use a repo-relative markdown link.

Suggested fix
-- **MySQLTuner-perl Specification**: [Naming Conventions & Style Compatibility](file:///documentation/AUTHENTICATION_PLUGINS.md) - Summary of naming styles.
+- **MySQLTuner-perl Specification**: [Naming Conventions & Style Compatibility](./AUTHENTICATION_PLUGINS.md) - Summary of naming styles.
📝 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
- **MySQLTuner-perl Specification**: [Naming Conventions & Style Compatibility](file:///documentation/AUTHENTICATION_PLUGINS.md) - Summary of naming styles.
- **MySQLTuner-perl Specification**: [Naming Conventions & Style Compatibility](./AUTHENTICATION_PLUGINS.md) - Summary of naming styles.
🤖 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 `@documentation/REFERENCES.md` at line 35, Replace the filesystem URI in the
REFERENCES.md entry for "**MySQLTuner-perl Specification**" (the link currently
using "file:///documentation/AUTHENTICATION_PLUGINS.md") with a repo-relative
Markdown link to documentation/AUTHENTICATION_PLUGINS.md (e.g. [Naming
Conventions & Style Compatibility](documentation/AUTHENTICATION_PLUGINS.md)) so
the link resolves correctly in rendered docs; update the link target text if
needed but keep the same visible label.

Comment thread mysqltuner.pl
Comment on lines +2835 to 2836
adjust_aborted_connects();
$result{'Status'} = \%mystat;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep upstream counters raw; normalize in derived fields instead.

adjust_aborted_connects() rewrites Aborted_connects and Connections in %mystat before they are stored in $result{'Status'}. That makes the exported status section diverge from actual SHOW STATUS output, which is going to surprise any DBA or downstream parser who expects the real counters rather than the haunted version. Keep the raw values intact and compute an adjusted metric separately.

As per coding guidelines, "MySQL/MariaDB/Percona variable names and status counters must match upstream exactly."

Also applies to: 3029-3036

🤖 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 `@mysqltuner.pl` around lines 2835 - 2836, The current
adjust_aborted_connects() call mutates %mystat before assigning it to
$result{'Status'}, which changes upstream counter names/values; instead,
preserve raw upstream counters by removing the in-place mutation: ensure %mystat
is copied unchanged into $result{'Status'} and have adjust_aborted_connects()
return a computed/derived value (or write its output into a separate key such as
$result{'Derived'}->{'Aborted_connects_adjusted'}) so callers can use the
normalized metric without altering the original Aborted_connects or Connections
fields; apply the same change to the other call site that invokes
adjust_aborted_connects() (the block around the second invocation).

Comment thread mysqltuner.pl
Comment on lines +2998 to +3011
sub get_state_file_path {
my $tmpdir = $ENV{TEMP} || $ENV{TMP} || '/tmp';
my $host = $opt{host} || 'localhost';
my $port = $opt{port} || '3306';
my $socket = $opt{socket} || '';

$host =~ s/[^a-zA-Z0-9_\.-]/_/g;
$port =~ s/[^a-zA-Z0-9_\.-]/_/g;
$socket =~ s/[^a-zA-Z0-9_\.-]/_/g;

my $filename = ".mysqltuner_${host}_${port}";
$filename .= "_${socket}" if $socket ne '';

return File::Spec->catfile( $tmpdir, $filename );
Copy link
Copy Markdown

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

State-file identity collapses different SSH/container targets into one bucket.

This key only uses host, port, and socket. In SSH/container mode those often fall back to the same 127.0.0.1:3306, so different instances reuse the same state file and inherit each other’s failed-attempt baseline. The result is delightfully wrong aborted-connect math. Include transport-specific identity such as ssh-host and container in the filename.

Suggested fix
 sub get_state_file_path {
     my $tmpdir = $ENV{TEMP}   || $ENV{TMP} || '/tmp';
     my $host   = $opt{host}   || 'localhost';
     my $port   = $opt{port}   || '3306';
     my $socket = $opt{socket} || '';
+    my $ssh    = $opt{'ssh-host'} || '';
+    my $ctr    = $opt{'container'} || '';

     $host   =~ s/[^a-zA-Z0-9_\.-]/_/g;
     $port   =~ s/[^a-zA-Z0-9_\.-]/_/g;
     $socket =~ s/[^a-zA-Z0-9_\.-]/_/g;
+    $ssh    =~ s/[^a-zA-Z0-9_\.-]/_/g;
+    $ctr    =~ s/[^a-zA-Z0-9_\.-]/_/g;

     my $filename = ".mysqltuner_${host}_${port}";
     $filename .= "_${socket}" if $socket ne '';
+    $filename .= "_ssh_${ssh}" if $ssh ne '';
+    $filename .= "_ctr_${ctr}" if $ctr ne '';

     return File::Spec->catfile( $tmpdir, $filename );
 }
🤖 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 `@mysqltuner.pl` around lines 2998 - 3011, get_state_file_path currently builds
the state filename only from $opt{host}, $opt{port}, and $opt{socket} which
causes different SSH/container targets that proxy to 127.0.0.1:3306 to collide;
update get_state_file_path to incorporate transport-specific identity (e.g.,
append an SSH identifier like $opt{ssh_host} or a container identifier when
running in container mode) into $filename before returning it so state files are
unique per target/transport while still sanitizing any added values the same way
as $host/$port/$socket.

Comment thread mysqltuner.pl
Comment on lines +3048 to +3050
if ( open( my $fh, '>', $state_file ) ) {
print $fh "$uptime:$total_attempts\n";
close($fh);
Copy link
Copy Markdown

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

Please don’t open('>') a predictable file in /tmp and hope for the best.

Writing the state file through a stable temp-path name will happily follow a symlink. For a script that is often run with elevated privileges, that is a rather expensive trust exercise. Write to a private temp file with 0600 permissions and rename() it into place, or at least reject symlinks before opening.

🤖 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 `@mysqltuner.pl` around lines 3048 - 3050, The current open( my $fh, '>',
$state_file ) call writes a predictable /tmp path and can follow symlinks; fix
by writing to a private temporary file and atomically renaming it into place (or
at minimum reject symlinks) before writing: check that $state_file is not a
symlink (using Perl's -l or lstat) and then create a temp file in the same
directory (e.g. using File::Spec->catfile(dirname($state_file), ...) or
File::Temp), set its mode to 0600, write the "$uptime:$total_attempts\n" to that
temp file via the $fh used in the diff, close it and then rename() the temp file
to $state_file to replace it atomically; ensure any intermediate failures unlink
the temp file.

Comment thread mysqltuner.pl
Comment on lines +4539 to +4557
my $target_hash = uc($auth_string);
my $found_weak = 0;
foreach my $pass (@passwords) {
$pass =~ s/\s//g;
chomp($pass);
my @variants = ( $pass, uc($pass), ucfirst($pass) );
foreach my $v (@variants) {
my $computed = '*'
. uc(
Digest::SHA::sha1_hex(
Digest::SHA::sha1($v)
)
);
if ( $computed eq $target_hash ) {
badprint
"User '$user'\@'$host' is using weak password: $v (checked offline)";
push( @generalrec,
"Set up a Secure Password for '$user'\@'$host' user."
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Don’t print the password you just proved is valid.

These badprint calls echo the matched dictionary value into stdout, --outputfile, reports, and dumpdir artifacts. That turns a weak-password check into a credential leak with extra steps. Report that a dictionary password matched; don’t hand the password back like a souvenir.

Suggested fix
-                                    badprint
-"User '$user'\@'$host' is using weak password: $v (checked offline)";
+                                    badprint
+"User '$user'\@'$host' is using a weak password from the configured dictionary (checked offline)";
@@
-                                    badprint
-"User '$target_user' is using weak password: $v";
+                                    badprint
+"User '$target_user' is using a weak password from the configured dictionary";
@@
-                                badprint
-"User '$target_user' is using weak password: $v";
+                                badprint
+"User '$target_user' is using a weak password from the configured dictionary";
As per coding guidelines, "Security-sensitive: credential handling, password checks. Flag any credential exposure."

Also applies to: 4601-4613, 4687-4698

🤖 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 `@mysqltuner.pl` around lines 4539 - 4557, The badprint call in the
password-check loop (inside the block using $target_hash, `@passwords`, `@variants`
and computing $computed) prints the matched dictionary password ($v), leaking
credentials; change the message passed to badprint (and similar calls around the
other matches) to indicate a dictionary/weak password was matched for User
'$user'\@'$host' without including $v, and keep the push to `@generalrec` but
ensure no variable containing the plaintext candidate (e.g., $v or any element
from `@passwords/`@variants) is interpolated into logs, reports, or dump
artifacts; apply the same change to the other occurrences noted (the other
badprint calls around the 4601-4613 and 4687-4698 regions).

Comment on lines +48 to +49
ok(grep(/Migrate to 'caching_sha2_password'/, $_), 'Recommendation for caching_sha2_password') for @main::secrec;
is(scalar(grep { $_ eq "Migrate to 'caching_sha2_password' for 1 user(s)" } @main::generalrec), 1, 'Consolidated recommendation pushed to generalrec');
Copy link
Copy Markdown

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only verification: find assertion patterns that can execute zero checks.
rg -nP "ok\\(grep\\([^\\n]+\\)\\s*,[^\\n]+\\)\\s*for\\s+\`@main`::secrec" tests/auth_plugin_checks.t

Repository: major/MySQLTuner-perl

Length of output: 308


Fix zero-iteration test assertions on empty @main::secrec (lines 48-49, also 91-92)

ok(... ) for @main::secrec runs zero checks when @main::secrec is empty, so the subtest can pass while doing absolutely nothing (Perl’s “nothing to see here” magic).

Suggested fix
-    ok(grep(/Migrate to 'caching_sha2_password'/, $_), 'Recommendation for caching_sha2_password') for `@main`::secrec;
+    ok(grep({ /Migrate to 'caching_sha2_password'/ } `@main`::secrec), 'Recommendation for caching_sha2_password');

-    ok(grep(/Migrate to 'ed25519' or 'unix_socket'/, $_), 'Recommendation for ed25519/unix_socket') for `@main`::secrec;
+    ok(grep({ /Migrate to 'ed25519' or 'unix_socket'/ } `@main`::secrec), 'Recommendation for ed25519/unix_socket');
🤖 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/auth_plugin_checks.t` around lines 48 - 49, The current tests use the
postfix "ok(... ) for `@main`::secrec" which performs zero assertions when
`@main`::secrec is empty; change them to first assert the array is non-empty (e.g.
ok(scalar `@main`::secrec > 0, "... has entries")) and then iterate explicitly
(foreach my $s (`@main`::secrec) { ok($s =~ /Migrate to 'caching_sha2_password'/,
'Recommendation for caching_sha2_password') }) so each element is checked and at
least one assertion runs; apply the same pattern to the other similar check(s)
referencing `@main`::secrec and ensure the consolidated check against
`@main`::generalrec still uses is(scalar(grep { ... } `@main`::generalrec), 1, ...)
after confirming the source arrays are non-empty.

Comment thread tests/test_issue_900.t
{
local @ARGV = ();
no warnings 'redefine';
require './mysqltuner.pl';
Copy link
Copy Markdown

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

Use a path-stable require for mysqltuner.pl.

Line 19 hard-codes ./mysqltuner.pl, so this test only works when launched from repo root. Future you (or CI variants) may enjoy a surprise failure.

Suggested fix
 {
     local `@ARGV` = ();
     no warnings 'redefine';
-    require './mysqltuner.pl';
+    my $script_dir = dirname(File::Spec->rel2abs(__FILE__));
+    require File::Spec->catfile($script_dir, '..', 'mysqltuner.pl');
 }
🤖 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/test_issue_900.t` at line 19, Replace the hard-coded require
'./mysqltuner.pl' in tests/test_issue_900.t with a path-stable require that
builds an absolute path relative to the test file (e.g., use FindBin or
File::Spec to compute the test file's directory and then require the
mysqltuner.pl from that computed path); update the require call (the one
containing './mysqltuner.pl') so the test no longer depends on being run from
the repo root.

@jmrenouard jmrenouard merged commit 1d07a3d into major:master May 25, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug dependencies Pull requests that update a dependency file enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants