Repository navigation
Add five signals and three detection rules - #82
Closed
proofofbots wants to merge 2 commits into
Closed
proofofbots wants to merge 2 commits into
proofofbots wants to merge 2 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add five signals and three detection rules
Three of these lean on state that follows the machine rather than the browser build, which is
awkward to spoof from a page, and one is a tamper probe.
Detection rules
hasTamperedNativeGetter(high) takes thehardwareConcurrencyaccessor offNavigator.prototype, deletes the globalFunctionandObject, and calls the getter'stoString(). Native code needs neither global to stringify itself. A getter installed fromJavaScript usually hides behind a hooked or proxied
Function.prototype.toString, and those hooksreach for the globals by name while they run, so the call throws.
Measured against a stock Playwright Chromium with different patches injected before page load:
navigatorPropertyDescriptors00000definePropertygetter, nothing else00000Proxy00000Function.prototype.toString00000Function.prototype.toString00000So it catches the shape puppeteer-extra-stealth ships and misses a bare
defineProperty, which isthe honest boundary. Worth noting from the same runs:
navigatorPropertyDescriptorsreads00000on stock Chromium and on every patched variant, since those navigator properties are accessors and
the check tests
res.value. It is not distinguishing anything on Chromium today.The probe is synchronous on purpose, because the globals are missing until it returns, and it
restores them with
definePropertyfrom a captured reference rather than by assignment. Assignmentwould leave
FunctionandObjectenumerable onwindow, which is observable, and the restorepath cannot say
Object.definePropertywhileObjectis deleted. Verified after collection:globals back, non-enumerable,
new Function(...)still works.hasPdfViewerMismatch(medium) comparesnavigator.pdfViewerEnabledwith the plugin array.Chromium and Gecko both tie the two together: viewer on and five hardcoded PDF entries, or viewer
off and an empty list. Patches routinely rebuild one and leave the other. Headless Chromium reports
falsewith 0 plugins and headed reportstruewith 5, so neither trips it.hasNoPlatformAuthenticator(low) reads whether the machine can answer a passkey itself, whichfollows the hardware and the OS rather than the browser build. Scoped to Chromium desktop in a
secure context. Deliberately low severity: an Intel Mac without Touch ID and a Windows install
without Hello answer no as well.
Signals
device.audio—baseLatency,sampleRate,maxChannelCountfrom anAudioContextthat isclosed immediately. Nothing is rendered through it, no user gesture involved.
device.battery—getBattery()state, with the infinite times normalised to-1. Kept outof the fsid on purpose, since level and charge times move during a session.
browser.webauthn—getClientCapabilities()as a fixed-order bitmask, withisUserVerifyingPlatformAuthenticatorAvailable()as the fallback for browsers without it, plusthe secure-context flag, without which an absent API says nothing.
browser.mediaCapabilities—decodingInfofor four video and two audio configurations, onedigit each (1 supported, 2 smooth, 4 power efficient).
canPlayTypeanswers what the build candecode; this answers what the machine can decode in hardware. On the same laptop, headless
Chromium reports
3033and headed reports7777.browser.plugins.pdfViewerEnabledand five feature bits (sumPrecise, which was alreadycollected but missing from the type, plus
managed,storageBuckets,presentationRequest,virtualKeyboard).fsid
New booleans are appended, so existing bit positions hold: detection 21 to 24 bits, automation 5 to
6, plugins 4 to 5, browser features 28 to 33. The section hashes do change, since the new signals
feed them, so fsids are not comparable across this boundary. Happy to gate that behind FS2 instead
if you would rather keep FS1 stable. The README bitmask reference is updated to match.
Testing
npx tsc --noEmitclean, 91 vitest tests pass (12 new, covering the three rules), and the Playwrightsuite passes 72 across chromium, firefox, webkit and mobile-chromium.