| 开发者 | brokensmile.2103 |
|---|---|
| 更新时间 | 2026年9月24日 00:55 |
| PHP版本: | 7.4 及以上 |
| WordPress版本: | 7.1 |
| 版权: | GPLv2 or later |
| 版权网址: | 版权信息 |
display:none or visibility:hidden, the two patterns CSS-aware bots specifically look for and skip) that bots fill but humans never see. The text field is also marked readonly, so browser and password-manager autofill (Chrome, saved-password prompts, and similar) never writes into it either — bots that scrape the raw HTML or drive a headless browser still fall for it exactly the same.hash_equals() to prevent timing attacks. Submissions under the minimum threshold are rejected; login/registration/other account-style forms use their own, shorter threshold by default (see Account Forms Minimum Submit Time), since a browser autofilling saved credentials lets a genuine visitor submit faster than someone typing a comment from scratch.navigator.webdriver, a zero-size browser window, HeadlessChrome/PhantomJS, Selenium/ChromeDriver traces) picked up from real Selenium/Puppeteer/Playwright sessions. The proof is bound to the form's own time token and never appears in plain text in the page, so a bot that scrapes the HTML and replays hidden values can't produce it. Static crawlers, instant bots, and unmasked headless browsers all get caught; real users don't.Sec-Fetch-Site: cross-site), a header page scripts cannot forge and privacy extensions don't strip. A request without the header is never rejected by this check. On by default.wp/v2/comments REST endpoint, which the classic form-based layers cannot cover since those requests never carry the honeypot fields or tokens./wp-content/plugins/The plugin hooks into comment_form_after_fields. If your theme uses a custom form, you may need to adjust the hook or manually call the render function.
Yes. Init Void Shield acts as the first line of defense. Other plugins can serve as a secondary layer.
No. Logged-in users are bypassed by default on the comment form. Guests only need to wait a few seconds between page load and submit — something every human naturally does.
Yes. Enable Apply to Logged-in Users in the settings if your site has open registration or untrusted members.
Not by default. The core layers only run on the classic comment form (preprocess_comment); comments posted directly to wp/v2/comments never carry a honeypot or JS token, so those checks don't apply. The optional "Block REST API Comments" setting is available to close that endpoint entirely if you do not use a headless app or other legitimate REST client for comments.
No, all three are off by default since they guard authentication itself. Turn them on individually under WordPress Core Forms in the settings, and confirm your own login flow still works afterward. Each covers WordPress's own default form markup at wp-login.php, and the login guard also covers wp_login_form() when used on the front end (e.g. a widget or theme template), unless you set Login Guard Scope to "wp-login.php only" — see the next question. A custom login page/plugin, or Multisite's wp-signup.php registration flow, renders different markup and isn't covered. Each guard can also be force-disabled per request with a dedicated filter (init_plugin_suite_void_shield_skip_login_verification, ..._skip_register_verification, ..._skip_lostpassword_verification), which takes priority over the settings-page toggle.
By default ("Everywhere"), the Login Guard protects both the native wp-login.php form and any front-end wp_login_form() usage (e.g. a custom login page, widget, or modal). If that front-end form lives on a page your cache plugin might serve stale, switch the scope to "wp-login.php only" to leave it entirely unguarded rather than risk a real visitor's login being rejected — the native wp-login.php form stays fully protected either way. This uses the Referer header as a heuristic to tell the two forms apart, which a determined bot could spoof, so it's a deliberate trade-off: only switch away from "Everywhere" if you've hit this specific caching situation.
No. Each is off by default and only takes effect if the matching plugin is active — the settings page shows a "detected / not detected" status next to each toggle.
No, same as the other integrations: each is off by default and only takes effect if the matching plugin is active. WooCommerce guards the "My Account" registration form, and optionally the WooCommerce login form (My Account and the checkout login prompt) and lost-password form; bbPress guards the New Topic and Reply forms; BuddyPress guards the registration (signup) form. The Multisite signup guard (wp-signup.php) only has an effect on a Multisite install and is also off by default.
Both build their forms client-side and collect submitted data through their own JavaScript field model rather than serializing the whole <form> element, so a honeypot field simply appended to the page would not reliably be sent to the server — it would look like protection without actually doing anything. Both also already ship their own built-in honeypot field. Support may be reconsidered if a reliable hook becomes available.
wpDiscuz builds its comment submissions from a fixed set of fields rather than serializing the whole form, so this plugin's checks can never see or verify them. When wpDiscuz is detected active, its comments are automatically exempted from verification instead of being rejected — a compatibility bypass, not an added layer of protection.
Each guard issues a signed token good for a limited time window: too fast (below Minimum Submit Time) and too old (above Maximum Token Age) are both rejected. The ceiling exists so a token cannot be captured once and replayed indefinitely; the default of 1 hour is generous enough for a visitor who takes their time filling out a form. Increase it (up to 30 days) if legitimate visitors on your site routinely take longer than that, or if your site uses full-page caching — see the next question.
On a cached page, the time token baked into the HTML reflects the moment the page was cached, not the moment a real visitor loaded it. If your cache lifetime is long, that gap can exceed the Maximum Token Age. Two options, either works on its own: raise Maximum Token Age to comfortably cover your cache lifetime, or enable Lazy Fetch under Advanced Protection, which refreshes the token client-side right after the page truly loads, so timing is always measured from the real visit regardless of cache age.
When enabled, a small same-origin JavaScript request (no jQuery, no external service) fetches a fresh, correctly signed time token right after the page loads, and overwrites the one baked in at render time. It's stateless — the endpoint only recomputes the same signed hash the page would already have shown, and is only registered at all while Lazy Fetch is enabled. If the request fails, or JavaScript is unavailable, the original baked-in token is used as-is, exactly like before Lazy Fetch existed — so enabling it can only help, never introduce a new failure mode. Off by default; applies to every guarded form, not just comments.
Versions before 1.4 had a bug where these two settings were saved correctly but never actually read back during verification, so the plugin always silently enforced the defaults (3 seconds / 1000ms) no matter what was configured. 1.4 fixes this, so a custom value you set earlier may now be enforced for the first time. This is a bug fix, not a new restriction — just double-check both values on the settings page after updating.
When enabled, a submission is only accepted if the browser recorded at least one real mouse, keyboard, touch, or scroll event before the JS token fires — and that event only counts once a short minimum delay of its own has passed, so a bot can't satisfy it by firing one synthetic event the instant the page loads. It targets bots that wait out the JavaScript Token Delay instead of a real page visit. It's off by default and works best paired with a JS delay of at least 1-2 seconds.
It rejects any submission whose User-Agent header identifies a scripted HTTP client — curl, Python's requests library, Go's default client, Scrapy, PostmanRuntime, and similar — rather than a real browser. It's on by default and is one of the safest checks in the plugin: every real browser, including all the major ones and their mobile variants, sends its own distinct browser User-Agent, never one of these library defaults. It specifically catches a bot that never runs JavaScript at all — one that parses the static HTML, avoids the honeypot fields, and replays the baked-in time/hash token — which the honeypot and JS layers can't see on their own. If you run your own legitimate script against your comment form (e.g. an internal test tool), either give it a normal browser User-Agent or use the init_plugin_suite_void_shield_skip_verification filter for that request.
When enabled, a comment submission is rejected if its Referer header is missing or points to a domain other than your own — this catches a bot that posts directly to the comment endpoint without ever actually loading the page. It's off by default and disclosed as a trade-off, same as Login Guard Scope's Referer heuristic: some privacy-focused browsers and extensions strip the Referer header even on a genuine same-site submission, and this check cannot tell that apart from a bot. Only enable it after confirming it doesn't affect real commenters on your site, and treat it as one more layer on top of the others rather than a replacement for them. A init_plugin_suite_void_shield_referer_exempt filter is available if you need to exempt specific requests (e.g. a proxy or caching setup that legitimately strips Referer).
Yes. Set a Custom Field Prefix under Advanced Protection. Field names are still dynamically derived per context and site salt on top of that prefix.
No. It only reads a handful of properties on the visitor's own device (navigator.webdriver, the browser window size, the User-Agent string, and globals/markers that only automation frameworks inject) — no external requests, no tracking.
Only aggregate counters (a total, a breakdown by channel, a breakdown by block reason, and a last-blocked timestamp) in one non-autoloaded option. No per-submission logs, IP addresses, or personal data are recorded. It can be turned off or reset from the settings page at any time.
No. Enable Show Dashboard Widget under Statistics. It's only visible to users who can manage options, and only shows the same aggregate counters as the settings page (no per-submission data).
This was a bug fixed in 1.8: some environments (an HTML minifier, a multilingual plugin, a security/output-filtering plugin, or WordPress's own convert_chars()) rewrite a bare & in page output into &, which is safe for ordinary HTML text but breaks the literal JavaScript inside a <script> tag, since browsers never decode entities there. If you're on 1.8 or later and still see this, please report it along with your active plugins/theme so the specific source can be identified.
That's Submit Hold at work. If a visitor clicks "Log In" before the guard is ready — most often because the browser autofilled a saved password and they clicked right away — the submission is held for a moment (at most the JavaScript Token Delay or the Minimum Submit Time, whichever is longer) and then sent automatically. Before 1.11 such a login was rejected outright. It applies to forms that submit without AJAX: the WordPress core forms, comments, WooCommerce account forms, bbPress, and BuddyPress. Contact Form 7, WPForms, and Gravity Forms send their forms from their own scripts and are left unchanged.
1.11 replaced the fixed JS token value with a signed proof unique to each form render. A page served from a full-page cache built before the update still contains the old script, whose submissions now fail that check. Purge your page cache (and any CDN cache) once after updating. If the reason keeps appearing afterward, it is a bot.
Modern browsers attach a Sec-Fetch-Site header to every request themselves; page scripts cannot set or change it. A genuine visitor submitting a form on your site always sends same-origin (or same-site from one of your subdomains). cross-site means the form was posted from a different website — a spam page auto-submitting a copy of your form, for example — which a real visitor never does. A request without the header (an older browser, or a plain script) is never rejected by this check, so it cannot produce a false positive on its own. The only setup that needs it off is one where a form on an unrelated domain legitimately posts to your site (e.g. a Multisite network with subsites on unrelated domains sharing one login form); the init_plugin_suite_void_shield_cross_site_exempt filter can exempt specific requests instead.
Three independent, opt-in checks under Comments: Maximum Links per Comment (0 = off), Block BBCode Links (a complete [url]…[/url] or [link]…[/link] pair, which WordPress never renders anyway), and Block URLs in Author Name. They catch spam a real person or a real browser posts, which the behavioral layers can't, and they also apply to wpDiscuz comments. Pingbacks and trackbacks are never checked. Blocked comments get the same soft-kill response as the rest of the comment guard.
Yes. See the Filters section below for the full list, or the documentation on GitHub for more detail.
human_verified, which was identical on every site and could be posted by a bot without running any JavaScript. It is now a signed proof bound to the form's own time token and context, embedded in the page only in sealed form. A submission carrying a missing, static, or mismatched proof is rejected with the new "Invalid JS proof" block reason. Lazy Fetch now refreshes the proof together with the time token. Purge your page cache once after updating — pages cached with the previous script will fail this check.Sec-Fetch-Site: cross-site). A request without the header is never rejected by this check. Exemptible per request via init_plugin_suite_void_shield_cross_site_exempt.HeadlessChrome/PhantomJS User-Agents, PhantomJS/Nightmare/DOM-automation globals, and Selenium ChromeDriver markers.init_plugin_suite_void_shield_skip_content_filters and init_plugin_suite_void_shield_comment_content_violation (for adding your own rules)..pot template for the new strings..pot template for the new section strings.init_plugin_suite_void_shield_min_time and _max_time filters now also receive the guard context as a second argument.readonly, so browser and password-manager autofill never fills it in; bot coverage is unaffected..pot template for the new setting's strings.convert_chars()), turning && into the literal text && on the page. Browsers never decode entities inside <script> content, so this broke JS parsing entirely and silently rejected every submission. The script no longer emits any bare & character.DOMContentLoaded, which had already fired by the time such a delayed script ran. It now checks document.readyState and runs immediately if the document is already past the loading state.init_plugin_suite_void_shield_blocked_user_agent_signatures.1 on the first save. Tightened to require an explicit '1' before treating a checkbox as on.register_setting()'s default argument, which triggers a WordPress core edge case in update_option() once a setting's value matches that default. Removed from every setting in this plugin; displayed defaults are unaffected.comment_post_ID POST field — not every comment form sends it under that exact name.wp_login_form() usage (e.g. a custom login page, widget, or modal) entirely while keeping full protection on the native wp-login.php form. Intended for sites where that front-end form lives on a page a full-page cache might serve stale; uses the Referer header as a heuristic to tell the two forms apart (a disclosed trade-off, not a cryptographic guarantee). The default ("Everywhere") is unchanged and remains the strongest option.fetch() request (plain JavaScript, no jQuery, no external service) as soon as the page truly loads, instead of relying only on the value baked in at render time — which, on a cached page, reflects when the cache was generated rather than when a real visitor loaded it. Applies to every guarded form. Falls back to the original baked-in token if the request fails or JavaScript is unavailable, so enabling it can only help, never introduce a new failure mode. Its REST route is registered under the initvoshi/v1 namespace, matching the naming convention used across the Init Plugin Suite.token_expired block reason.wp-signup.php (off by default, only has an effect on a Multisite install)..pot) and the Vietnamese translation for all new strings.wp_login_form() (used to place a login form anywhere on the front end, e.g. a widget or theme template). Previously only the native wp-login.php form was guarded; a front-end wp_login_form() submission carried no honeypot fields and was always rejected with "Invalid login attempt." once the login guard was enabled.init_plugin_suite_void_shield_skip_login_verification, init_plugin_suite_void_shield_skip_register_verification, and init_plugin_suite_void_shield_skip_lostpassword_verification — to force-disable each WordPress Core Forms guard for a given request regardless of its settings-page toggle.navigator.webdriver, zero-size window) layered on top of the existing JS token check; no external calls.init_plugin_suite_void_shield_honeypot_html filter's second parameter is now the generic guard context string (e.g. comment_123, login, cf7_4) instead of only a numeric comment post ID.wp/v2/comments REST endpoint, which the classic 4-layer honeypot cannot cover since it never sees those requests.wp_get_inline_script_tag(), matching the existing "Requires at least: 5.7" requirement.