KISS Project Tasks Plugin: Security & Performance Audit

by Viktoria Ivanova 56 views

discussion category : kissplugins,KISS-Projects-Tasks

Here’s a focused audit of the uploaded plugin codebase (KISS-Projects-Tasks) organized exactly as requested. Findings cite file names and approximate line numbers to make fixes fast.


1) Quick Wins

  • Escape a few remaining outputs (Kanban UI).

    • kanban.php: escape numeric/text variables echoed into HTML/attributes:

      • echo $unassigned_count;echo esc_html( (string) $unassigned_count ); (≈L356)
      • <span class="<?php echo ... ?>"> → wrap with esc_attr() (≈L651)
      • number_format(...) inside text nodes → wrap with esc_html() (≈L652–654).
  • Escape debug/status text.

    • kanban.php: echo '<!-- Status: ' . $status->name . ... -->'; (≈L400). Even in comments, wrap $status->name with esc_html() to be safe.
  • Block direct access everywhere. Most files already do if ( ! defined( 'WPINC' ) ) die; — keep this guard in all included PHP files (confirmed present in major modules).

2) High Priority

  • Harden capability model for CPT.

    • project-task-tracker.php: CPT is registered public with default capability_type => 'post' (≈L124–139). Many actions use a broad current_user_can( 'edit_posts' ). Fix: switch to custom capabilities and map them to roles:
    'public'             => false,
    'publicly_queryable' => false,
    'show_ui'            => true,
    'capability_type'    => ['project_task', 'project_tasks'],
    'map_meta_cap'       => true,
    

    Then use current_user_can( 'edit_project_task', $post_id ) / publish_project_tasks in AJAX handlers and saves. This prevents contributors or unintended roles from creating/editing tasks.

  • Reduce public surface of sensitive data. If tasks can contain private time logs/notes, keep archive and front-end access closed:

    • project-task-tracker.php: set 'public' => false, 'exclude_from_search' => true, 'publicly_queryable' => false. Your single template already gates with is_user_logged_in() and capability checks, but tightening CPT visibility closes alternative disclosure vectors (e.g., archives, generic REST if enabled later).
  • Verify nonce in early cookie handler.

    • reports.php: ptt_handle_sort_status_cookie() runs on admin_init and writes the ptt_sort_status cookie when $_GET['run_report'] is present (≈L87–105) but doesn’t verify the nonce there. Fix: if _wpnonce is present, call check_admin_referer( 'ptt_run_report_nonce' ) before writing cookies.

3) Medium Priority

  • Tighten per-object checks in AJAX.

    • shortcodes.php: AJAX callbacks guard with check_ajax_referer and current_user_can( 'edit_posts' ) (e.g., ptt_frontend_start_task_callback, ptt_frontend_manual_time_callback, etc.). When a $post_id is known, prefer current_user_can( 'edit_post', $post_id ) or custom caps (edit_project_task) to avoid over-broad permissions.
  • Validate/normalize taxonomy inputs.

    • shortcodes.php and kanban.php: when calling wp_set_object_terms() or building filters from IDs, continue using absint() and ensure IDs exist (term_exists()) before assignment to avoid notices and edge-case injections via malformed requests.
  • REST exposure (future-proofing). If you later enable show_in_rest, ensure every REST mutation route validates nonce/caps and does not expose calculated fields or notes unintentionally.

4) Low Priority

  • Version disclosure.

    • project-task-tracker.php header shows version (≈L6). Not a vulnerability by itself, but consider filtering it out in public footers/scripts if you ever emit it client-side.
  • Self-Test unprepared SHOW TABLES (informational).

    • self-test.php: "$wpdb->get_var( "SHOW TABLES LIKE '{$table}'" )" (≈L865) uses a trusted variable (core-prefixed table); keep as is or wrap with $wpdb->prepare for consistency.

1) Quick Wins

  • Avoid posts_per_page => -1 for large datasets. Found in multiple places:

    • kanban.php (≈L274), reports.php (≈~L300), shortcodes.php (≈L189, ≈L653), today.php (≈L709), self-test.php (≈L653). Fix: paginate (e.g., 50–200) and lazy-load more. Where you only need counts/IDs, add 'fields' => 'ids', 'no_found_rows' => true', 'update_post_term_cache' => false, 'update_post_meta_cache' => false.
  • Add lightweight query flags everywhere you list.

    • In list/report/kanban queries that don’t need full objects:

      'fields' => 'ids',
      'no_found_rows' => true,
      'update_post_term_cache' => false,
      'update_post_meta_cache' => false,
      
  • Conditionally enqueue assets on front end.

    • project-task-tracker.php: ptt_enqueue_assets is hooked to both admin_enqueue_scripts and wp_enqueue_scripts (≈L562, L564). Fix: on the front end, only enqueue when needed (e.g., is_singular( 'project_task' ), on pages using your shortcodes, or is_page( 'today' )). This trims CSS/JS on unrelated pages.

2) High Priority

  • Kanban query strategy.

    • kanban.php: base args use posts_per_page => -1 and then execute separate WP_Query per status (≈L274, L395). With many tasks, this multiplies DB load and PHP work. Fix: run one WP_Query for all relevant tasks (paginated), fetch IDs only, and group in PHP by task_status. Use:
    'fields'                  => 'ids',
    'no_found_rows'           => true,
    'update_post_term_cache'  => false,
    'update_post_meta_cache'  => false,
    'tax_query'               => [ ... selected filters ... ],
    'date_query'              => [ ... if activity filter is time-based ... ],
    

    Then load per-status meta/terms in batched calls (e.g., get_the_terms on a slice) or cache.

  • Use date_query instead of per-post date checks.

    • kanban.php: inside the loop, get_the_date() is compared to a cutoff (≈L92–100). Fix: push this into SQL with 'date_query' => [ ['after' => $cutoff_date] ] to reduce PHP filtering.
  • Cache report/kanban results.

    • You already cache Today view entries (transients in today-helpers.php, ≈L233–272). Apply a similar short-TTL transient for Report (reports.php) and Kanban queries keyed by filter params and user. Add invalidation on task save/status change (you already clear Today transients via direct DELETE ... LIKE in today-helpers.php, ≈L566–574).

3) Medium Priority

  • Front-end shortcodes: tighten queries.

    • shortcodes.php: ptt_get_tasks_for_project_callback() and ptt_render_planner_tasks() build queries by project and date range. Add:

      • Pagination or reasonable posts_per_page cap.
      • fields => 'ids' when only titles/links are needed; call get_the_title() on IDs as needed.
      • Where ordering is by meta (start_time), consider adding 'meta_type' => 'DATETIME' to help MySQL optimize casting, given Y-m-d H:i:s strings.
  • Today page: verify bounds.

    • today-helpers.php: you cap 'posts_per_page' => 200 (≈L295–303) and use post__in with the user’s task IDs — excellent. If some users have >200 tasks, add paging or “load more”.
  • Rewrite flush cleanup.

    • project-task-tracker.php: ptt_activate_kanban_additions() calls flush_rewrite_rules() (≈L518–526) but is not hooked; remove or register on activation to avoid confusion and accidental future misuse.

4) Low Priority

  • Cookie writes on admin_init.

    • reports.php: lightweight, but you can defer cookie writes until the form submit branch that already runs after check_admin_referer() (≈L271–275) to skip a second pass during admin_init.
  • Indexing tips (optional, large sites). Heavy use of meta keys like ptt_assignee, start_time, stop_time. If datasets grow large, consider:

    • Using meta_query with correct 'type' (NUMERIC/DATETIME) to let MySQL use indexes efficiently.
    • (Advanced) Adding custom tables for time logs if you outgrow postmeta performance.

Notable Positives (keep these!)

  • AJAX security: All AJAX callbacks I checked verify nonces and capabilities (check_ajax_referer('ptt_ajax_nonce', 'nonce') + role checks in shortcodes.php and today.php). ✅
  • Input handling: Throughout, superglobals are sanitized via absint(), intval(), sanitize_text_field(), sanitize_textarea_field(). ✅
  • Output escaping: Templates and many echoes already use esc_html(), esc_attr(), esc_url() (e.g., templates/single-project_task.php). ✅
  • Cache usage: The Today page uses short transients and clears them reliably on save via a targeted DELETE ... LIKE with $wpdb->prepare() (≈L566–574). ✅

Suggested Patch Checklist

  1. CPT visibility & caps (Security/High):

    • Make CPT non-public and map custom caps; update all checks to current_user_can( 'edit_project_task', $post_id ).
  2. Nonce check in ptt_handle_sort_status_cookie (Security/High).

  3. Escape remaining Kanban outputs (Security/Quick).

  4. Replace posts_per_page => -1 with pagination and add lightweight query flags (Perf/Quick).

  5. Kanban: single batched query + date_query (Perf/High).

  6. Add transients for Kanban/Reports (+ invalidation) (Perf/High).

  7. Conditional front-end enqueues (Perf/Quick).

  8. Remove or hook the dead ptt_activate_kanban_additions() (Perf/Low).

If you want, I can generate a ready-to-apply patch (diff) for the above hotspots.