Skip to content

Avoid overwriting contact methods on meta key collisions - #2940

Open
corsacca wants to merge 1 commit into
developfrom
fix-channel-key-collision
Open

corsacca wants to merge 1 commit into
developfrom
fix-channel-key-collision

Conversation

@corsacca

Copy link
Copy Markdown
Member

Problem

When a contact method value (email, phone, …) is added, Disciple_Tools_Posts::add_post_contact_method() stores it under contact_<type>_<hash>, where the hash is 3 characters from substr( md5( rand( 10000, 100000 ) ), 0, 3 ) — about 4096 possible keys. Nothing checked whether the key already existed, so when it did, update_post_meta() silently replaced the existing value.

Each added value collides with probability ≈ (existing values of that type) / 4096. On a local site, adding 200 emails one at a time to a contact kept only 196 and 197 of 200 in two runs.

This is also the cause of the intermittent CI failure in DT_Posts_DT_Posts_Update_Post::test_overwrite_existing_fields_update (Undefined array key 1 at unit-test-update-post.php:276): the new email occasionally replaced the fixture's existing one.

Change

Generate a new hash until metadata_exists() reports the key is free. The key format is unchanged.

Testing

  • Same 200-email run after the fix: 200 of 200 kept in three runs.
  • PHPCS and php -l pass.

🤖 Generated with Claude Code

New contact method values get a key with a 3 character random hash
(4096 possibilities) and nothing checked whether the key was in use,
so adding a value could silently overwrite an existing one. Generate a
new hash until the key is free. This also caused the intermittent
test_overwrite_existing_fields_update failure.
@github-actions

Copy link
Copy Markdown

Review

High: none.

Medium: none.

The do/while with metadata_exists() in add_post_contact_method (dt-posts/posts.php) correctly stops a repeated 3-hex hash from overwriting an existing contact channel value. When the field isn't in $post_settings['fields'], $new_meta_key is '' and the loop never runs, so behavior there is unchanged. Two things I noticed but didn't count as findings:

  • The loop is unbounded. It would only spin forever if all 4096 hash values were already used for one post and channel type, which is unrealistic.
  • Two concurrent requests could still pick the same key between the check and the write.

The PR looks ready to merge.

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