Repository navigation
fix(process): reject a pid that would signal a process group - #593
Merged
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #593 +/- ##
=======================================
Coverage 99.95% 99.95%
=======================================
Files 501 501
Lines 24099 24120 +21
=======================================
+ Hits 24089 24110 +21
Misses 10 10
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
The process endpoints declare required,min=1 on their pid path parameter and nothing enforced it. Both handlers validated the hostname and passed request.Pid straight through, so a value below 1 reached the provider, which validated the signal name and then called syscall.Kill with the pid as given. min=1 is the rule that matters here, not required. kill(2) overloads the sign: 0 is every process in the caller's process group, -1 is every process the caller may signal, and below -1 is a whole process group. required only rejects 0, so without min=1 the sign turns one signal into a host-wide one, and the agent usually runs as root. Validated in both places, because the provider contract says the request path does not discharge the provider's own checks and the provider is the frame that calls kill(2). The provider's check runs before the signal name is looked up, so a bad pid is refused whatever signal came with it. The 400 now names the value and the bound. validation.Var has no field name to report and min carries no hint, so the bare message read "Field validation for '' failed on the 'min' tag", which tells a caller nothing about what to send instead. Tests at both levels. The provider cases set no mock expectation, so the controller fails the test if Kill is reached at all, which is the assertion that matters rather than the error string. Verified against a running controller and agent: before, pid -5 returned a job id and crossed NATS; now all of 0, -1, -5 and -42 are refused at the API with no job created. Found by checking, one call site at a time, whether min was load-bearing where an audit had reported it as untestable. On 146 plain string fields required does mask it. On this one it was the only guard, and it was not wired up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FuKUsHFG1EqZXamffh9M2c
The guard was added to both Signal and Get, and only Signal's was tested, so `return nil, err` in Get was the one statement this change left uncovered. Codecov caught it: misses 10 -> 11. Two cases, shaped like Signal's. setupMock sets no expectation, so the controller fails the test if NewProcess is reached at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FuKUsHFG1EqZXamffh9M2c
retr0h
force-pushed
the
fix/process-pid-validation
branch
from
October 7, 2026 17:44
6cd1e67 to
30aa04f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The process endpoints declare
required,min=1on theirpidpath parameter. Nothing enforced it.What was wrong
Both handlers validated the hostname and then took the pid as given:
The provider validated the signal name and called
syscall.Kill(pid, sig)with the pid unchecked.allowedSignalsincludesKILL.min=1is the load-bearing rule here, notrequired.kill(2)overloads the sign of its first argument:> 00-1< -1-pidrequiredon an int only rejects0. Everything below it passesrequiredand is caught bymin=1alone. The agent generally runs as root on a managed host, so the sign is the difference between stopping one process and stopping the host. A caller holdingprocess:writeto stop a runaway process had the second available to them, and0is an easy accident rather than only an attack.The fix
Validated in both layers, because https://osapi-io.github.io/specs/components/osapi/providers says the request path does not discharge the provider's own checks, and the provider is the frame that actually calls
kill(2):validatePidin the API package, mirroring the declared tag, called inprocess_get.goandprocess_signal_post.go.SignalandGet. It runs before the signal name is looked up, so a bad pid is refused whatever signal arrives with it.DarwinandLinuxareErrUnsupportedstubs and need nothing.The 400 also now says what to do.
validation.Varhas no field name to report andmincarries no hint, so the bare message was:It now leads with
pid -1 must be greater than zeroand keeps the tag's message after it, so a test can still assert which rule rejected the request.Tests
Four provider cases covering
0,-1, a process group, and a bad pid with a bad signal name. Each sets no mock expectation, so gomock fails the test ifKillis reached at all. That is the assertion that matters; checking the error string alone would pass even if the signal had been sent first.Five HTTP cases across both endpoints asserting the body names
required(for0) ormin(for negatives).Verified against a running controller and agent
Before:
It became a job and crossed NATS. After,
0,-1,-5and-42are all refused at the API with no job created.How it was found
By going back through the
mindeclarations one call site at a time to check whether the rule was load-bearing, after an audit had reported it as untestable and I had wrongly concluded the declarations were redundant. On 146 plain string fieldsrequiredgenuinely does maskmin. Seven are not strings, and on this oneminwas the only guard and it was not wired up.just testpasses, coverage 100%.🤖 Generated with Claude Code
https://claude.ai/code/session_01FuKUsHFG1EqZXamffh9M2c