add event type & logging interceptor for unknown hosts (#157)

* add event type & logging interceptor for unknown hosts

* update logging

* add break

* rename host_observation interceptor to audit_logger

* restore MITMDecider and make AuditLogger telemetry skip MITM on CONNECT
This commit is contained in:
Sahil Bansal
2026-02-12 13:48:01 +05:30
committed by GitHub
parent 7fe9fc8763
commit 5b0517f92a
7 changed files with 177 additions and 7 deletions
+5
View File
@@ -76,3 +76,8 @@ type Interceptor interface {
// Called for each request matching ShouldIntercept
HandleRequest(ctx *RequestContext) (*InterceptorResponse, error)
}
// MITMDecider is optional; implement to control whether CONNECT requests are MITMd.
type MITMDecider interface {
ShouldMITM(ctx *RequestContext) bool
}
+51
View File
@@ -0,0 +1,51 @@
package interceptors
import (
"github.com/safedep/pmg/internal/eventlog"
"github.com/safedep/pmg/proxy"
)
// AuditLoggerInterceptor logs unknown outbound hosts observed by proxy mode.
// It is passive telemetry only and never blocks or mutates requests.
type AuditLoggerInterceptor struct{}
var _ proxy.Interceptor = (*AuditLoggerInterceptor)(nil)
var _ proxy.MITMDecider = (*AuditLoggerInterceptor)(nil)
func NewAuditLoggerInterceptor() *AuditLoggerInterceptor {
return &AuditLoggerInterceptor{}
}
func (i *AuditLoggerInterceptor) Name() string {
return "audit-logger-interceptor"
}
// ShouldIntercept is always true so we can observe all proxied traffic.
func (i *AuditLoggerInterceptor) ShouldIntercept(_ *proxy.RequestContext) bool {
return true
}
// ShouldMITM is false because this interceptor is telemetry-only.
func (i *AuditLoggerInterceptor) ShouldMITM(_ *proxy.RequestContext) bool {
return false
}
func (i *AuditLoggerInterceptor) HandleRequest(ctx *proxy.RequestContext) (*proxy.InterceptorResponse, error) {
if ctx == nil || ctx.Hostname == "" {
return &proxy.InterceptorResponse{Action: proxy.ActionAllow}, nil
}
if i.isKnownRegistryHost(ctx.Hostname) {
return &proxy.InterceptorResponse{Action: proxy.ActionAllow}, nil
}
eventlog.LogProxyHostObserved(ctx.Hostname, ctx.Method, "audit_logger_interceptor", map[string]interface{}{
"request_id": ctx.RequestID,
})
return &proxy.InterceptorResponse{Action: proxy.ActionAllow}, nil
}
func (i *AuditLoggerInterceptor) isKnownRegistryHost(hostname string) bool {
return npmRegistryDomains.ContainsHostname(hostname) || pypiRegistryDomains.ContainsHostname(hostname)
}
+44
View File
@@ -0,0 +1,44 @@
package interceptors
import (
"net/http"
"testing"
"github.com/safedep/pmg/proxy"
"github.com/stretchr/testify/assert"
)
func TestAuditLoggerInterceptor_Behavior(t *testing.T) {
i := NewAuditLoggerInterceptor()
assert.Equal(t, "audit-logger-interceptor", i.Name())
assert.True(t, i.ShouldIntercept(nil))
assert.False(t, i.ShouldMITM(nil))
}
func TestAuditLoggerInterceptor_KnownRegistryHost(t *testing.T) {
i := NewAuditLoggerInterceptor()
resp, err := i.HandleRequest(&proxy.RequestContext{
Hostname: "registry.npmjs.org",
Method: http.MethodConnect,
})
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, proxy.ActionAllow, resp.Action)
}
func TestAuditLoggerInterceptor_UnknownHost(t *testing.T) {
i := NewAuditLoggerInterceptor()
resp, err := i.HandleRequest(&proxy.RequestContext{
Hostname: "unknown.example.test",
Method: http.MethodConnect,
RequestID: "req-unknown",
})
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, proxy.ActionAllow, resp.Action)
}
+18 -4
View File
@@ -212,11 +212,25 @@ func (ps *proxyServer) configureMITM() {
ps.mu.RLock()
shouldMITM := false
for _, interceptor := range ps.interceptors {
if interceptor.ShouldIntercept(reqCtx) {
shouldMITM = true
log.Debugf("[%s] Interceptor %s will handle %s", reqCtx.RequestID, interceptor.Name(), host)
break
if !interceptor.ShouldIntercept(reqCtx) {
continue
}
mitm := true
if decider, ok := interceptor.(MITMDecider); ok {
mitm = decider.ShouldMITM(reqCtx)
}
if !mitm {
// Allow non-MITM interceptors (e.g., telemetry) to observe CONNECT traffic.
if _, err := interceptor.HandleRequest(reqCtx); err != nil {
log.Errorf("[%s] Interceptor %s error on CONNECT: %v", reqCtx.RequestID, interceptor.Name(), err)
}
continue
}
shouldMITM = true
log.Debugf("[%s] Interceptor %s will handle %s", reqCtx.RequestID, interceptor.Name(), host)
}
ps.mu.RUnlock()