Skip to content

Repair client secrets damaged by 2.0.0 and 2.0.1 - #7

Merged
ibuilder merged 1 commit into
mainfrom
fix/repair-corrupted-secrets
Aug 8, 2026
Merged

Repair client secrets damaged by 2.0.0 and 2.0.1#7
ibuilder merged 1 commit into
mainfrom
fix/repair-corrupted-secrets

Conversation

@ibuilder

@ibuilder ibuilder commented Aug 8, 2026

Copy link
Copy Markdown
Owner

2.0.2 stopped the settings sanitizer adding an encryption layer on every write — but it did nothing for sites already damaged. Those installs hold cipher text a single decrypt cannot recover, so they fail to authenticate with nothing on screen to explain why. The operator would have to guess that the stored credential itself is the problem.

What this does

The upgrade routine unwraps the value back to plain text and re-stores it correctly. Encryption::decrypt_deep() peels layers with a bounded ceiling; Encryption::depth() reports how many are present.

If it can't be recovered, the secret is cleared rather than left broken, and either outcome surfaces in a one-time admin notice. The stored token is dropped too, so the next request re-authenticates with the repaired secret instead of a stale one.

Verified against a real damaged site

Installed the actual v2.0.1 release, corrupted the secret through 2.0.1's own code path — not by hand-crafting the value — then upgraded:

BEFORE  version=2.0.1  depth=3  client_secret()='pwp2:Tnd7t4TV3YLRFwvRF'
AFTER   version=2.0.3  depth=1  client_secret()='THE-REAL-SECRET'
Check
Secret recovered to its original value
Stored at exactly one encryption layer
Repair recorded for the admin notice
Stale token cleared
Other settings and client ID untouched
Unrecoverable path clears + prompts re-entry
Notice shows once, then clears its flag
PHP notices during the upgrade 0

Tests

4 new unit tests: the repair itself, the two no-op cases (healthy secret, empty secret), and the unrecoverable path. 86 tests, 350 assertions, PHPCS clean.

One nuance worth recording

While reproducing this I confirmed the damage comes from internal writes — Settings::set() and migrate_legacy() — not from the settings form itself. A normal form submit passes raw $_POST through update_option(), so the sanitizer runs exactly once and encrypts correctly. It's the plugin's own subsequent writes that re-fed already-encrypted cipher text through it.

🤖 Generated with Claude Code

2.0.2 stopped the settings sanitizer adding an encryption layer on every
write, but it did nothing for sites that had already been damaged. Those
installs hold cipher text a single decrypt cannot recover, so they fail to
authenticate with nothing on screen to explain why — the operator would
have to guess that the stored credential itself is the problem.

The upgrade routine now unwraps the value back to plain text and re-stores
it correctly. Encryption::decrypt_deep() peels layers with a bounded
ceiling, and Encryption::depth() reports how many are present.

If the value cannot be recovered it is cleared rather than left broken, and
either outcome is reported in an admin notice that appears once. The stored
token is also dropped so the next request re-authenticates with the
repaired secret instead of a stale one.

Verified against a site running the real v2.0.1 release, corrupted through
2.0.1's own code path rather than by hand-crafting the value:

  BEFORE  version=2.0.1  depth=3  client_secret()='pwp2:Tnd7t4TV3YLRFwvRF'
  AFTER   version=2.0.3  depth=1  client_secret()='THE-REAL-SECRET'

Four unit tests cover the repair, the two no-op cases and the unrecoverable
path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ibuilder
ibuilder merged commit 43d7ce4 into main Aug 8, 2026
13 checks passed
@ibuilder
ibuilder deleted the fix/repair-corrupted-secrets branch August 8, 2026 15:23
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.

1 participant