Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f0ad571
fix: update user mapping
evgeniy-scherbina Oct 10, 2025
0450eac
feat: reimplement proxy
evgeniy-scherbina Oct 10, 2025
6f121bf
ci: run ci
evgeniy-scherbina Oct 10, 2025
8b0b93e
fix: https and ignore redirects
evgeniy-scherbina Oct 10, 2025
69da434
test: skip CONNECT test
evgeniy-scherbina Oct 10, 2025
1599be1
feat: block disallowed HTTP requests
evgeniy-scherbina Oct 10, 2025
8de8a23
tmp commit: seems working
evgeniy-scherbina Oct 13, 2025
2ed4f9a
fix: close connection
evgeniy-scherbina Oct 14, 2025
e455de2
feat: add audit
evgeniy-scherbina Oct 14, 2025
eab36f8
fix: proxy logging
evgeniy-scherbina Oct 14, 2025
b5084b4
fix: logger
evgeniy-scherbina Oct 14, 2025
6f1939d
refactor: minor improvement
evgeniy-scherbina Oct 14, 2025
1c6e284
refactor: minor improvement
evgeniy-scherbina Oct 14, 2025
d40d641
refactor: minor improvement
evgeniy-scherbina Oct 14, 2025
5f7fb47
refactor: minor improvements
evgeniy-scherbina Oct 14, 2025
ad613a4
refactor: minor refactoring
evgeniy-scherbina Oct 14, 2025
8ae17ea
Merge remote-tracking branch 'origin/main' into yevhenii/proxy-v2
evgeniy-scherbina Oct 14, 2025
8b01347
fix: linter
evgeniy-scherbina Oct 14, 2025
daef9c3
minor fix
evgeniy-scherbina Oct 14, 2025
6b71687
fix: linter
evgeniy-scherbina Oct 14, 2025
d576eb0
minor fix
evgeniy-scherbina Oct 16, 2025
88f979d
minor fix
evgeniy-scherbina Oct 16, 2025
7c118f9
tmp commit
evgeniy-scherbina Oct 16, 2025
2f0ca30
tmp commit
evgeniy-scherbina Oct 16, 2025
c4528e6
tmp commit
evgeniy-scherbina Oct 16, 2025
fd00e43
8087
evgeniy-scherbina Oct 16, 2025
7d6ce37
debug log
evgeniy-scherbina Oct 16, 2025
28a28fc
more debug logging
evgeniy-scherbina Oct 17, 2025
f83a392
more debug logging
evgeniy-scherbina Oct 17, 2025
8df6a20
remove unnecessary logging
evgeniy-scherbina Oct 17, 2025
1a5965a
revert allow everything policy
evgeniy-scherbina Oct 18, 2025
7559b66
Merge remote-tracking branch 'origin/main' into yevhenii/proxy-v3
evgeniy-scherbina Oct 18, 2025
59990d8
fix logger
evgeniy-scherbina Oct 18, 2025
a3c8e08
make port configurable
evgeniy-scherbina Oct 18, 2025
2005741
remove invalid comment
evgeniy-scherbina Oct 18, 2025
e83a46b
don't log any sensitive information
evgeniy-scherbina Oct 19, 2025
8f6cdec
fix bug with HTTP 2.0
evgeniy-scherbina Oct 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion audit/log_auditor.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ func (a *LogAuditor) AuditRequest(req Request) {
a.logger.Info("ALLOW",
"method", req.Method,
"url", req.URL,
"host", req.Host,
"rule", req.Rule)
} else {
a.logger.Warn("DENY",
"method", req.Method,
"url", req.URL)
"url", req.URL,
"host", req.Host,
)
}
}
1 change: 1 addition & 0 deletions audit/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Auditor interface {
type Request struct {
Method string
URL string
Host string
Allowed bool
Rule string // The rule that matched (if any)
}
3 changes: 2 additions & 1 deletion boundary.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Config struct {
TLSConfig *tls.Config
Logger *slog.Logger
Jailer jail.Jailer
ProxyPort int
}

type Boundary struct {
Expand All @@ -34,7 +35,7 @@ type Boundary struct {
func New(ctx context.Context, config Config) (*Boundary, error) {
// Create proxy server
proxyServer := proxy.NewProxyServer(proxy.Config{
HTTPPort: 8080,
HTTPPort: config.ProxyPort,
RuleEngine: config.RuleEngine,
Auditor: config.Auditor,
Logger: config.Logger,
Expand Down
24 changes: 17 additions & 7 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Config struct {
LogLevel string
LogDir string
Unprivileged bool
ProxyPort int64
}

// NewCommand creates and returns the root serpent command
Expand Down Expand Up @@ -86,6 +87,13 @@ func BaseCommand() *serpent.Command {
Description: "Run in unprivileged mode (no network isolation, uses proxy environment variables).",
Value: serpent.BoolOf(&config.Unprivileged),
},
{
Flag: "proxy-port",
Env: "PROXY_PORT",
Description: "Set a port for HTTP proxy.",
Default: "8080",
Value: serpent.Int64Of(&config.ProxyPort),
},
},
Handler: func(inv *serpent.Invocation) error {
args := inv.Args
Expand All @@ -100,15 +108,20 @@ func isChild() bool {

// Run executes the boundary command with the given configuration and arguments
func Run(ctx context.Context, config Config, args []string) error {
logger, err := setupLogging(config)
if err != nil {
return fmt.Errorf("could not set up logging: %v", err)
}

if isChild() {
log.Printf("boundary CHILD process is started")
logger.Info("boundary CHILD process is started")

vethNetJail := os.Getenv("VETH_JAIL_NAME")
err := jail.SetupChildNetworking(vethNetJail)
if err != nil {
return fmt.Errorf("failed to setup child networking: %v", err)
}
log.Printf("child networking is successfully configured")
logger.Info("child networking is successfully configured")

// Program to run
bin := args[0]
Expand All @@ -130,10 +143,6 @@ func Run(ctx context.Context, config Config, args []string) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

logger, err := setupLogging(config)
if err != nil {
return fmt.Errorf("could not set up logging: %v", err)
}
username, uid, gid, homeDir, configDir := util.GetUserInfo()

// Get command arguments
Expand Down Expand Up @@ -180,7 +189,7 @@ func Run(ctx context.Context, config Config, args []string) error {
// Create jailer with cert path from TLS setup
jailer, err := createJailer(jail.Config{
Logger: logger,
HttpProxyPort: 8080,
HttpProxyPort: int(config.ProxyPort),
Username: username,
Uid: uid,
Gid: gid,
Expand All @@ -199,6 +208,7 @@ func Run(ctx context.Context, config Config, args []string) error {
TLSConfig: tlsConfig,
Logger: logger,
Jailer: jailer,
ProxyPort: int(config.ProxyPort),
})
if err != nil {
return fmt.Errorf("failed to create boundary instance: %v", err)
Expand Down
47 changes: 46 additions & 1 deletion proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ func (p *Server) handleHTTPConnection(conn net.Conn) {
p.auditor.AuditRequest(audit.Request{
Method: req.Method,
URL: req.URL.String(),
Host: req.Host,
Allowed: result.Allowed,
Rule: result.Rule,
})
Expand Down Expand Up @@ -237,6 +238,7 @@ func (p *Server) handleTLSConnection(conn net.Conn) {
p.auditor.AuditRequest(audit.Request{
Method: req.Method,
URL: req.URL.String(),
Host: req.Host,
Allowed: result.Allowed,
Rule: result.Rule,
})
Expand Down Expand Up @@ -270,6 +272,23 @@ func (p *Server) forwardRequest(conn net.Conn, req *http.Request, https bool) {
Path: req.URL.Path,
RawQuery: req.URL.RawQuery,
}

//var requestBodyBytes []byte
//{
// var err error
// requestBodyBytes, err = io.ReadAll(req.Body)
// if err != nil {
// p.logger.Error("can't read response body", "error", err)
// return
// }
// err = req.Body.Close()
// if err != nil {
// p.logger.Error("Failed to close HTTP response body", "error", err)
// return
// }
// req.Body = io.NopCloser(bytes.NewBuffer(requestBodyBytes))
//}

var body = req.Body
if req.Method == http.MethodGet || req.Method == http.MethodHead {
body = nil
Expand Down Expand Up @@ -300,6 +319,16 @@ func (p *Server) forwardRequest(conn net.Conn, req *http.Request, https bool) {

p.logger.Debug("🔒 HTTPS Response", "status code", resp.StatusCode, "status", resp.Status)

p.logger.Debug("Forwarded Request",
"method", newReq.Method,
"host", newReq.Host,
//"requestBodyBytes", string(requestBodyBytes),
"URL", newReq.URL,
)
//for hKey, hVal := range newReq.Header {
// p.logger.Debug("Forwarded Request Header", hKey, hVal)
//}

// Read the body and explicitly set Content-Length header, otherwise client can hung up on the request.
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
Expand All @@ -315,10 +344,26 @@ func (p *Server) forwardRequest(conn net.Conn, req *http.Request, https bool) {
}
resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

// The downstream client (Claude) always communicates over HTTP/1.1.
// However, Go's default HTTP client may negotiate an HTTP/2 connection
// with the upstream server via ALPN during TLS handshake.
// This can cause the response's Proto field to be set to "HTTP/2.0",
// which would produce an invalid response for an HTTP/1.1 client.
// To prevent this mismatch, we explicitly normalize the response
// to HTTP/1.1 before writing it back to the client.
resp.Proto = "HTTP/1.1"
resp.ProtoMajor = 1
resp.ProtoMinor = 1

// Copy response back to client
err = resp.Write(conn)
if err != nil {
p.logger.Error("Failed to forward HTTP request", "error", err)
p.logger.Error("Failed to forward back HTTP response",
"error", err,
"host", req.Host,
"method", req.Method,
//"bodyBytes", string(bodyBytes),
)
return
}

Expand Down
Loading