Skip to content

Conversation

@sharvilshah
Copy link

@sharvilshah sharvilshah commented Feb 2, 2026

SNT-260

Test Plan:

  • Manual Test
  • Unit Test

This is how these events look look:

➜  storedsyncs cat santa.sync.v2.EventUploadRequest.1770060012.016283.json | jq .
{
  "machine_id": "96E2B8A7-A512-5504-B1E6-CFC025C68CB1",
  "usb_mount_events": [
    {
      "uuid": "0E57C8A1-8CD9-4449-B9DC-BA97DD6AF863",
      "device_model": "Extreme 55DD",
      "device_vendor": "SanDisk",
      "mount_on": "/Volumes/laptop"
    }
  ]
}
➜  storedsyncs cat santa.sync.v2.EventUploadRequest.1770061719.689881.json | jq .
{
  "machine_id": "96E2B8A7-A512-5504-B1E6-CFC025C68CB1",
  "usb_mount_events": [
    {
      "uuid": "25B8D05B-F4F5-4CB6-B0BB-0AF810B1D60F",
      "device_model": "SanDisk 3.2Gen1",
      "device_vendor": "USB",
      "mount_on": "/Volumes/sharvil"
    }
  ]
}

@github-actions github-actions bot added comp/santad Issues or PRs related to the daemon comp/santasyncservice Issues or PRs related to the sync protocol lang/objc++ PRs modifying files in ObjC++ comp/common size/m Size: medium labels Feb 2, 2026
@sharvilshah sharvilshah marked this pull request as ready for review February 2, 2026 21:15
@sharvilshah sharvilshah requested a review from a team as a code owner February 2, 2026 21:15
@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

📝 Walkthrough

Walkthrough

This PR introduces USB mount event emission by creating a new SNTStoredUSBMountEvent model class, integrating it into the device manager to capture USB mount details, and extending the sync service to upload these events to servers.

Changes

Cohort / File(s) Summary
Module & Dependency Updates
MODULE.bazel, Source/santad/BUILD, Source/santasyncservice/BUILD
Updated protos commit hash and added SNTStoredUSBMountEvent dependencies to Santad and sync service build targets.
USB Mount Event Model
Source/common/SNTStoredUSBMountEvent.h, Source/common/SNTStoredUSBMountEvent.mm, Source/common/BUILD
Introduced SNTStoredUSBMountEvent class as a subclass of SNTStoredEvent with properties for uuid, deviceModel, deviceVendor, and mountOnName; implements NSSecureCoding with custom init, encode/decode, and uniqueID logic using mountOnName as deduplication key.
USB Mount Event Tests
Source/common/SNTStoredUSBMountEventTest.mm
Added comprehensive unit tests covering UUID generation, unique ID behavior, encode/decode with secure coding, nil value handling, and string representation.
Device Manager Integration
Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.h, Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.mm
Added SNTUSBMountCallback type and usbMountCallback property; implemented USB mount event creation with device model/vendor details from disk inspection and callback invocation in handleAuthDeviceMount.
Service Callback Registration
Source/santad/Santad.mm
Registered usbMountCallback on device_client to forward SNTStoredUSBMountEvent instances to syncd_queue via addStoredEvent.
Sync Event Upload
Source/santasyncservice/SNTSyncEventUpload.mm
Added USBMountEvent message handling in V2 sync path, including MessageForUSBMountEvent creation, USB event population from SNTStoredUSBMountEvent, batch integration, and network upload serialization.

Sequence Diagram

sequenceDiagram
    actor Device as Device (USB Mount)
    participant DevMgr as Device Manager
    participant Santad as Santad Service
    participant Queue as Event Queue
    participant SyncService as Sync Service
    participant Server as Sync Server

    Device->>DevMgr: USB device detected
    DevMgr->>DevMgr: Create SNTStoredUSBMountEvent<br/>(uuid, model, vendor, mountName)
    DevMgr->>Santad: usbMountCallback(event)
    Santad->>Queue: addStoredEvent(event)
    Note over Queue: Event stored locally
    Queue->>SyncService: Event retrieval
    SyncService->>SyncService: Create USBMountEvent<br/>MessageForUSBMountEvent()
    SyncService->>SyncService: Populate batch with<br/>USB mount events
    SyncService->>Server: Upload event batch (V2)
    Server->>Server: Process USB mount event
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'sync: emit events for USB mount blocks' accurately summarizes the main objective of the changeset, which is to emit USB mount events to sync servers.
Description check ✅ Passed The description is related to the changeset, referencing SNT-260 and providing test plan details and example JSON output of the USB mount events being emitted.
Linked Issues check ✅ Passed The changeset fully implements the objective from SNT-260 to emit USB blocking events by adding SNTStoredUSBMountEvent infrastructure and integrating it into sync event upload flow [SNT-260].
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing USB mount event emission. The MODULE.bazel commit update appears to be a dependency update supporting this feature and is in-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sbs/snt-260-emit-events-for-usb-mount-blocks

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@Source/common/SNTStoredUSBMountEventTest.mm`:
- Around line 65-69: The secure unarchive call only whitelists
SNTStoredUSBMountEvent but must include every concrete class in the object
graph; update the allowedClasses NSSet used with unarchivedObjectOfClasses to
also include NSString, NSNumber, and NSDate (and any other concrete classes used
by SNTStoredUSBMountEvent or its superclass SNTStoredEvent) so secure decoding
succeeds; ensure both occurrences (the unarchive at places referencing
SNTStoredUSBMountEvent, e.g., the block using unarchivedObjectOfClasses and the
similar block later around the other test) are updated to include these classes.
🧹 Nitpick comments (1)
Source/common/SNTStoredUSBMountEvent.mm (1)

62-65: Consider a UUID fallback when mountOnName is missing.
Returning "(null)" can collapse dedupe keys if mountOnName is nil; a UUID fallback avoids silent coalescing.

♻️ Suggested fallback to avoid dedupe collisions
-  return [NSString stringWithFormat:@"%@", self.mountOnName];
+  if (self.mountOnName.length > 0) {
+    return self.mountOnName;
+  }
+  return self.uuid ?: @"";

Comment on lines +65 to +69
// Unarchive the event
NSSet *allowedClasses = [NSSet setWithObject:[SNTStoredUSBMountEvent class]];
SNTStoredEvent *unarchivedEvent = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowedClasses
fromData:archivedEvent
error:nil];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the relevant files
find . -name "SNTStoredUSBMountEvent*" -type f

Repository: northpolesec/santa

Length of output: 190


🏁 Script executed:

# Get the test file content around the mentioned lines
cat -n Source/common/SNTStoredUSBMountEventTest.mm | sed -n '60,100p'

Repository: northpolesec/santa

Length of output: 2312


🏁 Script executed:

# Find SNTStoredUSBMountEvent implementation
find . -name "SNTStoredUSBMountEvent.h" -o -name "SNTStoredUSBMountEvent.m" | head -5

Repository: northpolesec/santa

Length of output: 102


🏁 Script executed:

# Read SNTStoredUSBMountEvent header
cat -n Source/common/SNTStoredUSBMountEvent.h

Repository: northpolesec/santa

Length of output: 1416


🏁 Script executed:

# Read SNTStoredUSBMountEvent implementation
cat -n Source/common/SNTStoredUSBMountEvent.mm

Repository: northpolesec/santa

Length of output: 2336


🏁 Script executed:

# Check CoderMacros.h to understand the DECODE macro
find . -name "CoderMacros.h" -exec cat -n {} \;

Repository: northpolesec/santa

Length of output: 4886


🏁 Script executed:

# Check if SNTStoredEvent (the parent class) also has NSString properties
find . -name "SNTStoredEvent.h" -exec cat -n {} \;

Repository: northpolesec/santa

Length of output: 1650


🏁 Script executed:

# Check the SNTStoredEvent implementation to see its encoding/decoding
find . -name "SNTStoredEvent.mm" -o -name "SNTStoredEvent.m" | head -1 | xargs cat -n 2>/dev/null || echo "No implementation found"

Repository: northpolesec/santa

Length of output: 2339


🌐 Web query:

NSKeyedUnarchiver unarchivedObjectOfClasses NSString allowedClasses secure coding

💡 Result:

When you use NSKeyedUnarchiver.unarchivedObject(ofClasses:from:) / +unarchivedObjectOfClasses:fromData:error:, the allowed-classes set must include every concrete class that might be decoded anywhere in the object graph, not just the top-level container. If it doesn’t, you’ll see errors like “value for key NS.objects was of unexpected class …” or warnings that a “safe plist type” like NSString was decoded even though it wasn’t listed (and that this “will be disallowed in the future”). [1], [2], [3]

Typical fix (NSString missing from allowedClasses)

Swift

let allowed: [AnyClass] = [NSArray.self, NSString.self]   // plus whatever else is inside
let obj = try NSKeyedUnarchiver.unarchivedObject(ofClasses: Set(allowed), from: data)

[1], [2]

Objective-C

NSSet *allowed = [NSSet setWithObjects:NSArray.class, NSString.class, nil];
NSError *error = nil;
id obj = [NSKeyedUnarchiver unarchivedObjectOfClasses:allowed fromData:data error:&error];

[1], [2]

What to put in allowedClasses

Include:

  • The root class you expect back (e.g., NSArray, NSDictionary, your model class)
  • Any nested container classes (NSArray, NSDictionary, NSSet, etc.)
  • Any plist/scalar object classes that appear inside (NSString, NSNumber, NSData, NSDate, etc.)
  • Any custom classes stored inside the containers

Apple’s forums specifically call out that if you decode an NSArray that contains custom objects, you must use unarchivedObjectOfClasses: and include the contained classes too (and this applies recursively for nested collections). [3]

Avoid “broad” classes like [NSObject class] as an allowed class; Foundation warns this bypasses the security model and may become an error. [4]

Sources: Apple method doc page (signature/availability) [1]; iOS 15+ “safe plist type ‘NSString’ missing from allowed classes” warning discussion [2]; Apple Developer Forums explanation that you must include contained classes for collections [3]; Foundation warning about allowing NSObject (security bypass) [4].


Add all required classes to allowedClasses for secure unarchiving.

The unarchivedObjectOfClasses method requires every concrete class in the entire object graph to be whitelisted. The current code only allows SNTStoredUSBMountEvent, but decoding will attempt to instantiate NSString (from the four NSString properties), NSNumber (from the parent's idx), and NSDate (from the parent's occurrenceDate). All must be included in allowedClasses or secure decoding will fail.

Proposed fix
-  NSSet *allowedClasses = [NSSet setWithObject:[SNTStoredUSBMountEvent class]];
+  NSSet *allowedClasses = [NSSet setWithObjects:[SNTStoredUSBMountEvent class],
+                           [NSString class], [NSNumber class], [NSDate class], nil];

Also applies to: 95-98

🤖 Prompt for AI Agents
In `@Source/common/SNTStoredUSBMountEventTest.mm` around lines 65 - 69, The secure
unarchive call only whitelists SNTStoredUSBMountEvent but must include every
concrete class in the object graph; update the allowedClasses NSSet used with
unarchivedObjectOfClasses to also include NSString, NSNumber, and NSDate (and
any other concrete classes used by SNTStoredUSBMountEvent or its superclass
SNTStoredEvent) so secure decoding succeeds; ensure both occurrences (the
unarchive at places referencing SNTStoredUSBMountEvent, e.g., the block using
unarchivedObjectOfClasses and the similar block later around the other test) are
updated to include these classes.

/// limitations under the License.

#import "Source/common/SNTStoredUSBMountEvent.h"
#include <Foundation/Foundation.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <Foundation/Foundation.h>
#include <Foundation/Foundation.h>

Or remove this import, it's not needed as it's already imported in the header.

Comment on lines +54 to +55
return [NSString stringWithFormat:@"SNTStoredUSBMountEvent[%@]: %@, By: %@", self.idx,
self.deviceModel, self.mountOnName];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extreme 55DD, By: /Volumes/laptop reads a bit odd. How about this?

Suggested change
return [NSString stringWithFormat:@"SNTStoredUSBMountEvent[%@]: %@, By: %@", self.idx,
self.deviceModel, self.mountOnName];
return [NSString stringWithFormat:@"SNTStoredUSBMountEvent[%@]: %@ %@ on: %@", self.idx,
self.deviceVendor, self.deviceModel, self.mountOnName];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/common comp/santad Issues or PRs related to the daemon comp/santasyncservice Issues or PRs related to the sync protocol lang/objc++ PRs modifying files in ObjC++ size/m Size: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants