Hardening ig build against command injection
Inspektor Gadget packages eBPF gadgets as OCI images, then builds and runs them in a path that touches shell, Makefile logic, and privileged execution. That gives build-time input more reach than it should have. If a variable can cross into a shell command, it is no longer just metadata.
Where the build path crosses from variables into shell
The weak point was the build flow that interpolated Makefile variables into commands. Once attacker-controlled values land in shell expansion, the build stops being a build and starts being a command runner with extra steps.
That matters most in CI and release jobs. Those systems tend to trust tags, paths, and version strings more than they should. If ig build accepts values that end up inside shell syntax, a malicious branch name or build parameter can turn into code execution during packaging. That is a tidy way to ruin a pipeline.
The fix is to treat build inputs as data, not snippets of shell. Any path, tag, or version string should be passed as a value into controlled execution, not interpolated into a command line that the shell gets to interpret.
Replacing Makefile interpolation with Go-controlled execution
Moving the build logic into Go changes where command execution happens. The program can pass arguments directly, quote them properly, and refuse odd values before anything reaches a shell. That removes the broadest part of the attack surface without taking away build flexibility.
Pin down the inputs that used to reach the shell
The first job is to identify every field that can flow into build commands. Tag names, image names, output paths, version strings, and flags all count. If a value can alter the command structure, it needs to be handled as an argument, not as part of a formatted string.
This is boring work. It also stops the sort of bug that only shows up when someone tries a slightly nasty input and the pipeline obligingly executes it.
Keep build-time flexibility without handing over command execution
A fixed build does not have to be a rigid build. The code can still accept parameters, select output locations, and vary image tags. The boundary is simple: input can choose behaviour, but it cannot rewrite the command that performs it.
That usually means direct exec calls, explicit argument arrays, and no shell unless there is no other sane choice. When a shell is unavoidable, the input set should be tiny and heavily constrained. Wide-open interpolation is how a packaging step becomes an attack surface.
What to check in CI before you trust the fixed release
A patched tag is not enough on its own. CI has to prove that hostile values stay inert and that the build still behaves under the same version you plan to ship.
Run the build with hostile values and inspect the result
Feed ig build inputs that would be unpleasant if they reached a shell. Use semicolons, backticks, command substitutions, spaces, and path separators in places where the build accepts free-form values. The test should confirm that these characters stay inside arguments and do not change execution.
Then inspect the generated artefacts and logs. The expected outcome is dull: the build either rejects bad input or handles it as plain text. Any sign that the shell parsed part of it means the fix is incomplete.
Lock dependency versions and verify the patched tag
A fixed build path does not help much if CI pulls shifting dependencies around it. Pin action versions, pin tool versions, and avoid floating references that drift without warning. Release verification should point at the patched tag, not a local rebuild with a convenient checksum.
For a project that loads privileged eBPF code on Linux hosts and Kubernetes nodes, the packaging step deserves the same care as the runtime. OCI images are part of the delivery path, so the release process is part of the trust boundary.



