' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. 3: Value or formatting element shown in this message. */ esc_html__( 'Site %1$s · Network %2$s · Times shown in %3$s', 'logged-ai' ), esc_html( (string) $scope->site_id ), esc_html( (string) $scope->network_id ), esc_html( $time->zone() ) ) . '
' );
return $time;
}
/**
* Render one escaped hidden administration form field.
*
* @param string $key Fixed form field name to escape for output.
* @param string $value Hidden form field string escaped in its HTML attribute context.
*
* @return void
*/
private static function field( string $key, string $value ): void {
echo '';
}
/**
* Render an escaped submit control using one supported button style.
*
* @param string $label Fixed translated or caller-selected submit control label.
* @param string $style Requested button style; only primary receives the primary appearance.
*
* @return void
*/
private static function submit( string $label, string $style = 'secondary' ): void {
echo '';
}
/**
* Open the fixed request-action POST form with its operation and nonce fields.
*
* @param string $operation Supported request action encoded into the nonce-bound form.
*
* @return void
*/
private static function form( string $operation ): void {
echo '';
}
}
if ( ! $focused && ! array_filter( $filters ) ) {
echo ( '
' );
}
foreach ( $groups as $group ) {
$definition = RequestsClassifier::rules()[ $group['family'] ] ?? array( 'label' => 'Retained request family' );
echo ( '' . '' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. 3: Value or formatting element shown in this message. 4: Value or formatting element shown in this message. */ esc_html( $display_text[ 15 ] ), esc_html( PresentationTranslations::lookup( $display_labels, $definition['label'] ) ), esc_html( $time->text( (int) $group['window_start'] ) ), esc_html( $group['admitted_count'] ), esc_html( $group['source'] ) ) . '' . '
' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. 3: Value or formatting element shown in this message. */ esc_html( $display_text[ 19 ] ), esc_html( (string) $focused ), '', '' ) . '
' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. */ esc_html( $display_text[ 23 ] ), esc_html( PresentationTranslations::lookup( $display_labels, (string) $row['reason'] ) ), esc_html( $row['confidence'] ) ) . '
' . esc_html__( 'Exact paths and prefixes require this observation’s safe resource template. Root is available only with a restrictive query condition. Prefixes end at a segment boundary.', 'logged-ai' ) . '
' . '' );
echo ( '' . '
' . '' . '' . '
' . '
' . esc_html__( 'Exact-source policies can affect checkout, login and API reads, shared-network users and monitors. They stop when source evidence expires or privacy/source settings change. Review exceptions and OS recovery before activating.', 'logged-ai' ) . '
' );
self::submit( 'Preview reviewed conditions', 'secondary' );
echo '';
}
/**
* Render current scoped response state, recovery guidance, policies and action history.
*
* @return void
*/
public static function response(): void {
// Resolve translation callbacks before reading authorized request evidence.
$display_text = array(
/* translators: Values or formatting elements shown in this message. */
0 => __( 'Mode: %1$s%2$s%3$s. Revision: %4$s. Policies are read from the database for each eligible request; expiry is checked without cron.', 'logged-ai' ),
1 => __( 'Disabled by operator recovery constant', 'logged-ai' ),
2 => __( 'Enabled for valid active policies', 'logged-ai' ),
3 => __( 'Observe only / response disabled', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
4 => __( 'Response health: %1$s. Invalid policy state fails open.', 'logged-ai' ),
5 => __( 'Exact-source targets are hidden without sensitive-context permission. Resuming retained active IP policies requires that permission; revocation and emergency disable remain available.', 'logged-ai' ),
6 => __( ' Enable valid active policies for their original remaining duration.', 'logged-ai' ),
7 => __( 'Operator recovery', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
8 => __( 'Set %1$s before WordPress loads, or run %2$s. Recovery does not require reaching this screen. There is no public recovery URL, cookie, header or query parameter.', 'logged-ai' ),
9 => __( 'Manual policies', 'logged-ai' ),
10 => __( 'Saved active state is configuration, not proof of a response. The request evidence must show a subsequent independently observed WordPress-layer response. Automatic temporary response, when configured, has separate controls and eligibility checks.', 'logged-ai' ),
11 => __( 'No policies for this site. Inspect an eligible request to create a preview.', 'logged-ai' ),
12 => __( 'Response authority changed.', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
13 => __( 'Compiled conditions: %1$s', 'logged-ai' ),
14 => __( 'Hidden: sensitive-context permission is required. Saved policy state is unchanged.', 'logged-ai' ),
15 => __( 'Personal target erased or unavailable under current privacy settings', 'logged-ai' ),
16 => __( 'Exceptions apply only to this policy and inherit its scope, GET/HEAD methods and expiry. Exact-source policies require a fresh preview after any settings change.', 'logged-ai' ),
17 => __( 'Policy history', 'logged-ai' ),
18 => __( 'Latest 100 scoped actions. A requested action does not imply a successful save; saved actions and errors are separate.', 'logged-ai' ),
19 => __( 'Time (selected timezone)', 'logged-ai' ),
20 => __( 'Action', 'logged-ai' ),
21 => __( 'Actor ID', 'logged-ai' ),
22 => __( 'Policy', 'logged-ai' ),
23 => __( 'OS operator / unknown user', 'logged-ai' ),
24 => __( 'site setting', 'logged-ai' ),
/* translators: 1: WordPress site identifier. 2: WordPress network identifier. */
25 => __( 'site %1$s, network %2$s', 'logged-ai' ),
);
$display_labels = PresentationTranslations::snapshot( array(
'Manual policy response decision',
'Manual policy condition',
'A deliberately configured manual condition matched; this is not an attack finding. Check the recorded action to see whether a response was issued.',
'Ordinary benign requests can match a manual condition. A match alone does not prove a denial or an attack.',
'An observation-only manual condition matched. This policy recorded the match without denying the request; no attack classification is inferred.',
'Observation-only policy %1$s matched (revision %2$s). This policy did not deny the request.',
'A deliberately configured manual condition matched; this is a response decision, not an attack finding.',
'Ordinary benign requests can match a manual condition. No attack classification is inferred from this response.',
'Configuration-resource probe pattern',
'Exact reserved configuration filename or recognized backup variant.',
'An authorized scanner or broken integration can request these resources.',
'Source-control or diagnostic discovery',
'Exact source-control metadata or diagnostic resource template.',
'Authorized diagnostics can deliberately request these resources.',
'Traversal-shaped resource request',
'Two parent-directory segments precede a recognized configuration/system-resource suffix.',
'Malformed links and authorized scanners are possible; target application normalization is unknown.',
'SQL-shaped numeric parameter',
'A numeric-identifier parameter contains a compound boolean or UNION projection with a comment delimiter.',
'A test or code example can contain the same structure; no execution or intent is established.',
'Executable-markup-shaped parameter',
'A numeric/navigation parameter contains paired script markup, an event-handler tag, or an executable URL scheme.',
'Authorized testing can contain the same syntax. Editing/search content parameters are not classified.',
'Component metadata enumeration',
'A plugin readme or theme metadata path was requested; enumeration is not exploitation.',
'Documentation tools and legitimate theme stylesheets can use related routes; theme style.css is excluded.',
'Unusual method at a non-API resource',
'TRACE/TRACK/CONNECT or a write method was used on a reserved metadata/configuration resource.',
'Protocol/security tests may intentionally exercise methods; ordinary API writes and OPTIONS are excluded.',
'The scoped policy was saved. The next eligible request must independently verify WordPress-layer response.',
'The policy was revoked. New eligible requests read current database state.',
'WordPress-layer response is disabled. Observation continues.',
'WordPress-layer response is enabled for currently valid, unexpired active policies.',
'Requests, in context.',
'Export latest 500 minimized requests (JSON)',
'Retained request family',
'First / last (selected timezone)',
'Source association',
'Unavailable; this is a site/time/family group',
'Partial analyses',
'Unknown final statuses',
'HTTP 1xx / 2xx / 3xx / 4xx / 5xx',
'Recorded WordPress responses',
'Sample evidence IDs',
'Retained request finding',
'Interpret only the recorded rule and available context.',
'Evidence ID',
'Alert work',
'Local request reference',
'Sensor / source',
'Rule / revision',
'Safe resource template',
'Method',
'Client-context availability',
'Partial analysis',
'yes; observation only',
'no within the bounded supported view',
'Final HTTP status',
'Logged.ai action',
'Unconfirmed local response; no verified issuance receipt',
'No Logged.ai response',
'Application effect',
'Unknown',
'Captured (selected timezone)',
'Ingested (selected timezone)',
'Response observed (selected timezone)',
'15 minutes',
'1 hour',
'24 hours',
'7 days',
'Preview temporary policy',
'Exact safe resource path',
'Reserved source-control path prefix',
'Structured query on one exact path',
'Exact retained derived address (full mode required)',
'Preview reviewed conditions',
'Response',
'Disable all local response for this site',
'Enable remaining active policies',
'Policy ID',
'Origin',
'State',
'Revision',
'Methods',
'Scope',
'Expiry (selected timezone)',
'Parser',
'Preview hash',
'Revoke',
'Latest at most 500 admitted observations visible at query time within the configured request retention. Concurrent and later inserts are not a transactionally complete snapshot. Telemetry is admission-limited.',
'Review temporary policy',
'Safe target',
'GET and HEAD',
'Duration after confirmation',
'Enforcement layer',
'WordPress init, priority -999; not edge/server/static',
'Parser revision',
'/?dumb-code-to-hack-site',
'Confirm reviewed policy',
'Response authority is required for the selected user.',
'Use --yes to enable still-valid active policies; durations are not extended.',
'LOGGED_AI_DISABLE_RESPONSE is active. Remove it using operator configuration first.',
'The scoped response setting could not be changed. Enabling retained IP policies requires --user with response and sensitive-context permissions.',
'Use revoke POLICY_ID with exactly --dry-run or --yes in the selected site.',
'The policy ID is invalid.',
'The policy ID is invalid, outside this site, unavailable or already revoked.',
'Use status, list, disable, enable --yes, or revoke POLICY_ID --dry-run|--yes.',
'Response authority changed.',
'Use requests prune --dry-run to preview one selected site, or requests prune --yes to apply one bounded batch. Choose exactly one flag and an explicit --url; no all-sites operation is supported.',
'Use requests list, requests export, requests prune --dry-run, or requests prune --yes. The separate Logged.ai Pro add-on supplies reference import with explicit source configuration.',
'Request evidence authority is required for the selected user.',
'Request evidence authority changed.',
'Authorized scanners, diagnostic tools and unrelated visitors can produce this pattern. A direct network peer may represent many people.',
'Repeated distinct reserved-resource requests',
'At least 10 complete admitted GET/HEAD findings cover at least three distinct safe reserved-resource templates in one fixed UTC minute.',
'Known configuration/source/diagnostic templates; complete parser result; this authorized site and source label. Status can be unknown.',
'Repeated distinct reserved resources with observed 404s',
'At least 10 complete admitted GET/HEAD findings have a finalized observed 404 across at least three distinct safe reserved-resource templates in one fixed UTC minute.',
'Request-sequence access is unavailable.',
'Request-sequence authority changed.',
'Current retained admitted findings only. Counts are lower bounds; missing telemetry is not quantifiable. Fixed UTC-minute boundaries can split a sequence. Site and exact-peer views overlap and must not be summed. No attacker identity, historical alert, exposure, prevention or automated-response eligibility is established.',
'Admitted / complete eligible',
'First / last (display timezone)',
'Safe templates',
'Known finalized 404s',
'Recorded local responses',
'Unknown final status',
'Window still open',
'Sample observation IDs',
) );
$scope = self::scope( 'logged_ai_manage_response' );
$time = self::start( 'Response', $scope );
self::message();
$state = Requests::state( $scope->network_id, $scope->site_id );
$enabled = $state && 1 === (int) $state['enabled'];
echo ( '
' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. 3: Value or formatting element shown in this message. 4: Value or formatting element shown in this message. */ esc_html( $display_text[ 0 ] ), '', esc_html( ( Requests::recovery_disabled() ? $display_text[ 1 ] : ( $enabled ? $display_text[ 2 ] : $display_text[ 3 ] ) ) ), '', esc_html( (string) ( $state['revision'] ?? 'unavailable' ) ) ) . '
' );
}
self::submit( $enabled ? 'Disable all local response for this site' : 'Enable remaining active policies', 'secondary' );
echo ( '' . '
' . esc_html( $display_text[ 7 ] ) . '
' . '
' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. */ esc_html( $display_text[ 8 ] ), 'define(\'LOGGED_AI_DISABLE_RESPONSE\', true);', 'wp logged-ai response disable --url=YOUR_SITE' ) . '
';
}
/**
* Validate and dispatch nonce-bound request export or deliberate policy operations.
*
* @return void
*/
public static function post(): void {
$request_form = \LoggedAI\AdminInput::form( array( 'path' => 'url', 'exceptions' => 'url' ) );
if ( 'POST' !== \LoggedAI\AdminInput::method() ) {
wp_die( esc_html__( 'POST is required.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 405 ) );
}
$operation = isset( $request_form['operation'] ) && is_string( $request_form['operation'] ) ? $request_form['operation'] : '';
$scope = self::scope( 'export' === $operation ? 'logged_ai_export_events' : 'logged_ai_manage_response' );
check_admin_referer( 'logged_ai_request_action' );
if ( 'export' === $operation ) {
$reader = self::browser( 'logged_ai_export_events' );
$filters = self::request_filters( true );
nocache_headers();
if ( ! $scope->valid_for( 'logged_ai_export_events' ) ) {
wp_die( esc_html__( 'Request export authority changed.', 'logged-ai' ), '', array( 'response' => 403 ) );
}
$result = $reader->download( $filters, static function ( string $json ): void {
header( 'Content-Type: application/json; charset=UTF-8' );
header( 'Content-Disposition: attachment; filename="logged-ai-requests.json"' );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The guarded reader supplies a complete JSON document for this JSON-only download response.
echo $json;
} );
if ( is_wp_error( $result ) ) {
self::loaded( $result );
}
exit;
}
if ( in_array( $operation, array( 'preview', 'preview_typed' ), true ) ) {
$evidence = $request_form['evidence_id'] ?? null;
$ttl = $request_form['ttl'] ?? null;
if ( ! is_string( $evidence ) || ! preg_match( '/\A[1-9][0-9]{0,18}\z/D', $evidence ) || (string) (int) $evidence !== $evidence || ! is_string( $ttl ) || ! in_array( $ttl, array_map( 'strval', Requests::TTLS ), true ) ) {
wp_die( esc_html__( 'Select a valid request observation and duration.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
$input = null;
if ( 'preview_typed' === $operation ) {
$input = array();
foreach ( array( 'kind', 'path', 'key', 'operator', 'value' ) as $field ) {
$raw = $request_form[ $field ] ?? '';
if ( ! is_string( $raw ) || strlen( $raw ) > 256 ) {
wp_die( esc_html__( 'Invalid response condition.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
$input[ $field ] = $raw;
if ( strlen( $input[ $field ] ) > 128 ) {
wp_die( esc_html__( 'Invalid response condition.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
}
$exceptions = $request_form['exceptions'] ?? array();
if ( ! is_array( $exceptions ) || count( $exceptions ) > 5 ) {
wp_die( esc_html__( 'Select at most five valid response exceptions.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
$input['exceptions'] = array();
foreach ( $exceptions as $exception ) {
if ( ! is_string( $exception ) || strlen( $exception ) > 256 ) {
wp_die( esc_html__( 'Invalid response exception.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
$exception = $exception;
if ( strlen( $exception ) > 128 ) {
wp_die( esc_html__( 'Invalid response exception.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
$input['exceptions'][] = $exception;
}
}
$display = self::preview_translations();
$preview = Requests::preview( $scope->network_id, $scope->site_id, (int) $evidence, (int) $ttl, $input );
if ( is_wp_error( $preview ) ) {
wp_die( esc_html( PresentationTranslations::text( $preview->get_error_message() ) ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 409 ) );
}
self::preview_screen( $scope, $preview, $display );
exit;
}
$notice = '';
if ( 'confirm' === $operation ) {
if ( '1' !== ( $request_form['acknowledge'] ?? '' ) ) {
wp_die( esc_html__( 'Review and acknowledge the exact scoped effect and recovery procedure.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
$id = $request_form['policy_id'] ?? null;
$hash = $request_form['compiled_hash'] ?? null;
if ( ! is_string( $id ) || ! preg_match( '/\A[a-f0-9]{32}\z/D', $id ) || ! is_string( $hash ) || ! preg_match( '/\A[a-f0-9]{64}\z/D', $hash ) ) {
wp_die( esc_html__( 'Invalid response confirmation. Review a fresh preview.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
$activate = 'active' === ( $request_form['mode'] ?? '' );
if ( ! in_array( $request_form['mode'] ?? '', array( 'active', 'observe' ), true ) ) {
wp_die( esc_html__( 'Select an explicit response mode.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
$saved = Requests::confirm( $scope->network_id, $scope->site_id, $id, $hash, $activate );
if ( is_wp_error( $saved ) ) {
wp_die( esc_html( PresentationTranslations::text( $saved->get_error_message() ) ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 409 ) );
}
$notice = 'saved';
} elseif ( 'revoke' === $operation ) {
$id = $request_form['policy_id'] ?? null;
if ( ! is_string( $id ) || ! preg_match( '/\A[a-f0-9]{32}\z/D', $id ) ) {
wp_die( esc_html__( 'Select a valid response policy.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
if ( ! Requests::revoke( $scope->network_id, $scope->site_id, $id ) ) {
wp_die( esc_html__( 'The scoped policy was not changed; it may already be revoked or unavailable.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 409 ) );
}
$notice = 'revoked';
} elseif ( in_array( $operation, array( 'disable', 'enable' ), true ) ) {
if ( 'enable' === $operation && ( '1' !== ( $request_form['acknowledge'] ?? '' ) || Requests::recovery_disabled() ) ) {
wp_die( esc_html__( 'Enable requires acknowledgment and an inactive recovery constant.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
if ( ! Requests::set_enabled( $scope->network_id, $scope->site_id, 'enable' === $operation ) ) {
wp_die( esc_html__( 'The response setting could not be saved.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 409 ) );
}
$notice = 'enable' === $operation ? 'enabled' : 'disabled';
} else {
wp_die( esc_html__( 'Unknown request operation.', 'logged-ai' ), esc_html__( 'Logged.ai', 'logged-ai' ), array( 'response' => 400 ) );
}
wp_safe_redirect(
add_query_arg(
array(
'page' => 'logged-ai-response',
'notice' => $notice,
),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* Render the exact saved draft, bounded sample projection and explicit confirmation controls.
*
* @param object $scope Current operation scope whose native grants are checked.
* @param array $policy Saved operator-bound manual-policy draft to present for review.
* @param array $display Labels resolved before the authorized draft was created.
*
* @return void
*/
/**
* Translate fixed preview labels before the authorized draft is created.
*
* @return array Prepared plain and escaped labels for the private renderer.
*/
private static function preview_translations(): array {
// Resolve translation callbacks before reading authorized request evidence.
$display_text = array(
0 => __( 'Policy preview authority changed.', 'logged-ai' ),
1 => __( 'Preview temporary policy · Logged.ai', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
2 => __( '%1$sNo policy has been activated.%2$s This preview expires in five minutes and is bound to this operator, site, parser revision and exact conditions.', 'logged-ai' ),
3 => __( 'GET and HEAD', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
4 => __( '%s seconds', 'logged-ai' ),
5 => __( 'WordPress init, priority -999; not edge/server/static', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
6 => __( 'Exact compiled conditions: %1$s', 'logged-ai' ),
7 => __( 'Exceptions allow only this policy’s matching GET/HEAD requests at the exact listed paths in this site until policy expiry. They do not grant WordPress permissions or override other policies. Changing exceptions requires a fresh policy preview; revoke the old policy separately.', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
8 => __( '%1$sShared-source warning:%2$s this source address may represent a proxy, shared household, office, scanner or monitor. Checkout, login, recovery and API GET/HEAD requests from it may be denied. The source target stops at %3$s or after privacy/source settings change, even if the policy duration is longer. Only an authenticated user with response authority can reach this plugin’s Response screen through a narrow recovery exception. Keep OS recovery access available.', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
9 => __( 'Projected matches: %1$s of at most 100 recent admitted minimized findings; historical target data unavailable for %2$s inspected rows. Routine benign requests are not retained. Query conditions cannot be replayed because query contents are not retained. This is a structural projection, not a full historical replay or a count of all matching traffic. No requests are resubmitted.', 'logged-ai' ),
10 => __( 'Same versioned matcher: boundary examples', 'logged-ai' ),
11 => __( 'Example', 'logged-ai' ),
12 => __( 'Result', 'logged-ai' ),
13 => __( 'Matches exact condition', 'logged-ai' ),
14 => __( 'No denial', 'logged-ai' ),
15 => __( 'These are matcher examples, not traffic classifications. For exact-source policies the examples assume the selected derived address. Benign traffic can match a deliberate policy; other code can have run before this hook. Method/path ambiguity fails open for typed conditions.', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
16 => __( 'Recovery: the OS operator can run %1$s or set %2$s to boolean true before WordPress loads. There is no public bypass.', 'logged-ai' ),
17 => __( 'Policy confirmation authority changed.', 'logged-ai' ),
18 => __( 'Policy mode', 'logged-ai' ),
19 => __( ' Save observation-only policy (never denies)', 'logged-ai' ),
20 => __( ' Activate this temporary policy and enable local response for valid existing policies', 'logged-ai' ),
21 => __( ' I reviewed the exact site, path, methods, finite duration, coverage limits and OS recovery instructions.', 'logged-ai' ),
/* translators: Values or formatting elements shown in this message. */
22 => __( '%1$sCancel and return to Requests%2$s', 'logged-ai' ),
);
$display_labels = PresentationTranslations::snapshot( array(
'Manual policy response decision',
'Manual policy condition',
'A deliberately configured manual condition matched; this is not an attack finding. Check the recorded action to see whether a response was issued.',
'Ordinary benign requests can match a manual condition. A match alone does not prove a denial or an attack.',
'An observation-only manual condition matched. This policy recorded the match without denying the request; no attack classification is inferred.',
'Observation-only policy %1$s matched (revision %2$s). This policy did not deny the request.',
'A deliberately configured manual condition matched; this is a response decision, not an attack finding.',
'Ordinary benign requests can match a manual condition. No attack classification is inferred from this response.',
'Configuration-resource probe pattern',
'Exact reserved configuration filename or recognized backup variant.',
'An authorized scanner or broken integration can request these resources.',
'Source-control or diagnostic discovery',
'Exact source-control metadata or diagnostic resource template.',
'Authorized diagnostics can deliberately request these resources.',
'Traversal-shaped resource request',
'Two parent-directory segments precede a recognized configuration/system-resource suffix.',
'Malformed links and authorized scanners are possible; target application normalization is unknown.',
'SQL-shaped numeric parameter',
'A numeric-identifier parameter contains a compound boolean or UNION projection with a comment delimiter.',
'A test or code example can contain the same structure; no execution or intent is established.',
'Executable-markup-shaped parameter',
'A numeric/navigation parameter contains paired script markup, an event-handler tag, or an executable URL scheme.',
'Authorized testing can contain the same syntax. Editing/search content parameters are not classified.',
'Component metadata enumeration',
'A plugin readme or theme metadata path was requested; enumeration is not exploitation.',
'Documentation tools and legitimate theme stylesheets can use related routes; theme style.css is excluded.',
'Unusual method at a non-API resource',
'TRACE/TRACK/CONNECT or a write method was used on a reserved metadata/configuration resource.',
'Protocol/security tests may intentionally exercise methods; ordinary API writes and OPTIONS are excluded.',
'The scoped policy was saved. The next eligible request must independently verify WordPress-layer response.',
'The policy was revoked. New eligible requests read current database state.',
'WordPress-layer response is disabled. Observation continues.',
'WordPress-layer response is enabled for currently valid, unexpired active policies.',
'Requests, in context.',
'Export latest 500 minimized requests (JSON)',
'Retained request family',
'First / last (selected timezone)',
'Source association',
'Unavailable; this is a site/time/family group',
'Partial analyses',
'Unknown final statuses',
'HTTP 1xx / 2xx / 3xx / 4xx / 5xx',
'Recorded WordPress responses',
'Sample evidence IDs',
'Retained request finding',
'Interpret only the recorded rule and available context.',
'Evidence ID',
'Alert work',
'Local request reference',
'Sensor / source',
'Rule / revision',
'Safe resource template',
'Method',
'Client-context availability',
'Partial analysis',
'yes; observation only',
'no within the bounded supported view',
'Final HTTP status',
'Logged.ai action',
'Unconfirmed local response; no verified issuance receipt',
'No Logged.ai response',
'Application effect',
'Unknown',
'Captured (selected timezone)',
'Ingested (selected timezone)',
'Response observed (selected timezone)',
'15 minutes',
'1 hour',
'24 hours',
'7 days',
'Preview temporary policy',
'Exact safe resource path',
'Reserved source-control path prefix',
'Structured query on one exact path',
'Exact retained derived address (full mode required)',
'Preview reviewed conditions',
'Response',
'Disable all local response for this site',
'Enable remaining active policies',
'Policy ID',
'Origin',
'State',
'Revision',
'Methods',
'Scope',
'Expiry (selected timezone)',
'Parser',
'Preview hash',
'Revoke',
'Latest at most 500 admitted observations visible at query time within the configured request retention. Concurrent and later inserts are not a transactionally complete snapshot. Telemetry is admission-limited.',
'Review temporary policy',
'Safe target',
'GET and HEAD',
'Duration after confirmation',
'Enforcement layer',
'WordPress init, priority -999; not edge/server/static',
'Parser revision',
'/?dumb-code-to-hack-site',
'Confirm reviewed policy',
'Response authority is required for the selected user.',
'Use --yes to enable still-valid active policies; durations are not extended.',
'LOGGED_AI_DISABLE_RESPONSE is active. Remove it using operator configuration first.',
'The scoped response setting could not be changed. Enabling retained IP policies requires --user with response and sensitive-context permissions.',
'Use revoke POLICY_ID with exactly --dry-run or --yes in the selected site.',
'The policy ID is invalid.',
'The policy ID is invalid, outside this site, unavailable or already revoked.',
'Use status, list, disable, enable --yes, or revoke POLICY_ID --dry-run|--yes.',
'Response authority changed.',
'Use requests prune --dry-run to preview one selected site, or requests prune --yes to apply one bounded batch. Choose exactly one flag and an explicit --url; no all-sites operation is supported.',
'Use requests list, requests export, requests prune --dry-run, or requests prune --yes. The separate Logged.ai Pro add-on supplies reference import with explicit source configuration.',
'Request evidence authority is required for the selected user.',
'Request evidence authority changed.',
'Authorized scanners, diagnostic tools and unrelated visitors can produce this pattern. A direct network peer may represent many people.',
'Repeated distinct reserved-resource requests',
'At least 10 complete admitted GET/HEAD findings cover at least three distinct safe reserved-resource templates in one fixed UTC minute.',
'Known configuration/source/diagnostic templates; complete parser result; this authorized site and source label. Status can be unknown.',
'Repeated distinct reserved resources with observed 404s',
'At least 10 complete admitted GET/HEAD findings have a finalized observed 404 across at least three distinct safe reserved-resource templates in one fixed UTC minute.',
'Request-sequence access is unavailable.',
'Request-sequence authority changed.',
'Current retained admitted findings only. Counts are lower bounds; missing telemetry is not quantifiable. Fixed UTC-minute boundaries can split a sequence. Site and exact-peer views overlap and must not be summed. No attacker identity, historical alert, exposure, prevention or automated-response eligibility is established.',
'Admitted / complete eligible',
'First / last (display timezone)',
'Safe templates',
'Known finalized 404s',
'Recorded local responses',
'Unknown final status',
'Window still open',
'Sample observation IDs',
) );
return array( 'text' => $display_text, 'labels' => $display_labels );
}
private static function preview_screen( object $scope, array $policy, array $display ): void {
$display_text = $display['text'];
$display_labels = $display['labels'];
global $wpdb;
$time = self::time();
if ( ! $scope->valid_for_all( 'ip' === ( $policy['target_kind'] ?? '' ) ? array( 'logged_ai_manage_response', 'logged_ai_view_sensitive_context' ) : array( 'logged_ai_manage_response' ) ) ) {
wp_die( esc_html( $display_text[ 0 ] ), '', array( 'response' => 403 ) );
}
$samples = self::loaded( Requests::observations( $scope->network_id, $scope->site_id, 100 ) );
$typed = RequestsPolicy::VERSION === $policy['parser_version'];
$condition = $typed ? json_decode( $policy['target_json'], true, 8 ) : null;
$settings = Requests::privacy_settings();
$matches = 0;
$unavailable = 0;
foreach ( $samples as $sample ) {
if ( $typed ) {
if ( 'query' === $condition['kind'] || (int) $sample['partial'] || ! in_array( $sample['safe_path'], RequestsClassifier::safe_paths(), true ) || ( 'ip' === $condition['kind'] && 'full' !== $sample['ip_mode'] ) ) {
++$unavailable;
continue;
}
if ( RequestsPolicy::matches( $policy, RequestsPolicy::view( $sample['safe_path'], $sample['method'], $sample['client_mask'] ), $settings ) ) {
++$matches;
}
} elseif ( $sample['safe_path'] === $policy['safe_path'] && ! (int) $sample['partial'] && in_array( $sample['method'], array( 'GET', 'HEAD' ), true ) ) {
++$matches;
}
}
if ( ! $scope->valid_for_all( 'ip' === ( $policy['target_kind'] ?? '' ) ? array( 'logged_ai_manage_response', 'logged_ai_view_sensitive_context' ) : array( 'logged_ai_manage_response' ) ) ) {
wp_die( esc_html( $display_text[ 0 ] ), '', array( 'response' => 403 ) );
}
// Native admin-post response with no executable scripts or remote assets.
nocache_headers();
header( 'Content-Type: text/html; charset=UTF-8' );
wp_enqueue_style( 'logged-ai-preview', plugins_url( 'assets/admin.css', LOGGED_AI_FILE ), array(), LOGGED_AI_VERSION );
wp_enqueue_style( 'logged-ai-preview-requests', plugins_url( 'assets/requests.css', LOGGED_AI_FILE ), array( 'logged-ai-preview' ), LOGGED_AI_VERSION );
echo '' . '' . '' . '' . esc_html( $display_text[ 1 ] ) . '' );
wp_print_styles( array( 'logged-ai-preview', 'logged-ai-preview-requests' ) );
echo '';
self::start( 'Review temporary policy', $scope, $time );
if ( ! $scope->valid_for_all( 'ip' === ( $policy['target_kind'] ?? '' ) ? array( 'logged_ai_manage_response', 'logged_ai_view_sensitive_context' ) : array( 'logged_ai_manage_response' ) ) ) {
wp_die( esc_html( $display_text[ 0 ] ), '', array( 'response' => 403 ) );
}
echo ( '
' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. */ esc_html( $display_text[ 2 ] ), '', '' ) . '
' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. 3: Value or formatting element shown in this message. */ esc_html( $display_text[ 8 ] ), '', '', esc_html( $time->text( $condition['source_expires_at'] ) ) ) . '
' );
}
}
echo ( '
' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. */ esc_html( $display_text[ 9 ] ), esc_html( (string) $matches ), esc_html( (string) $unavailable ) ) . '
' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. */ esc_html( $display_text[ 16 ] ), 'wp logged-ai response disable --url=YOUR_SITE', 'LOGGED_AI_DISABLE_RESPONSE' ) . '
' . sprintf( /* translators: 1: Value or formatting element shown in this message. 2: Value or formatting element shown in this message. */ esc_html( $display_text[ 22 ] ), '', '' ) . '
' . '' . '' . '' . '' );
}
/**
* Dispatch scoped OS-operator response inspection, recovery and explicit revocation commands.
*
* OS-level operator commands. --url chooses one WordPress site, never all sites.
*
* @param array $args Positional WP-CLI command arguments.
* @param array $assoc Named WP-CLI command flags or filters.
*
* @return void
*/
public static function response_cli( array $args, array $assoc ): void {
[$network, $site] = Requests::scope_ids();
$command = $args[0] ?? 'status';
if ( get_current_user_id() > 0 && ! Scope::can( 'logged_ai_manage_response' ) ) {
\WP_CLI::error( __( 'Response authority is required for the selected user.', 'logged-ai' ) );
}
$result = null;
if ( 'status' === $command ) {
$result = array(
'network_id' => (string) $network,
'site_id' => (string) $site,
'recovery_constant' => Requests::recovery_disabled(),
'state' => Requests::state( $network, $site ),
'layer' => 'wordpress_init',
'policy_cache' => 'none',
);
} elseif ( 'list' === $command ) {
$result = Requests::policies( $network, $site );
} elseif ( 'disable' === $command || 'enable' === $command ) {
if ( 'enable' === $command && ! isset( $assoc['yes'] ) ) {
\WP_CLI::error( __( 'Use --yes to enable still-valid active policies; durations are not extended.', 'logged-ai' ) );
}
if ( 'enable' === $command && Requests::recovery_disabled() ) {
\WP_CLI::error( __( 'LOGGED_AI_DISABLE_RESPONSE is active. Remove it using operator configuration first.', 'logged-ai' ) );
}
if ( ! Requests::set_enabled( $network, $site, 'enable' === $command ) ) {
\WP_CLI::error( __( 'The scoped response setting could not be changed. Enabling retained IP policies requires --user with response and sensitive-context permissions.', 'logged-ai' ) );
}
$result = array(
'action' => $command,
'site_id' => (string) $site,
'saved' => true,
);
} elseif ( 'revoke' === $command ) {
$dry_run = array( 'dry-run' => true ) === $assoc;
if ( 2 !== count( $args ) || ( ! $dry_run && array( 'yes' => true ) !== $assoc ) ) {
\WP_CLI::error( __( 'Use revoke POLICY_ID with exactly --dry-run or --yes in the selected site.', 'logged-ai' ) );
}
$id = $args[1] ?? '';
if ( ! is_string( $id ) || ! preg_match( '/\A[a-f0-9]{32}\z/D', $id ) ) {
\WP_CLI::error( __( 'The policy ID is invalid.', 'logged-ai' ) );
}
if ( $dry_run ) {
$result = Requests::revoke_preview( $network, $site, $id );
} elseif ( ! Requests::revoke( $network, $site, $id ) ) {
\WP_CLI::error( __( 'The policy ID is invalid, outside this site, unavailable or already revoked.', 'logged-ai' ) );
} else {
$result = array(
'action' => 'revoked',
'policy_id' => $id,
'site_id' => (string) $site,
);
}
} else {
\WP_CLI::error( __( 'Use status, list, disable, enable --yes, or revoke POLICY_ID --dry-run|--yes.', 'logged-ai' ) );
}
if ( is_wp_error( $result ) ) {
\WP_CLI::error( PresentationTranslations::text( $result->get_error_message() ) );
}
if ( 'list' === $command ) {
if ( get_current_user_id() > 0 ) {
$scope = Scope::current( 'logged_ai_manage_response' );
if ( is_wp_error( $scope ) ) {
\WP_CLI::error( __( 'Response authority changed.', 'logged-ai' ) );
}
$result = Requests::policies_for_scope( $scope, $result );
if ( is_wp_error( $result ) ) {
\WP_CLI::error( PresentationTranslations::text( $result->get_error_message() ) );
}
} else {
$result = Requests::policies_for_viewer( $result, $network, $site );
}
}
\WP_CLI::line( wp_json_encode( $result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) );
}
/**
* Dispatch bounded request listing/export, explicit retention and registered import handling.
*
* @param array $args Positional WP-CLI command arguments.
* @param array $assoc Named WP-CLI command flags or filters.
*
* @return void
*/
public static function requests_cli( array $args, array $assoc ): void {
if ( 'import' === ( $args[0] ?? '' ) && has_action( 'logged_ai_requests_import_cli' ) ) {
if ( ! defined( 'LOGGED_AI_PRO_EDITION_API' ) || 2 !== LOGGED_AI_PRO_EDITION_API ) {
\WP_CLI::error( __( 'Install the matching separate Logged.ai Pro add-on before using its importer.', 'logged-ai' ) );
}
do_action( 'logged_ai_requests_import_cli', $args, $assoc );
return;
}
[$network, $site] = Requests::scope_ids();
if ( 'prune' === ( $args[0] ?? '' ) ) {
$dry_run = array( 'dry-run' => true ) === $assoc;
if ( 1 !== count( $args ) || ( ! $dry_run && array( 'yes' => true ) !== $assoc ) ) {
\WP_CLI::error( __( 'Use requests prune --dry-run to preview one selected site, or requests prune --yes to apply one bounded batch. Choose exactly one flag and an explicit --url; no all-sites operation is supported.', 'logged-ai' ) );
}
$result = Requests::retention( $dry_run );
if ( is_wp_error( $result ) ) {
\WP_CLI::error( PresentationTranslations::text( $result->get_error_message() ) );
}
\WP_CLI::line( wp_json_encode( $result, JSON_PRETTY_PRINT ) );
return;
}
if ( ! in_array( $args[0] ?? 'list', array( 'list', 'export' ), true ) ) {
\WP_CLI::error( __( 'Use requests list, requests export, requests prune --dry-run, or requests prune --yes. The separate Logged.ai Pro add-on supplies reference import with explicit source configuration.', 'logged-ai' ) );
}
$required = 'export' === ( $args[0] ?? 'list' ) ? 'logged_ai_export_events' : 'logged_ai_view_events';
$limit = $assoc['limit'] ?? ( 'logged_ai_export_events' === $required ? '500' : '50' );
if ( ! is_string( $limit ) || ! preg_match( '/\A[1-9][0-9]{0,2}\z/D', $limit ) || (int) $limit > 500 ) {
\WP_CLI::error( __( 'Use a limit from 1 to 500.', 'logged-ai' ) );
}
$filters = array();
foreach ( $assoc as $key => $value ) {
if ( 'limit' !== $key ) {
$filters[ $key ] = $value;
}
}
$reader = RequestsBrowse::current( $required );
if ( is_wp_error( $reader ) ) {
\WP_CLI::error( $reader->get_error_message() );
}
if ( 'logged_ai_export_events' === $required ) {
$result = $reader->download( $filters, static fn( string $json ) => \WP_CLI::line( $json ), (int) $limit );
if ( is_wp_error( $result ) ) {
\WP_CLI::error( $result->get_error_message() );
}
return;
}
$result = $reader->page( $filters, (int) $limit );
if ( is_wp_error( $result ) ) {
\WP_CLI::error( $result->get_error_message() );
}
if ( 'logged_ai_view_events' === $required ) {
$result['observations'] = $result['rows'];
unset( $result['rows'] );
$result['network_id'] = (string) $network;
$result['site_id'] = (string) $site;
$result['membership'] = 'Latest matching admitted observations within request retention; dates use UTC. The continuation cursor is exclusive. Not a complete traffic snapshot.';
}
\WP_CLI::line( wp_json_encode( $result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) );
}
}