Skip to content

feat(search): Google-style search operators, server security hardening, Material 3 web UI#97

Merged
ErikChevalier merged 6 commits into
mainfrom
feat/operators-security-m3-parity
Jul 2, 2026
Merged

feat(search): Google-style search operators, server security hardening, Material 3 web UI#97
ErikChevalier merged 6 commits into
mainfrom
feat/operators-security-m3-parity

Conversation

@ErikChevalier

Copy link
Copy Markdown
Contributor

What & why

Brings the desktop app to parity with the Android 26.07.01 feature set (SearchMob PR #96).

Google-style search operators. New engines/query_operators.py (a port of Android's QueryOperators.kt with the same semantics and test cases): "exact phrase", -term, site: / -site:, intitle:, inurl:, filetype: (or ext:), before: / after: dates, and OR. Parsed once at the route/GUI boundary; operators the engines understand are forwarded, and every structural filter is also enforced locally over the merged results, so they behave consistently across engines. The operator-free text drives relevance scoring, sort freshness, the summary, and spell correction, so scoping clauses (including the verticals' internal site: groups, which previously fed the lexical scorer) never pollute the match score. The served home page gains a collapsible operator cheat sheet.

Served-page security hardening.

  • A literal Origin: null on a mutation POST is now rejected as cross-site (it used to slip through because urlsplit("null") has an empty netloc and empty hosts are allowed for HTTP/1.0 clients); Referrer-Policy moves from no-referrer to same-origin so our own forms carry a real origin (cross-origin navigation still leaks nothing, and result links keep rel="noreferrer").
  • Every response now carries Content-Security-Policy, Cache-Control: no-store, and Permissions-Policy.
  • The network-mode access token is compared in constant time (secrets.compare_digest) and can be sent via Authorization: Bearer or X-SearchMob-Token headers instead of only the ?token= query parameter (which stays for OpenSearch templates).

Material 3 served UI. The served pages are restyled to the Android M3 look: elevated search bar with focus states, rounded cards, pill buttons, chip state layers, theme-derived colors. The CSS-variable theming is unchanged and all 14 palettes keep working in light and dark.

Vault-locked fail-soft behavior needed no changes: every store on the served-search path already degrades to defaults, and the new operator path is pure.

Verification

  • 763 tests pass (44 ported operator-parser cases, scoring-purity, local filter enforcement, Origin: null rejection, header presence, token transport/constant-time matrix, cheat-sheet markup); ruff and mypy --strict green.
  • Verified live against a running server: headers present on every response, Origin: null and cross-site POSTs get 403 while same-origin passes, site:/-site: queries return only matching hosts on the served page and /api/search, cheat sheet renders and expands, results and home pages screenshot-checked in dark and light themes.

🤖 Generated with Claude Code

FlintWave and others added 6 commits July 2, 2026 11:35
Parses "exact phrase", -term, site:/-site:, intitle:, inurl:, filetype:
(alias ext:), before:/after: dates, and OR/| into an engine query
(operators the engines understand, forwarded verbatim), an operator-free
clean text (for relevance, correction, and the summary lookup), and the
structural filters that are enforced locally over the merged results.
Mirrors engine/query/QueryOperators.kt, including tokenizer robustness
against unterminated quotes and blank phrases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EngineContext gains a ranking_terms field (mirroring the Android
SearchQuery.rankingTerms): when the fetched query carries scoping
clauses, the aggregator's lexical blend and language affinity reason
about the operator-free text instead, so a vertical's internal site: OR
group or a user's operators never count as subject matter in the match
score. None falls back to the query, keeping existing callers unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both /search routes and the in-app search parse the query once at the
boundary (right after the scope-token pass): the engine query is
forwarded upstream (scoped for the active vertical), every structural
filter is enforced locally over the merged results before sort/
personalization/rules (drops only, scores untouched), and the
operator-free clean text drives the summary lookup, the freshness
heuristic, click training, and lexical scoring. The on-device spell
corrector is skipped for operator-laden queries, whose syntax it would
just mangle. Mirrors MetaSearchResultProvider.kt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port the Android served-page security pass:

- Reject a literal "Origin: null" on mutation POSTs. An attacker page
  can force that opaque value onto a genuinely cross-site POST (a page
  served with Referrer-Policy: no-referrer, or a sandboxed iframe), and
  it used to slip through because urlsplit("null") yields an empty host
  and an empty host is allowed for HTTP/1.0 clients.
- Serve Referrer-Policy: same-origin instead of no-referrer, so our own
  forms carry a real origin the CSRF check can verify. Cross-origin
  navigation still sends nothing, and result links keep rel=noreferrer.
- Send Content-Security-Policy, Cache-Control: no-store, and
  Permissions-Policy on every response, so queries never persist in a
  browser cache and no external script/style/frame can ever load.
- Compare the network-access token in constant time
  (secrets.compare_digest) and accept it via Authorization: Bearer or
  X-SearchMob-Token headers as well as ?token= (which stays for the
  OpenSearch templates but leaks into history and bookmarks).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t sheet

Match the Android served pages' Material 3 character while keeping the
CSS-variable theming (all 14 palettes, light and dark) and the existing
class structure: an elevated search bar with hover and focus-within
states, rounded 16px cards, pill buttons, accent state layers (color-mix
with rgba fallbacks), quiet hover surfaces on results, and keyboard
focus rings on every interactive element including details summaries.
The active category chip derives from the theme accent instead of a
fixed color pair. The home page gains a collapsible "Search operators"
cheat-sheet card (a native details element, keyboard-operable with no
JavaScript) listing the supported operators with short descriptions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ErikChevalier ErikChevalier merged commit afa63a1 into main Jul 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant