Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 10 additions & 24 deletions .github/workflows/handoff.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand All @@ -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(', '));
Expand Down Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand Down
Loading