Skip to content

Commit 1b8c051

Browse files
committed
update README
1 parent e0ef384 commit 1b8c051

File tree

9 files changed

+1287
-155
lines changed

9 files changed

+1287
-155
lines changed

README.md

Lines changed: 103 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,157 @@
1-
# gitMDM
1+
# gitMDM 🧪
22

3-
Security-first compliance reporting that doesn't compromise your infrastructure.
3+
The MDM for startups that actually care about security.
44

5-
![gitMDM Logo](media/logo_small.png)
5+
## Your Problem
66

7-
## The Problem
7+
Your startup just hit the enterprise sales milestone where someone asks "are you SOC 2 compliant?" Meanwhile, your engineering team runs OpenBSD on ThinkPads, Arch on Frameworks, and that one person still dailying Plan 9.
88

9-
Every MDM is a backdoor. They typically require root access and arbitrary remote code execution. They're incompatible with secure-by-default operating systems. Yet auditors require them for SOC 2.
9+
Traditional MDMs want root access, auto-update themselves from the internet, and can execute arbitrary code pushed from their cloud. Your security engineer just had an aneurysm.
1010

11-
## The Solution
11+
## Our Solution
1212

13-
gitMDM proves compliance without compromising security:
14-
- **No arbitrary remote code execution** - Checks are compiled into the agent binary
15-
- **No privileged access** - Runs as a normal user
16-
- **No phone-home** - Your git repo, your endpoint, your control
17-
- **Works everywhere** - Including secure-by-default systems such as OpenBSD.
13+
gitMDM proves compliance without the backdoor:
1814

19-
## Screenshots
20-
21-
### Device List
22-
<a href="media/dashboard.png"><img src="media/dashboard.png" alt="Dashboard" width="400"/></a>
23-
24-
### Device Details
25-
26-
<a href="media/report.png"><img src="media/report.png" alt="Agent Report" width="400"/></a>
27-
28-
### Remediation
29-
<a href="media/remediate.png"><img src="media/remediate.png" alt="Remediation Steps" width="400"/></a>
30-
31-
## How It Works
32-
33-
```
34-
[Agent] [Server] [Git]
35-
Run compiled checks → Receive reports only → Tamper-resistant audit trail
3615
```
16+
Traditional MDM: "Install our kernel extension!"
17+
Your Team: "How about no."
3718
38-
The server **cannot** push commands. Ever. That's the point.
19+
gitMDM: "Run a read-only agent that reports to YOUR server"
20+
Your Team: "...continue"
21+
```
3922

40-
## Quick Start
23+
### Why Your Security Team Will Actually Approve This
4124

42-
```bash
43-
# Server - now with 100% less git CLI dependency!
44-
# Auto-generates a join key from hardware ID if you're too lazy to set one
45-
./gitmdm-server -git /path/to/compliance # Creates repo if it doesn't exist
46-
# or
47-
./gitmdm-server -clone /existing/repo # Uses existing local repo
48-
49-
# The server will display something like:
50-
# ═══════════════════════════════════════════════════════════════
51-
# GitMDM Server Started
52-
# Join Key: 926DD23A5B
53-
#
54-
# To register an agent, run:
55-
# ./gitmdm-agent --server http://localhost:8080 --join 926DD23A5B
56-
# ═══════════════════════════════════════════════════════════════
57-
58-
# Agent - checks in every 20 minutes (because 5 was too clingy)
59-
./gitmdm-agent --server http://localhost:8080 --join 926DD23A5B
60-
```
25+
- **Zero Remote Execution**: Can't push commands. Not won't. Can't. The server only receives data.
26+
- **No Auto-Updates**: Agent is a static binary. Updates require YOU to rebuild and redeploy.
27+
- **Runs as User**: No root, no SYSTEM. Just a regular user process.
28+
- **You Own Everything**: Your server, your git repo, your data. Host it in your VPC.
29+
- **Audit Everything**: Every change is a git commit. `git blame` for compliance.
6130

62-
### Environment Variables (for the Docker crowd)
31+
## Quick Start for the Impatient
6332

6433
```bash
65-
# Server accepts these if you're allergic to flags
66-
export GIT_REPO=git@github.com:org/compliance.git
67-
export PORT=8080
68-
export JOIN_KEY=SUPERSECRET123 # Or let it auto-generate one
69-
./gitmdm-server
70-
```
34+
# On your secure server (or laptop, we don't judge)
35+
./gitmdm-server -git /opt/compliance
7136

72-
## Local Checks
37+
# On your OpenBSD machine
38+
$ doas pkg_add gitmdm-agent # just kidding, compile it yourself
39+
$ ./gitmdm-agent --install --server https://comply.internal --join XXXX
7340

74-
You can run the compliance checks even without a server:
41+
# On your Linux laptop
42+
$ ./gitmdm-agent --install --server https://comply.internal --join XXXX
7543

76-
```bash
77-
./out/gitmdm-agent -run all
44+
# On that Mac the designer insisted on
45+
$ ./gitmdm-agent --install --server https://comply.internal --join XXXX
7846
```
7947

80-
You'll see output similar to:
48+
Join keys stored in `~/.config/gitmdm/` (or wherever your OS says), not in process lists.
8149

82-
```log
83-
🔍 Running security checks...
50+
## What SOC 2 Actually Requires vs What We Check
8451

85-
⚠️ 3 issues require attention
52+
| SOC 2 Says | Traditional MDMs Do | We Do |
53+
|------------|---------------------|--------|
54+
| Disk encryption | Run as root, phone home for instructions | Read `/proc/mounts` as user |
55+
| Screen locks | Auto-update from vendor's CDN | Check your screensaver config |
56+
| OS updates | Force reboots during demos | Report version numbers |
57+
| Firewall enabled | Execute arbitrary scripts from cloud | Check `iptables -L` output |
8658

87-
🔸 screen lock
88-
🐞 Problem: Screen idle time too long (1 hour, SOC 2 requires ≤15 min); Screen lock delay too long (4 hours, SOC 2 requires ≤15 min)
89-
💻 Evidence: defaults -currentHost read com.apple.screensaver idleTime && sysadminctl -screenLock status
59+
## Platform Detection That Actually Works
9060

91-
🔧 How to fix:
92-
1. Open System Settings > Lock Screen
93-
2. Set 'Start Screen Saver when inactive' to 15 minutes or less
94-
3. Open System Settings > Lock Screen
95-
4. Set 'Require password after screen saver begins' to 'immediately'
61+
```yaml
62+
# Your snowflake setups, our problem:
63+
- MATE on OpenBSD (we see you)
64+
- Sway on Alpine (of course)
65+
- i3 on Debian (classic)
66+
- Whatever that custom Wayland compositor you wrote is
67+
- Even macOS (unfortunate, but supported)
9668
```
9769
98-
## What You Get
70+
We detect 11+ desktop environments because your team refuses to standardize.
71+
72+
## Security Architecture
9973
100-
SOC 2 compliance evidence in git:
10174
```
102-
devices/
103-
├── 926DD23A5B/ # Hardware IDs, not names (privacy!)
104-
├── info.json # Device metadata
105-
├── disk_encryption.json
106-
├── screen_lock.json
107-
└── firewall.json
108-
└── README.md # Auto-created, unlike this one
75+
[Agent] [Server] [Git]
76+
| | |
77+
|-- HTTPS ------->| |
78+
| (reports) |--- git push -->|
79+
| | |
80+
X <-- CANNOT -----| |
81+
(execute)
10982
```
11083

111-
Every check, every change, in git. No database to corrupt, no API to hack.
112-
113-
## Supported Platforms
84+
The server literally cannot execute commands. We removed the code. It's not there.
11485

115-
Linux, macOS, Windows, FreeBSD, OpenBSD, NetBSD, DragonFlyBSD, Solaris, illumos
86+
## For Your Compliance Team
11687

117-
## checks.yaml
88+
"How do we prove compliance?"
11889

119-
```yaml
120-
checks:
121-
disk_encryption:
122-
openbsd: "bioctl softraid0 | grep -q CRYPTO"
123-
linux: "lsblk -o NAME,FSTYPE | grep -q crypto_LUKS"
124-
darwin: "fdesetup status | grep -q 'On'"
90+
```bash
91+
$ cd compliance-repo
92+
$ git log --oneline
93+
8f3d2a1 workstation-42: disk encryption enabled
94+
7b2c3f9 laptop-dev-3: screen lock fixed
95+
5a1e8c4 desktop-1: firewall enabled
12596
```
12697

127-
Edit, compile, deploy. No runtime configuration files to tamper with.
98+
"What if someone tampers with the agent?"
99+
100+
They can. It's their machine. They can also lie on spreadsheets. At least this has timestamps.
128101

129-
## Security Guarantees
102+
"Is this enterprise-ready?"
130103

131-
- **Server compromise = read-only access to compliance reports** (and they're in git anyway)
132-
- **No arbitrary code execution** - Not even with root on the server
133-
- **Agent decides what runs** - Compiled-in checks, not runtime shenanigans
134-
- **Bash restricted mode** - When we absolutely must shell out
135-
- **No git CLI required** - Pure Go implementation (go-git), works in containers
136-
- **Join key required** - Keeps the riffraff out of your compliance data
104+
No. But neither was Stripe when you started using it.
137105

138106
## Building
139107

140108
```bash
141-
make all
109+
make all # Static binaries, because dynamic linking is attack surface
142110
```
143111

144-
Compiles to static binaries because dynamic linking is for people who enjoy debugging production at 3 AM.
112+
No npm. No pip. No containers. Just Go.
113+
114+
## Installation That Respects Your OS
145115

146-
## Code Philosophy
116+
- **Linux**: systemd user service (falls back to cron if you're systemd-free)
117+
- **OpenBSD**: cron (because rc.d requires root and we're not animals)
118+
- **macOS**: launchd (the least worst option)
119+
- **FreeBSD/NetBSD**: cron (see OpenBSD)
147120

148-
Written in Go, blessed by Rob Pike's simplicity principles:
149-
- Functions read like recipes, not puzzle boxes
150-
- No clever abstractions that require a PhD to understand
151-
- Minimal dependencies (yaml, retry, go-git - that's it!)
152-
- If a function is <7 lines and called once, it's inlined
153-
- Security through simplicity, not complexity theater
121+
Pre-flight check ensures the server exists before installing. Novel concept.
154122

155-
## FAQ
123+
## FAQ for Security-Conscious Teams
156124

157-
**Q: Is this SOC 2 compliant?**
158-
A: It generates the reports auditors need. Without the backdoors.
125+
**Q: Can this execute remote commands?**
126+
A: No. Check the code. The handler doesn't exist.
159127

160-
**Q: What if we need to change checks?**
161-
A: Rebuild and redeploy. Immutability is a feature, not a bug.
128+
**Q: What about supply chain attacks?**
129+
A: It's 2 dependencies: yaml and retry. Vendor them if paranoid.
162130

163-
**Q: Why git?**
164-
A: Cryptographic proof, audit trail, existing tooling, no database to "accidentally" DROP.
131+
**Q: Does it require root?**
132+
A: Never. User-level only. Your kernel remains unmolested.
133+
134+
**Q: What data does it collect?**
135+
A: Read `checks.yaml`. It's compiled in. No surprises.
136+
137+
**Q: Can we self-host?**
138+
A: That's the only option. There's no cloud service. You run it.
139+
140+
**Q: What if an agent is compromised?**
141+
A: It can lie about that device's compliance. That's it. No lateral movement.
142+
143+
**Q: OpenBSD pledge/unveil support?**
144+
A: On the roadmap. PRs welcome from fellow paranoids.
145+
146+
---
165147

166-
**Q: Does it need git installed?**
167-
A: Nope! Uses go-git. Works in your hipster minimal container.
148+
*Built by engineers who rm -rf node_modules on principle.*
168149

169-
**Q: What's a join key?**
170-
A: A speed bump for script kiddies. Not Fort Knox, but keeps honest people honest.
150+
**⚠️ EXPERIMENTAL**
151+
*But still more trustworthy than your current MDM.*
171152

172-
**Q: Why 20-minute check-ins?**
173-
A: Because 5 minutes was needy, and daily was negligent. Goldilocks would approve.
153+
*Remember: Compliance theater is still theater, but at least our stage doesn't have backdoors.*
174154

175155
---
176156

177-
*Built for organizations that refuse to compromise security for compliance.*
157+
*"Because your security posture shouldn't require the missionary position."*

cmd/agent/executeCheck.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func (a *Agent) executeCommand(ctx context.Context, checkName string, rule confi
107107
if !shellBuiltins[primaryCmd] && !strings.Contains(primaryCmd, "/") {
108108
// Temporarily set PATH for LookPath
109109
oldPath := os.Getenv("PATH")
110-
if err := os.Setenv("PATH", getSecurePath()); err != nil {
110+
if err := os.Setenv("PATH", securePath()); err != nil {
111111
log.Printf("[WARN] Failed to set PATH for command check: %v", err)
112112
}
113113
_, lookupErr := exec.LookPath(primaryCmd)

cmd/agent/executeCommand.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import (
1414
"time"
1515
)
1616

17-
// getSecurePath returns a secure PATH based on the OS.
18-
func getSecurePath() string {
17+
// securePath returns a secure PATH based on the OS.
18+
func securePath() string {
1919
switch runtime.GOOS {
2020
case "windows":
2121
return "C:\\Windows\\System32;C:\\Windows;C:\\Windows\\System32\\wbem"
@@ -66,7 +66,7 @@ func (*Agent) executeCommandWithPipes(ctx context.Context, checkName, command st
6666
if !shellBuiltins[primaryCmd] && !strings.Contains(primaryCmd, "/") {
6767
// Temporarily set PATH for LookPath
6868
oldPath := os.Getenv("PATH")
69-
if err := os.Setenv("PATH", getSecurePath()); err != nil {
69+
if err := os.Setenv("PATH", securePath()); err != nil {
7070
log.Printf("[WARN] Failed to set PATH for command check: %v", err)
7171
}
7272
_, lookupErr := exec.LookPath(primaryCmd)
@@ -114,7 +114,7 @@ func (*Agent) executeCommandWithPipes(ctx context.Context, checkName, command st
114114
}
115115

116116
// Set a secure, minimal PATH for the subprocess
117-
securePath := getSecurePath()
117+
securePath := securePath()
118118
if *debug {
119119
log.Printf("[DEBUG] Using secure PATH for %s: %s", runtime.GOOS, securePath)
120120
}

0 commit comments

Comments
 (0)