Skip to content

Commit e085bbf

Browse files
authored
Merge branch 'main' into worktree-rename-api-endpoint
2 parents 338fb9c + fa5d3d2 commit e085bbf

2 files changed

Lines changed: 80 additions & 1 deletion

File tree

internal/detector/configaudit/pipconfig.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ func (d *PipConfigDetector) detectPip(ctx context.Context) (string, []string, st
167167
if err != nil {
168168
continue
169169
}
170+
if d.exec.IsAppleCLTStub(ctx, path) {
171+
// Skip Apple's /usr/bin/ shims on Macs without Command Line Tools;
172+
// invoking --version against them pops a GUI install prompt.
173+
continue
174+
}
170175
args := append([]string(nil), cand.args...)
171176
args = append(args, "--version")
172177
stdout, _, exit, err := d.exec.RunWithTimeout(ctx, 5*time.Second, cand.binary, args...)
@@ -190,7 +195,15 @@ func (d *PipConfigDetector) detectPip(ctx context.Context) (string, []string, st
190195
// was available at all.
191196
func (d *PipConfigDetector) runPip(ctx context.Context, timeout time.Duration, pipArgs ...string) (string, int, bool) {
192197
for _, cand := range pipInvocationsToTry {
193-
if _, err := d.exec.LookPath(cand.binary); err != nil {
198+
path, err := d.exec.LookPath(cand.binary)
199+
if err != nil {
200+
continue
201+
}
202+
if d.exec.IsAppleCLTStub(ctx, path) {
203+
// Same guard as detectPip: don't invoke Apple's /usr/bin/ shims
204+
// on Macs without Command Line Tools — they pop a GUI install
205+
// prompt. detectPip should have already returned ok=false in
206+
// this case, but guard here too so a future caller can't bypass.
194207
continue
195208
}
196209
args := append([]string(nil), cand.args...)

internal/detector/configaudit/pipconfig_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,69 @@ func TestParseEffective_RedactsEmbeddedCreds(t *testing.T) {
322322
t.Errorf("effective.config should have redacted to user:****@host form, got: %q", got)
323323
}
324324
}
325+
326+
// On a Mac without Xcode Command Line Tools, /usr/bin/pip3 and /usr/bin/python3
327+
// are Apple shims that pop a GUI install prompt the moment they're invoked.
328+
// PipConfigDetector must skip them — otherwise rolling out the agent to Jamf
329+
// endpoints that don't deploy CLT triggers a Developer Tools dialog for every
330+
// user on first scan.
331+
//
332+
// We stub pip3/python3 to return SUCCESS so the assertion catches the bug: if
333+
// the guard isn't applied, detectPip will invoke the shim, get a successful
334+
// response, and set Available=true. With the guard, the shim is never invoked
335+
// and Available stays false. A test that left the stubs unset would pass even
336+
// against the unfixed code, because the mock's "no command stub" error would
337+
// be swallowed as if pip just wasn't installed.
338+
func TestPipConfigDetector_SkipsAppleStubWithoutCLT(t *testing.T) {
339+
mock := executor.NewMock()
340+
mock.SetGOOS("darwin")
341+
mock.SetAppleCLTInstalled(false)
342+
mock.SetPath("pip3", "/usr/bin/pip3")
343+
mock.SetPath("python3", "/usr/bin/python3")
344+
// Stub both shims to *succeed* — if the guard fails, detectPip will
345+
// consume these and mistakenly set Available=true.
346+
mock.SetCommand("pip 24.0 from /usr/bin (python 3.12)\n", "", 0, "pip3", "--version")
347+
mock.SetCommand("pip 24.0 from /usr/bin (python 3.12)\n", "", 0, "python3", "-m", "pip", "--version")
348+
349+
d := NewPipConfigDetector(mock)
350+
d.ownerLookup = fixedPipOwner()
351+
d.gitTracked = func(_ context.Context, _ string) bool { return false }
352+
d.inGitRepo = func(_ string) bool { return false }
353+
354+
audit := d.Detect(context.Background(), nil)
355+
if audit.Available {
356+
t.Errorf("expected Available=false when only Apple stubs are on PATH, got true (path=%q, invocation=%q) — the guard let the shim run", audit.Path, audit.Invocation)
357+
}
358+
if audit.Effective != nil {
359+
t.Errorf("expected Effective=nil when pip detection was skipped, got %+v", audit.Effective)
360+
}
361+
}
362+
363+
// When CLT is installed, /usr/bin/pip3 resolves to the real CLT-shipped pip
364+
// and must be invoked normally. Confirms the guard is darwin+CLT-absent only,
365+
// not a blanket /usr/bin/ skip.
366+
func TestPipConfigDetector_DetectsUsrBinWhenCLTInstalled(t *testing.T) {
367+
mock := executor.NewMock()
368+
mock.SetGOOS("darwin")
369+
mock.SetAppleCLTInstalled(true)
370+
mock.SetPath("pip3", "/usr/bin/pip3")
371+
mock.SetCommand("pip 24.0 from /usr/bin (python 3.12)\n", "", 0, "pip3", "--version")
372+
mock.SetCommand("", "", 0, "pip3", "config", "debug")
373+
mock.SetCommand("", "", 0, "pip3", "config", "list", "-v")
374+
375+
d := NewPipConfigDetector(mock)
376+
d.ownerLookup = fixedPipOwner()
377+
d.gitTracked = func(_ context.Context, _ string) bool { return false }
378+
d.inGitRepo = func(_ string) bool { return false }
379+
380+
audit := d.Detect(context.Background(), nil)
381+
if !audit.Available {
382+
t.Fatalf("expected Available=true with CLT installed, got false")
383+
}
384+
if audit.Path != "/usr/bin/pip3" {
385+
t.Errorf("expected Path=/usr/bin/pip3, got %q", audit.Path)
386+
}
387+
if audit.Version != "24.0" {
388+
t.Errorf("expected Version=24.0, got %q", audit.Version)
389+
}
390+
}

0 commit comments

Comments
 (0)