skip to content

A retro DNS sweep hits a C2 domain from a CI runner five months ago. What does that prove?

level: seniorimportance: should knowfreq 50%

answer

  1. separate the record from the story
  2. a name asked for, nothing more
  3. resolution is not connection
  4. no process, no session, no payload
  5. the runner is gone, the log is not

basics

~10 s

A resolver record proves only that a client asked for that name at that time. It does not prove a connection followed, which process asked, that data moved, or that the host was compromised.

solid answer

~50 s

The record supports exactly one claim: at 02:11 on that date, something behind the address recorded as `ci-runner-07` asked the resolver for that name, and the resolver answered. It does not name the process — a DNS query log carries no process attribution — so it could be the implant, a package manager resolving a mirror, or a security tool detonating a sample. It does not show a connection: resolution is not a session, and if the egress proxy only started logging in June there is nothing to corroborate one. It shows no bytes and no payload. The client identity is soft too, because ephemeral runners recycle addresses. So I would state it as a strong lead that pins a window, then pivot to what outlived the host: flow records, the build system's job and dependency logs, and the registry record of which update version that pipeline pulled.

code

text · 7 lines
text
2026-03-14T02:11:07Z  resolver=10.0.0.53  client=10.42.7.19 (ci-runner-07)
                      qname=telemetry-sync.example  qtype=A  rcode=NOERROR
2026-03-14T02:11:07Z  resolver=10.0.0.53  client=10.42.7.19 (ci-runner-07)
                      qname=telemetry-sync.example  qtype=A  rcode=NOERROR
...
# no proxy record covers 10.42.7.19 in this window - egress proxy logging began 2026-06-01
# runner 10.42.7.19 was destroyed at job end; no host artefacts remain

go deeper

for a junior

Be able to say what a DNS query log line actually records — a client address, a time, a name asked for, a response code — and to notice that no process name appears in it.

for a middle

Explain the gap between resolution and connection, and name the records that would close it: flow, proxy and host network telemetry, and what each of those adds and still cannot show.

for a senior

Show that you escalate hard but claim precisely, pivot to the surfaces that outlived an ephemeral host, and reconcile a recycled address to a real machine before naming one.

for a principal

Own how conclusions drawn from partial telemetry get worded outside the team — what a report to an executive, a customer or the vendor may assert, and what caveat is required to travel with it.

## Separate the record from the story The single most valuable habit in this domain is to state what an artefact *is*, then state the inference separately and at the strength the artefact supports. A retro hit is where that habit is tested hardest, because the sweep began with an artefact somebody already labelled malicious — so everything found by it arrives pre-loaded with a conclusion. ## What a DNS query log actually contains A resolver query log records, per request: a timestamp, the querying client's address, the queried name, the query type, and usually the response code. That is the whole of it. From it you may assert: - **a name was asked for**, at a time, by something behind an address. You may not assert, from this record alone: - **which process asked.** The resolver sees a UDP or TCP query from an address. There is no PID, no image path, no command line. On a build host the plausible candidates include a package manager, a container image pull, a browser, a security agent detonating a sample, a scanner, and an implant — and the log distinguishes none of them. - **that a connection followed.** Resolution and connection are separate steps. A process may resolve and never connect; it may resolve and be blocked at egress; another component may have resolved on its behalf. Proving a session needs a flow record, a proxy entry or host network telemetry. - **what moved.** No query log carries payload. Even a flow record, which does prove a session existed and how many bytes and packets crossed, carries no payload at all — it can show that bytes moved, never what they were. - **that the host was compromised.** That is a verdict assembled from several artefacts, not read off one. A response code of NOERROR tells you the name resolved. It does not tell you what the querying process did with the answer. ## The identity problem five months out On an engineering estate, the client address is a weak handle on a host. Self-hosted runners are ephemeral: a container or VM is created for a job and destroyed after it, and its address returns to the pool. Five months later that address may have belonged to a dozen different workloads. So resolving "which machine was this" is its own step — reconcile the address and timestamp against DHCP or IPAM leases, the runner scheduler's job records, or the orchestrator's assignment history. Until that is done, "ci-runner-07" is a label on a log line, not an identified host. ## The host is gone; the log is not This is the defining constraint of a retro finding, and it changes what investigation is even available. There is no memory image to take, no disk to acquire, no running process to inspect — the machine that produced the record was destroyed months ago as part of normal operation. You are reconstructing from surfaces that outlived it: - **flow records** for the window, which can show whether a session to the resolved address existed and roughly how much crossed it; - **the build system's own records** — the job log for that pipeline run, the base image it started from, dependency resolution output, artefact provenance; - **the package registry**, which knows exactly which version of the vendor's update that pipeline pulled and when; - **any peer host** that took the same update and still exists, which is where live forensics is actually possible. That last pivot is often the most productive move: the destroyed runner is a dead end, but the same trojanised update almost certainly landed somewhere still running. ## Building a defensible claim A well-formed conclusion from this hit reads roughly: *"Resolver logs show queries for a domain that the vendor attributes to the second stage of this campaign, originating from an address assigned to a CI runner between these dates. We have not yet corroborated an outbound session, and host artefacts for that runner no longer exist. Our current hypothesis is that the trojanised update version identified by the vendor was installed on this pipeline; the registry record showing which version was pulled would confirm or refute it."* Compare that with "the CI runner was beaconing to C2 five months ago" — which asserts a session, an implant, and continuity of behaviour that a single query record simply does not carry. The second sentence is what gets challenged, and correctly. ## Why the overstatement matters operationally Overstating a retro hit propagates. It drives a compromise declaration, which drives credential rotation across the build system, notifications, possibly a customer disclosure — all potentially correct, but you want them driven by evidence you can still defend a month later when someone asks how you knew. Understating is not the fix either: a resolved C2 domain from a build host in the campaign window is a serious lead and should be escalated immediately. The discipline is escalate hard, claim precisely.

  • The runner that produced the query no longer exists. What can you still get?
    Whatever outlived it. The build system's job log for that run, the base image it started from, dependency-resolution output and artefact provenance; the package registry's record of which version of the vendor update that pipeline pulled and when; flow records covering the window; and any peer host that took the same update and is still running, which is where live host forensics is actually possible. The destroyed runner is a dead end, the campaign is not.
  • Would a NetFlow record from the same window change your conclusion?
    It would strengthen it materially. A flow record carries the five-tuple, byte and packet counts and timestamps, so it can show that a session to the resolved address existed and roughly how much crossed it — moving the claim from "a name was resolved" to "bytes went somewhere". It still carries no payload, so it cannot show what was sent, and it still names no process. Stronger claim, same two limits.
  • How do you pin which host actually held that address five months ago?
    Reconcile the address and timestamp against DHCP or IPAM lease records, the runner scheduler's job assignment history, or the orchestrator's pod and node records for that window. On ephemeral infrastructure an address is recycled constantly, so until that reconciliation is done the hostname in the log is a label somebody printed, not an identified machine.

saying these in an interview costs you the question

  • Says the DNS log proves a connection to the C2 server
  • Reads a resolver record as naming the process that asked
  • Treats NOERROR as evidence that data was exchanged
  • Treats a five-month-old client address as a stable host identity
  • Declares the host compromised on the strength of one query record

context