mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
Switch the audit cloudSink from the authenticated SyncClientBundle to dry's new emit-only endpointsync.EventEmitterClient. Normal command invocations now persist events to the local cloud-sync WAL without resolving credentials or opening a data-plane connection; delivery still happens only through the authenticated sync paths (pmg cloud sync, sync-background auto-sync, and the proxy daemon drain), which are unchanged. Previously, running with cloud.enabled but no credentials skipped the cloud sink entirely, so no events were recorded. Those events are now staged in the WAL and sync once the user authenticates. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
378 lines
13 KiB
Go
378 lines
13 KiB
Go
package audit
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
controltowerv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/controltower/v1"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// testSink is a cloudSink with no emitter, suitable for testing translation only.
|
|
var testSink = &cloudSink{invocationID: "test-invocation"}
|
|
|
|
func TestTranslateMalwareBlocked(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeMalwareBlocked,
|
|
PackageVersion: testPackageVersion("evil-pkg", "1.2.3", "npm"),
|
|
AnalysisID: "analysis-123",
|
|
IsMalware: true,
|
|
IsVerified: true,
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
result := results[0]
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_PACKAGE_DECISION, result.GetEventType())
|
|
require.True(t, result.HasPackageDecision())
|
|
|
|
decision := result.GetPackageDecision()
|
|
assert.Equal(t, controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_BLOCKED, decision.GetAction())
|
|
assert.Equal(t, "analysis-123", decision.GetAnalysisId())
|
|
assert.True(t, decision.GetIsMalware())
|
|
assert.True(t, decision.GetIsVerified())
|
|
assert.NotNil(t, decision.GetPackageVersion())
|
|
}
|
|
|
|
func TestTranslateMalwareConfirmed(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeMalwareConfirmed,
|
|
PackageVersion: testPackageVersion("suspect-pkg", "2.0.0", "npm"),
|
|
AnalysisID: "analysis-456",
|
|
IsMalware: false,
|
|
IsVerified: false,
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
result := results[0]
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_PACKAGE_DECISION, result.GetEventType())
|
|
require.True(t, result.HasPackageDecision())
|
|
|
|
decision := result.GetPackageDecision()
|
|
assert.Equal(t, controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_CONFIRMED, decision.GetAction())
|
|
assert.Equal(t, "analysis-456", decision.GetAnalysisId())
|
|
assert.False(t, decision.GetIsMalware())
|
|
assert.False(t, decision.GetIsVerified())
|
|
assert.False(t, decision.GetIsVerified())
|
|
}
|
|
|
|
func TestTranslateCooldownSkipped(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeCooldownSkipped,
|
|
PackageVersion: testPackageVersion("exempt-pkg", "1.0.0", "npm"),
|
|
Reason: "dependency_cooldown.skip",
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
result := results[0]
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_PACKAGE_DECISION, result.GetEventType())
|
|
require.True(t, result.HasPackageDecision())
|
|
|
|
decision := result.GetPackageDecision()
|
|
assert.Equal(t, controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_COOLDOWN_SKIPPED, decision.GetAction())
|
|
require.NotNil(t, decision.GetPackageVersion(), "package_version must be propagated to the cloud sink")
|
|
assert.Equal(t, "exempt-pkg", decision.GetPackageVersion().GetPackage().GetName())
|
|
assert.Equal(t, "1.0.0", decision.GetPackageVersion().GetVersion())
|
|
}
|
|
|
|
func TestTranslateInstallTrustedAllowed(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeInstallTrustedAllowed,
|
|
PackageVersion: testPackageVersion("trusted-pkg", "2.0.0", "npm"),
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
result := results[0]
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_PACKAGE_DECISION, result.GetEventType())
|
|
require.True(t, result.HasPackageDecision())
|
|
|
|
decision := result.GetPackageDecision()
|
|
assert.Equal(t, controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_TRUSTED, decision.GetAction())
|
|
require.NotNil(t, decision.GetPackageVersion())
|
|
assert.Equal(t, "trusted-pkg", decision.GetPackageVersion().GetPackage().GetName())
|
|
}
|
|
|
|
func TestTranslateInsecureBypassReturnsEmpty(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeInstallInsecureBypass,
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
assert.Empty(t, results)
|
|
}
|
|
|
|
func TestTranslateSandboxOverride(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeSandboxOverride,
|
|
ProfileName: "strict",
|
|
Overrides: []map[string]string{
|
|
{"read": "/tmp"},
|
|
{"write": "/var/log"},
|
|
},
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
result := results[0]
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_SANDBOX_OVERRIDE, result.GetEventType())
|
|
require.True(t, result.HasSandboxOverride())
|
|
|
|
override := result.GetSandboxOverride()
|
|
assert.Equal(t, "strict", override.GetSandboxProfile())
|
|
assert.ElementsMatch(t, []string{"read:/tmp", "write:/var/log"}, override.GetOverrides())
|
|
}
|
|
|
|
func TestTranslateError(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeError,
|
|
Message: "something went wrong",
|
|
Error: errors.New("connection refused"),
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
result := results[0]
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_ERROR, result.GetEventType())
|
|
require.True(t, result.HasError())
|
|
|
|
pmgErr := result.GetError()
|
|
assert.Equal(t, "*errors.errorString", pmgErr.GetErrorType())
|
|
assert.Equal(t, "something went wrong", pmgErr.GetMessage())
|
|
}
|
|
|
|
func TestTranslateErrorNilError(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeError,
|
|
Message: "unknown issue",
|
|
Error: nil,
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
result := results[0]
|
|
|
|
pmgErr := result.GetError()
|
|
assert.Equal(t, "", pmgErr.GetErrorType())
|
|
assert.Equal(t, "unknown issue", pmgErr.GetMessage())
|
|
}
|
|
|
|
func TestTranslateCooldownBlocked(t *testing.T) {
|
|
publishDate := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
|
event := AuditEvent{
|
|
Type: EventTypeDependencyCooldown,
|
|
PackageVersion: testPackageVersion("new-pkg", "1.0.0", "npm"),
|
|
PublishDate: publishDate,
|
|
CooldownDays: 30,
|
|
DaysAgo: 6,
|
|
DaysLeft: 24,
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
result := results[0]
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_PACKAGE_DECISION, result.GetEventType())
|
|
require.True(t, result.HasPackageDecision())
|
|
|
|
decision := result.GetPackageDecision()
|
|
assert.Equal(t, controltowerv1.PmgPackageAction_PMG_PACKAGE_ACTION_COOLDOWN_BLOCKED, decision.GetAction())
|
|
assert.NotNil(t, decision.GetPackageVersion())
|
|
|
|
require.True(t, decision.HasCooldown())
|
|
cooldown := decision.GetCooldown()
|
|
assert.Equal(t, publishDate.Unix(), cooldown.GetPublishDate().AsTime().Unix())
|
|
assert.Equal(t, uint32(30), cooldown.GetCooldownDays())
|
|
assert.Equal(t, uint32(6), cooldown.GetDaysSincePublish())
|
|
assert.Equal(t, uint32(24), cooldown.GetDaysRemaining())
|
|
}
|
|
|
|
func TestTranslateHostObservation(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeProxyHostObserved,
|
|
Hostname: "evil.example.com",
|
|
Method: "CONNECT",
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
result := results[0]
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_HOST_OBSERVATION, result.GetEventType())
|
|
require.True(t, result.HasHostObservation())
|
|
|
|
obs := result.GetHostObservation()
|
|
assert.Equal(t, "evil.example.com", obs.GetHostname())
|
|
assert.Equal(t, "CONNECT", obs.GetMethod())
|
|
}
|
|
|
|
func TestTranslateUnsupportedEventReturnsEmpty(t *testing.T) {
|
|
unsupported := []EventType{
|
|
EventTypeDependencyResolved,
|
|
EventTypeInstallStarted,
|
|
EventTypeInstallAllowed,
|
|
EventTypeInstallInsecureBypass,
|
|
}
|
|
|
|
for _, et := range unsupported {
|
|
t.Run(string(et), func(t *testing.T) {
|
|
results := testSink.translateToPmgEvents(AuditEvent{Type: et})
|
|
assert.Empty(t, results)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMapPackageManager(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected controltowerv1.PmgPackageManager
|
|
}{
|
|
{"npm", "npm", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_NPM},
|
|
{"npx", "npx", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_NPM},
|
|
{"pnpm", "pnpm", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_PNPM},
|
|
{"pnpx", "pnpx", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_PNPM},
|
|
{"yarn", "yarn", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_YARN},
|
|
{"bun", "bun", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_BUN},
|
|
{"pip", "pip", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_PIP},
|
|
{"pip3", "pip3", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_PIP},
|
|
{"pipx", "pipx", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_PIP},
|
|
{"poetry", "poetry", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_POETRY},
|
|
{"uv", "uv", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_UV},
|
|
{"uvx", "uvx", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_UV},
|
|
{"unknown", "cargo", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_UNSPECIFIED},
|
|
{"empty", "", controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_UNSPECIFIED},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert.Equal(t, tc.expected, mapPackageManager(tc.input))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTranslateSessionComplete(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeSessionComplete,
|
|
SessionData: &SessionData{
|
|
PackageManager: "npm",
|
|
FlowType: FlowTypeProxy,
|
|
Outcome: OutcomeSuccess,
|
|
TotalAnalyzed: 10,
|
|
AllowedCount: 8,
|
|
BlockedCount: 1,
|
|
ConfirmedCount: 1,
|
|
TrustedSkipped: 2,
|
|
InsecureBypassed: 0,
|
|
CooldownBlockedCount: 3,
|
|
Duration: 5 * time.Second,
|
|
SandboxEnabled: true,
|
|
ParanoidMode: false,
|
|
TransitiveEnabled: true,
|
|
},
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 1)
|
|
|
|
summary := results[0].GetSessionSummary()
|
|
require.NotNil(t, summary)
|
|
assert.Equal(t, controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_NPM, summary.GetPackageManager())
|
|
assert.Equal(t, controltowerv1.PmgFlowType_PMG_FLOW_TYPE_PROXY, summary.GetFlowType())
|
|
assert.Equal(t, uint32(10), summary.GetTotalAnalyzed())
|
|
assert.Equal(t, uint32(8), summary.GetAllowedCount())
|
|
assert.Equal(t, uint32(1), summary.GetBlockedCount())
|
|
assert.Equal(t, uint32(1), summary.GetConfirmedCount())
|
|
assert.Equal(t, uint32(2), summary.GetTrustedSkipped())
|
|
assert.Equal(t, uint32(3), summary.GetCooldownBlockedCount())
|
|
assert.True(t, summary.GetSandboxEnabled())
|
|
assert.False(t, summary.GetParanoidMode())
|
|
assert.True(t, summary.GetTransitiveEnabled())
|
|
assert.Equal(t, controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_SUCCESS, summary.GetOutcome())
|
|
}
|
|
|
|
func TestTranslateSessionCompleteWithInsecureBypass(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeSessionComplete,
|
|
SessionData: &SessionData{
|
|
PackageManager: "pip",
|
|
FlowType: FlowTypeGuard,
|
|
Outcome: OutcomeInsecureBypass,
|
|
InsecureBypassed: 3,
|
|
},
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
require.Len(t, results, 2)
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_SESSION_SUMMARY, results[0].GetEventType())
|
|
|
|
assert.Equal(t, controltowerv1.PmgEventType_PMG_EVENT_TYPE_INSECURE_BYPASS, results[1].GetEventType())
|
|
bypass := results[1].GetInsecureBypass()
|
|
require.NotNil(t, bypass)
|
|
assert.Equal(t, controltowerv1.PmgPackageManager_PMG_PACKAGE_MANAGER_PIP, bypass.GetPackageManager())
|
|
assert.Equal(t, uint32(3), bypass.GetPackagesBypassed())
|
|
}
|
|
|
|
func TestTranslateSessionCompleteNilSessionData(t *testing.T) {
|
|
event := AuditEvent{
|
|
Type: EventTypeSessionComplete,
|
|
SessionData: nil,
|
|
}
|
|
|
|
results := testSink.translateToPmgEvents(event)
|
|
assert.Empty(t, results)
|
|
}
|
|
|
|
func TestMapFlowType(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input FlowType
|
|
expected controltowerv1.PmgFlowType
|
|
}{
|
|
{"guard", FlowTypeGuard, controltowerv1.PmgFlowType_PMG_FLOW_TYPE_GUARD},
|
|
{"proxy", FlowTypeProxy, controltowerv1.PmgFlowType_PMG_FLOW_TYPE_PROXY},
|
|
{"unknown", FlowType("other"), controltowerv1.PmgFlowType_PMG_FLOW_TYPE_UNSPECIFIED},
|
|
{"empty", FlowType(""), controltowerv1.PmgFlowType_PMG_FLOW_TYPE_UNSPECIFIED},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert.Equal(t, tc.expected, mapFlowType(tc.input))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMapSessionOutcome(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input Outcome
|
|
expected controltowerv1.PmgSessionOutcome
|
|
}{
|
|
{"success", OutcomeSuccess, controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_SUCCESS},
|
|
{"blocked", OutcomeBlocked, controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_BLOCKED},
|
|
{"user_cancelled", OutcomeUserCancelled, controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_USER_CANCELLED},
|
|
{"error", OutcomeError, controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_ERROR},
|
|
{"dry_run", OutcomeDryRun, controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_DRY_RUN},
|
|
{"insecure_bypass", OutcomeInsecureBypass, controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_INSECURE_BYPASS},
|
|
{"unknown", Outcome("other"), controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_UNSPECIFIED},
|
|
{"empty", Outcome(""), controltowerv1.PmgSessionOutcome_PMG_SESSION_OUTCOME_UNSPECIFIED},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert.Equal(t, tc.expected, mapSessionOutcome(tc.input))
|
|
})
|
|
}
|
|
}
|