skip to content

A signed release jar calls an endpoint that exists in no source file - how do you investigate?

level: seniorimportance: should knowfreq 46%

answer

  1. the signature is not exculpatory
  2. start from the bytes, not the repo
  3. rebuild the tag elsewhere and compare
  4. list everything with write access to output
  5. assume every build secret is burned

basics

~20 s

Investigate from the artifact backwards, not the repository forwards. Preserve the published bytes, rebuild the tagged source somewhere you control and compare, then enumerate every build step with write access to the output. Treat the builder and its secrets as compromised.

solid answer

~50 s

First, the signature is not exculpatory: it proves the release key signed those exact bytes and that nobody altered them afterwards, and signing happens *after* the build, so a compromised build produces a validly signed backdoored artifact. So I work from the artifact. Preserve the published jar and its digest as evidence. Build the tagged source myself on a machine unrelated to the pipeline and compare against what shipped - even a coarse comparison of classes and call sites localises the injection to a stage. Then enumerate everything the release build could execute with write access to the output: build scripts, plugins and their transitive dependencies, annotation processors and bytecode rewriters, packaging and shading steps, the runner image. Diff those inputs against the last release I have reason to trust. Meanwhile treat the builder as owned: every credential it could read is burned, and rebuilding from the same machinery is not a fix.

go deeper

for a junior

Know the one thing that decides this scenario: a signature says who vouched for the bytes and that they are unchanged, never that they came from the source you reviewed.

for a middle

Be able to name the build stages with write access to the output and explain which of them would produce a call site in generated, rewritten or bundled code rather than in application classes.

for a senior

Show a sequenced investigation - preserve the artifact, rebuild the tag elsewhere and compare to localise the stage, diff build inputs against the last trusted release - alongside containment that assumes the builder and its secrets are owned.

for a principal

Own the downstream duty and the honest disclosure: which release range consumers must treat as suspect, what your organisation can actually evidence about how any release was produced, and what you will change so the next answer is not "we cannot demonstrate that".

## Frame the problem correctly before touching anything The scenario: a payments SDK published as a jar, consumed by many downstream services that handle card data. Someone finds an outbound call in the shipped bytecode that appears in no source file, no reviewed diff and no dependency's documented behaviour. The release verifies against the publisher's signature. Two framing errors will wreck the investigation if you make them. **"It is signed, so the artifact is fine."** A signature carries two claims: a particular key vouched for these bytes, and the bytes have not changed since. It carries no claim that the bytes correspond to any source revision. Signing is the last step of the build, so injection anywhere earlier is signed along with everything else. A validly signed backdoored artifact is the *expected* output of a compromised builder, not a contradiction. **"It must be in the source somewhere."** Grepping the repository harder is the most common wasted day. If this is build-time injection, the repository is genuinely clean, and every hour spent there is an hour the builder stays live. ## Step one: preserve and pin the evidence Pull the published artifact from wherever consumers get it, record its digest, and keep it. Record which released versions are affected as far back as you can check - a dormant payload may have shipped several releases before anyone noticed the call. Do not delete the release yet; you will need it, and consumers already have copies. ## Step two: establish the delta Build the tagged source yourself, on a machine that is not part of the pipeline, and compare the result with what shipped. You are not trying to produce an identical file; you are trying to *localise*. Which classes exist in the published jar that your build does not produce? Which methods differ? Is the call site in application code, in a bundled dependency, or in generated or repackaged code? The answer tells you which stage of the build introduced it - compilation, post-compile rewriting, dependency bundling, or packaging. This is also the moment to check the boring explanation: a dependency you legitimately bundle may be the thing making the call, in which case this is a compromised-component problem rather than a compromised-builder one, and the response is different. ## Step three: enumerate what the build was allowed to execute List every actor with write access to the output between source and signature: | Where | What to check | | --- | --- | | Build configuration | scripts, custom tasks, anything that downloads and runs | | Build plugins | the plugins themselves *and their transitive dependencies* | | Post-compile steps | annotation processors, bytecode rewriters, shading, obfuscation | | Bundling | which dependency versions actually landed inside the jar | | Environment | the runner image, preinstalled toolchain, injected agents, env vars | | Publishing | the step that handles final bytes and holds the signing key | Then diff that list against the last release you have independent reason to trust. Injection almost always correlates with something that appeared or moved: a plugin version that floated, a base image that was rebuilt, a build-only dependency that gained a new transitive edge. ## Step four: contain on the assumption the builder is owned If code was written into the artifact during the build, something had execution on the builder. Therefore: - **Every secret the build could read is burned.** Registry credentials, cloud tokens, and the signing key material if it lived anywhere the build could reach. Rotate, do not "monitor". - **Do not rebuild and republish from the same machinery.** A fix built by the compromised builder is a compromised fix, and republishing also destroys the state you wanted to examine. - **Consumers matter more than you do here.** You are the upstream; downstream services have the artifact embedded and running next to card data. Their exposure window is your release history, not your incident timeline. ## What you can and cannot conclude, stated honestly An auditor handed the git history of a build they did not run can conclude what the *intended* inputs were and who approved them. They cannot conclude anything about the released binary. To make a claim about the binary you need evidence about the *execution* that produced it - what ran, on what, with what inputs - which is a different kind of claim from what is inside the artifact or who signed it. If your organisation cannot produce that evidence today, the correct answer to "was release 4.2.1 built from tag v4.2.1?" is "we cannot demonstrate that", and saying so is more valuable than a confident guess.

  • What does the valid signature actually tell you in this scenario?
    That the release key signed exactly these bytes and that nobody has altered them since. That is all. It makes no statement about which source produced them, because signing is the final build step and therefore signs whatever the build emitted. Treating signature verification as evidence of correspondence to source is the specific mistake this scenario is built to expose.
  • Which credentials do you consider burned?
    Everything the build process could read: registry and package-repository credentials, cloud and deployment tokens, any API keys mounted into the build, and signing key material if it was accessible from the build rather than held in isolation. Rotation is not optional here - execution on the builder means read access to whatever the builder was given.
  • Why not immediately rebuild and republish a clean version?
    Because a rebuild on the same machinery is produced by the same suspect environment, so the fix may carry the payload. Republishing also overwrites or obscures the state you need to determine which releases were affected. Build the remediation somewhere provably separate, and only after you know which stage introduced the code.
  • How far back does the investigation reach?
    As far back as the last release whose contents you can corroborate independently. Payloads are commonly dormant, so the version where someone noticed the call is rarely the first version that contained it. Compare each prior release against a fresh build of its tag until the artifacts stop diverging.

saying these in an interview costs you the question

  • Concludes the artifact is safe because the signature verifies
  • Searches only application source for the malicious call
  • Rebuilds and republishes from the same builder immediately
  • Ignores build-only dependencies and plugin transitive graphs
  • Assumes the newest release is the first affected one
  • Leaves build credentials in place because nothing else looks abused

context