Skip to content

Award points for flag submissions on auto-validated crackmes (#127) - #180

Open
xusheng6 wants to merge 3 commits into
mainfrom
feature/flag-validation-points
Open

Award points for flag submissions on auto-validated crackmes (#127)#180
xusheng6 wants to merge 3 commits into
mainfrom
feature/flag-validation-points

Conversation

@xusheng6

@xusheng6 xusheng6 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Starts the point system from #127 with the parts everything else depends on: a flag that can be submitted, a solve that gets recorded, and a score to show for it. The rest of the issue is deliberately left for follow-ups.

What's here

Authors opt in at upload time. A new Auto-validation checkbox on the upload form asks for two things:

  • The flag the crackme prints when it's beaten, in a standardised CM1{...} format (printable ASCII, no spaces or braces, ≤56 chars).
  • A private source archive — source code, build scripts, whatever a reviewer needs to rebuild it. It is stored outside static/, is never published or linked, and is only reachable through the reviewer download route.

The flag is stored as a bcrypt hash, never in cleartext. A database leak of every crackme's flag would quietly retire the whole point system, so nobody — author, reviewer or admin — can read one back out of the site. That's what the source archive is for: the review page has a Check flag box, so a reviewer rebuilds the crackme, solves it, and tests the flag they derive against the hash. A crackme whose flag doesn't match is unsolvable for points and should be rejected rather than shipped broken. The tested flag is kept out of the reviewer operation log for the same reason.

Reviewers assign an official difficulty when approving. That number fixes what a solve of the crackme is worth (difficulty × 100), independently of the community difficulty rating, which keeps drifting as people rate it. Crackmes approved before this existed have no official difficulty and fall back to their rounded community rating.

Solvers submit the flag from the crackme page. A correct flag records a solve, awards the points and notifies the solver; a wrong one says so. Authors can't submit flags for their own crackmes, re-submitting a correct flag never awards twice, and the route is rate-limited to 20/hour per user so a badly chosen flag still can't be brute-forced.

Profiles show the score in a new panel, with a Solves tab listing what was solved, for how many points, and when.

Design notes

  • Solves are keyed by the user's immutable id, not their username, so a rename can't zero out a score.
  • The score is summed from solve records, not counted on the user document, so it can't drift out of step with the solves it represents — a deleted crackme takes its points with it.
  • Points are snapshotted onto each solve at the moment it's earned. The scoring rules are explicitly provisional; re-pricing future solves shouldn't silently rewrite everyone's history. Everything about the formula lives in app/services/points.py so a later change is one edit.
  • Deleting a crackme takes its solves and source archive with it; deleting a user takes their solves. Both now appear in the account-deletion preview.
  • The FAQ and crackme submission rules explain the format, the opt-in and, plainly, that scoring is new and the numbers may change. The crackme page says the same next to the flag box.

Not here (follow-ups from #127)

First blood on old crackmes, points for writeups, author-funded bounties, decay by solve count, and six-month retirement. Retirement in particular is worth landing together with first blood and writeup points — on its own it would just make older crackmes worth nothing.

Testing

tests/test_solves.py (37 new tests) covers flag format and hashing, the scoring formula and its fallback, opt-in upload with and without a valid flag/source, the whole solve flow (correct, wrong, duplicate, own crackme, non-opted-in crackme, anonymous), the crackme and profile rendering, and the reviewer tools (check flag, source download, official difficulty on approval, source cleanup on reject, solve cascade on crackme and user deletion). Full suite: 275 passed.

🤖 Generated with Claude Code

xusheng6 and others added 2 commits August 2, 2026 18:34
Starts the point system from issue #127 with the parts everything else
depends on: a flag that can be submitted, a solve that gets recorded, and
a score to show for it.

Authors opt in when uploading. Opting in requires the flag their crackme
prints when beaten, plus a private zip of the source and build scripts.
The flag is stored as a bcrypt hash and never in cleartext - a leak of
every crackme's flag would quietly retire the whole system - so nobody,
author or reviewer, can read one back out. That is what the source
archive is for: a reviewer rebuilds the crackme, solves it, and tests the
flag they derive against the hash from the review page. A crackme whose
flag doesn't match is unsolvable for points and should be rejected. The
archive lives outside static/ and is only reachable through the reviewer
download route.

Reviewers assign an official difficulty when approving, which fixes what
a solve of that crackme is worth (difficulty x 100) independently of the
community rating, which keeps drifting. Crackmes approved before this
existed fall back to their rounded community rating.

Solves are keyed by the user's immutable id rather than their username,
so a rename can't zero out a score, and the score is summed from the
solve records rather than counted on the user document, so it can't drift
out of step with them. Points are snapshotted onto each solve at the
moment it is earned: the scoring rules are explicitly provisional (issue
#127 still has first blood on old crackmes, writeup points, bounties and
decay to settle), and re-pricing future solves shouldn't silently rewrite
everyone's history.

Deleting a crackme takes its solves and source archive with it, and
deleting a user takes their solves; both show up in the deletion preview.

Follow-ups from the issue, deliberately not here: first blood on old
crackmes, writeup points, bounties, decay, and six-month retirement.

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

The flag is now stored as-is instead of hashed. Reviewers need to read it:
confirming a crackme is really solvable, and fixing a flag the author
mistyped, both need the actual value, and a hash left a wrong flag
undetectable until users started failing on it. The review page prints
the flag directly, which retires the check-flag tool the hash needed.
Cleartext means the flag must not leave the reviewer tool, so it is
passed to that template and nowhere else; a test asserts the crackme
page, listings, profiles, search and RSS never render it, and neither
the operation log (mirrored to Discord) nor the author's notification
quotes it.

The reviewer crackme editor now covers everything, rather than
difficulty being settable only while approving a submission - a typo
there used to be unfixable once the crackme was live. It edits the flag
(setting or clearing one turns auto-validation on or off), the official
difficulty, the private source archive, and the metadata, labels and
binary it already handled; it works on pending crackmes as well as
approved ones, and the review page links to it. Clearing a flag leaves
existing solves and their points alone - they were earned fairly.

Its language/arch/platform option lists now come from
app/services/crackme_fields.py like every other form, instead of a
hardcoded copy that could drift from the upload form.

Renaming a crackme is deliberately still not here: PR #179 delivers that
across the whole site, including this page, and duplicating it would
just collide.

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

xusheng6 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Pushed both changes.

Flags are now stored in cleartext. The review page prints the flag directly, which retires the check-flag tool the hash needed — reviewers read it, build the crackme, and confirm it's the flag it actually prints. It also means a mistyped flag can be corrected instead of only being detectable once users start failing on it.

Since it's cleartext, it must not leave the reviewer tool: it's passed to that template and nowhere else, and a test asserts the crackme page, listings, profiles, search and RSS never render it. The reviewer operation log (which is mirrored to a Discord channel) and the author's change notification both report that the flag changed without quoting it.

The reviewer editor now covers every field, instead of difficulty being settable only during approval — where a typo was unfixable once the crackme went live. /review/editcrackme now edits:

  • the flag — setting one on a crackme that had none turns auto-validation on; clearing it turns it off. Existing solves keep the points they earned either way.
  • the official difficulty, clearable back to the community rating
  • the private source archive (download the current one, upload a replacement)
  • plus the description, language, arch, platform, labels and binary it already handled

It works on pending crackmes as well as approved ones, and the review page links to it. Its language/arch/platform lists now come from app/services/crackme_fields.py like every other form, rather than a hardcoded copy that could drift from the upload form.

One thing deliberately left out: renaming. #179 already delivers crackme renaming across the whole site (including this page, with the cascade to the denormalized copies on comments, writeups and label requests), so building it again here would just collide with it. Happy to fold it in if you'd rather this PR be self-contained.

Suite: 282 passed, 8 new tests covering the editor and flag confidentiality.

Review feedback on the upload experience and the flag format.

A rejected upload no longer costs the user everything they typed. The
form now posts in the background, so an error just prints a message and
leaves the page - including the files they picked, which no server-side
re-render can restore - exactly as it was. Without JS the form posts
normally and the server re-renders it with the submitted values filled
back in, files excepted. A successful upload became a post/redirect/get
to /upload/crackme/submitted, so refreshing the confirmation can't
re-post it.

The flag format is now CMO{...} rather than CM1{...}.

Auto-validation leads with "Optional. This crackme has a unique flag and
can be auto-validated." and is ticked by default, and the labels block
moved to the end of the form - it is long enough that having it in the
middle made everything after it hard to find.

The score panel on a profile drops the (?) marker; the explanation is
still there as a tooltip on the label.

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