Skip to content

Commit 82c5446

Browse files
Harden support access: notify on login, restrict log downloads
- Email the token creator (or site admin) when a token is used, with time, IP, user agent, and a one-click revocation link - Restrict download_realtime_log to the export/import-YYYYMMDD-HHMMSS.php filename pattern, matching delete_run_log - Document support token hygiene in the README
1 parent f5bbea6 commit 82c5446

3 files changed

Lines changed: 72 additions & 6 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ Override AI1WM filters for debugging without code changes:
6464

6565
One-click report generation aggregating all diagnostic data. Available as JSON download or copyable text format. Includes environment, filesystem, database, plugins, ecosystem, conflicts, and operations data.
6666

67+
## Support Access Best Practices
68+
69+
The token-based support access feature grants a third party temporary login to your site. Treat each token as a live credential.
70+
71+
- **Revoke immediately after the session ends.** Don't wait for the 72-hour expiry. Go to **ServMask Debug > Support** and click Revoke on the active token.
72+
- **Share the login URL over a secure channel only.** Anyone with the URL can log in until it expires or is revoked. Avoid sending it via plain email or public chat; prefer your support ticket portal or an encrypted message.
73+
- **Watch the email notification.** When a token is used, the admin who generated it receives an email with the time, IP, and user agent of the session. If you did not expect a session, revoke the token immediately.
74+
- **Use Debug Only when possible.** Full Administrator access is rarely needed. Debug Only grants access to the debug page and AI1WM operations without exposing the rest of the site.
75+
- **Review the Audit Log** after every support session to see what was accessed and changed.
76+
6777
## Architecture
6878

6979
```

lib/controller/class-ai1wm-debug-ajax-controller.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ public static function download_realtime_log() {
140140
$filename = basename( $current );
141141
}
142142

143+
if ( ! preg_match( '/^(export|import)-\d{8}-\d{6}\.php$/', $filename ) ) {
144+
echo 'Invalid log file.';
145+
exit;
146+
}
147+
143148
$filepath = AI1WM_DEBUG_LOGS_PATH . DIRECTORY_SEPARATOR . $filename;
144149
if ( ! file_exists( $filepath ) ) {
145150
echo 'Log file not found.';

lib/model/class-ai1wm-debug-access.php

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,14 +282,13 @@ public static function handle_login() {
282282
// Set support session flag in user meta
283283
update_user_meta( $user_id, '_ai1wm_debug_support_session', '1' );
284284

285-
// Log the login
286285
$token_prefix = substr( $token, 0, 8 );
287-
Ai1wm_Debug_Audit::log_action( $token_prefix, 'login', 'Support user logged in via token' );
286+
$token_hash = hash( 'sha256', $token );
287+
$tokens = Ai1wm_Debug_Config::get( AI1WM_DEBUG_ACCESS_TOKENS_OPTION, array() );
288+
$level = isset( $tokens[ $token_hash ]['level'] ) ? $tokens[ $token_hash ]['level'] : 'debug_only';
288289

289-
// Determine redirect based on access level
290-
$token_hash = hash( 'sha256', $token );
291-
$tokens = Ai1wm_Debug_Config::get( AI1WM_DEBUG_ACCESS_TOKENS_OPTION, array() );
292-
$level = isset( $tokens[ $token_hash ]['level'] ) ? $tokens[ $token_hash ]['level'] : 'debug_only';
290+
Ai1wm_Debug_Audit::log_action( $token_prefix, 'login', 'Support user logged in via token' );
291+
self::notify_login( $token_hash, $ip );
293292

294293
// Prevent token leaking via Referer header
295294
header( 'Referrer-Policy: no-referrer' );
@@ -303,6 +302,58 @@ public static function handle_login() {
303302
exit;
304303
}
305304

305+
/**
306+
* Email the token creator (or site admin) that a support session has started
307+
*
308+
* @param string $token_hash
309+
* @param string $ip
310+
*/
311+
private static function notify_login( $token_hash, $ip ) {
312+
$tokens = Ai1wm_Debug_Config::get( AI1WM_DEBUG_ACCESS_TOKENS_OPTION, array() );
313+
if ( ! isset( $tokens[ $token_hash ] ) ) {
314+
return;
315+
}
316+
317+
$data = $tokens[ $token_hash ];
318+
319+
$recipient = '';
320+
if ( ! empty( $data['created_by'] ) ) {
321+
$creator = get_user_by( 'id', $data['created_by'] );
322+
if ( $creator && ! empty( $creator->user_email ) ) {
323+
$recipient = $creator->user_email;
324+
}
325+
}
326+
327+
if ( empty( $recipient ) ) {
328+
$recipient = get_option( 'admin_email' );
329+
}
330+
331+
if ( empty( $recipient ) ) {
332+
return;
333+
}
334+
335+
$site_name = get_bloginfo( 'name' );
336+
$level = isset( $data['level'] ) ? $data['level'] : 'debug_only';
337+
$username = isset( $data['username'] ) ? $data['username'] : '';
338+
$ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? substr( $_SERVER['HTTP_USER_AGENT'], 0, 255 ) : '';
339+
$time = current_time( 'mysql' );
340+
$page_url = admin_url( 'admin.php?page=servmask-debug' );
341+
342+
$subject = sprintf( '[%s] ServMask Debug support session started', $site_name );
343+
344+
$message = "A ServMask Debug support session just started on your site.\n\n";
345+
$message .= "Site: " . site_url() . "\n";
346+
$message .= "Time: " . $time . "\n";
347+
$message .= "Access level: " . $level . "\n";
348+
$message .= "Support user: " . $username . "\n";
349+
$message .= "IP address: " . $ip . "\n";
350+
$message .= "User agent: " . $ua . "\n\n";
351+
$message .= "If you did not expect this session, revoke the token now:\n";
352+
$message .= $page_url . "\n";
353+
354+
wp_mail( $recipient, $subject, $message );
355+
}
356+
306357
/**
307358
* Delete a support user and destroy their sessions
308359
*

0 commit comments

Comments
 (0)