/**
* mcp/skills/checkSystemExtension.ts — check_system_extension skill
*
* Verifies that security agent system extensions are loaded and user-approved.
* Security agents require system extensions to function at the kernel level.
* Unapproved extensions appear installed but are non-functional.
* macOS only for system extensions; Windows checks driver/service status.
*
* Platform strategy
* -----------------
* darwin `systemextensionsctl list` — parse tabular output
* win32 PowerShell Get-MpComputerStatus and Get-Service
*
* Smoke test
* npx tsx -r dotenv/config mcp/skills/checkSystemExtension.ts
*/
import * as os from "os" ;
import { exec } from "child_process" ;
import { promisify } from "util" ;
import { z } from "zod" ;
const execAsync = promisify (exec);
// -- Meta ---------------------------------------------------------------------
export const meta = {
name: "check_system_extension" ,
description:
"Verifies that security agent system extensions are loaded and user-approved. " +
"Security agents require system extensions to function at the kernel level. " +
"Unapproved extensions appear installed but are non-functional. " +
"macOS only for system extensions; Windows checks driver/service status." ,
riskLevel: "low" ,
destructive: false ,
requiresConsent: false ,
supportsDryRun: false ,
affectedScope: [ "user" ],
auditRequired: false ,
schema: {
bundleId: z
. string ()
. optional ()
. describe (
"Extension bundle ID to check (e.g. 'com.crowdstrike.falcon.Agent'). " +
"Omit to list all security-related extensions" ,
),
},
} as const ;
// -- Types --------------------------------------------------------------------
interface ExtensionEntry {
teamId : string ;
bundleId : string ;
version : string ;
state : string ;
isActive : boolean ;
requiresApproval : boolean ;
}
// -- PowerShell helper --------------------------------------------------------
async function runPS ( script : string ) : Promise < string > {
const encoded = Buffer. from (script, "utf16le" ). toString ( "base64" );
const { stdout } = await execAsync (
`powershell.exe -NoProfile -NonInteractive -EncodedCommand ${ encoded }` ,
{ maxBuffer: 20 * 1024 * 1024 },
);
return stdout. trim ();
}
// -- darwin implementation ----------------------------------------------------
async function checkSystemExtensionDarwin (
bundleId ?: string ,
) : Promise <{ platform : string ; extensions : ExtensionEntry []; allActive : boolean }> {
let stdout = "" ;
try {
({ stdout } = await execAsync ( "systemextensionsctl list 2>/dev/null" , {
maxBuffer: 2 * 1024 * 1024 ,
}));
} catch (err) {
stdout = (err as { stdout ?: string }).stdout ?? "" ;
}
const lines = stdout. trim (). split ( " \n " );
const extensions : ExtensionEntry [] = [];
// Each data line looks like:
// <teamId> * <bundleId> (version) [state]
// e.g.:
// X9E956P446 * com.crowdstrike.falcon.Agent (21.10.8813.0) [activated enabled]
const lineRe = / ^ ( \S + ) \s + [ \* \s]\s + ( \S + ) \s + \( ( [ ^ )] + ) \) \s + \[ ( [ ^ \] ] + ) \] / ;
for ( const line of lines) {
const m = line. trim (). match (lineRe);
if ( ! m) continue ;
const [, teamId , bid , version , state ] = m;
if (bundleId && bid !== bundleId) continue ;
// Filter to security-related extensions when no bundleId specified
const securityKeywords = [
"crowdstrike" , "sentinelone" , "jamf" , "carbonblack" , "cylance" ,
"defender" , "microsoft" , "security" , "endpoint" , "falcon" , "sentinel" ,
];
if ( ! bundleId) {
const lower = bid. toLowerCase ();
if ( ! securityKeywords. some (( kw ) => lower. includes (kw))) continue ;
}
const isActive = state. includes ( "activated" ) && state. includes ( "enabled" );
const requiresApproval = state. includes ( "waiting for user" );
extensions. push ({ teamId, bundleId: bid, version, state, isActive, requiresApproval });
}
return {
platform: "darwin" ,
extensions,
allActive: extensions. length > 0 && extensions. every (( e ) => e.isActive),
};
}
// -- win32 implementation -----------------------------------------------------
async function checkSystemExtensionWin32 (
_bundleId ?: string ,
) : Promise <{ platform : string ; extensions : ExtensionEntry []; allActive : boolean }> {
const ps = `
$ErrorActionPreference = 'SilentlyContinue'
$mpStatus = Get-MpComputerStatus | Select-Object AMRunningMode,AntispywareEnabled,RealTimeProtectionEnabled,NISEnabled
$secSvcs = Get-Service | Where-Object { $_.DisplayName -match 'security|endpoint|falcon|sentinel|defender|crowdstrike' } |
Select-Object Name,DisplayName,Status
[PSCustomObject]@{
mpStatus = $mpStatus
services = @($secSvcs)
} | ConvertTo-Json -Depth 3 -Compress` . trim ();
const raw = await runPS (ps);
const extensions : ExtensionEntry [] = [];
if (raw) {
try {
const parsed = JSON . parse (raw) as {
mpStatus ?: { AMRunningMode ?: string ; AntispywareEnabled ?: boolean ; RealTimeProtectionEnabled ?: boolean };
services ?: Array <{ Name : string ; DisplayName : string ; Status : string }>;
};
if (parsed.mpStatus) {
const mp = parsed.mpStatus;
const state = mp.RealTimeProtectionEnabled ? "active" : "inactive" ;
extensions. push ({
teamId: "Microsoft" ,
bundleId: "com.microsoft.defender" ,
version: mp.AMRunningMode ?? "unknown" ,
state,
isActive: !! mp.RealTimeProtectionEnabled,
requiresApproval: false ,
});
}
for ( const svc of parsed.services ?? []) {
const isActive = svc.Status === "Running" ;
extensions. push ({
teamId: "service" ,
bundleId: svc.Name,
version: svc.DisplayName,
state: svc.Status,
isActive,
requiresApproval: false ,
});
}
} catch {
// JSON parse failure — return empty
}
}
return {
platform: "win32" ,
extensions,
allActive: extensions. length > 0 && extensions. every (( e ) => e.isActive),
};
}
// -- Exported run function ----------------------------------------------------
export async function run ({
bundleId ,
} : {
bundleId ?: string ;
} = {}) {
const platform = os. platform ();
return platform === "win32"
? checkSystemExtensionWin32 (bundleId)
: checkSystemExtensionDarwin (bundleId);
}
// -- CLI smoke test -----------------------------------------------------------
if ( false ) {
run ({})
. then ( r => console. log ( JSON . stringify (r, null , 2 )))
. catch (( err : Error ) => { console. error (err.message); process. exit ( 1 ); });
}