diff --git a/docs/PROXY_MODE.md b/docs/PROXY_MODE.md
index 8a366da96..681dd2875 100644
--- a/docs/PROXY_MODE.md
+++ b/docs/PROXY_MODE.md
@@ -1,6 +1,6 @@
 # Proxy Mode
 
-Proxy mode (`awmg proxy`) is an HTTP forward proxy that intercepts GitHub API requests and applies DIFC (Data Information Flow Control) filtering using the same guard WASM module as the MCP gateway.
+Proxy mode (`awmg proxy`) is an HTTP(S) forward proxy that intercepts GitHub API requests and applies DIFC (Data Information Flow Control) filtering using the same guard WASM module as the MCP gateway.
 
 ## Motivation
 
@@ -8,18 +8,34 @@ The MCP gateway enforces DIFC on MCP tool calls, but tools that call the GitHub
 
 ## Quick Start
 
+### With `gh` CLI (TLS mode)
+
 ```bash
-# Start the proxy
+# Start the proxy with self-signed TLS
 awmg proxy \
   --guard-wasm guards/github-guard/github_guard.wasm \
   --policy '{"allow-only":{"repos":["org/repo"],"min-integrity":"approved"}}' \
   --github-token "$GITHUB_TOKEN" \
-  --listen localhost:8080
+  --listen localhost:8443 \
+  --tls
 
-# Point gh CLI at the proxy
-GH_HOST=localhost:8080 GH_TOKEN="$GITHUB_TOKEN" gh issue list -R org/repo
+# Trust the generated CA and point gh at the proxy
+export GH_HOST=localhost:8443
+export NODE_EXTRA_CA_CERTS=/tmp/gh-aw/mcp-logs/proxy-tls/ca.crt
+gh issue list -R org/repo
+```
 
-# Or use curl directly
+### With `curl` (plain HTTP)
+
+```bash
+# Start the proxy without TLS
+awmg proxy \
+  --guard-wasm guards/github-guard/github_guard.wasm \
+  --policy '{"allow-only":{"repos":["org/repo"],"min-integrity":"approved"}}' \
+  --github-token "$GITHUB_TOKEN" \
+  --listen localhost:8080
+
+# Use curl directly
 curl -H "Authorization: token $GITHUB_TOKEN" \
   http://localhost:8080/api/v3/repos/org/repo/issues
 ```
@@ -27,10 +43,10 @@ curl -H "Authorization: token $GITHUB_TOKEN" \
 ## How It Works
 
 ```
-HTTP client  →  awmg proxy (localhost:8080)  →  api.github.com
-                       ↓
-               6-phase DIFC pipeline
-               (same guard WASM module)
+gh CLI  →  awmg proxy (localhost:8443, TLS)  →  api.github.com
+                     ↓
+             6-phase DIFC pipeline
+             (same guard WASM module)
 ```
 
 1. The proxy receives an HTTP request (REST GET or GraphQL POST)
@@ -46,13 +62,15 @@ Write operations (PUT, POST, DELETE, PATCH) pass through unmodified.
 
 | Flag | Default | Description |
 |------|---------|-------------|
-| `--guard-wasm` | *(required)* | Path to the guard WASM module |
+| `--guard-wasm` | *(auto-detected in container)* | Path to the guard WASM module |
 | `--policy` | | Guard policy JSON (e.g., `{"allow-only":{"repos":["org/repo"]}}`) |
-| `--github-token` | `$GITHUB_TOKEN` | GitHub API token for upstream requests |
-| `--listen` / `-l` | `127.0.0.1:8080` | HTTP listen address |
+| `--github-token` | *(forwards client auth)* | Fallback GitHub API token for requests without Authorization header |
+| `--listen` / `-l` | `127.0.0.1:8080` | Proxy listen address |
 | `--log-dir` | `/tmp/gh-aw/mcp-logs` | Log file directory |
 | `--guards-mode` | `filter` | DIFC mode: `strict`, `filter`, or `propagate` |
 | `--github-api-url` | `https://api.github.com` | Upstream GitHub API URL |
+| `--tls` | `false` | Enable HTTPS with auto-generated self-signed certificates |
+| `--tls-dir` | `<log-dir>/proxy-tls` | Directory for generated TLS certificate files |
 
 ## DIFC Pipeline
 
@@ -112,23 +130,47 @@ Owner and repo are extracted from GraphQL variables (`$owner`, `$name`/`$repo`)
 
 ## Container Usage
 
-The proxy is included in the same container image as the MCP gateway:
+The proxy is included in the same container image as the MCP gateway. The baked-in guard WASM module at `/guards/github/00-github-guard.wasm` is auto-detected, so `--guard-wasm` is not needed.
+
+### With TLS (for `gh` CLI)
+
+```bash
+# Start the proxy — the entrypoint detects "proxy" and skips gateway checks
+docker run --rm -p 8443:8443 \
+  -e GITHUB_TOKEN \
+  -v /tmp/proxy-logs:/tmp/gh-aw/mcp-logs \
+  ghcr.io/github/gh-aw-mcpg:latest proxy \
+  --policy '{"allow-only":{"repos":["org/repo"],"min-integrity":"approved"}}' \
+  --listen 0.0.0.0:8443 \
+  --tls
+
+# Trust the CA cert from the mounted log volume
+export GH_HOST=localhost:8443
+export NODE_EXTRA_CA_CERTS=/tmp/proxy-logs/proxy-tls/ca.crt
+gh issue list -R org/repo
+```
+
+### Plain HTTP (for `curl` / testing)
 
 ```bash
-docker run --rm \
-  --entrypoint /app/awmg \
-  -p 8080:8080 \
+docker run --rm -p 8080:8080 \
   -e GITHUB_TOKEN \
-  ghcr.io/github/gh-aw-mcpg:latest \
-  proxy \
-  --guard-wasm /guards/github/00-github-guard.wasm \
+  -v /tmp/proxy-logs:/tmp/gh-aw/mcp-logs \
+  ghcr.io/github/gh-aw-mcpg:latest proxy \
   --policy '{"allow-only":{"repos":["org/repo"],"min-integrity":"none"}}' \
-  --github-token "$GITHUB_TOKEN" \
-  --listen 0.0.0.0:8080 \
-  --guards-mode filter
+  --listen 0.0.0.0:8080
+
+curl -H "Authorization: token $GITHUB_TOKEN" \
+  http://localhost:8080/repos/org/repo/issues
 ```
 
-Note: The container entrypoint defaults to `run_containerized.sh` (MCP gateway mode). Use `--entrypoint /app/awmg` to run proxy mode directly.
+### Container Notes
+
+- **Entrypoint routing**: The container entrypoint (`run_containerized.sh`) detects `proxy` as the first argument and skips gateway-specific checks (Docker socket, stdin config, `MCP_GATEWAY_PORT`/`DOMAIN`/`API_KEY`).
+- **Guard auto-detection**: `--guard-wasm` defaults to the baked-in `/guards/github/00-github-guard.wasm` inside the container.
+- **Log volume**: Mount a host directory to `/tmp/gh-aw/mcp-logs` to persist proxy logs and access the generated TLS CA certificate.
+- **Listen address**: Use `0.0.0.0` (not `127.0.0.1`) inside the container so the port mapping works.
+- **Token**: Pass `GITHUB_TOKEN` as an environment variable; the proxy resolves it automatically.
 
 ## Guards Mode
 
@@ -138,8 +180,39 @@ Note: The container entrypoint defaults to `run_containerized.sh` (MCP gateway m
 | `filter` | Removes filtered items, returns remaining (default) |
 | `propagate` | Labels accumulate on the agent; no filtering |
 
+## TLS Support
+
+The `gh` CLI forces HTTPS when connecting to a custom `GH_HOST`. The `--tls` flag generates a short-lived (24h) self-signed CA and server certificate at startup, enabling direct `gh` CLI integration without an external TLS terminator.
+
+### Generated Files
+
+When `--tls` is enabled, the proxy writes to `--tls-dir` (default: `<log-dir>/proxy-tls/`):
+
+| File | Purpose |
+|------|---------|
+| `ca.crt` | CA certificate — add to client trust store |
+| `server.crt` | Server certificate (localhost, 127.0.0.1, ::1) |
+| `server.key` | Server private key (0600 permissions) |
+
+### Trusting the CA
+
+**gh CLI / Node.js**:
+```bash
+export NODE_EXTRA_CA_CERTS=/tmp/gh-aw/mcp-logs/proxy-tls/ca.crt
+```
+
+**System-wide (Ubuntu)**:
+```bash
+cp /tmp/gh-aw/mcp-logs/proxy-tls/ca.crt /usr/local/share/ca-certificates/mcpg-proxy.crt
+update-ca-certificates
+```
+
+**curl**:
+```bash
+curl --cacert /tmp/gh-aw/mcp-logs/proxy-tls/ca.crt https://localhost:8443/health
+```
+
 ## Known Limitations
 
-- **gh CLI HTTPS requirement**: `gh` forces HTTPS when connecting to `GH_HOST`. The proxy serves plain HTTP, so direct `gh` CLI interception requires a TLS-terminating reverse proxy in front. Use `curl` or `gh api --hostname` with HTTP for testing.
 - **GraphQL nested filtering**: Deeply nested GraphQL response structures depend on guard support for item-level labeling.
 - **Read-only filtering**: Only GET requests and GraphQL POST queries are filtered. Write operations pass through unmodified.
diff --git a/internal/cmd/proxy.go b/internal/cmd/proxy.go
index 5742d847e..484eca18e 100644
--- a/internal/cmd/proxy.go
+++ b/internal/cmd/proxy.go
@@ -1,12 +1,14 @@
 package cmd
 
 import (
+	"crypto/tls"
 	"fmt"
 	"log"
 	"net"
 	"net/http"
 	"os"
 	"os/signal"
+	"path/filepath"
 	"syscall"
 
 	"github.com/github/gh-aw-mcpg/internal/logger"
@@ -23,43 +25,80 @@ var (
 	proxyLogDir    string
 	proxyDIFCMode  string
 	proxyAPIURL    string
+	proxyTLS       bool
+	proxyTLSDir    string
 )
 
 func init() {
 	rootCmd.AddCommand(newProxyCmd())
 }
 
+// containerGuardWasmPath is the baked-in guard path in the container image.
+const containerGuardWasmPath = "/guards/github/00-github-guard.wasm"
+
+// detectGuardWasm returns the baked-in container guard path if it exists,
+// or empty string if not found (requiring the user to specify --guard-wasm).
+func detectGuardWasm() string {
+	if _, err := os.Stat(containerGuardWasmPath); err == nil {
+		return containerGuardWasmPath
+	}
+	return ""
+}
+
 func newProxyCmd() *cobra.Command {
+	defaultGuard := detectGuardWasm()
+
 	cmd := &cobra.Command{
 		Use:   "proxy",
 		Short: "Run as a GitHub API filtering proxy",
-		Long: `Run the gateway in proxy mode — an HTTP forward proxy that intercepts
+		Long: `Run the gateway in proxy mode — an HTTP(S) forward proxy that intercepts
 gh CLI requests and applies DIFC filtering using the same guard WASM module.
 
-Usage with the gh CLI:
+Container usage (uses baked-in guard automatically):
+
+  docker run --rm -p 8443:8443 \
+    -e GITHUB_TOKEN \
+    -v /tmp/proxy-logs:/tmp/gh-aw/mcp-logs \
+    ghcr.io/github/gh-aw-mcpg:latest proxy \
+    --policy '{"allow-only":{"repos":["org/repo"],"min-integrity":"approved"}}' \
+    --listen 0.0.0.0:8443 \
+    --tls
+
+  # Trust the CA cert from the mounted volume
+  export GH_HOST=localhost:8443
+  export NODE_EXTRA_CA_CERTS=/tmp/proxy-logs/proxy-tls/ca.crt
+  gh issue list -R org/repo
+
+Local usage:
 
-  # Start the proxy
   awmg proxy \
     --guard-wasm guards/github-guard/github_guard.wasm \
     --policy '{"allow-only":{"repos":["org/repo"],"min-integrity":"approved"}}' \
-    --github-token "$GITHUB_TOKEN" \
-    --listen localhost:8080
-
-  # Point gh at the proxy
-  GH_HOST=localhost:8080 GH_TOKEN="$GITHUB_TOKEN" gh issue list -R org/repo`,
+    --listen localhost:8443 --tls`,
 		SilenceUsage: true,
 		RunE:         runProxy,
 	}
 
-	cmd.Flags().StringVar(&proxyGuardWasm, "guard-wasm", "", "Path to the guard WASM module (required)")
+	guardHelp := "Path to the guard WASM module"
+	if defaultGuard != "" {
+		guardHelp += " (auto-detected: " + defaultGuard + ")"
+	} else {
+		guardHelp += " (required)"
+	}
+	cmd.Flags().StringVar(&proxyGuardWasm, "guard-wasm", defaultGuard, guardHelp)
 	cmd.Flags().StringVar(&proxyPolicy, "policy", getDefaultGuardPolicyJSON(), "Guard policy JSON")
-	cmd.Flags().StringVar(&proxyToken, "github-token", os.Getenv("GITHUB_TOKEN"), "GitHub API token")
-	cmd.Flags().StringVarP(&proxyListen, "listen", "l", "127.0.0.1:8080", "HTTP proxy listen address")
+	cmd.Flags().StringVar(&proxyToken, "github-token", "", "Fallback GitHub API token (default: forwards client Authorization header)")
+	cmd.Flags().StringVarP(&proxyListen, "listen", "l", "127.0.0.1:8080", "Proxy listen address")
 	cmd.Flags().StringVar(&proxyLogDir, "log-dir", getDefaultLogDir(), "Log file directory")
 	cmd.Flags().StringVar(&proxyDIFCMode, "guards-mode", "filter", "DIFC enforcement mode: strict, filter, propagate")
 	cmd.Flags().StringVar(&proxyAPIURL, "github-api-url", proxy.DefaultGitHubAPIBase, "Upstream GitHub API URL")
+	cmd.Flags().BoolVar(&proxyTLS, "tls", false, "Enable HTTPS with auto-generated self-signed certificates")
+	cmd.Flags().StringVar(&proxyTLSDir, "tls-dir", "", "Directory for TLS certificates (default: <log-dir>/proxy-tls)")
 
-	cmd.MarkFlagRequired("guard-wasm")
+	// Only require --guard-wasm when no baked-in guard is available
+	if defaultGuard == "" {
+		cmd.MarkFlagRequired("guard-wasm")
+	}
 
 	return cmd
 }
@@ -76,16 +115,24 @@ func runProxy(cmd *cobra.Command, args []string) error {
 		log.Printf("Warning: Failed to initialize JSONL logger: %v", err)
 	}
 
-	logger.LogInfo("startup", "MCPG Proxy starting: listen=%s, guard=%s, mode=%s", proxyListen, proxyGuardWasm, proxyDIFCMode)
+	logger.LogInfo("startup", "MCPG Proxy starting: listen=%s, guard=%s, mode=%s, tls=%v", proxyListen, proxyGuardWasm, proxyDIFCMode, proxyTLS)
 
-	// Resolve GitHub token
+	// Resolve GitHub token (optional — proxy forwards client auth by default)
 	token := proxyToken
 	if token == "" {
 		token = os.Getenv("GH_TOKEN")
 	}
+	if token == "" {
+		token = os.Getenv("GITHUB_TOKEN")
+	}
 	if token == "" {
 		token = os.Getenv("GITHUB_PERSONAL_ACCESS_TOKEN")
 	}
+	if token != "" {
+		logger.LogInfo("startup", "Fallback GitHub token configured from flag/env")
+	} else {
+		logger.LogInfo("startup", "No fallback token — proxy will forward client Authorization headers")
+	}
 
 	// Create the proxy server
 	proxySrv, err := proxy.New(ctx, proxy.Config{
@@ -99,13 +146,30 @@ func runProxy(cmd *cobra.Command, args []string) error {
 		return fmt.Errorf("failed to create proxy server: %w", err)
 	}
 
-	// Create and start the HTTP server
+	// Generate TLS certificates if requested
+	var tlsCfg *proxy.TLSConfig
+	if proxyTLS {
+		tlsDir := proxyTLSDir
+		if tlsDir == "" {
+			tlsDir = filepath.Join(proxyLogDir, "proxy-tls")
+		}
+		tlsCfg, err = proxy.GenerateSelfSignedTLS(tlsDir)
+		if err != nil {
+			return fmt.Errorf("failed to generate TLS certificates: %w", err)
+		}
+		logger.LogInfo("startup", "TLS certificates generated: ca=%s", tlsCfg.CACertPath)
+	}
+
+	// Create the HTTP server
 	httpServer := &http.Server{
 		Addr:    proxyListen,
 		Handler: proxySrv.Handler(),
 	}
+	if tlsCfg != nil {
+		httpServer.TLSConfig = tlsCfg.Config
+	}
 
-	// Start HTTP server in background
+	// Start server in background
 	go func() {
 		listener, err := net.Listen("tcp", proxyListen)
 		if err != nil {
@@ -114,17 +178,34 @@ func runProxy(cmd *cobra.Command, args []string) error {
 			return
 		}
 
+		if tlsCfg != nil {
+			listener = tls.NewListener(listener, tlsCfg.Config)
+		}
+
 		actualAddr := listener.Addr().String()
-		log.Printf("MCPG Proxy listening on %s", actualAddr)
-		logger.LogInfo("startup", "Proxy listening on %s", actualAddr)
+		scheme := "http"
+		if tlsCfg != nil {
+			scheme = "https"
+		}
+
+		log.Printf("MCPG Proxy listening on %s://%s", scheme, actualAddr)
+		logger.LogInfo("startup", "Proxy listening on %s://%s", scheme, actualAddr)
 
 		// Print connection info
 		fmt.Fprintf(os.Stderr, "\nMCPG GitHub API Proxy\n")
-		fmt.Fprintf(os.Stderr, "  Listening: %s\n", actualAddr)
+		fmt.Fprintf(os.Stderr, "  Listening: %s://%s\n", scheme, actualAddr)
 		fmt.Fprintf(os.Stderr, "  Mode:      %s\n", proxyDIFCMode)
 		fmt.Fprintf(os.Stderr, "  Guard:     %s\n", proxyGuardWasm)
-		fmt.Fprintf(os.Stderr, "\nConnect with:\n")
-		fmt.Fprintf(os.Stderr, "  GH_HOST=%s GH_TOKEN=<token> gh ...\n\n", actualAddr)
+		if tlsCfg != nil {
+			fmt.Fprintf(os.Stderr, "  CA cert:   %s\n", tlsCfg.CACertPath)
+			fmt.Fprintf(os.Stderr, "\nConnect with:\n")
+			fmt.Fprintf(os.Stderr, "  export GH_HOST=%s\n", clientAddr(actualAddr))
+			fmt.Fprintf(os.Stderr, "  export NODE_EXTRA_CA_CERTS=%s\n", tlsCfg.CACertPath)
+			fmt.Fprintf(os.Stderr, "  gh issue list -R org/repo\n\n")
+		} else {
+			fmt.Fprintf(os.Stderr, "\nConnect with:\n")
+			fmt.Fprintf(os.Stderr, "  curl http://%s/repos/org/repo/issues\n\n", actualAddr)
+		}
 
 		if err := httpServer.Serve(listener); err != nil && err != http.ErrServerClosed {
 			log.Printf("HTTP server error: %v", err)
@@ -139,3 +220,18 @@ func runProxy(cmd *cobra.Command, args []string) error {
 
 	return httpServer.Close()
 }
+
+// clientAddr returns a client-friendly address from a listener address.
+// When the host is a wildcard (0.0.0.0, ::, or empty), it substitutes
+// "localhost" so the printed GH_HOST value is usable from a client.
+func clientAddr(addr string) string {
+	host, port, err := net.SplitHostPort(addr)
+	if err != nil {
+		return addr
+	}
+	switch host {
+	case "", "0.0.0.0", "::", "[::]":
+		return net.JoinHostPort("localhost", port)
+	}
+	return addr
+}
diff --git a/internal/cmd/proxy_test.go b/internal/cmd/proxy_test.go
new file mode 100644
index 000000000..8a9446a0d
--- /dev/null
+++ b/internal/cmd/proxy_test.go
@@ -0,0 +1,52 @@
+package cmd
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestClientAddr(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    string
+		expected string
+	}{
+		{
+			name:     "IPv4 wildcard becomes localhost",
+			input:    "0.0.0.0:8080",
+			expected: "localhost:8080",
+		},
+		{
+			name:     "IPv6 wildcard :: becomes localhost",
+			input:    "[::]:8443",
+			expected: "localhost:8443",
+		},
+		{
+			name:     "explicit localhost unchanged",
+			input:    "localhost:3000",
+			expected: "localhost:3000",
+		},
+		{
+			name:     "explicit 127.0.0.1 unchanged",
+			input:    "127.0.0.1:9090",
+			expected: "127.0.0.1:9090",
+		},
+		{
+			name:     "non-loopback host unchanged",
+			input:    "192.168.1.1:8080",
+			expected: "192.168.1.1:8080",
+		},
+		{
+			name:     "invalid address returned as-is",
+			input:    "not-an-addr",
+			expected: "not-an-addr",
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			assert.Equal(t, tc.expected, clientAddr(tc.input))
+		})
+	}
+}
diff --git a/internal/proxy/handler.go b/internal/proxy/handler.go
index 47a7118d4..a85191644 100644
--- a/internal/proxy/handler.go
+++ b/internal/proxy/handler.go
@@ -142,11 +142,12 @@ func (h *proxyHandler) handleWithDIFC(w http.ResponseWriter, r *http.Request, pa
 	}
 
 	// **Phase 3: Forward to upstream GitHub API**
+	clientAuth := r.Header.Get("Authorization")
 	var resp *http.Response
 	if graphQLBody != nil {
-		resp, err = s.forwardToGitHub(ctx, http.MethodPost, "/graphql", bytes.NewReader(graphQLBody), "application/json")
+		resp, err = s.forwardToGitHub(ctx, http.MethodPost, "/graphql", bytes.NewReader(graphQLBody), "application/json", clientAuth)
 	} else {
-		resp, err = s.forwardToGitHub(ctx, r.Method, path, nil, "")
+		resp, err = s.forwardToGitHub(ctx, r.Method, path, nil, "", clientAuth)
 	}
 	if err != nil {
 		logHandler.Printf("[DIFC] Phase 3 failed: %v", err)
@@ -275,7 +276,7 @@ func (h *proxyHandler) passthrough(w http.ResponseWriter, r *http.Request, path
 		defer r.Body.Close()
 	}
 
-	resp, err := h.server.forwardToGitHub(r.Context(), r.Method, path, body, r.Header.Get("Content-Type"))
+	resp, err := h.server.forwardToGitHub(r.Context(), r.Method, path, body, r.Header.Get("Content-Type"), r.Header.Get("Authorization"))
 	if err != nil {
 		http.Error(w, "upstream request failed", http.StatusBadGateway)
 		return
diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go
index 2c2e71c2b..1ddc3ee6e 100644
--- a/internal/proxy/proxy.go
+++ b/internal/proxy/proxy.go
@@ -58,7 +58,8 @@ type Config struct {
 	// Policy is the guard policy JSON (e.g. {"allow-only":{...}}).
 	Policy string
 
-	// GitHubToken is the token forwarded to the upstream GitHub API.
+	// GitHubToken is a fallback token for upstream GitHub API requests.
+	// When empty, the proxy forwards the client's Authorization header instead.
 	GitHubToken string
 
 	// GitHubAPIURL overrides the upstream API base URL (default: https://api.github.com).
@@ -73,9 +74,6 @@ func New(ctx context.Context, cfg Config) (*Server, error) {
 	if cfg.WasmPath == "" {
 		return nil, fmt.Errorf("guard WASM path is required")
 	}
-	if cfg.GitHubToken == "" {
-		return nil, fmt.Errorf("GitHub token is required")
-	}
 
 	apiURL := cfg.GitHubAPIURL
 	if apiURL == "" {
@@ -195,8 +193,10 @@ func (s *stubBackendCaller) CallTool(_ context.Context, toolName string, _ inter
 	return nil, fmt.Errorf("CallTool not supported in proxy mode")
 }
 
-// forwardToGitHub sends a request to the upstream GitHub API and returns the response body.
-func (s *Server) forwardToGitHub(ctx context.Context, method, path string, body io.Reader, contentType string) (*http.Response, error) {
+// forwardToGitHub sends a request to the upstream GitHub API.
+// clientAuth is the Authorization header from the inbound client request;
+// if non-empty it is forwarded as-is, otherwise the configured fallback token is used.
+func (s *Server) forwardToGitHub(ctx context.Context, method, path string, body io.Reader, contentType string, clientAuth string) (*http.Response, error) {
 	url := s.githubAPIURL + path
 	logProxy.Printf("forwarding %s %s → %s", method, path, url)
 
@@ -205,7 +205,12 @@ func (s *Server) forwardToGitHub(ctx context.Context, method, path string, body
 		return nil, fmt.Errorf("failed to create upstream request: %w", err)
 	}
 
-	req.Header.Set("Authorization", "token "+s.githubToken)
+	// Prefer the client's own Authorization header; fall back to configured token.
+	if clientAuth != "" {
+		req.Header.Set("Authorization", clientAuth)
+	} else if s.githubToken != "" {
+		req.Header.Set("Authorization", "token "+s.githubToken)
+	}
 	req.Header.Set("Accept", "application/vnd.github+json")
 	req.Header.Set("User-Agent", "awmg-proxy/1.0")
 	if contentType != "" {
diff --git a/internal/proxy/tls.go b/internal/proxy/tls.go
new file mode 100644
index 000000000..7031ba39a
--- /dev/null
+++ b/internal/proxy/tls.go
@@ -0,0 +1,191 @@
+// Package proxy — TLS support for the GitHub API filtering proxy.
+//
+// When running in self-signed TLS mode, the proxy auto-generates a CA and
+// localhost server certificate at startup. This allows the gh CLI (which
+// forces HTTPS for custom GH_HOST values) to connect via:
+//
+//	GH_HOST=localhost:8443 gh issue list -R org/repo
+//
+// The CA certificate is written to a file so callers can inject it into their
+// trust store (e.g., via NODE_EXTRA_CA_CERTS or update-ca-certificates).
+package proxy
+
+import (
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/tls"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"fmt"
+	"math/big"
+	"net"
+	"os"
+	"path/filepath"
+	"time"
+
+	"github.com/github/gh-aw-mcpg/internal/logger"
+)
+
+var logTLS = logger.New("proxy:tls")
+
+// TLSConfig holds the paths to the generated certificate files.
+type TLSConfig struct {
+	// CACertPath is the path to the PEM-encoded CA certificate.
+	// Callers should add this to their trust store or set NODE_EXTRA_CA_CERTS.
+	CACertPath string
+
+	// CertPath is the path to the PEM-encoded server certificate.
+	CertPath string
+
+	// KeyPath is the path to the PEM-encoded server private key.
+	KeyPath string
+
+	// TLSConfig is the assembled tls.Config ready for use with http.Server.
+	Config *tls.Config
+}
+
+// GenerateSelfSignedTLS creates a self-signed CA and server certificate for
+// localhost. All files are written to dir. The CA cert is suitable for
+// injection into client trust stores.
+//
+// Generated files:
+//   - ca.crt    — CA certificate (share with clients)
+//   - server.crt — Server certificate (localhost + 127.0.0.1)
+//   - server.key — Server private key
+func GenerateSelfSignedTLS(dir string) (*TLSConfig, error) {
+	logTLS.Print("generating self-signed TLS certificates for localhost")
+
+	if err := os.MkdirAll(dir, 0755); err != nil {
+		return nil, fmt.Errorf("failed to create TLS directory %s: %w", dir, err)
+	}
+
+	// --- Generate CA ---
+	caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return nil, fmt.Errorf("failed to generate CA key: %w", err)
+	}
+
+	caSerial, err := randomSerial()
+	if err != nil {
+		return nil, err
+	}
+
+	notBefore := time.Now().Add(-1 * time.Hour)
+	notAfter := notBefore.Add(24 * time.Hour)
+
+	caTemplate := &x509.Certificate{
+		SerialNumber: caSerial,
+		Subject: pkix.Name{
+			Organization: []string{"MCPG Proxy"},
+			CommonName:   "MCPG Proxy CA",
+		},
+		NotBefore:             notBefore,
+		NotAfter:              notAfter,
+		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+		MaxPathLen:            0,
+	}
+
+	caCertDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create CA certificate: %w", err)
+	}
+
+	caCert, err := x509.ParseCertificate(caCertDER)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse CA certificate: %w", err)
+	}
+
+	// --- Generate server certificate ---
+	serverKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		return nil, fmt.Errorf("failed to generate server key: %w", err)
+	}
+
+	serverSerial, err := randomSerial()
+	if err != nil {
+		return nil, err
+	}
+
+	serverTemplate := &x509.Certificate{
+		SerialNumber: serverSerial,
+		Subject: pkix.Name{
+			Organization: []string{"MCPG Proxy"},
+			CommonName:   "localhost",
+		},
+		DNSNames:    []string{"localhost"},
+		IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
+		NotBefore:   time.Now().Add(-1 * time.Hour),
+		NotAfter:    time.Now().Add(24 * time.Hour),
+		KeyUsage:    x509.KeyUsageDigitalSignature,
+		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+	}
+
+	serverCertDER, err := x509.CreateCertificate(rand.Reader, serverTemplate, caCert, &serverKey.PublicKey, caKey)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create server certificate: %w", err)
+	}
+
+	// --- Write files ---
+	caCertPath := filepath.Join(dir, "ca.crt")
+	certPath := filepath.Join(dir, "server.crt")
+	keyPath := filepath.Join(dir, "server.key")
+
+	if err := writePEM(caCertPath, "CERTIFICATE", caCertDER, 0644); err != nil {
+		return nil, fmt.Errorf("failed to write CA cert: %w", err)
+	}
+	if err := writePEM(certPath, "CERTIFICATE", serverCertDER, 0644); err != nil {
+		return nil, fmt.Errorf("failed to write server cert: %w", err)
+	}
+
+	serverKeyDER, err := x509.MarshalECPrivateKey(serverKey)
+	if err != nil {
+		return nil, fmt.Errorf("failed to marshal server key: %w", err)
+	}
+	if err := writePEM(keyPath, "EC PRIVATE KEY", serverKeyDER, 0600); err != nil {
+		return nil, fmt.Errorf("failed to write server key: %w", err)
+	}
+
+	// --- Build tls.Config ---
+	serverCertPair, err := tls.LoadX509KeyPair(certPath, keyPath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to load server cert pair: %w", err)
+	}
+
+	tlsCfg := &tls.Config{
+		Certificates: []tls.Certificate{serverCertPair},
+		MinVersion:   tls.VersionTLS12,
+	}
+
+	logTLS.Printf("TLS certificates generated in %s (valid 24h)", dir)
+
+	return &TLSConfig{
+		CACertPath: caCertPath,
+		CertPath:   certPath,
+		KeyPath:    keyPath,
+		Config:     tlsCfg,
+	}, nil
+}
+
+func randomSerial() (*big.Int, error) {
+	serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
+	if err != nil {
+		return nil, fmt.Errorf("failed to generate serial number: %w", err)
+	}
+	if serial.Sign() == 0 {
+		serial = new(big.Int).Add(serial, big.NewInt(1))
+	}
+	return serial, nil
+}
+
+func writePEM(path, blockType string, derBytes []byte, perm os.FileMode) error {
+	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return pem.Encode(f, &pem.Block{Type: blockType, Bytes: derBytes})
+}
diff --git a/internal/proxy/tls_test.go b/internal/proxy/tls_test.go
new file mode 100644
index 000000000..9ed90789f
--- /dev/null
+++ b/internal/proxy/tls_test.go
@@ -0,0 +1,152 @@
+package proxy
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"net"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestGenerateSelfSignedTLS(t *testing.T) {
+	t.Run("generates valid CA and server certificates", func(t *testing.T) {
+		dir := t.TempDir()
+		tlsCfg, err := GenerateSelfSignedTLS(dir)
+		require.NoError(t, err)
+		require.NotNil(t, tlsCfg)
+
+		// Files exist
+		assert.FileExists(t, tlsCfg.CACertPath)
+		assert.FileExists(t, tlsCfg.CertPath)
+		assert.FileExists(t, tlsCfg.KeyPath)
+
+		// Paths are in the expected directory
+		assert.Equal(t, filepath.Join(dir, "ca.crt"), tlsCfg.CACertPath)
+		assert.Equal(t, filepath.Join(dir, "server.crt"), tlsCfg.CertPath)
+		assert.Equal(t, filepath.Join(dir, "server.key"), tlsCfg.KeyPath)
+
+		// tls.Config is populated
+		require.NotNil(t, tlsCfg.Config)
+		assert.Len(t, tlsCfg.Config.Certificates, 1)
+		assert.Equal(t, uint16(tls.VersionTLS12), tlsCfg.Config.MinVersion)
+	})
+
+	t.Run("CA cert is trusted for server cert", func(t *testing.T) {
+		dir := t.TempDir()
+		tlsCfg, err := GenerateSelfSignedTLS(dir)
+		require.NoError(t, err)
+
+		// Load CA cert into a pool
+		caCertPEM, err := os.ReadFile(tlsCfg.CACertPath)
+		require.NoError(t, err)
+
+		caPool := x509.NewCertPool()
+		ok := caPool.AppendCertsFromPEM(caCertPEM)
+		require.True(t, ok, "CA cert should be parseable PEM")
+
+		// Start a TLS server with the generated config
+		srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			w.WriteHeader(http.StatusOK)
+		}))
+		srv.TLS = tlsCfg.Config
+		srv.StartTLS()
+		defer srv.Close()
+
+		// Client trusting only our CA should connect successfully
+		client := &http.Client{
+			Transport: &http.Transport{
+				TLSClientConfig: &tls.Config{
+					RootCAs: caPool,
+				},
+			},
+		}
+
+		resp, err := client.Get(srv.URL)
+		require.NoError(t, err, "TLS handshake should succeed with CA trust")
+		defer resp.Body.Close()
+		assert.Equal(t, http.StatusOK, resp.StatusCode)
+	})
+
+	t.Run("untrusted client rejects server cert", func(t *testing.T) {
+		dir := t.TempDir()
+		tlsCfg, err := GenerateSelfSignedTLS(dir)
+		require.NoError(t, err)
+
+		srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			w.WriteHeader(http.StatusOK)
+		}))
+		srv.TLS = tlsCfg.Config
+		srv.StartTLS()
+		defer srv.Close()
+
+		// Client with default (system) trust store should reject the cert
+		client := &http.Client{
+			Transport: &http.Transport{
+				TLSClientConfig: &tls.Config{},
+			},
+		}
+
+		_, err = client.Get(srv.URL)
+		require.Error(t, err, "TLS handshake should fail without CA trust")
+		assert.Contains(t, err.Error(), "certificate")
+	})
+
+	t.Run("server cert covers localhost, 127.0.0.1, and ::1", func(t *testing.T) {
+		dir := t.TempDir()
+		tlsCfg, err := GenerateSelfSignedTLS(dir)
+		require.NoError(t, err)
+
+		// Parse the server certificate
+		leaf, err := x509.ParseCertificate(tlsCfg.Config.Certificates[0].Certificate[0])
+		require.NoError(t, err)
+
+		assert.Contains(t, leaf.DNSNames, "localhost")
+		foundLoopback4 := false
+		foundLoopback6 := false
+		for _, ip := range leaf.IPAddresses {
+			if ip.Equal(net.IPv4(127, 0, 0, 1)) {
+				foundLoopback4 = true
+			}
+			if ip.Equal(net.IPv6loopback) {
+				foundLoopback6 = true
+			}
+		}
+		assert.True(t, foundLoopback4, "server cert should cover 127.0.0.1")
+		assert.True(t, foundLoopback6, "server cert should cover ::1")
+	})
+
+	t.Run("key files have restricted permissions", func(t *testing.T) {
+		dir := t.TempDir()
+		tlsCfg, err := GenerateSelfSignedTLS(dir)
+		require.NoError(t, err)
+
+		info, err := os.Stat(tlsCfg.KeyPath)
+		require.NoError(t, err)
+		assert.Equal(t, os.FileMode(0600), info.Mode().Perm(), "private key should be owner-only")
+	})
+
+	t.Run("creates directory if missing", func(t *testing.T) {
+		dir := filepath.Join(t.TempDir(), "nested", "subdir")
+		_, err := GenerateSelfSignedTLS(dir)
+		require.NoError(t, err)
+		assert.DirExists(t, dir)
+	})
+
+	t.Run("certificates are short-lived (24h)", func(t *testing.T) {
+		dir := t.TempDir()
+		tlsCfg, err := GenerateSelfSignedTLS(dir)
+		require.NoError(t, err)
+
+		leaf, err := x509.ParseCertificate(tlsCfg.Config.Certificates[0].Certificate[0])
+		require.NoError(t, err)
+
+		validity := leaf.NotAfter.Sub(leaf.NotBefore)
+		assert.InDelta(t, 25*3600, validity.Seconds(), 3600, "cert validity should be ~25h (24h + 1h backdate)")
+	})
+}
diff --git a/run_containerized.sh b/run_containerized.sh
index cc4a35bd6..0dedbf761 100755
--- a/run_containerized.sh
+++ b/run_containerized.sh
@@ -314,10 +314,41 @@ build_command_args() {
     echo "$flags"
 }
 
+# Run proxy mode — lightweight path that skips gateway-specific checks
+# (no Docker socket, no stdin config, no MCP_GATEWAY_PORT/DOMAIN/API_KEY needed)
+run_proxy_mode() {
+    log_info "Starting MCP Gateway in proxy mode..."
+
+    local log_dir="${MCP_GATEWAY_LOG_DIR:-/tmp/gh-aw/mcp-logs}"
+
+    # Build args: insert --log-dir default if not already specified
+    local args=("$@")
+    local has_log_dir=false
+    for arg in "$@"; do
+        if [ "$arg" = "--log-dir" ] || [[ "$arg" == --log-dir=* ]]; then
+            has_log_dir=true
+            break
+        fi
+    done
+    if [ "$has_log_dir" = false ]; then
+        args+=("--log-dir" "$log_dir")
+    fi
+
+    log_info "Command: ./awmg ${args[*]}"
+    exec ./awmg "${args[@]}"
+}
+
 # Main execution
 main() {
     log_info "Starting MCP Gateway in containerized mode..."
 
+    # If first argument is "proxy", use the lightweight proxy path.
+    # Proxy mode doesn't need Docker socket, stdin config, or gateway env vars.
+    if [ "$1" = "proxy" ]; then
+        run_proxy_mode "$@"
+        return
+    fi
+
     # Auto-detect baked-in WASM guards if env var not explicitly set.
     # Setting MCP_GATEWAY_WASM_GUARDS_DIR="" explicitly disables auto-detection.
     if [ -z "${MCP_GATEWAY_WASM_GUARDS_DIR+set}" ] && [ -d "/guards" ]; then
