From 7af7db10939386685d6d2bd406d84a2505ff1a2c Mon Sep 17 00:00:00 2001 From: hacky-camper Date: Mon, 2 Nov 2026 17:15:35 -0700 Subject: [PATCH 1/2] ci: send solvers to the claim page instead of posting a flag --- .github/workflows/handoff.yml | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/.github/workflows/handoff.yml b/.github/workflows/handoff.yml index 2ace69d..28eeef1 100644 --- a/.github/workflows/handoff.yml +++ b/.github/workflows/handoff.yml @@ -1,4 +1,5 @@ -# Checks "hand it over" PRs and comments a per-user flag. +# Checks "hand it over" PRs and gives feedback. It never issues or prints a flag: solvers claim +# the flag from the separate claim service (vars.CLAIM_URL), which re-verifies the solve itself. # # Security notes (read before editing): # - pull_request_target runs THIS file from the default branch, with secrets and a @@ -8,8 +9,8 @@ # interpolate ${{ github.event.pull_request.* }} into a shell `run:` step. # - Answers live in repo secrets, not in this file, so reading it spoils nothing. # -# Required repo secrets: HANDOFF_SALT, EXPECTED_PORT, EXPECTED_BREAKER -# Repo variable (not secret): SUBMIT_URL, the page where people submit the final flag +# Required repo secrets: EXPECTED_PORT, EXPECTED_BREAKER +# Repo variable (not secret): CLAIM_URL, the claim service page name: handoff check @@ -32,15 +33,13 @@ jobs: steps: - uses: actions/github-script@v8 env: - HANDOFF_SALT: ${{ secrets.HANDOFF_SALT }} EXPECTED_PORT: ${{ secrets.EXPECTED_PORT }} EXPECTED_BREAKER: ${{ secrets.EXPECTED_BREAKER }} - SUBMIT_URL: ${{ vars.SUBMIT_URL }} + CLAIM_URL: ${{ vars.CLAIM_URL }} with: script: | - const crypto = require('crypto'); - const { HANDOFF_SALT, EXPECTED_PORT, EXPECTED_BREAKER, SUBMIT_URL } = process.env; - const missing = Object.entries({ HANDOFF_SALT, EXPECTED_PORT, EXPECTED_BREAKER }) + const { EXPECTED_PORT, EXPECTED_BREAKER, CLAIM_URL } = process.env; + const missing = Object.entries({ EXPECTED_PORT, EXPECTED_BREAKER }) .filter(([, v]) => !v).map(([k]) => k); if (missing.length) { core.setFailed('Missing repo secrets: ' + missing.join(', ')); @@ -88,28 +87,15 @@ jobs: } else if (!namedBreaker) { message = "the port's right! last thing: who broke it? it's whoever made the commit that changed the port (`git log -p -- config.json` shows the author). put their git username in this PR's title or description (click the `ยทยทยท` at the top right of the description, then **Edit**). no need for a new PR."; } else { - const tag = crypto.createHmac('sha256', HANDOFF_SALT) - .update(login.toLowerCase()) - .digest('hex'); - const flag = 'hackcamp2026{found_' + tag + '}'; message = [ "the fort map loaded. the HackCamp flag was under the third cushion from the left the whole time. you found it.", "", "sleepy-dev is still asleep in the rubble. they will be extremely relieved.", "", - "the real flag (this is the one you submit):", + "**last step: claim the real flag at " + (CLAIM_URL || "the claim page linked on the HackCamp site") + "**", + "you'll need your github username (`" + login + "`), this PR's number (**#" + pr.number + "**), and the name of whoever broke it.", "", - "```", - flag, - "```", - "", - "this one's tied to your github username (@" + login + "), so it only works for you.", - "", - SUBMIT_URL - ? "**submit it here:** " + SUBMIT_URL + " (you'll need the flag and your github username, `" + login + "`)" - : "submit it on the HackCamp site with your github username, `" + login + "`.", - "", - "closing this PR so the repo stays broken for the next person, and hiding who broke it from the title and description so they have to dig it up themselves. that's not a rejection. you're done.", + "closing this PR so the repo stays broken for the next person, and hiding who broke it from the title and description so they have to dig it up themselves. that's not a rejection. you can still comment here, and the claim page will ask you to.", ].join('\n'); done = true; } From fa5e5cd3bebba6615e485079c2051db10a86ab0b Mon Sep 17 00:00:00 2001 From: hacky-camper Date: Mon, 2 Nov 2026 17:16:02 -0700 Subject: [PATCH 2/2] readme: real flag comes from the claim page --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6990e76..a27e9da 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ a tiny cozy app that keeps track of where everything in the fort is: cushions, b > - you'll see three kinds of `{...}` things: > - **markers** like `hc26{a_few_words}`: you're on the right track. don't submit them, just follow what they say. > - **fakes**: sleepy-dev had chatgpt dump a pile of obviously fake flags into the history. ignore them. -> - **the real flag** looks like `hackcamp2026{found_` followed by 64 letters and numbers `}`. you only get it at the very end, and it's the one thing you submit. +> - **the real flag** looks like `hackcamp2026{617_7H3_34M_C0MM17_` followed by 32 letters and numbers `}`. it's not in this repo anywhere: after your fix PR, the bot links you to a claim page that hands it out. it's the one thing you submit. > - stuck? the page you came from has a "stuck? skip ahead" section with exact steps. > - broke something? delete this folder and clone again. you lose nothing. @@ -54,7 +54,7 @@ you can't push to this repo directly. to propose a fix: ``` 4. go to your fork on GitHub. there'll be a banner offering to **Contribute / Open pull request**. click it, fill in the description, and create the pull request. -a bot checks every PR and replies within a minute or two. +a bot checks every PR and replies within a minute or two. when it says you're done, it links the claim page for the real flag. ## status