The isUpgrade() check used exact string match (== "Upgrade") for the
Connection header value. A client sending "Connection: upgrade"
(lowercase) would bypass this check. While not exploitable in practice
(the Upgrade header check catches real upgrades), this hardens the
proxy with defense-in-depth.
Also adds a comprehensive security test suite covering jailbreak
attempts: method override smuggling, path traversal, dryRun parameter
injection, upgrade header smuggling, review endpoint spoofing, unusual
HTTP methods, concurrent request filtering, and credential leakage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The readonly proxy now permits:
- K8s "review" POST endpoints (SubjectAccessReview, TokenReview, etc.)
that query auth state without persisting resources
- Requests with ?dryRun=All for server-side validation
Review endpoints are matched with anchored regexps pinned to
authorization.k8s.io and authentication.k8s.io API groups, preventing
spoofing via custom resources with the same name.
Refactors the handler into small, independently tested filter functions
(isUpgrade, isReadOnly, isNonMutatingPost, isDryRun) composed by a
checkRequest commander.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Document -r/--readonly flag with usage example
- Reorganize Examples into Usage section with kubectx/kubens subsections
- Each command example in its own code block
- Remove outdated 'written in bash' badge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add description strings from bash scripts to Go help output
- Add -r/--readonly usage lines to kubectx help
- Reorder help to show -s/-r flags more prominently
- Normalize flag separators to comma-space consistently
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces ReadonlyShellOp and InteractiveReadonlyShellOp that spawn
an isolated sub-shell routing kubectl traffic through the readonly
reverse proxy. The proxy blocks write operations while allowing reads.
- Extracts minimal kubeconfig, starts proxy, rewrites kubeconfig to
point at proxy, spawns shell with KUBECTX_READONLY_SHELL=1
- Proxy and temp files cleaned up on shell exit
- Flag parsing mirrors -s/--shell pattern with optional context arg
- Interactive fzf mode supported when no context specified
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces a new internal/proxy package that provides a localhost HTTP
reverse proxy enforcing read-only access to the Kubernetes API server.
- Allows GET, HEAD, OPTIONS requests (kubectl get/describe/logs/top/watch)
- Blocks POST, PUT, DELETE, PATCH with metav1.Status 405 responses
- Blocks Connection: Upgrade requests (kubectl exec/cp/port-forward)
- Uses client-go transport for TLS/auth to the real API server
- Rewrites kubeconfig: server URL to proxy, strips auth, sets insecure-skip-tls-verify
- Appends [RO] suffix to context name in rewritten kubeconfig
- DEBUG=1 enables request/response logging
- Comprehensive test coverage for all proxy behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When `kubectx -s` (or `--shell`) is invoked without a context name and
fzf is available in an interactive terminal, launch fzf to let the user
pick a context, then start an isolated shell scoped to that selection.
This mirrors the existing behavior where `kubectx` with no arguments
launches fzf for context switching, and `kubectx -d` with no arguments
launches fzf for context deletion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace shallow checkout + `git fetch --tags` with `fetch-depth: 0` so
GoReleaser has the full commit history between tags to generate release
notes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When kubens needs to query the Kubernetes API (e.g. to check if a
namespace exists), it builds a REST client from the in-memory
kubeconfig bytes using clientcmd.RESTConfigFromKubeConfig(). This
function has no knowledge of the kubeconfig file's location on disk,
so it cannot resolve relative paths in exec credential plugin commands
(e.g. `command: ../scripts/get-token.sh`). This causes a "no such file
or directory" error for users whose kubeconfig uses relative paths in
exec-based authentication.
The fix threads the kubeconfig file path through a new PathHinter
optional interface on ReadWriteResetCloser. When a file path is
available, newKubernetesClientSet now uses
clientcmd.NewNonInteractiveDeferredLoadingClientConfig with
ExplicitPath, which resolves relative paths relative to the kubeconfig
file's directory — matching kubectl's own behavior. The old
bytes-based fallback is preserved for in-memory configs (e.g. tests).
Fixes#488
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, kubectx would error with "multiple files in KUBECONFIG are
currently not supported" when KUBECONFIG contained colon-separated paths.
This is a common setup where users maintain separate kubeconfig files for
different clusters/environments.
This change evolves the internal Kubeconfig struct from holding a single
file to a slice of file entries, matching kubectl's merge semantics:
- Reading current-context: first file with a non-empty value wins
- Writing current-context: always written to the first file
- Listing contexts: merged from all files, first occurrence wins for
duplicate names
- Modifying a context (delete/rename/set-namespace): written to the
file that owns that context
- Missing files in the KUBECONFIG list are silently skipped (matching
kubectl behavior), but permission errors are propagated
The Loader interface already returned []ReadWriteResetCloser, so all
public method signatures remain unchanged — zero modifications needed
in cmd/kubectx/ or cmd/kubens/ callers.
Fixes#485Fixes#211
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes#484: The ::set-output:: workflow command is deprecated. Updated
CI workflow to use the $GITHUB_OUTPUT environment file syntax instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Return an error early in InteractiveSwitchOp if the kubeconfig has no
contexts, instead of launching fzf with an empty list.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
t.Setenv is the modern standard Go testing utility (since Go 1.17) that automatically restores environment variables when the test completes. This replaces the custom testutil.WithEnvVar function which manually saved and restored env var state.
The testutil.go file is deleted as it only contained WithEnvVar. The testutil package remains for its other utilities like KubeconfigBuilder.
Adds a new CacheDir() function that respects the XDG_CACHE_HOME environment variable,
matching the bash scripts' behavior. kubectx and kubens now prefer XDG_CACHE_HOME when
set, falling back to $HOME/.kube otherwise. This aligns the Go implementations with
the bash scripts' cache directory logic.
Includes comprehensive tests covering all scenarios:
- XDG_CACHE_HOME set (returns the XDG value)
- XDG_CACHE_HOME unset (falls back to $HOME/.kube)
- Neither set (returns empty string)
This change addresses eight key improvements to the kubectx/kubens codebase:
Resource Management Fixes:
- Fix use-after-close bugs where Kubeconfig was accessed after Close()
- Fix resource leaks on error paths by ensuring defer kc.Close() is called
- Fix YAML encoder not being closed after Encode(), causing buffered data loss
API Design Improvements:
- Change ContextNames() to return ([]string, error) instead of silently returning
nil on error, making parse failures distinguishable from empty results
- Change GetCurrentContext() to return (string, error) instead of returning ""
for both "not set" and parse error cases
- Update all 16 call sites across cmd/kubectx and cmd/kubens packages to handle
the new error returns while preserving backward-compatible behavior
Error Handling:
- Add explicit error handling for printer.Success() calls in 5+ locations
by prefixing unchecked calls with _ =
Performance:
- Add slice pre-allocation in namespace list pagination using slices.Grow()
before append loops, reducing allocations when fetching 500+ item batches
All changes maintain backward compatibility for missing kubeconfig keys while
improving error transparency and resource safety.
Modernize the codebase to use idiomatic Go 1.25 patterns, removing deprecated APIs and reducing external dependencies.
- Replace deprecated `io/ioutil` with `os.ReadFile`, `os.WriteFile`, `os.MkdirTemp`, `os.CreateTemp`
- Replace `interface{}` with `any` (Go 1.18+)
- Remove `github.com/pkg/errors` dependency entirely, using stdlib `fmt.Errorf` with `%w` and `errors.New`
- Use `errors.As()` instead of direct type assertions on error values
- Use `strings.Cut()` for delimiter parsing instead of `strings.Split` + length check
- Use `slices.Contains()` for linear search in `ContextExists()`
- Use `t.Setenv()` and `t.TempDir()` in tests instead of manual env save/restore and temp dir cleanup
- Delete unused `internal/testutil/tempfile.go` helper
- Update GitHub Actions CI and release workflows from Go 1.22 to 1.25
Net result: -70 lines, 1 fewer external dependency.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds a GitHub Actions workflow that comments on PRs modifying the root kubectx or kubens bash scripts
The comment uses a [!WARNING] alert to inform contributors that bash implementations are frozen and to propose changes to the Go implementation instead
Skips the comment if the PR author is ahmetb
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>