Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ go_library(
deps = [
"//tables/alt_system_info",
"//tables/authdb",
"//tables/carafe",
"//tables/chromeuserprofiles",
"//tables/crowdstrike_falcon",
"//tables/energyimpact",
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ For production deployment, you should refer to the [osquery documentation](https
|------------------------------| --------------------------------------------------------------------------------------------- |-------------------------| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `alt_system_info` | Alternative system_info table | macOS | This table is an alternative to the built-in system_info table in osquery, which triggers an `Allow "osquery" to find devices on local networks?` prompt on macOS 15.0. On versions other than 15.0, this table falls back to the built-in system_info table. Note: this table returns an empty `cpu_subtype` field. See [#58](https://github.com/macadmins/osquery-extension/pull/58) for more details. |
| `authdb` | macOS Authorization database | macOS | Use the constraint `name` to specify a right name to query, otherwise all rights will be returned. |
| `carafe_homebrew_formulae` | Installed Homebrew formula inventory written by Carafe | macOS | Reads `/Library/Application Support/MacAdmins/Carafe/homebrew_formulae.json`, produced by `carafe inventory homebrew`. |
| `crowdstrike_falcon` | Provides basic information about the currently installed Falcon sensor. | Linux / macOS | Requires Falcon to be installed. |
| `energy_impact` | Process energy impact data from `powermetrics` | macOS | Use the `interval` constraint to specify sampling duration in milliseconds (default: 1000). |
| `file_lines` | Read an arbitrary file | Linux / macOS / Windows | Use the constraint `path` and `last` to specify the file to read lines from |
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/macadmins/osquery-extension/tables/wifi_network"

"github.com/macadmins/osquery-extension/tables/authdb"
"github.com/macadmins/osquery-extension/tables/carafe"
osquery "github.com/osquery/osquery-go"
"github.com/osquery/osquery-go/plugin/table"
)
Expand Down Expand Up @@ -127,6 +128,7 @@ func main() {
),
table.NewPlugin("macos_thermal_pressure", thermalthrottling.ThermalPressureColumns(), thermalthrottling.ThermalPressureGenerate),
table.NewPlugin("macos_soc_power", socpower.SocPowerColumns(), socpower.SocPowerGenerate),
table.NewPlugin("carafe_homebrew_formulae", carafe.HomebrewFormulaeColumns(), carafe.HomebrewFormulaeGenerate),
}
plugins = append(plugins, darwinPlugins...)
}
Expand Down
23 changes: 23 additions & 0 deletions tables/carafe/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "carafe",
srcs = ["homebrew_formulae.go"],
importpath = "github.com/macadmins/osquery-extension/tables/carafe",
visibility = ["//visibility:public"],
deps = [
"//pkg/utils",
"@com_github_osquery_osquery_go//plugin/table",
],
)

go_test(
name = "carafe_test",
srcs = ["homebrew_formulae_test.go"],
embed = [":carafe"],
deps = [
"@com_github_osquery_osquery_go//plugin/table",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
],
)
104 changes: 104 additions & 0 deletions tables/carafe/homebrew_formulae.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package carafe

import (
"context"
"encoding/json"
"os"
"strconv"

"github.com/macadmins/osquery-extension/pkg/utils"
"github.com/osquery/osquery-go/plugin/table"
)

var homebrewFormulaePath = "/Library/Application Support/MacAdmins/Carafe/homebrew_formulae.json"

type homebrewInventory struct {
SchemaVersion string `json:"schema_version"`
GeneratedAt string `json:"generated_at"`
BrewPath string `json:"brew_path"`
Arch string `json:"arch"`
Formulae []homebrewFormulaeRow `json:"formulae"`
}

type homebrewFormulaeRow struct {
Name string `json:"name"`
FullName string `json:"full_name"`
Tap string `json:"tap"`
InstalledVersion string `json:"installed_version"`
InstalledOnRequest bool `json:"installed_on_request"`
SourceURL string `json:"source_url"`
Homepage string `json:"homepage"`
License string `json:"license"`
Checksum string `json:"checksum"`
Outdated bool `json:"outdated"`
}

func HomebrewFormulaeColumns() []table.ColumnDefinition {
return []table.ColumnDefinition{
table.TextColumn("name"),
table.TextColumn("full_name"),
table.TextColumn("tap"),
table.TextColumn("installed_version"),
table.TextColumn("installed_on_request"),
table.TextColumn("source_url"),
table.TextColumn("homepage"),
table.TextColumn("license"),
table.TextColumn("checksum"),
table.TextColumn("outdated"),
table.TextColumn("generated_at"),
table.TextColumn("brew_path"),
table.TextColumn("arch"),
table.TextColumn("schema_version"),
}
}

func HomebrewFormulaeGenerate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
fs := utils.OSFileSystem{}
inventory, err := loadHomebrewFormulaeInventory(fs, homebrewFormulaePath)
if err != nil {
return nil, err
}
if inventory == nil {
return nil, nil
}

var results []map[string]string
for _, formula := range inventory.Formulae {
results = append(results, map[string]string{
"name": formula.Name,
"full_name": formula.FullName,
"tap": formula.Tap,
"installed_version": formula.InstalledVersion,
"installed_on_request": strconv.FormatBool(formula.InstalledOnRequest),
"source_url": formula.SourceURL,
"homepage": formula.Homepage,
"license": formula.License,
"checksum": formula.Checksum,
"outdated": strconv.FormatBool(formula.Outdated),
"generated_at": inventory.GeneratedAt,
"brew_path": inventory.BrewPath,
"arch": inventory.Arch,
"schema_version": inventory.SchemaVersion,
})
}

return results, nil
}

func loadHomebrewFormulaeInventory(fs utils.FileSystem, path string) (*homebrewInventory, error) {
if !utils.FileExists(fs, path) {
return nil, nil
}

data, err := os.ReadFile(path)
if err != nil {
return nil, err
}

var inventory homebrewInventory
if err := json.Unmarshal(data, &inventory); err != nil {
return nil, err
}

return &inventory, nil
}
92 changes: 92 additions & 0 deletions tables/carafe/homebrew_formulae_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package carafe

import (
"context"
"os"
"path/filepath"
"testing"

"github.com/osquery/osquery-go/plugin/table"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const testHomebrewFormulaeInventory = `{
"schema_version": "1",
"generated_at": "2026-06-01T12:30:00Z",
"brew_path": "/opt/homebrew/bin/brew",
"arch": "arm64",
"formulae": [
{
"name": "git",
"full_name": "git",
"tap": "homebrew/core",
"installed_version": "2.50.0",
"installed_on_request": true,
"source_url": "https://www.kernel.org/pub/software/scm/git/git-2.50.1.tar.xz",
"homepage": "https://git-scm.com",
"license": "GPL-2.0-only",
"checksum": "abc123",
"outdated": true
}
]
}`

func TestHomebrewFormulaeGenerate(t *testing.T) {
homebrewFormulaePath = filepath.Join(t.TempDir(), "homebrew_formulae.json")
require.NoError(t, os.WriteFile(homebrewFormulaePath, []byte(testHomebrewFormulaeInventory), 0600))

rows, err := HomebrewFormulaeGenerate(context.Background(), table.QueryContext{})

require.NoError(t, err)
expectedRows := []map[string]string{
{
"name": "git",
"full_name": "git",
"tap": "homebrew/core",
"installed_version": "2.50.0",
"installed_on_request": "true",
"source_url": "https://www.kernel.org/pub/software/scm/git/git-2.50.1.tar.xz",
"homepage": "https://git-scm.com",
"license": "GPL-2.0-only",
"checksum": "abc123",
"outdated": "true",
"generated_at": "2026-06-01T12:30:00Z",
"brew_path": "/opt/homebrew/bin/brew",
"arch": "arm64",
"schema_version": "1",
},
}
assert.Equal(t, expectedRows, rows)
}

func TestHomebrewFormulaeGenerateMissingFile(t *testing.T) {
homebrewFormulaePath = filepath.Join(t.TempDir(), "missing.json")

rows, err := HomebrewFormulaeGenerate(context.Background(), table.QueryContext{})

require.NoError(t, err)
assert.Nil(t, rows)
}

func TestHomebrewFormulaeGenerateInvalidJSON(t *testing.T) {
homebrewFormulaePath = filepath.Join(t.TempDir(), "homebrew_formulae.json")
require.NoError(t, os.WriteFile(homebrewFormulaePath, []byte("{"), 0600))

rows, err := HomebrewFormulaeGenerate(context.Background(), table.QueryContext{})

assert.Nil(t, rows)
assert.Error(t, err)
}

func TestHomebrewFormulaeColumns(t *testing.T) {
columns := HomebrewFormulaeColumns()

names := make([]string, 0, len(columns))
for _, column := range columns {
names = append(names, column.Name)
}
assert.Contains(t, names, "name")
assert.Contains(t, names, "installed_version")
assert.Contains(t, names, "generated_at")
}
Loading