WP Manifestindependent plugin directory
manifest / utilities / personal-crm

Personal CRM WP Registry grade D+. High-severity findings · 3 findingsWP RegistryHigh-severity findings · 3 findingsOpen the reportD+ #3 in Utilities

Personal CRM WordPress plugin

★ 5stars
721release downloads
≈330active sites
v1.1.1latest · 6mo ago

Install

Download personal-crm.zip From the v1.1.1 release on GitHub, Apr 10, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/akirk/personal-crm/releases/download/v1.1.1/personal-crm.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

A WordPress-based personal CRM for managing your personal and professional relationships. Track contacts, organize them into groups, remember important dates, and never miss a birthday or anniversary.

Features

Contact Management

  • Store detailed profiles: name, email, location, timezone, and social links (GitHub, LinkedIn, WordPress.org)
  • Track personal details: birthdays, partner info, children
  • Add custom notes and links for each person
  • Gravatar integration for profile photos

Groups & Organization

  • Create hierarchical groups (teams, friend circles, family, etc.)
  • Track membership history with join/leave dates
  • Assign people to multiple groups
  • Set a default group for quick access

Events & Reminders

  • Automatic birthday and anniversary tracking
  • Custom events with multi-day support
  • Calendar views (list and month)
  • Upcoming events sidebar

Search & Navigation

  • Cmd+K quick finder for instant access
  • Full-text search across all contacts
  • Pin favorite people and groups to My Apps

Data Management

  • Export/import via JSONL
  • Audit reports for data completeness
  • Privacy mode for sensitive displays

Extensibility

  • Hooks and filters for plugin integration
  • Works with Keeping Contact for contact frequency tracking
  • Works with Contact Sync for CardDAV sync

Requirements

  • WordPress 6.0+
  • PHP 7.4+

Installation

  1. Upload the plugin to /wp-content/plugins/personal-crm
  2. Run composer install in the plugin directory
  3. Activate through the Plugins menu
  4. Visit /crm to get started

License

GPL-2.0-or-later. See LICENSE for details.

Releases

3 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v1.1.1 latest
Apr 10, 2026 6mo ago
personal-crm.zip
69
Mar 17, 2026 7mo ago
personal-crm.zip
165
Jan 23, 2026 9mo ago
personal-crm.zip
487

Active-site estimate ≈330 comes from the median of recent superseded releases. Method.

D+ grade

Security

Personal CRM 1.1.1 · audited by WP Registry

High-severity findings.

3 high
Audited release
1.1.1
Findings
3
Worst severity
high
Content hash
547b1268561393dc7f8cf539…

Findings

  • high Subscriber (read) can view and mutate the entire personal CRM

    missing_capability

    WpApp is constructed with require_capability => 'read' (comment: 'Require login'). Router::check_access_permission() therefore admits every logged-in role that has read, including Subscriber and WooCommerce Customer, to every /crm template. There is no second current_user_can() on the admin person/group POST handlers, so a Subscriber can list all contacts at /crm/people, open /crm/person/{username} (email, birthday, partner, children, notes, LinkedIn/GitHub), and POST add_person, edit_person, delete_person, add_note, create_group, save_general, assign_groups, and event/link CRUD into wp_personal_crm_*. wp_ajax_personal_crm_quick_update only checks is_user_logged_in() plus a nonce and will overwrite birthday/email on any person. If the WordPress Abilities API is present, abilities are registered with show_in_rest and permission_callback current_user_can('read') / current_user_can('edit_posts'), so a Subscriber can REST-dump all people (including emails) and a Contributor can create people, notes, and groups via REST. A private method only_allow_access_from_localhost() was never wired. Export/import remain manage_options. An unauthenticated twin wp_ajax_nopriv_personal_crm_get_person_details is also registered with no nonce or capability; because it calls get_person($group_slug, $username) while Storage::get_person() takes only a username, it returns role/location/birthday/links when a person username equals a group slug.

    includes/personal-crm.php:38-92

    Recommendation

    Gate the CRM app on a privileged capability (manage_options, or a dedicated personal_crm_manage cap granted only to intended users). Re-check that capability (not merely is_user_logged_in) inside every POST/AJAX/Abilities callback. Remove the nopriv AJAX action. Do not expose Abilities with show_in_rest unless the permission_callback matches the same privileged cap.

  • high CSRF: CRM create/edit/delete people, notes, and groups have no nonces

    csrf

    admin/actions.php, admin/tabs/people.php, admin/tabs/events.php, admin/tabs/links.php, admin/index.php (save_general), assign-groups.php, and the add/edit/delete note forms on person.php process REQUEST_METHOD===POST with no wp_nonce_field, check_admin_referer, or wp_verify_nonce (the only nonces in the plugin are import, export download, and two AJAX actions). Combined with the read-only router gate, any logged-in user is a valid victim, including the site owner on a single-user personal WordPress install. A cross-site POST to /crm/admin/person/{username} with action=delete_person or add_note, or to /crm/admin with action=create_group / save_general, mutates wp_personal_crm_people, notes, groups, and events. Observable: attacker page auto-submits a form; victim's CRM gains a note or loses a contact with no further click in the CRM UI. Welcome import is an exception (wp_verify_nonce + manage_options).

    admin/actions.php:9-217

    Recommendation

    Add wp_nonce_field() to every CRM form and verify with check_admin_referer()/wp_verify_nonce() before any storage write. Reject the request if the nonce is missing. Pair this with a privileged capability check so a stolen subscriber session cannot mutate CRM data even with a valid nonce.

  • high Reflected XSS via notes_view in person.php script tag

    xss_reflected

    person.php prints $_GET['notes_view'] into an inline <script> block using only addslashes(). addslashes does not encode <, >, or /. A logged-in user (any role with read) who opens /crm/person/{existing-username}?notes_view=</script><img src=x onerror=...> causes the HTML parser to close the script tag and execute the following markup in the victim's origin. A Subscriber can discover usernames at /crm/people, then phish the site owner with that URL. If the victim is an administrator, the script can issue same-origin POSTs to wp-admin (session takeover). The value is also copied into a hidden form field with esc_attr (safe) but the script-tag echo is the executing sink. Nearby addslashes($person) / addslashes($timezone) are less practical because usernames/timezones are sanitize_text_field'd on save.

    person.php:711-797

    Recommendation

    Never echo request parameters into a script tag with addslashes. Use wp_json_encode() or esc_js(), or pass notes_view only through esc_attr() in HTML and read it from the DOM. Prefer a strict allow-list (compiled|chronological).

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.