Repository navigation
fix: stop the Search view answering for a half-typed query - #6
Merged
Merged
Conversation
Opening the Search view started the deeper pass, and the debounce in
useAnalysis started a second one 220 ms later because it re-ran on view
changes. That second pass read the search box mid-typing, so typing
"flag{" a key at a time could leave the report answering for "f". And
findFlags took "f" as a declared flag format and found it in the middle
of the hex input.
- Only input and auto-run changes schedule a re-run (watchInput, now a
plain store subscription so the timing is testable).
- Opening the view starts a pass unless the running one is over the same
input; runCtf drops a result whose input was replaced while it ran.
- A declared format must look like a flag prefix (optional brace), and
the fallback search only matches where a word starts.
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.
What was wrong
Open the Search view, type
flag{one key at a time, and sometimes the report said 2 flags found: the real one, plus a long run of the hex input labelled "Flag-shaped, with a prefix this competition or a well-known one uses", and a "Read it backwards" flag hint. Setting the query in one go always gave the right answer. Caught while recording a demo.Two things went wrong together.
1. A second pass read the search box mid-typing (
src/hooks/useAnalysis.ts).Opening the Search view starts the deeper pass right away (
setActivity). The debounce effect inuseAnalysisalso listedviewas a dependency, so 220 ms later it started a second pass, reading the search box at whatever point the typing had reached. Typing does not start a pass on purpose, so that second pass had the last word. If the box heldfat that moment, the report answered forf.The token in
runCtfalready stops an older pass overwriting a newer one, so the responses were not arriving out of order. The trouble was a duplicate pass that sampled a half-typed query.Reproduced on the live site by scripting the page: when the first key lands 160 ms after the view opens, the result is "2 flags found" every time, because the timer reads
f. At 60 ms or 400 ms it is "1 flag found", because the timer readsflor the empty box.2. Any text counted as a declared flag format (
src/engine/ctf/flags.ts).findFlagstook any non-empty format as the competition's prefix, and its fallback for formats without braces searched for that text anywhere, mid-word included (f[^\s]{2,256}).fis a hex digit, so it matched the middle of the hex input at depth 0. The reversal check calls the same function, which is where the extra "Read it backwards" hint came from. Onlyfbreaks, becausefl,flaandflagnever occur in hex.The fix
watchInput(), a plain store subscription the hook starts, so it can be tested without a DOM. Only input and auto-run changes schedule a re-run. The view is read when the input changes, not when the timer fires, so opening the view inside the 220 ms window does not bring the second pass back.runCtfdrops a result whose input was replaced while it ran. Before, a pass could finish after the input was cleared or changed and still land.[A-Za-z0-9_-], with an optional opening brace. That is the same prefix grammar the flag shape already uses. The brace is ignored when comparing, so typingmyctf{now promotesmyctf{…}the waymyctfalready did. The fallback search is anchored to the start of a word, like the shape is.Tests
src/engine/ctf.test.ts: threefindFlagscases (brace-typed format, lone letter, match in the middle of a word). An end-to-endhints()run over the demo's input with'',f,fl,flagandflag{in the box must find exactly the one flag every time.src/hooks/useAnalysis.test.ts(new): the real store and engine, withsetTimeoutfaked. It replays the demo's timing: open the view, first key at 160 ms, then one every 90 ms. It failed before the fix with passes run for['', 'f']. Two more tests guard the cases above: an in-flight pass over an older input, and an input replaced while a pass runs. The second was confirmed red with the check disabled.npm run lint,npm run typecheck,npm testandnpm run buildpass. Checked the built app in a browser with the same scripted timing (key at 160 ms after the view opens): it now shows 1 flag found, where the live site shows 2.