GA7 Solver's Handbook

Tools in Data Science · GA7 · CI/CD, Security & Cloud

The Solver's Handbook

Nine of the ten questions, each reduced to the procedure that actually gets you the marks — the order the rules must be applied in, and the specific traps the hidden probes are built around.

What this is not: there are no answers here, and nothing personal — no service URLs, no repository links, no keys, no assigned tenant or host values. Those differ per student anyway. This is method only. The Street View geolocation question is deliberately left out; it has no transferable procedure.

Ground rules for the five services

Questions 1 through 5 are the same exercise wearing five costumes: JSON in, a decision out, graded by hidden probes you never see. Everything below applies to all of them, so get it right once.

Write the rules as a pure function first

One function, body in, dict out. No web framework, no imports beyond the standard library. The HTTP layer comes later and stays thin — that way you can test hundreds of cases in a second without a network.

The stated order is part of the spec

Where a question says "the first applicable reason", a payload with two faults has exactly one correct answer. Transcribe the checks in the order they are written and never reorder them for convenience.

Never raise

The graders send malformed bodies on purpose. An uncaught exception becomes a 500 and scores zero for that probe. Wrap everything and map failures to the question's own "invalid" code instead.

Be strict about booleans and numbers

Check x is True, not truthiness — the string "true" and the integer 1 are not booleans. In Python a boolean is an int, so "must be a number" checks need an explicit isinstance(x, bool) rejection.

Test the matrix offline

For each question write: one fully clean payload, one payload per violation in isolation, one payload combining faults, and a handful of malformed bodies. If a clean payload does not come back clean, nothing else matters.

A constant answer cannot pass

Every one of these questions says the probes are shuffled and include both good and bad payloads. Always-allow and always-block both score near zero. There is no shortcut around implementing the rules.

Q1

CI/CD container release gate

returns every applicable code 2 marks

Decide whether a CI run may promote a container image, and prove it with a real workflow. This is the one question with two graded halves: the live probes carry most of it, the public workflow evidence carries the rest.

  1. Collect, don't short-circuit. Unlike the other gates, this one wants all the violation codes for a payload. Build a list, append as you go, de-duplicate at the end, and promote only when the list is empty.

  2. Permissions are set equality, not a subset check. Compare the whole permissions object against the exact required scopes. An extra scope, a missing scope, or a right key with a wrong value are all the same single violation.

  3. The unsafe trigger is a fixed value. One specific trigger is never acceptable for a release gate; flag it whenever it appears rather than trying to infer intent from the event field. Don't over-fire on a merely mismatched event — that isn't the violation being tested.

  4. Three test conditions collapse into one code. Tests passing, the matrix completing, and fail-fast being off are separate booleans but a single violation. Any one of them failing emits it once.

  5. Pinning applies only to third parties. Actions under the first-party namespace may use a version tag. Everything else must match a full 40-character lowercase hex SHA — anchor the regex at both ends so a 39- or 41-character string fails.

  6. The image checks are five independent gates. Build style, runtime user, how the build secret was supplied, critical vulnerability count, and digest pinning each emit their own code and can fire together.

  7. Production adds two more, not one. The branch and event requirement is one code; the missing approval flag is a different code. A production payload can trip both at once.

  8. Then build the evidence. Public repository, a workflow whose name: matches the required string character for character, a step whose name: matches its required string character for character, triggered on a push to the default branch, and actually run green. Submit the workflow page URL — the one that lists runs — not the URL of an individual run.

Where people lose it

  • An uppercase SHA. The spec says lowercase hexadecimal. A regex that accepts A-F silently passes a payload that should fail.
  • Missing fields treated as passing. An absent vulnerability count or digest flag is a violation, not a default.
  • Submitting a run URL. The backend reads the workflow file and the branch status badge from the workflow page. A run URL fails that read even though the run is green.
  • A name that is close. Extra spacing or different capitalisation in the workflow or step name is a miss. Copy the strings, don't retype them.
Q2

LLM action firewall

first applicable reason 2 marks

A compromised model proposes a tool call; you decide whether the tool actually runs. The entire point is that the boundary is structural — schemas, scopes, approval, safe rendering — and never a judgement about the text.

top-level schema→ tool allowlist→ argument schema→ tenant scope→ email domain→ approval→ HTML safety
  1. Implement that order literally. Return on the first failure. A payload that is both an unknown tool and badly shaped has one correct answer, and it is the earlier check.

  2. Validate the envelope before the action. The provenance value, the approval boolean, and the action object with its tool string and args object are all part of the top-level shape. The optional untrusted-content field must be a string if it is present at all.

  3. Argument schemas are exact. Exactly the listed keys — an extra key is as invalid as a missing one. Enforce the stated length bounds and non-empty requirements, and check both ends of a range.

  4. Compare the tenant with equality against your own assigned value. Anything else is a scope failure, not a schema failure.

  5. Take the recipient domain from after the last @, lowercase it, and compare with ==. A subdomain of the allowed domain, a domain that merely ends with it, a trailing dot, surrounding whitespace, or a second @ must all be refused.

  6. Check approval after the domain, because that is the order the question gives, even though it feels backwards.

  7. For rendered HTML, block exactly four categories: an opening script, iframe, object or embed tag; an inline event-handler attribute; and a javascript URL. Match the event handler in attribute position so ordinary prose doesn't trip it, and make sure a plain paragraph, a normal link and a relative image still come back clean.

Where people lose it

  • Scanning the untrusted text. There is an explicit probe: a valid read-only action carrying an instruction to ignore prior rules must still be allowed. Any keyword list fails it. Do not implement one.
  • Substring matching the domain. The look-alike probes are designed to beat in and endswith.
  • Over-blocking HTML. Benign markup is part of the probe set. A regex for on…= that matches inside ordinary words will cost you the clean cases.
  • Letting provenance decide. Untrusted provenance alone is never a reason to block; only the structural checks are.
Q3

Terraform plan policy gate

first applicable reason 2 marks

One normalised resource change, reviewed before apply. Eight checks in a fixed sequence; the sequence is most of the difficulty.

  1. Type-validate first. The body and every nested object must have the shown value types before any policy question is asked. Get this wrong and payloads that should be rejected for shape come back rejected for the wrong reason.

  2. Match the workspace exactly, then move on. This sits ahead of the state and provider checks, so a payload with a wrong workspace and a bad backend answers with the workspace.

  3. State means both things: a backend drawn from the allowed set and locking switched on. Either one failing produces the same single reason.

  4. Pinning has exactly two acceptable shapes: a full three-part version, optionally with an equals sign, or a pessimistic constraint. Comparison operators, wildcards, and the word "latest" are unpinned — and so is a bare partial version with no operator at all.

  5. Labels are a presence-and-value check. All three assigned labels must be there with exact values. Extra labels beyond those three are not forbidden by the wording, so don't reject them.

  6. A secret is either null or a reference. A reference means the required scheme prefix plus something after it — the bare prefix with nothing following is still a failure. Anything else is a plaintext secret.

  7. Destructive deletes need approval, but only for the three stateful resource types listed. Deleting anything else does not require the flag.

  8. Force-destroy on a bucket is absolute. By the time a request reaches this check the workspace has already been validated as production, so no extra environment condition is needed.

Where people lose it

  • Precedence bugs. Build a test that plants two faults at once and assert you get the earlier reason. This is the single most common way to lose marks here.
  • Accepting a bare version. A two-part version with no operator is not a pin.
  • Rejecting extra labels. The requirement is "all three present with exact values", not "exactly these three".
  • Forgetting the approved-delete happy path. The probes include valid creates, valid updates and approved deletes; all three must be approved.
Q4

LLM output handling gate

first match wins 2 marks

Model output is untrusted input. Decide whether a string is safe to hand to a given sink. The hard parts are the decode pass and the hostname comparison — both are explicitly probed.

schema→ decoded-payload check→ channel rules on the original
  1. Schema includes a length bound. Body shape, channel membership, output type, and the maximum length are one combined check, evaluated on the original string.

  2. Decode exactly once, in the stated sequence: percent-escapes, then the specific HTML entities the question names — numeric and the five named ones, nothing more — then unicode escapes. Do not reach for a full entity table; the spec is deliberately narrower.

  3. The encoded verdict has two conditions. The decoded string must differ from the original and the decoded string must trip one of the channel rules. A percent-encoded but harmless string is clean, and that case is probed.

  4. Then run the channel rules against the original, not the decoded version. Each channel has its own ordered list; markdown and URL share theirs, HTML has two extra checks in front, and the SQL and shell channels have a single check each.

  5. Extract URLs per channel. Quoted source and link attribute values for HTML; the target inside the parentheses for markdown, images included, allowing for a trailing title; the whole trimmed string for the URL channel.

  6. Parse the hostname and compare with equality. Lowercase it, drop a trailing dot, and test against your two allowed hosts. Subdomains are not allowed, which rules out any suffix test.

  7. Treat protocol-relative references as absolute. A reference beginning with two slashes resolves against the page scheme and a browser will fetch it, so it goes through the same hostname check. Genuinely relative paths are fine.

  8. The dangerous-scheme test is two tests. A literal scheme appearing anywhere in the text, tolerating whitespace before the colon, and any extracted URL whose scheme isn't HTTP or HTTPS.

Where people lose it

  • Substring-matching the allowlist. Two probes are built for this: an allowed host in the credentials position before the @, and an allowed host inside a query string. Both must fail. Only the parsed hostname counts.
  • A host that merely looks allowed. The allowed name with an extra label on either side is a different host.
  • Running channel rules on the decoded string. The decode result decides one thing only — whether the reason is the encoded one. After that, the original text is what gets judged.
  • Word boundaries in the SQL check. Decide deliberately whether the keyword matches as a bare substring or a whole word, and write the test that documents your choice.
Q5

OSINT corroboration engine

verdict, confidence, sources 2 marks

Does the evidence actually support the claim, how confident does that make you, and which sources justify it. Every timestamp arrives in the request, which means the service must be perfectly reproducible.

  1. Never read the clock. No current-time call anywhere in the module. Freshness is always the request's own reference timestamp minus the observation, compared against the request's own window. Grep your own file afterwards to prove it.

  2. Validate the envelope into the "invalid" verdict. A non-object body, a non-string claim value, a missing or unparseable reference timestamp, a non-numeric window, or a non-array source list all land here — with the lowest confidence and an empty source list.

  3. Drop invalid sources entirely, before anything else. A source only counts if its identifier, origin, value and observation timestamp are all strings and its type is in the allowed set. An unrecognised type doesn't weaken the claim; it simply isn't there.

  4. Contradiction comes next, and needs three things at once: fresh, authoritative, and a value different from the claim. All such sources go into the returned list, sorted ascending — origin de-duplication does not apply here.

  5. For support, filter then collapse. Keep fresh sources whose value equals the claim, group them by origin, and keep one representative per origin: the lexicographically smallest identifier. Two or more representatives surviving is what "corroborated" means.

  6. Confidence is about type diversity, not count. Representatives spanning two or more distinct types is the high case; all sharing one type is the middle case.

  7. Everything else is unverified with the lowest confidence and an empty list: no sources, a single independent source, several mirrors of one origin, or agreement that is entirely stale.

Where people lose it

  • Letting a stale authoritative disagreement contradict. The question calls this out explicitly. Staleness is checked before authority matters.
  • Counting a non-authoritative disagreement. It neither contradicts nor supports — it is simply not counted, in either direction.
  • Mirrors counted twice. Independence is defined purely by the origin field. Two records from one origin are one voice.
  • A boolean passing as a number. Test that a True staleness window is rejected as invalid.
  • Boundary freshness. Exactly at the window is fresh; one second past it is not. Write both tests.
Q7

Search operators: hit the target set exactly

one query string 1 mark

One query, a token budget, and a set of target documents that must match exactly — every target, nothing else. Do not try to reason this out by eye; the index contains decoys that differ from a target in exactly one field.

  1. Paste the index into a script. Every document is given to you as structured data. Getting it into a list of records is the whole setup.

  2. Implement each operator exactly as the question defines it, not as the real search engine behaves. In particular: the site operator matches the host itself or any subdomain, which means an equality test or a dotted-suffix test — and the date operators are strict on both ends.

  3. Diff the targets against the rest. Group all documents by each field in turn and look at which values the targets share and the non-targets don't. This tells you which dimensions are even worth spending tokens on.

  4. Find the decoys deliberately. For each candidate token, list the non-targets it fails to exclude. Those near-misses are what your remaining tokens have to kill, and they point straight at which field still needs constraining.

  5. Then brute force. Build a pool of candidate tokens — every plausible operator value, plus the negation of each, plus the date thresholds across the full year range — and search combinations up to the token limit. Prefer the shortest query that works.

  6. Assert exact set equality before you submit: the matched set equals the target set, with zero extras and zero missing. Print both differences so a near-miss is obvious rather than silent.

Where people lose it

  • A hyphenated sibling domain. A host that shares a prefix with your domain but sits on a different registrable name is not a subdomain of it. Test on the dotted boundary, never on a bare substring.
  • Off-by-one on the year. The date operators exclude the year you name. Check the boundary document explicitly.
  • Blowing the token budget. A grouped alternation counts as one token, and a negation is part of the token it attaches to — use both to stay under the limit.
  • Assuming a phrase behaves differently from a bare term. Match it the way the question defines matching, which is case-insensitive and substring-based across the same fields.
Q8

WAF rule order

count · id · count 1 mark

Rules top to bottom, first terminal match decides. Three sub-answers, all of which must be right. This is a simulator problem, not a reading problem.

  1. Encode the rules as an ordered list of action-and-predicate pairs, in the given order, transcribed one at a time. Most wrong answers here are transcription errors, not logic errors.

  2. Model terminality correctly. Block, challenge and skip stop evaluation; the logging action does not and evaluation continues past it. Falling off the end means the request is allowed.

  3. Define "reaches the origin" once: the skip outcome or the fell-through-allowed outcome. A challenge does not reach the origin even though it isn't a block.

  4. Map every request field to the rule field it is tested against, including the header lookup, and keep the comparison operators honest — the inequality operators are strict, membership tests are exact, and the substring and prefix operators are not the same thing.

  5. Answer part one by running all requests and counting.

  6. Answer part two by diffing, not by inspection. Build the reordered rule list, run every request through both, and print the list of requests whose outcome changed. The question promises exactly one — if you get zero or several, you have a transcription bug, so go back to step one.

  7. Answer part three by rewriting only the one named rule in the original order and re-running the count.

  8. Print which rule decided each request. It costs one line and it is how you sanity-check the whole simulator.

Where people lose it

  • Negation scope. The negation binds to the expression that follows it, which inside a parenthesised conjunction is not the same as negating the whole clause.
  • Ignoring the duplicated rules. The list repeats similar rules further down with different thresholds. Many are unreachable — that is deliberate, and the reordering question turns on it.
  • Strict versus inclusive comparisons. A score exactly equal to a threshold does not match a strict comparison. Several requests sit exactly on the boundary.
  • An empty header treated as absent. Compare it as the value it is; an empty string is not the expected origin.
Q9

Media forensics

token · digits · count 1 mark

Three artefacts, three extractions, no model needed anywhere. All three parts must be right, so verify each one independently rather than trusting a single run.

  1. Image — read the bit plane the question names. One specific colour channel, the lowest bit, one bit per pixel, in reading order across then down. Group into bytes most-significant bit first and stop at the first null byte. If you get mojibake, the usual culprits are the wrong channel or column-major traversal.

  2. Audio — segment on the silence, then measure. Find the gaps rather than assuming exact sample offsets, but cross-check the segments you find against the nominal tone and gap durations. If the segment count is off, your silence threshold is wrong.

  3. Take the dominant frequency per segment with an FFT or Goertzel, then snap to the nearest table entry. Print the measured peak for every tone — each one should land within a few hertz of a table value. If one is halfway between two entries, your window is crossing a boundary.

  4. Frames — split the sheet on the stated grid and remember the layout is playback order, reading across rows.

  5. Take a robust base colour per tile. The noise is bounded and symmetric, so a per-channel median is the safe estimator. Compare consecutive tiles with a tolerance comfortably above the noise bound and well below a real scene change.

  6. Count transitions, not scenes. The answer is the number of adjacent pairs that differ — for a sheet of n frames there are n − 1 pairs to test.

  7. Print your evidence: the recovered string, every measured frequency, every base colour and every pairwise delta. If the deltas cluster cleanly into "tiny" and "large" with nothing in between, your count is trustworthy.

Where people lose it

  • Case. The hex digits are asked for in lower case; the recovered string keeps whatever case it was hidden with.
  • Counting scenes instead of changes. Four scenes across a sequence means three changes.
  • A mean instead of a median. With bounded noise both usually work, but the median is the one that cannot be dragged by an outlier row.
  • Trusting one part. All three must be right for the mark, so an easy part being obviously correct tells you nothing about the other two.
Q10

Audit a GitHub Actions workflow

codes · job id 1 mark

Report exactly which findings are present — no more, no fewer. The file is written so that some alarming patterns are genuinely safe and some innocuous ones are not, and listing a finding that isn't there is scored as wrong.

  1. Work finding by finding, not line by line. Take each finding code, apply its stated rule to the whole file, and record present or absent with the line that proves it.

  2. For the privileged-context finding, look for the combination: a trigger that runs with the base repository's permissions together with a checkout of the pull request's own head. Either one alone is not the finding; the pair is.

  3. For pinning, split the actions by namespace first. First-party actions may be referenced by tag and are not findings. For each third-party action, count the reference: it must be exactly forty hexadecimal characters. Check every one — the file mixes pinned and unpinned third-party actions on purpose.

  4. For over-broad permissions, compute the union of what the jobs need and compare it against the workflow-level block. If the workflow level grants nothing beyond that union, the finding is absent no matter how the block looks at a glance.

  5. For the secret-in-log finding, ask only one question: can the value reach step output. A secret interpolated into an echoed string can. A secret handed to a script through the environment block, where nothing prints it, cannot.

  6. For the approval finding, look for an environment attached to the deploying job. Its presence is what carries the protection; a branch condition is not a substitute and its absence is not this finding.

  7. For injection, distinguish the two shapes. Event-controlled data interpolated straight into a shell command is the finding. The same value passed through the environment block and referenced as a shell variable is the documented safe form.

  8. Then name the abusable job. It is the one where an outside contributor's own code executes while the workflow holds elevated privileges — trace which job checks out and runs code that the contributor controls.

Where people lose it

  • Padding the answer. Adding a plausible-looking code that isn't actually present is scored wrong. Only list what you can point at a line for.
  • Counting the SHA by eye. Count the characters properly — a reference that looks like a commit hash may be the wrong length.
  • Assuming a deploy job with a branch check is gated. A condition on the ref restricts when it runs, not who approves it.
  • Flagging a secret merely because it appears. The rule is about reaching the log, not about being referenced.

Before you save

The deployed questions are re-checked by the server on every save, so "it worked when I built it" is not a state you can rely on.

Call it from outside your machine

Test the public URL, not localhost. A service that answers locally and 404s publicly is the most common failure, usually because the host rewrote the path before your code saw it.

Turn off deployment protection

Several hosts put new deployments behind an auth wall by default. The grader is anonymous — if a plain request gets redirected to a login page, you score zero however correct the logic is.

Re-run the clean payloads last

It is easy to tighten a rule until the good cases start failing. The final check before saving is that each endpoint still approves a fully valid request.

Keep it up until the deadline

Free tiers sleep, suspend, or exhaust their monthly allowance. Pick something that stays reachable, and check it again on the last day rather than assuming.