KISS Project Tasks Plugin: Security & Performance Audit
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 withesc_attr()
(≈L651)number_format(...)
inside text nodes → wrap withesc_html()
(≈L652–654).
-
-
Escape debug/status text.
kanban.php
:echo '<!-- Status: ' . $status->name . ... -->';
(≈L400). Even in comments, wrap$status->name
withesc_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 defaultcapability_type => 'post'
(≈L124–139). Many actions use a broadcurrent_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 withis_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 onadmin_init
and writes theptt_sort_status
cookie when$_GET['run_report']
is present (≈L87–105) but doesn’t verify the nonce there. Fix: if_wpnonce
is present, callcheck_admin_referer( 'ptt_run_report_nonce' )
before writing cookies.
3) Medium Priority
-
Tighten per-object checks in AJAX.
shortcodes.php
: AJAX callbacks guard withcheck_ajax_referer
andcurrent_user_can( 'edit_posts' )
(e.g.,ptt_frontend_start_task_callback
,ptt_frontend_manual_time_callback
, etc.). When a$post_id
is known, prefercurrent_user_can( 'edit_post', $post_id )
or custom caps (edit_project_task
) to avoid over-broad permissions.
-
Validate/normalize taxonomy inputs.
shortcodes.php
andkanban.php
: when callingwp_set_object_terms()
or building filters from IDs, continue usingabsint()
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 bothadmin_enqueue_scripts
andwp_enqueue_scripts
(≈L562, L564). Fix: on the front end, only enqueue when needed (e.g.,is_singular( 'project_task' )
, on pages using your shortcodes, oris_page( 'today' )
). This trims CSS/JS on unrelated pages.
2) High Priority
-
Kanban query strategy.
kanban.php
: base args useposts_per_page => -1
and then execute separateWP_Query
per status (≈L274, L395). With many tasks, this multiplies DB load and PHP work. Fix: run oneWP_Query
for all relevant tasks (paginated), fetch IDs only, and group in PHP bytask_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 directDELETE ... LIKE
intoday-helpers.php
, ≈L566–574).
- You already cache Today view entries (transients in
3) Medium Priority
-
Front-end shortcodes: tighten queries.
-
shortcodes.php
:ptt_get_tasks_for_project_callback()
andptt_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; callget_the_title()
on IDs as needed.- Where ordering is by meta (
start_time
), consider adding'meta_type' => 'DATETIME'
to help MySQL optimize casting, givenY-m-d H:i:s
strings.
- Pagination or reasonable
-
-
Today page: verify bounds.
today-helpers.php
: you cap'posts_per_page' => 200
(≈L295–303) and usepost__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()
callsflush_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 aftercheck_admin_referer()
(≈L271–275) to skip a second pass duringadmin_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.
- Using
Notable Positives (keep these!)
- AJAX security: All AJAX callbacks I checked verify nonces and capabilities (
check_ajax_referer('ptt_ajax_nonce', 'nonce')
+ role checks inshortcodes.php
andtoday.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
-
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 )
.
- Make CPT non-public and map custom caps; update all checks to
-
Nonce check in
ptt_handle_sort_status_cookie
(Security/High). -
Escape remaining Kanban outputs (Security/Quick).
-
Replace
posts_per_page => -1
with pagination and add lightweight query flags (Perf/Quick). -
Kanban: single batched query +
date_query
(Perf/High). -
Add transients for Kanban/Reports (+ invalidation) (Perf/High).
-
Conditional front-end enqueues (Perf/Quick).
-
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.