# check_app_permissions

Checks what system permissions an application has been granted (Full Disk Access, Accessibility, Camera, Microphone, etc.). Use before reinstalling software or when an app reports permission errors.

## Metadata

#### Name

check_app_permissions

#### Updated

2 months ago

#### Source

[GitHub source](https://github.com/idemeum/skills/blob/main/checkAppPermissions.ts)

#### Risk

Low

#### Requires consent

false

#### Affected scope

user

## Code

```
/**
 * mcp/skills/checkAppPermissions.ts — check_app_permissions skill
 *
 * Checks what system permissions an application has been granted (Full Disk
 * Access, Accessibility, Camera, Microphone, etc.). Use before reinstalling
 * software or when an app reports permission errors.
 *
 * Platform strategy
 * -----------------
 * darwin  sqlite3 query against ~/Library/Application Support/com.apple.TCC/TCC.db
 * win32   PowerShell registry query HKCU:\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager
 *
 * Smoke test
 *   npx tsx -r dotenv/config mcp/skills/checkAppPermissions.ts "CrowdStrike Falcon"
 */

import * as os       from "os";
import * as nodePath from "path";
import { exec }      from "child_process";
import { promisify } from "util";
import { z }         from "zod";

import { TCC_CATEGORIES } from "./_shared/tcc";

const execAsync = promisify(exec);

// -- Meta ---------------------------------------------------------------------

export const meta = {
  name: "check_app_permissions",
  description:
    "Checks what system permissions an application has been granted " +
    "(Full Disk Access, Accessibility, Camera, Microphone, etc.). " +
    "Use before reinstalling software or when an app reports permission errors.",
  riskLevel:       "low",
  destructive:     false,
  requiresConsent: false,
  supportsDryRun:  false,
  affectedScope:   ["user"],
  auditRequired:   false,
  schema: {
    appName: z
      .string()
      .describe("Application name to check (e.g. 'CrowdStrike Falcon', 'Jamf Connect')"),
  },
} as const;

// -- Types --------------------------------------------------------------------

interface PermissionEntry {
  category: string;
  status:   "allowed" | "denied" | "unknown";
}

interface PermissionsResult {
  appName:     string;
  platform:    string;
  permissions: PermissionEntry[];
  error?:      string;
}

// -- 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: 10 * 1024 * 1024 },
  );
  return stdout.trim();
}

// -- darwin implementation ----------------------------------------------------

async function checkAppPermissionsDarwin(appName: string): Promise<PermissionsResult> {
  if (!/^[a-zA-Z0-9 _\-.']+$/.test(appName)) {
    throw new Error(`[check_app_permissions] Invalid appName: ${appName}`);
  }

const tccDb = nodePath.join(
    os.homedir(),
    "Library",
    "Application Support",
    "com.apple.TCC",
    "TCC.db",
  );

const safeAppName = appName.replace(/'/g, "''");
  const query = `SELECT service, client, auth_value FROM access WHERE LOWER(client) LIKE LOWER('%${safeAppName}%') OR LOWER(policy_id) LIKE LOWER('%${safeAppName}%');`;

try {
    const { stdout } = await execAsync(
      `sqlite3 '${tccDb.replace(/'/g, "'\\''")}' "${query}" 2>&1`,
      { maxBuffer: 5 * 1024 * 1024 },
    );

const rows = stdout.trim().split("\n").filter(Boolean);

if (rows.length === 0) {
      return {
        appName,
        platform:    "darwin",
        permissions: [],
        error:       "No TCC entries found for this application. App may not have requested permissions yet, or requires elevated access to read system TCC database.",
      };
    }

const permissions: PermissionEntry[] = rows.map((row) => {
      const parts     = row.split("|");
      const authValue = parseInt(parts[2] ?? "0", 10);
      const status: PermissionEntry["status"] =
        authValue === 2 ? "allowed" :
        authValue === 0 ? "denied"  :
        "unknown";
      return {
        category: TCC_CATEGORIES[parts[0] ?? ""] ?? parts[0],
        status,
      };
    });

return { appName, platform: "darwin", permissions };
  } catch (err) {
    const systemTccDb = "/Library/Application Support/com.apple.TCC/TCC.db";
    try {
      const { stdout } = await execAsync(
        `sqlite3 '${systemTccDb}' "${query}" 2>&1`,
        { maxBuffer: 5 * 1024 * 1024 },
      );
      const rows = stdout.trim().split("\n").filter(Boolean);
      const permissions: PermissionEntry[] = rows.map((row) => {
        const parts     = row.split("|");
        const authValue = parseInt(parts[2] ?? "0", 10);
        const status: PermissionEntry["status"] =
          authValue === 2 ? "allowed" :
          authValue === 0 ? "denied"  :
          "unknown";
        return { category: TCC_CATEGORIES[parts[0] ?? ""] ?? parts[0], status };
      });
      return { appName, platform: "darwin", permissions };
    } catch {
      return {
        appName,
        platform:    "darwin",
        permissions: [],
        error:       `Could not read TCC database: ${(err as Error).message}. Full Disk Access may be required.`,
      };
    }
  }
}

// -- win32 implementation -----------------------------------------------------

const WIN_CAPABILITIES = [
  "webcam",
  "microphone",
  "location",
  "contacts",
  "calendar",
  "phoneCall",
  "email",
  "appointments",
  "userAccountInformation",
  "radios",
  "bluetoothSync",
  "appDiagnostics",
  "gazeInput",
  "broadFileSystemAccess",
];

async function checkAppPermissionsWin32(appName: string): Promise<PermissionsResult> {
  if (!/^[a-zA-Z0-9 _\-.']+$/.test(appName)) {
    throw new Error(`[check_app_permissions] Invalid appName: ${appName}`);
  }

const safeApp = appName.replace(/'/g, "''");
  const caps    = WIN_CAPABILITIES.map((c) => `'${c}'`).join(",");

const ps = `
$ErrorActionPreference = 'SilentlyContinue'
$basePath = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore'
$results = @()
foreach ($cap in @(${caps})) {
  $capPath = "$basePath\$cap"
  if (Test-Path $capPath) {
    $apps = Get-ChildItem -Path $capPath -ErrorAction SilentlyContinue
    foreach ($app in $apps) {
      if ($app.PSChildName -like "*${safeApp}*") {
        $val = (Get-ItemProperty -Path $app.PSPath -Name 'Value' -ErrorAction SilentlyContinue).Value
        $results += [PSCustomObject]@{
          category = $cap
          status   = if ($val -eq 'Allow') { 'allowed' } elseif ($val -eq 'Deny') { 'denied' } else { 'unknown' }
        }
      }
    }
  }
}
$results | ConvertTo-Json -Depth 2 -Compress`.trim();

try {
    const raw = await runPS(ps);
    if (!raw || raw === "null") {
      return { appName, platform: "win32", permissions: [], error: "No capability entries found for this application." };
    }
    const parsed      = JSON.parse(raw) as PermissionEntry | PermissionEntry[];
    const permissions = Array.isArray(parsed) ? parsed : [parsed];
    return { appName, platform: "win32", permissions };
  } catch (err) {
    return {
      appName,
      platform:    "win32",
      permissions: [],
      error:       (err as Error).message,
    };
  }
}

// -- Exported run function ----------------------------------------------------

export async function run({ appName }: { appName: string }) {
  const platform = os.platform();
  return platform === "win32"
    ? checkAppPermissionsWin32(appName)
    : checkAppPermissionsDarwin(appName);
}

// -- CLI smoke test -----------------------------------------------------------

if (false) {
  run({ appName: process.argv[2] ?? "Slack" })
    .then((r) => console.log(JSON.stringify(r, null, 2)))
    .catch((err: Error) => { console.error(err.message); process.exit(1); });
}
```
