QHx CLI Reference
Overview
Section titled “Overview”The qhx CLI is a compartment-aware wrapper around kubectl that automatically applies MLS labels and enforces compartment boundaries. It provides the same interface as kubectl while adding security context awareness.
Key features:
- Automatic MLS label application
- Compartment context switching
- Cross-compartment access prevention
- Enhanced output with compartment information
- Compatible with kubectl commands
Target audience: Developers, operators, and security teams working with MLS-classified workloads.
Installation
Section titled “Installation”Download Binary
Section titled “Download Binary”# Linux (amd64)curl -LO https://github.com/messier42/qhx-cli/releases/latest/download/qhx-linux-amd64chmod +x qhx-linux-amd64sudo mv qhx-linux-amd64 /usr/local/bin/qhx
# macOS (amd64)curl -LO https://github.com/messier42/qhx-cli/releases/latest/download/qhx-darwin-amd64chmod +x qhx-darwin-amd64sudo mv qhx-darwin-amd64 /usr/local/bin/qhx
# macOS (arm64)curl -LO https://github.com/messier42/qhx-cli/releases/latest/download/qhx-darwin-arm64chmod +x qhx-darwin-arm64sudo mv qhx-darwin-arm64 /usr/local/bin/qhx
# Verify installationqhx versionFrom Source
Section titled “From Source”# Requires Go 1.21+git clone https://github.com/messier42/qhx-cli.gitcd qhx-climake installShell Completion
Section titled “Shell Completion”# Bashqhx completion bash | sudo tee /etc/bash_completion.d/qhx
# Zshqhx completion zsh | sudo tee /usr/share/zsh/site-functions/_qhx
# Fishqhx completion fish | sudo tee ~/.config/fish/completions/qhx.fishConfiguration
Section titled “Configuration”Configuration File
Section titled “Configuration File”The CLI reads configuration from ~/.qhx/config.yaml:
currentContext: us-ts-gestalt
contexts: us-ts-gestalt: country: US sensitivity: TS compartment: GESTALT kubeContext: production-cluster namespace: default
uk-s-marble: country: UK sensitivity: S compartment: MARBLE kubeContext: production-cluster namespace: default
au-c-baseline: country: AU sensitivity: C compartment: BASELINE kubeContext: staging-cluster namespace: default
# Default user groups (applied to all contexts)defaultUserGroups: - system:authenticated
# Kubeconfig path (optional, defaults to ~/.kube/config)kubeconfig: ~/.kube/configEnvironment Variables
Section titled “Environment Variables”# Set compartment via environmentexport QHX_COUNTRY=USexport QHX_SENSITIVITY=TSexport QHX_COMPARTMENT=GESTALT
# Override kubeconfigexport KUBECONFIG=~/.kube/production-config
# Debug modeexport QHX_DEBUG=truePrecedence: Environment variables override configuration file values.
Core Commands
Section titled “Core Commands”qhx license info
Section titled “qhx license info”Inspect a signed QHx license file locally without installing it into a cluster.
Usage:
qhx license info ./license.qhxlicenseqhx license install
Section titled “qhx license install”Verify a signed license file locally and install it into Kubernetes as a Secret for QHx Manager to discover.
Usage:
qhx license install --namespace qhx-system ./license.qhxlicenseqhx license status
Section titled “qhx license status”Print the cluster-wide QHx licensing status by reading the singleton
QHxLicenseStatus object maintained by QHx Manager.
Usage:
qhx license statusqhx curl
Section titled “qhx curl”Make HTTP requests with workload identity validation and request notarization.
Purpose: Test QHx-protected services and verify notarization levels.
Usage:
qhx curl [flags] <url>Notarization Levels:
The -n or --notarization-level flag selects the notarization level:
workload(default) - Validate workload identity statement only (lowest overhead)logRequest- Validate workload identity; request is logged by notarysignRequest- Validate workload identity and signed receipt; highest notarization (highest overhead)
Key Features:
- Validates QHx workload identity statements
- Verifies request notarization receipts
- Prints detailed notarization data
- Compatible with standard curl flags
- Exits with non-zero code if validation fails
Examples:
# Basic request with default (workload) notarizationqhx curl http://api-server:8080/health
# Request with signed receiptqhx curl -n signRequest http://api-server:8080/api/inference
# POST request with dataqhx curl -X POST -d '{"query":"hello"}' \ -H "Content-Type: application/json" \ http://api-server:8080/api/chat
# Print workload identity statementqhx curl --print-workload http://api-server:8080/
# Print request receipt (requires signRequest)qhx curl -n signRequest --print-receipt http://api-server:8080/
# Show response headersqhx curl -i http://api-server:8080/
# Silent mode (no progress)qhx curl -s http://api-server:8080/healthWorkload Identity Statement Output:
qhx curl --print-workload http://ollama-server:8080/
# Output:Trust Domain: qhx.devSPIFFE ID: spiffe://qhx.dev/ns/ai-demo/sa/ollama/pod/ollama-abc123/...Namespace: ai-demoService Account: ollamaPod Name: ollama-server-786f9f59d-5djstPod UID: 9d86dd35-31fd-4271-bb10-579bfe434d58
Container Images: - ollama/ollama:latest - qhx/proxy:v0.6.1
Labels: app: ollama-server pod-template-hash: 786f9f59d
Annotations: mls.qhx.dev/compartment: GESTALT mls.qhx.dev/level: TS
Raw Claims (JSON):{ "iss": "spiffe://qhx.dev/ns/qhx-central/sa/central-proxy/...", "sub": "spiffe://qhx.dev/ns/ai-demo/sa/ollama/...", "aud": "https://qhx.dev/workload-statement", "iat": 1760673752, "qhx": { "trustDomain": "qhx.dev", "namespace": "ai-demo", "podName": "ollama-server-786f9f59d-5djst", ... }}Request Receipt Output:
qhx curl -n signRequest --print-receipt \ -X POST -d '{"model":"llama3.2:1b","messages":[{"role":"user","content":"Hello"}]}' \ http://ollama-server:8080/v1/chat/completions
# Output:Signed Request Receipt "K8MKUqJsgmenKSchf06c5w==":==================================================Issuer: spiffe://qhx.dev/ns/qhx-central/sa/central-proxy/...Audience: [https://qhx.dev/receipt]Issued At: 2026-02-02T10:48:07Z
Workload Statement ID: VGkHGqleoIQHBiGlowdMh947YojeuMwTjhtELksaPs8=
HTTP Request: Method: POST Path: /v1/chat/completions Headers: Content-Type: application/json Body (110 bytes): {"model":"llama3.2:1b",...}
HTTP Response: Status Code: 200 Headers: Content-Type: application/json Body (351 bytes): {"id":"chatcmpl-500",...}
Raw Claims (JSON):{ "iss": "spiffe://qhx.dev/ns/qhx-central/sa/central-proxy/...", "aud": "https://qhx.dev/receipt", "iat": 1760680087, "qhx": { "workloadStatementID": "VGkHGqleoIQHBiGlowdMh947...", "request": {...}, "response": {...} }}Flags:
| Flag | Description |
|---|---|
-d, --data <str> | Specify HTTP POST data |
-H, --header <str> | Add HTTP header to request |
-n, --notarization-level <level> | Select notarization level (workload, logRequest, signRequest) |
--print-workload | Print workload identity statement |
--print-receipt | Print request notarization receipt (requires signRequest) |
-X, --request <method> | Select HTTP request method (GET, POST, PUT, DELETE, etc.) |
-i, --show-headers | Show HTTP headers in response |
-s, --silent | Silent mode (no progress output) |
--validate <bool> | Require workload statement verification (default: true) |
Global Flags:
| Flag | Description |
|---|---|
--log-level <level> | Enable development logging (console output, debug level) |
--log-format <format> | Log output format: text or json (default: text) |
--log-stacktrace | Include stack traces in error logs |
--spiffe-socket-path <path> | Path to Workload API socket (default: unix:///spiffe-workload-api/agent.sock) |
-v, --verbose <int> | Log verbosity level (higher is more verbose, default: -2) |
Use Cases:
1. Testing AI Inference with Audit Trail:
# Call LLM with signed receipt for complianceqhx curl -n signRequest --print-receipt \ -X POST -H "Content-Type: application/json" \ -d '{"model":"llama3.2:1b","messages":[{"role":"user","content":"Classify this document"}]}' \ http://ollama-server:8080/v1/chat/completions \ > inference-receipt.json
# Receipt proves:# - Which model handled the request# - Exact request/response# - Timestamp# - Workload identity2. Verifying Service Identity:
# Confirm which workload is runningqhx curl --print-workload http://api-server:8080/health | grep "Container Images"
# Output shows exact OCI image digest:# Container Images:# - api-server:v1.0@sha256:abc123...3. Debugging Network Policies:
# Test if request is allowedqhx curl http://blocked-service:8080/
# If blocked by policy:# Error: Connection refused (likely NetworkPolicy denial)
# If blocked by QHx:# Error: Workload statement verification failed4. Performance Testing:
# Test overhead of different notarization levelstime qhx curl -n workload http://api:8080/ # ~1ms overheadtime qhx curl -n logRequest http://api:8080/ # ~2ms overheadtime qhx curl -n signRequest http://api:8080/ # ~5-10ms overheadExit Codes:
- 0 - Success (request completed and notarization validated)
- 1 - Network error (connection failed)
- 2 - Workload statement validation failed
- 3 - Request receipt validation failed
- 4 - HTTP error (4xx or 5xx response)
Validation Behavior:
By default, qhx curl requires workload statement validation (--validate=true). If the server does not return valid notarization headers, the command fails:
# Calling non-QHx service fails validationqhx curl http://external-api.com/# Error: workload statement verification required but Qhx-Workload-ID header not present
# Disable validation for non-QHx servicesqhx curl --validate=false http://external-api.com/# ✅ Success (no validation)Comparison with standard curl:
| Feature | curl | qhx curl |
|---|---|---|
| HTTP requests | ✅ | ✅ |
| TLS/mTLS | ✅ | ✅ (automatic via SPIFFE) |
| Workload identity | ❌ | ✅ |
| Request notarization | ❌ | ✅ |
| Offline verification | ❌ | ✅ (with signed receipts) |
qhx identity
Section titled “qhx identity”Display current compartment context.
Usage:
qhx identityExample output:
Compartment: US-TS-GESTALTCountry: USSensitivity: TSCompartment: GESTALTKube Context: production-clusterUser: alice@example.comUser Groups: system:authenticated, qhx:country:us, qhx:sensitivity:ts, qhx:compartment:gestaltUse case: Verify compartment before deploying classified workloads.
qhx apply
Section titled “qhx apply”Apply Kubernetes resources with automatic MLS label injection.
Usage:
qhx apply -f FILENAME [flags]Behavior:
- Reads current compartment context
- Injects
mls.qhx.dev/levelandmls.qhx.dev/compartmentlabels - Applies resources via Kubernetes API
- Returns standard kubectl output
Examples:
# Apply deployment in current compartmentqhx apply -f deployment.yaml
# Apply with namespace overrideqhx apply -f app.yaml -n my-namespace
# Apply from URLqhx apply -f https://example.com/manifest.yaml
# Apply directoryqhx apply -f ./manifests/
# Dry-run to see what would be appliedqhx apply -f deployment.yaml --dry-run=client -o yamlLabel injection example:
Input YAML:
apiVersion: apps/v1kind: Deploymentmetadata: name: api-server namespace: productionspec: replicas: 3 selector: matchLabels: app: api template: metadata: labels: app: api spec: containers: - name: api image: api:v1.0After qhx apply (with context US-TS-GESTALT):
apiVersion: apps/v1kind: Deploymentmetadata: name: api-server namespace: production labels: qhx.io/compartment: US-TS-GESTALT # Injected annotations: mls.qhx.dev/level: us:ts # Injected mls.qhx.dev/compartment: us:gestalt # Injectedspec: replicas: 3 selector: matchLabels: app: api template: metadata: labels: app: api qhx.io/compartment: US-TS-GESTALT # Injected annotations: mls.qhx.dev/level: us:ts # Injected mls.qhx.dev/compartment: us:gestalt # Injected spec: containers: - name: api image: api:v1.0qhx get
Section titled “qhx get”Retrieve resources with compartment information.
Usage:
qhx get RESOURCE [NAME] [flags]Enhanced output: Adds COMPARTMENT column to standard kubectl output.
Examples:
# List all pods with compartmentqhx get pod
# Output:# NAME READY STATUS RESTARTS COMPARTMENT# us-api-7f8c9d-xyz 1/1 Running 0 US-TS-GESTALT# uk-web-5b6d8a-abc 1/1 Running 0 UK-S-MARBLE# au-db-9c4e2b-def 1/1 Running 0 AU-C-BASELINE
# Get specific pod detailsqhx get pod us-api-7f8c9d-xyz -o yaml
# List deployments across all namespacesqhx get deployment -A
# Watch pods in real-timeqhx get pod -w
# Filter by labelqhx get pod -l app=api
# Custom columnsqhx get pod -o custom-columns=NAME:.metadata.name,COMPARTMENT:.metadata.labels.qhx\\.io/compartmentCompartment filtering:
# Show only resources in current compartmentqhx get pod --filter-compartment
# Show resources in specific compartmentqhx get pod --compartment=UK-S-MARBLEqhx exec
Section titled “qhx exec”Execute commands in pods with compartment boundary enforcement.
Usage:
qhx exec POD_NAME [-c CONTAINER] -- COMMAND [args...]Security: Prevents execution in pods outside current compartment.
Examples:
# Execute command in pod (same compartment)qhx exec us-api-7f8c9d-xyz -- curl http://localhost:8080/health# ✅ Success (same compartment)
# Attempt to execute in different compartmentqhx exec uk-web-5b6d8a-abc -- curl http://localhost:8080/health# ❌ Error: Cannot execute in pod in another compartment (UK-S-MARBLE)# Current compartment: US-TS-GESTALT
# Specify containerqhx exec us-api-7f8c9d-xyz -c sidecar -- ps aux
# Interactive shellqhx exec -it us-api-7f8c9d-xyz -- /bin/bash
# Execute with stdinecho "SELECT * FROM users;" | qhx exec us-db-pod -- psql -U adminOverride (admin only):
# Force execution in different compartment (requires special permission)qhx exec uk-web-5b6d8a-abc --force -- curl http://localhost:8080/healthqhx logs
Section titled “qhx logs”Retrieve logs from pods.
Usage:
qhx logs POD_NAME [-c CONTAINER] [flags]Examples:
# Get logs from podqhx logs us-api-7f8c9d-xyz
# Follow logsqhx logs -f us-api-7f8c9d-xyz
# Logs from specific containerqhx logs us-api-7f8c9d-xyz -c qhx-proxy
# Previous logs (from crashed container)qhx logs us-api-7f8c9d-xyz --previous
# Last 100 linesqhx logs us-api-7f8c9d-xyz --tail=100
# Logs since timestampqhx logs us-api-7f8c9d-xyz --since=1hqhx delete
Section titled “qhx delete”Delete resources (with compartment awareness).
Usage:
qhx delete RESOURCE NAME [flags]Safety: Warns when deleting resources in different compartment.
Examples:
# Delete deploymentqhx delete deployment us-api
# Delete multiple resourcesqhx delete pod us-api-xyz us-api-abc
# Delete by labelqhx delete pod -l app=old-version
# Delete from fileqhx delete -f deployment.yaml
# Force delete (skip warnings)qhx delete deployment us-api --forceqhx describe
Section titled “qhx describe”Show detailed information about resources.
Usage:
qhx describe RESOURCE NAMEEnhanced output: Highlights MLS labels and compartment information.
Example:
qhx describe pod us-api-7f8c9d-xyz
# Output includes:# Name: us-api-7f8c9d-xyz# Namespace: production# Labels: app=api# qhx.io/compartment=US-TS-GESTALT# Annotations: mls.qhx.dev/level: us:ts# mls.qhx.dev/compartment: us:gestalt# ...qhx port-forward
Section titled “qhx port-forward”Forward ports from pods or services.
Usage:
qhx port-forward POD_NAME [LOCAL_PORT:]REMOTE_PORT [flags]Examples:
# Forward port 8080 to localhost:8080qhx port-forward us-api-7f8c9d-xyz 8080:8080
# Forward to different local portqhx port-forward us-api-7f8c9d-xyz 9090:8080
# Forward multiple portsqhx port-forward us-api-7f8c9d-xyz 8080:8080 9090:9090
# Forward from serviceqhx port-forward service/us-api 8080:80qhx cp
Section titled “qhx cp”Copy files to/from containers.
Usage:
qhx cp SOURCE DEST [flags]Examples:
# Copy from pod to localqhx cp us-api-7f8c9d-xyz:/var/log/app.log ./app.log
# Copy from local to podqhx cp ./config.yaml us-api-7f8c9d-xyz:/etc/app/config.yaml
# Copy from specific containerqhx cp us-api-7f8c9d-xyz:/logs/error.log ./error.log -c sidecarContext Management
Section titled “Context Management”qhx context
Section titled “qhx context”Manage compartment contexts.
Usage:
qhx context [SUBCOMMAND]Subcommands:
List available contexts:
qhx context list
# Output:# CURRENT NAME COUNTRY SENSITIVITY COMPARTMENT KUBE-CONTEXT# * us-ts-gestalt US TS GESTALT prod-cluster# uk-s-marble UK S MARBLE prod-cluster# au-c-baseline AU C BASELINE staging-clusterSwitch to a different context:
# Switch contextqhx context use uk-s-marble
# Verify switchqhx identity# Output:# Compartment: UK-S-MARBLE# ...create
Section titled “create”Create a new context:
qhx context create jp-s-quantum \ --country=JP \ --sensitivity=S \ --compartment=QUANTUM \ --kube-context=production-clusterdelete
Section titled “delete”Remove a context:
qhx context delete au-c-baselinecurrent
Section titled “current”Show current context:
qhx context current# Output: us-ts-gestaltAdvanced Features
Section titled “Advanced Features”Compartment Filtering
Section titled “Compartment Filtering”Filter resources by compartment:
# Show only pods in current compartmentqhx get pod --current-compartment
# Show pods in specific compartmentqhx get pod --compartment US-TS-GESTALT
# Show pods NOT in current compartmentqhx get pod --exclude-current-compartmentDry Run Mode
Section titled “Dry Run Mode”Preview changes without applying:
# See what labels would be injectedqhx apply -f deployment.yaml --dry-run=client -o yaml
# Server-side dry runqhx apply -f deployment.yaml --dry-run=serverJSON/YAML Output
Section titled “JSON/YAML Output”# Get pod as JSONqhx get pod us-api-xyz -o json
# Get deployment as YAMLqhx get deployment us-api -o yaml
# Custom JSON pathqhx get pod us-api-xyz -o jsonpath='{.metadata.labels.qhx\.io/compartment}'Watching Resources
Section titled “Watching Resources”# Watch podsqhx get pod -w
# Watch eventsqhx get events -w
# Watch with timestampsqhx get pod -w --show-labels=trueIntegration with kubectl
Section titled “Integration with kubectl”Compatibility
Section titled “Compatibility”The qhx CLI is fully compatible with kubectl. Any kubectl command works with qhx:
# These are equivalent (with compartment context added):kubectl get podqhx get pod
kubectl apply -f deployment.yamlqhx apply -f deployment.yamlAliases
Section titled “Aliases”Add to your shell configuration (~/.bashrc or ~/.zshrc):
# Use qhx by defaultalias kubectl='qhx'
# Quick compartment switchingalias qhx-us='qhx context use us-ts-gestalt'alias qhx-uk='qhx context use uk-s-marble'alias qhx-au='qhx context use au-c-baseline'Compartment Scripts
Section titled “Compartment Scripts”Shell Integration
Section titled “Shell Integration”Create compartment setup scripts:
export QHX_COUNTRY=USexport QHX_SENSITIVITY=TSexport QHX_COMPARTMENT=GESTALTPS1='(US-TS)$ '
# Source the scriptsource ./us_compartment.shMulti-Compartment Workflow
Section titled “Multi-Compartment Workflow”Use case: Deploy the same application to multiple compartments
#!/bin/bashfor compartment in us-ts-gestalt uk-s-marble au-c-baseline; do echo "Deploying to $compartment..." qhx context use $compartment qhx apply -f deployment.yaml qhx rollout status deployment/api-serverdone
echo "Deployment complete across all compartments"Configuration Examples
Section titled “Configuration Examples”Development Setup
Section titled “Development Setup”# ~/.qhx/config.yaml (development)currentContext: dev-unclassified
contexts: dev-unclassified: country: US sensitivity: U compartment: DEV kubeContext: minikube namespace: default
defaultUserGroups: - system:authenticated - qhx:country:us - qhx:sensitivity:uProduction Setup
Section titled “Production Setup”# ~/.qhx/config.yaml (production)currentContext: us-ts-gestalt
contexts: us-ts-gestalt: country: US sensitivity: TS compartment: GESTALT kubeContext: prod-us-west-1 namespace: classified-apps
us-s-default: country: US sensitivity: S compartment: DEFAULT kubeContext: prod-us-west-1 namespace: secret-apps
# Require explicit context switchingrequireExplicitContextSwitch: true
# Audit logauditLog: enabled: true path: ~/.qhx/audit.logTroubleshooting
Section titled “Troubleshooting”Issue: “Cannot execute in pod in another compartment”
Section titled “Issue: “Cannot execute in pod in another compartment””Cause: Attempting to execute commands in a pod outside current compartment.
Solution:
# Check current compartmentqhx identity
# Switch to correct compartmentqhx context use <correct-compartment>
# Or use --force (admin only)qhx exec <pod> --force -- <command>Issue: Labels not being applied
Section titled “Issue: Labels not being applied”Cause: Configuration not set or environment variables missing.
Check:
# Verify configurationcat ~/.qhx/config.yaml
# Verify environmentenv | grep QHX
# Test with dry-runqhx apply -f test.yaml --dry-run=client -o yaml | grep mls.qhx.devIssue: “Context not found”
Section titled “Issue: “Context not found””Cause: Referenced context doesn’t exist in config.
Solution:
# List available contextsqhx context list
# Create missing contextqhx context create <context-name> --country=... --sensitivity=... --compartment=...Best Practices
Section titled “Best Practices”1. Always Verify Context
Section titled “1. Always Verify Context”# Before any operationqhx identity2. Use Dry-Run for Testing
Section titled “2. Use Dry-Run for Testing”# Preview changesqhx apply -f deployment.yaml --dry-run=client -o yaml | less3. Label Resources Explicitly
Section titled “3. Label Resources Explicitly”# Include compartment in resource namesmetadata: name: us-api-server # Prefix with compartment4. Audit Logging
Section titled “4. Audit Logging”Enable audit logging in production:
auditLog: enabled: true path: ~/.qhx/audit.log level: info # debug, info, warn, error5. Compartment Naming Convention
Section titled “5. Compartment Naming Convention”<country>-<sensitivity>-<compartment>
Examples:- us-ts-gestalt- uk-s-marble- au-c-baseline- jp-u-publicRelated Documentation
Section titled “Related Documentation”- Compartment Management Guide - Setting up compartments
- MLS Policy Authoring - Policy configuration
- Attestation Strategies - Workload identity
- Quick Start Guide - Getting started
Command Reference
Section titled “Command Reference”| Command | Description | Example |
|---|---|---|
qhx curl | HTTP requests with notarization | qhx curl -n signRequest <url> |
qhx identity | Show current compartment | qhx identity |
qhx apply | Apply with labels | qhx apply -f app.yaml |
qhx get | Get resources | qhx get pod |
qhx exec | Execute in pod | qhx exec pod -- cmd |
qhx logs | Get logs | qhx logs pod |
qhx delete | Delete resources | qhx delete pod name |
qhx describe | Describe resource | qhx describe pod name |
qhx port-forward | Forward ports | qhx port-forward pod 8080 |
qhx cp | Copy files | qhx cp pod:/path ./ |
qhx context | Manage contexts | qhx context list |
Support
Section titled “Support”- Documentation: https://docs.messier42.com
- GitHub: https://github.com/messier42/qhx-cli
- Issues: https://github.com/messier42/qhx-cli/issues