Skip to content

fix(kubernetes): Do not store container command lines in the serialized launcher data - #415

Open
yuechao-qin wants to merge 2 commits into
masterfrom
ycq/remove-command-lines-from-serialized-launcher-data
Open

yuechao-qin wants to merge 2 commits into
masterfrom
ycq/remove-command-lines-from-serialized-launcher-data

Conversation

@yuechao-qin

@yuechao-qin yuechao-qin commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A container that consumes a secret as a value receives that value on its command line. The Kubernetes launchers store the launched object verbatim in ContainerExecution.launcher_data — at launch and again on every poll — so the secret lands in the database in plaintext and is served back to any authenticated caller in the execution API's debug_info. Deleting or rotating the secret does not scrub the stored copy.

Before / after

// GET /api/execution_nodes/{id}/container_execution_state  ->  .debug_info ... .containers[0]
// before
{ "name": "main", "image": "alpine", "command": ["python3", "-c", "..."], "args": ["--token", "ghp_realSecretValue"] }
// after
{ "name": "main", "image": "alpine" }
Surface Before After
ContainerExecution.launcher_data (database) holds command and args in plaintext both keys absent
debug_info in GET /api/execution_nodes/{id}/container_execution_state returns them verbatim absent — the API returns the stored column, there is no second source
the Pod or Job sent to the cluster real command line unchanged, so the container still starts normally

Fix

Remove command and args from the serialized dict. _serialize_kubernetes_object_to_compact_dict is the single seam every stored copy passes through, so one call covers all of them:

Stored copy Produced at
debug_pod LaunchedKubernetesPod.to_dict
debug_job.spec.template LaunchedKubernetesJob.to_dict
debug_pods.<name>, one per Pod the same to_dict, once per Pod

15 added lines, nothing removed: one call plus a recursive key removal next to the existing _remove_keys_with_none_values.

Notes

  • The removal is not selective. Every command line goes — the main container, init containers, sidecars, and probe or lifecycle exec.command. Selectivity is what made fix(kubernetes): Do not store secret values in the serialized launcher data #395 large (an annotation carrying redacted positions, written at launch and read back during serialization); this is the alternative suggested in review there, and supersedes it.
  • debug_info no longer shows what a container ran, for every execution rather than only those consuming a secret. That is the cost of the patch being 15 lines.
  • Rows written before this change still hold plaintext. A one-off pass over launcher_data is still owed, and is independent of this PR.
  • Only the Kubernetes launchers are touched. The local Docker launcher stores a docker inspect dict with the same exposure and is unaffected here.

Tests

tests/launchers/kubernetes/test_kubernetes_command_line_removal.py — six tests, no cluster needed; they build a V1Pod/V1Job and go through both the serializer and the two launched-container to_dict methods.

Test Asserts
test_serialized_pod_has_no_command_line keys gone from spec.containers[0], image kept, secret absent from the whole dict
test_serialized_job_has_no_command_line_on_its_pod_template same at spec.template.spec.containers[0]
test_serialized_pod_has_no_command_line_anywhere an init container, a sidecar and a readinessProbe.exec.command are all stripped
test_launched_container_to_dict_has_no_command_line the Pod launcher's whole stored payload — kubernetes.debug_pod
test_launched_job_to_dict_has_no_command_line_in_any_copy the Job launcher's whole stored payload — kubernetes_job.debug_job.spec.template and every entry of debug_pods
test_launched_pod_keeps_its_command_line the input object is not mutated — the cluster still gets the real command line

Mutation-checked rather than asserted: disabling the call, removing either recursion branch, or narrowing the walk to spec.containers each turns two or three of these red; restoring the code returns all four to green. Full suite: 577 passed.

_remove_keys_with_none_values(obj_dict)
# A container that consumes a secret as a value receives that value on its command line,
# so a stored command line can hold a secret in plaintext.
_remove_command_lines(obj_dict)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice idea to have the fix in a single place. But this makes the behavior of the function called _serialize_kubernetes_object_to_compact_dict surprising.
Let's move the _remove_command_lines call to the places where _serialize_kubernetes_object_to_compact_dict is called.
Alternatively, we can add another function like _serialize_kubernetes_object_to_compact_dict_and_censor_it that calls _serialize_kubernetes_object_to_compact_dict then _remove_command_lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants