Skip to content

booking: give browser autofill nothing to classify in the honeypot - #51

Closed
distronode-com wants to merge 2 commits into
Calnode:mainfrom
distronode-corporation:up/honeypot-autofill
Closed

distronode-com wants to merge 2 commits into
Calnode:mainfrom
distronode-corporation:up/honeypot-autofill

Conversation

@distronode-com

Copy link
Copy Markdown
Contributor

Fixes #33. Thanks to @MinosChatzidakis for the diagnosis: Chrome autofill was filling the honeypot, and renaming the field fixed it for them.

The problem

The booking page's hidden honeypot was <label>Company<input name="company" autocomplete="off">. Chromium classifies fields using components/autofill/core/browser/form_parsing/resources/legacy_regex_patterns.json, where COMPANY_NAME is company|business|organization|organisation, matched against both the label and the name attribute. Chrome filled the field from the visitor's address profile despite autocomplete="off", and POST /v1/bookings rejected a real person as a bot.

The change

book.html only. The input now has no label and is name="hp", id="f-hp". The submit script reads it by id and still posts company, so the API, the embed widget and third-party clients are unchanged, and a filled value is still rejected.

How the name was checked

I fetched that pattern file at chromium/src 0520a5aa3f76 and ran candidates through every pattern, in all languages, as a case-insensitive search (ICU find() semantics). hp and f-hp match nothing. As a sanity check, company hits COMPANY_NAME and extra hits PHONE_EXTENSION.

A label-free input still gets a label from Chrome: form_autofill_util.cc infers one from the surrounding DOM. For this markup that is the Back button text plus the chosen slot, for example Back Fri, Sep 18 · 10:00 AM. I generated that string for every weekday, month and half hour in all eight shipped locales (32,264 strings). One pattern matched: German Wednesday Zurück Mi., … hits MIDDLE_INITIAL. name_field_parser.cc assigns MIDDLE_INITIAL only inside a sequence that has both a first-name and a last-name field, and this form has one full-name field, so the match cannot become a classification.

Not covered: Chrome's server-side predictions, Safari, Firefox and password managers. I did not reproduce the original fill with a real Chrome address profile.

Embed widget

No change needed. Its honeypot has never had a label, name or id, and its inferred label comes from the same Back-plus-slot text.

Tests

  • New: TestBookPage_honeypotGivesAutofillNothingToClassify renders the page and requires one input, no label or text, no placeholder, aria-label, title or value, a name and id free of common autofill words, and a submit script that posts that input as company. Restoring the old markup fails 6 assertions; restoring only the "Company" label fails 2.
  • Existing: TestCreateBooking_honeypotRejected still covers the server side, and it fails when the check is disabled.
  • In a real browser against a built binary: a normal booking posted "company":"" and got 201 with no console errors. A filled honeypot got 400, and the server logged booking rejected: honeypot filled.
  • gofmt -l . is clean, and go vet ./..., go build ./... and go test ./... pass.

docs/ARCHITECTURE.md §9 now explains why the honeypot input must give autofill nothing to classify.

🤖 Generated with Claude Code

Some real people could not book: POST /v1/bookings answered 400. The
booking page's hidden honeypot was <label>Company<input name="company">,
and @MinosChatzidakis worked out in #33 that Chrome's autofill fills it
from the visitor's address profile. The server then treats a person as a
bot. autocomplete="off" was already on the input and did not stop it.

That label and name are exactly what Chrome looks for. Its classification
patterns live in
components/autofill/core/browser/form_parsing/resources/
legacy_regex_patterns.json (chromium/src 0520a5aa3f76), where COMPANY_NAME
is company|business|organization|organisation, matched against both the
label and the name attribute.

The input now has no label and is name="hp", id="f-hp". The submit script
reads it by that id and still posts it as "company", so the API, the embed
widget and any third-party client are unchanged, and a filled value is
still rejected.

The name was chosen by running candidates through every pattern in that
file, all languages, as a case-insensitive search the way ICU find() does:
"hp" and "f-hp" match nothing. A label-free input is not label-free to
Chrome, though. form_autofill_util.cc infers a label from nearby text, and
for this markup its div walk climbs to #form-view and picks up the Back
button and the chosen slot, e.g. "Back Fri, Sep 18 · 10:00 AM". That
string was generated for every weekday, month and half hour in all eight
shipped locales (32,264 strings) and run through the same patterns. One
matches: German Wednesday, "Zurück Mi., ...", hits MIDDLE_INITIAL on "Mi".
Chromium assigns MIDDLE_INITIAL only inside a first-name and last-name
sequence (name_field_parser.cc), and this form has a single full-name
field, so that cannot become a classification.

The embed widget needs no change. Its honeypot has never had a label, name
or id, and its inferred label comes from the same Back-plus-slot text.

This is checked against Chromium's local heuristics only. Its server-side
crowdsourced predictions, Safari, Firefox and password managers were not
tested, and no real Chrome profile was autofilled.

TestBookPage_honeypotGivesAutofillNothingToClassify renders the page and
holds the honeypot to: one input, no label or text, no placeholder,
aria-label, title or value, a name and id free of common autofill words,
and a submit script that posts that input as "company". Putting the old
markup back fails it on six assertions, and restoring only the "Company"
label fails it on two. TestCreateBooking_honeypotRejected already covers
the server side and still fails when the check is disabled.

In a real browser against a built binary, a normal booking posted
"company":"" and got 201 with no console errors, and a filled honeypot got
400 with "booking rejected: honeypot filled" in the log.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes The booking-page honeypot is stripped of Chrome Autofill classification signals so real bookers are no longer rejected as bots (#33), while the API field stays company.

  • Neutral honeypot input — book.html drops the "Company" label and uses name="hp" / id="f-hp"; the submit script still posts $('f-hp').value as JSON company, so the server check, embed widget, and API clients stay unchanged.
  • Regression test — TestBookPage_honeypotGivesAutofillNothingToClassify pins rendered markup (no label or autofill-shaped name/id) and the submit-script coupling; restoring the old field fails it.
  • Docs — ARCHITECTURE §9 and the unreleased changelog record why the input must give Autofill nothing to classify.

Pullfrog  | View workflow run | Using Grok | 𝕏

@shockalotti shockalotti left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Minimal, honest scope: book.html only, with the embed exemption stated and justified in both CHANGELOG and ARCHITECTURE (its honeypot never had a label/name/id). The rendered-markup test is the right pin — it checks the property (nothing autofill can classify) rather than the specific strings, plus the API-compat half (still posts as company). Fixes #33 with no API change.

@shockalotti

Copy link
Copy Markdown
Contributor

Approved and ready to merge, but this now conflicts with main (CHANGELOG Unreleased section — several PRs landed ahead of it). I cannot push to the fork (no write access), so please merge main into this branch, keeping both CHANGELOG entries, and I will merge immediately after. Nothing else outstanding from review.

CHANGELOG conflict only: kept both [Unreleased] entries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@distronode-com

Copy link
Copy Markdown
Contributor Author

Merged main in (0b40e04). The only conflict was the CHANGELOG [Unreleased] section, and both entries are kept. go vet and the full Go suite pass locally.

Same note as on #50: 86cc414 on fix/mcp-bearer-archived fixes #33 as well, and conflicts with this in book.html. Both stop the autofill. This one leaves the API field as company; 86cc414 renames it to hp_extra and ignores company. Keep whichever you prefer, and if it's 86cc414, close this one.

@distronode-com

Copy link
Copy Markdown
Contributor Author

Closing this in favour of 86cc414, merged in #57, which fixes #33 by renaming the field. Thanks for the review.

@github-actions github-actions Bot locked and limited conversation to collaborators Sep 17, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error submiting the form to create a new event

2 participants