Files
fleet/orbit/pkg/table/adobe_plugins/adobe_plugins_windows.go
Sharon Katz 7d26e7e475 Add adobe_plugins osquery extension table (#45208)
Closes #45206

## Summary

- Adds a new `adobe_plugins` osquery extension table to fleetd (macOS +
Windows)
- Parses CEP (`CSXS/manifest.xml`) and UXP (`manifest.json`) manifests
for rich metadata
- Falls back to filesystem info for native plug-ins where no manifest
exists
- Supports a `scan_level` WHERE constraint: `standard` (default) or
`deep`

## Table schema

| Column | Type | Description |
| --- | --- | --- |
| `path` | TEXT | Full path to the plugin directory or file |
| `name` | TEXT | Plugin display name (from manifest or directory name)
|
| `version` | TEXT | Plugin version (from manifest) |
| `vendor` | TEXT | Plugin author/publisher (from manifest) |
| `bundle_id` | TEXT | Plugin bundle identifier (from manifest) |
| `host_application` | TEXT | Target app(s): Photoshop, Illustrator,
Premiere Pro, etc. |
| `extension_type` | TEXT | `CEP`, `UXP`, or `native` |
| `user` | TEXT | Username for user-scoped installs; empty for
system-wide |
| `platform` | TEXT | `darwin` or `windows` |
| `scan_level` | TEXT | WHERE constraint only — `standard` (default) or
`deep` |

## How I tested it

> **Note:** Manual testing was done by installing two real open-source
CEP extensions (downloaded from GitHub) on a macOS host without a full
Adobe CC installation. This validates the table logic, manifest parsing,
and osquery integration end-to-end against real-world manifest formats.
**QA should test against machines with full Adobe Creative Cloud
installations** (Photoshop, Premiere, Illustrator, etc.) to verify the
scan paths match what Adobe actually ships, and to exercise `scan_level
= 'deep'` with real native plug-in directories. Expect a few more dev
cycles after QA feedback.

### 1. Unit tests — 22 passing

```
$ go test ./orbit/pkg/table/adobe_plugins/... -v
--- PASS: TestParseCEPPlugin/valid_manifest
--- PASS: TestParseCEPPlugin/missing_manifest_falls_back_to_dir_name
--- PASS: TestParseCEPPlugin/malformed_manifest_falls_back_to_dir_name
--- PASS: TestParseUXPPlugin/valid_manifest
--- PASS: TestParseUXPPlugin/missing_manifest_falls_back_to_dir_name
--- PASS: TestParseUXPPlugin/manifest_with_id_but_no_name_uses_id
--- PASS: TestParseNativePlugin/* (5 subtests)
--- PASS: TestResolveHostApps/* (7 subtests)
--- PASS: TestScanEntry/* (2 subtests)
PASS
```

### 2. Cross-platform compilation

```
$ go build ./orbit/pkg/table/adobe_plugins/...                  # macOS 
$ GOOS=windows go build ./orbit/pkg/table/adobe_plugins/...     # Windows 
$ GOOS=linux go build ./orbit/pkg/table/adobe_plugins/...       # Linux stub 
$ go build ./orbit/cmd/fleetd_tables/                           # Full fleetd binary 
$ go vet ./orbit/pkg/table/adobe_plugins/...                    # Clean 
```

### 3. Manual end-to-end testing on macOS (osquery 5.23.0)

#### Setup

Built the fleetd extension binary, then installed two **real open-source
CEP extensions** from GitHub into the user-scoped scan path
(`~/Library/Application Support/Adobe/CEP/extensions/`):

1. **[adobe-discord-rpc](https://github.com/Kuredew/adobe-discord-rpc)**
— a real CEP extension targeting 11 Adobe apps. Has no `<Author>`
element (tests missing-vendor edge case). Complex manifest with many
host app codes.

2. **[cep-template](https://github.com/khanyuinc/cep-template)** — a CEP
starter template targeting After Effects only. Minimal manifest.

```bash
# Build extension
go build -o build/fleetd-tables-test ./orbit/cmd/fleetd_tables/

# Install real extensions
CEP_DIR="$HOME/Library/Application Support/Adobe/CEP/extensions"
mkdir -p "$CEP_DIR/adobe-discord-rpc/CSXS"
# downloaded CSXS/manifest.xml from GitHub into the directory
mkdir -p "$CEP_DIR/cep-template/CSXS"
# downloaded CSXS/manifest.xml from GitHub into the directory
```

#### Running the query

```bash
OSQUERYD="/opt/orbit/bin/osqueryd/macos-app/stable/osquery.app/Contents/MacOS/osqueryd"
$OSQUERYD -S --allow_unsafe --extensions_timeout=10 \
  --extensions_require=com.fleetdm.fleetd_tables.osquery_extension.v1 \
  --extension build/fleetd-tables-test \
  --json "SELECT * FROM adobe_plugins;"
```

#### Actual output (verbatim)

```json
[
  {
    "bundle_id": "com.kureichi.discordrpc",
    "extension_type": "CEP",
    "host_application": "After Effects, Photoshop, Premiere Pro, InCopy, Audition, Dreamweaver, Animate, InDesign, Illustrator, Prelude",
    "name": "adobe-discord-rpc",
    "path": "/Users/sharonkatz/Library/Application Support/Adobe/CEP/extensions/adobe-discord-rpc",
    "platform": "darwin",
    "scan_level": "",
    "user": "sharonkatz",
    "vendor": "",
    "version": "3.1.1"
  },
  {
    "bundle_id": "com.yourcompany",
    "extension_type": "CEP",
    "host_application": "After Effects",
    "name": "cep-template",
    "path": "/Users/sharonkatz/Library/Application Support/Adobe/CEP/extensions/cep-template",
    "platform": "darwin",
    "scan_level": "",
    "user": "sharonkatz",
    "vendor": "",
    "version": "1.0"
  }
]
```

#### osqueryi table output

```
+-------------------+---------+-------------------------+----------------------------------------------------------------------------------------------------------------+----------------+------------+
| name              | version | bundle_id               | host_application                                                                                               | extension_type | user       |
+-------------------+---------+-------------------------+----------------------------------------------------------------------------------------------------------------+----------------+------------+
| adobe-discord-rpc | 3.1.1   | com.kureichi.discordrpc | After Effects, Photoshop, Premiere Pro, InCopy, Audition, Dreamweaver, Animate, InDesign, Illustrator, Prelude | CEP            | sharonkatz |
| cep-template      | 1.0     | com.yourcompany         | After Effects                                                                                                  | CEP            | sharonkatz |
+-------------------+---------+-------------------------+----------------------------------------------------------------------------------------------------------------+----------------+------------+
```

#### What this verified

| Scenario | Result |
| --- | --- |
| Real CEP manifest with 11 host apps |  All codes resolved (AEFT→After
Effects, PHSP/PHXS→Photoshop, PPRO→Premiere Pro, etc.) |
| Missing `<Author>` element |  `vendor` is empty string, no crash |
| Minimal CEP manifest (single host) |  `host_application=After
Effects`, version/bundle_id correct |
| User-scoped detection |  `user=sharonkatz` populated |
| Schema registration |  `.schema adobe_plugins` shows all 10 columns |
| No Adobe installed + no plugins |  0 rows, no error |
| Deep scan with no app bundles |  0 extra rows, no error |

### Windows

Not tested yet — Windows paths are implemented and cross-compile, but
need manual verification on a Windows host with Adobe CC.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Added Adobe plugins osquery table for macOS and Windows platforms
  * Discovers and catalogs Adobe CEP, UXP, and native plugins
* Extracts plugin metadata including version, vendor, host applications,
and installation paths
  * Supports configurable scan depth for comprehensive plugin discovery

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45208)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-14 14:46:48 -04:00

110 lines
2.4 KiB
Go

//go:build windows
package adobe_plugins
import (
"os"
"path/filepath"
"strings"
"github.com/rs/zerolog"
)
func getScanPaths(level string, logger zerolog.Logger) ([]scanPath, error) {
var paths []scanPath
// System-wide CEP extensions
paths = append(paths, scanPath{
basePath: `C:\Program Files\Common Files\Adobe\CEP\extensions`,
extensionType: "CEP",
})
paths = append(paths, scanPath{
basePath: `C:\Program Files (x86)\Common Files\Adobe\CEP\extensions`,
extensionType: "CEP",
})
// System-wide UXP extensions
paths = append(paths, scanPath{
basePath: `C:\Program Files\Common Files\Adobe\UXP\extensions`,
extensionType: "UXP",
})
// Per-user CEP and UXP extensions
users, err := listLocalUsers()
if err != nil {
logger.Warn().Err(err).Msg("failed to enumerate local users, skipping per-user paths")
}
for _, u := range users {
paths = append(paths, scanPath{
basePath: filepath.Join(u.homeDir, "AppData", "Roaming", "Adobe", "CEP", "extensions"),
extensionType: "CEP",
user: u.name,
})
paths = append(paths, scanPath{
basePath: filepath.Join(u.homeDir, "AppData", "Roaming", "Adobe", "UXP", "extensions"),
extensionType: "UXP",
user: u.name,
})
}
if level == "deep" {
paths = append(paths,
scanPath{
basePath: `C:\Program Files\Adobe\Adobe Photoshop *\Plug-ins`,
extensionType: "native",
hostApp: "Photoshop",
},
scanPath{
basePath: `C:\Program Files\Adobe\Adobe Premiere Pro *\Plug-ins`,
extensionType: "native",
hostApp: "Premiere Pro",
},
scanPath{
basePath: `C:\Program Files\Adobe\Adobe After Effects *\Plug-ins`,
extensionType: "native",
hostApp: "After Effects",
},
)
}
return paths, nil
}
type localUser struct {
name string
homeDir string
}
func listLocalUsers() ([]localUser, error) {
entries, err := os.ReadDir(`C:\Users`)
if err != nil {
return nil, err
}
skipNames := map[string]struct{}{
"public": {},
"default": {},
"default user": {},
"all users": {},
}
var users []localUser
for _, e := range entries {
if !e.IsDir() {
continue
}
name := e.Name()
if strings.HasPrefix(name, ".") {
continue
}
if _, skip := skipNames[strings.ToLower(name)]; skip {
continue
}
users = append(users, localUser{
name: name,
homeDir: filepath.Join(`C:\Users`, name),
})
}
return users, nil
}