Skip to content

Commit 32ab6db

Browse files
committed
feat(audit): add machine-readable summaries
1 parent d50d7ce commit 32ab6db

9 files changed

Lines changed: 219 additions & 15 deletions

File tree

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ For fast local script loops before the final check:
8181
./scripts/bootstrap/verify-repo.sh --skip-security
8282
```
8383

84+
To install the same fast gate as a local pre-push hook:
85+
86+
```zsh
87+
./scripts/bootstrap/install-git-hooks.sh
88+
```
89+
8490
For a live machine that should use these dotfiles:
8591

8692
```zsh

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ Use repo checks before committing:
7777
./scripts/bootstrap/verify-repo.sh
7878
```
7979

80+
To install the local pre-push guard for the fast repo gate:
81+
82+
```zsh
83+
./scripts/bootstrap/install-git-hooks.sh
84+
```
85+
8086
Use live-machine checks only on a machine that should actually use these
8187
dotfiles:
8288

docs/agent-readiness.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,22 @@ flow to drive. Agent readiness means an agent can:
1515
| --- | --- | --- | --- |
1616
| Bootable | pass | `scripts/bootstrap/brew-bundle.sh` installs shared plus profile bundles; `scripts/bootstrap/install.sh` links tracked files. | First-time macOS still needs Command Line Tools, Homebrew, and GitHub auth. |
1717
| Testable | pass | `scripts/bootstrap/verify-repo.sh` runs shell syntax, ShellCheck, Actionlint, diff hygiene, agent-entrypoint checks, and repo secret scans. | Live bootstrap checks require a matching personal or devbox Mac. |
18-
| Observable | partial | Verification and audit scripts print stable sectioned output; CI exposes Verify and Secret scanning logs. | Scripts do not emit JSON or SARIF reports yet. |
18+
| Observable | pass | Verification and audit scripts print stable sectioned output; security audits also support compact `--json` summaries; CI exposes Verify and Secret scanning logs. | SARIF output is not generated yet. |
1919
| Verifiable | pass | `.github/workflows/verify.yml`, `.github/workflows/secrets.yml`, `scripts/bootstrap/verify.sh`, `scripts/devbox/verify.sh`, and audit scripts. | Host-local service and token checks cannot run meaningfully on GitHub-hosted CI. |
2020

2121
Overall grade: **B for a bootstrap repo**.
2222

2323
The repo has a reliable repo gate, CI gates, and separate host-local audits.
24-
The remaining readiness gap is structured machine-readable audit output.
24+
The remaining readiness gap is richer machine-readable report formats such as
25+
SARIF for tools that can emit it cleanly.
2526

2627
## Verification Matrix
2728

2829
| Situation | Command | What it proves |
2930
| --- | --- | --- |
3031
| Before committing repo changes | `./scripts/bootstrap/verify-repo.sh` | Scripts parse, ShellCheck passes, workflows lint, diffs are clean, agent entrypoints are valid, and secret scanners pass. |
3132
| Fast local loop | `./scripts/bootstrap/verify-repo.sh --skip-security` | Same repo checks without Gitleaks/TruffleHog. Run the full command before commit. |
33+
| Install local push guard | `./scripts/bootstrap/install-git-hooks.sh` | Adds a pre-push hook that runs `verify-repo.sh --skip-security` before pushing. |
3234
| Personal Mac bootstrap | `./scripts/bootstrap/verify.sh --profile personal` | Required CLIs, Homebrew bundle, mise, Codex defaults, and linked config exist on the live host. |
3335
| Devbox bootstrap | `./scripts/bootstrap/verify.sh --profile devbox` | Shared/devbox CLIs, Homebrew bundle, mise, Codex defaults, and linked config exist on the live host. |
3436
| Devbox service boundary | `./scripts/devbox/verify.sh` | process-compose and generated env/token boundaries match the local devbox contract. |
@@ -75,6 +77,5 @@ See [GitHub pipelines](github-pipelines.md) for triggers and non-goals.
7577

7678
## Future Improvements
7779

78-
- Add optional JSON output to audit scripts for easier agent parsing.
79-
- Add an installable pre-push hook that runs `verify-repo.sh --skip-security`.
80-
- Add a short machine-readable summary mode for `security-audit.sh` scripts.
80+
- Add optional SARIF output for scanners that support it cleanly.
81+
- Add a lightweight report collector for comparing both devbox users in one run.

docs/security-audits.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ That command runs the repository secret scan through
3131
diff checks. Run `./scripts/security/audit.sh --skip-mscp` directly when you
3232
only need the secret scanners.
3333

34+
For agent or dashboard consumption, add `--json`:
35+
36+
```zsh
37+
./scripts/security/audit.sh --skip-mscp --json
38+
```
39+
3440
GitHub Actions also runs Gitleaks and TruffleHog on pushes to `main`, pull
3541
requests, weekly schedule, and manual dispatch through
3642
`.github/workflows/secrets.yml`.
@@ -84,6 +90,9 @@ Run this from a normal personal Mac user:
8490
./scripts/security/audit-personal.sh
8591
```
8692

93+
Use `./scripts/security/audit-personal.sh --json` when an agent needs a compact
94+
status summary.
95+
8796
It checks:
8897

8998
- default shells do not export `OP_SERVICE_ACCOUNT_TOKEN`
@@ -110,6 +119,9 @@ Run this from each devbox user:
110119
./scripts/devbox/security-audit.sh
111120
```
112121

122+
Use `./scripts/devbox/security-audit.sh --json` when collecting per-user devbox
123+
audit summaries over SSH.
124+
113125
It checks:
114126

115127
- default shells do not export `OP_SERVICE_ACCOUNT_TOKEN`

scripts/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ Repository-only verification:
1919
./scripts/bootstrap/verify-repo.sh
2020
```
2121

22+
Install the local pre-push guard:
23+
24+
```zsh
25+
./scripts/bootstrap/install-git-hooks.sh
26+
```
27+
2228
Personal bootstrap:
2329

2430
```zsh
@@ -36,6 +42,7 @@ Security audits:
3642

3743
./scripts/security/audit.sh --skip-mscp
3844
./scripts/security/audit-personal.sh
45+
./scripts/security/audit-personal.sh --json
3946
```
4047

4148
Devbox checks:
@@ -44,6 +51,7 @@ Devbox checks:
4451

4552
./scripts/devbox/verify.sh
4653
./scripts/devbox/security-audit.sh
54+
./scripts/devbox/security-audit.sh --json
4755
```
4856

4957
Before committing script changes:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
5+
hooks_dir="$repo_root/.git/hooks"
6+
pre_push="$hooks_dir/pre-push"
7+
8+
if [ ! -d "$repo_root/.git" ]; then
9+
printf 'not a normal Git checkout: %s\n' "$repo_root" >&2
10+
exit 1
11+
fi
12+
13+
mkdir -p "$hooks_dir"
14+
15+
if [ -e "$pre_push" ] || [ -L "$pre_push" ]; then
16+
if ! grep -q 'uinaf-dotfiles: pre-push' "$pre_push" 2>/dev/null; then
17+
backup="$pre_push.backup.$(date +%Y%m%d%H%M%S)"
18+
mv "$pre_push" "$backup"
19+
printf 'backed up existing pre-push hook to %s\n' "$backup"
20+
fi
21+
fi
22+
23+
cat > "$pre_push" <<'HOOK'
24+
#!/usr/bin/env bash
25+
set -euo pipefail
26+
27+
# uinaf-dotfiles: pre-push
28+
repo_root="$(git rev-parse --show-toplevel)"
29+
exec "$repo_root/scripts/bootstrap/verify-repo.sh" --skip-security
30+
HOOK
31+
32+
chmod 0755 "$pre_push"
33+
printf 'installed %s\n' "$pre_push"

scripts/devbox/security-audit.sh

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ token_file="${UINAF_OP_SERVICE_ACCOUNT_TOKEN_FILE:-/var/db/uinaf/devbox-secrets/
1010
openclaw_env_file="${UINAF_OPENCLAW_ENV_FILE:-/var/db/uinaf/devbox-env/$devbox_user/openclaw.env}"
1111
openclaw_env_link="${UINAF_OPENCLAW_ENV_LINK:-$HOME/.openclaw/.env}"
1212
expected_admin_users="${UINAF_EXPECTED_ADMIN_USERS:-}"
13+
json_output=0
1314
warn_count=0
1415
fail_count=0
1516
secret_scan_count=0
@@ -24,6 +25,7 @@ Runs a non-destructive devbox drift audit for the current Unix user.
2425
Options:
2526
--config PATH local devbox config, default: ~/.config/uinaf/devbox.env
2627
--expected-admin-users LIST space-separated admin users expected on this Mac
28+
--json print a machine-readable summary instead of prose
2729
-h, --help
2830
2931
The script checks secret boundaries, process-compose isolation, Git/GitHub
@@ -33,23 +35,51 @@ USAGE
3335
}
3436

3537
section() {
38+
[ "$json_output" -eq 1 ] && return
3639
printf '\n## %s\n' "$1"
3740
}
3841

3942
ok() {
43+
[ "$json_output" -eq 1 ] && return
4044
printf 'ok %s\n' "$1"
4145
}
4246

4347
warn() {
4448
warn_count=$((warn_count + 1))
49+
[ "$json_output" -eq 1 ] && return
4550
printf 'warn %s\n' "$1" >&2
4651
}
4752

4853
fail_check() {
4954
fail_count=$((fail_count + 1))
55+
[ "$json_output" -eq 1 ] && return
5056
printf 'FAILED: %s\n' "$1" >&2
5157
}
5258

59+
json_string() {
60+
local value="$1"
61+
value="${value//\\/\\\\}"
62+
value="${value//\"/\\\"}"
63+
printf '"%s"' "$value"
64+
}
65+
66+
print_json_summary() {
67+
local status="pass"
68+
if [ "$fail_count" -gt 0 ]; then
69+
status="fail"
70+
fi
71+
72+
printf '{"audit":'
73+
json_string "devbox-security"
74+
printf ',"status":'
75+
json_string "$status"
76+
printf ',"failed":%s,"warnings":%s,"user":' "$fail_count" "$warn_count"
77+
json_string "$USER"
78+
printf ',"devbox_user":'
79+
json_string "$devbox_user"
80+
printf ',"secret_scan_count":%s}\n' "$secret_scan_count"
81+
}
82+
5383
mode_of() {
5484
stat -f '%Lp' "$1"
5585
}
@@ -132,6 +162,10 @@ while [ "$#" -gt 0 ]; do
132162
expected_admin_users="${2:-}"
133163
shift 2
134164
;;
165+
--json)
166+
json_output=1
167+
shift
168+
;;
135169
--help|-h)
136170
usage
137171
exit 0
@@ -176,7 +210,15 @@ else
176210
fail_check "current shell exports OP_SERVICE_ACCOUNT_TOKEN"
177211
fi
178212

179-
if zsh -lic 'test -z "${OP_SERVICE_ACCOUNT_TOKEN+x}"'; then
213+
if [ "$json_output" -eq 1 ]; then
214+
zsh_login_has_no_token="$(zsh -lic 'test -z "${OP_SERVICE_ACCOUNT_TOKEN+x}"' >/dev/null 2>&1; printf '%s' "$?")"
215+
elif zsh -lic 'test -z "${OP_SERVICE_ACCOUNT_TOKEN+x}"'; then
216+
zsh_login_has_no_token=0
217+
else
218+
zsh_login_has_no_token=1
219+
fi
220+
221+
if [ "$zsh_login_has_no_token" = "0" ]; then
180222
ok "login shell does not export OP_SERVICE_ACCOUNT_TOKEN"
181223
else
182224
fail_check "login shell exports OP_SERVICE_ACCOUNT_TOKEN"
@@ -370,7 +412,11 @@ else
370412
fail_check "tailscale is missing"
371413
fi
372414

373-
printf '\ndevbox security audit summary: %s failed, %s warnings\n' "$fail_count" "$warn_count"
415+
if [ "$json_output" -eq 1 ]; then
416+
print_json_summary
417+
else
418+
printf '\ndevbox security audit summary: %s failed, %s warnings\n' "$fail_count" "$warn_count"
419+
fi
374420

375421
if [ "$fail_count" -gt 0 ]; then
376422
exit 1

scripts/security/audit-personal.sh

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
set -euo pipefail
33

44
expected_admin_users="${UINAF_EXPECTED_ADMIN_USERS:-}"
5+
json_output=0
56
warn_count=0
67
fail_count=0
78

@@ -14,6 +15,7 @@ Runs a non-destructive personal/non-devbox drift audit for the current Unix user
1415
1516
Options:
1617
--expected-admin-users LIST space-separated admin users expected on this Mac
18+
--json print a machine-readable summary instead of prose
1719
-h, --help
1820
1921
The script checks local secret boundaries, Git/GitHub identity state, SSH key
@@ -23,23 +25,49 @@ USAGE
2325
}
2426

2527
section() {
28+
[ "$json_output" -eq 1 ] && return
2629
printf '\n## %s\n' "$1"
2730
}
2831

2932
ok() {
33+
[ "$json_output" -eq 1 ] && return
3034
printf 'ok %s\n' "$1"
3135
}
3236

3337
warn() {
3438
warn_count=$((warn_count + 1))
39+
[ "$json_output" -eq 1 ] && return
3540
printf 'warn %s\n' "$1" >&2
3641
}
3742

3843
fail_check() {
3944
fail_count=$((fail_count + 1))
45+
[ "$json_output" -eq 1 ] && return
4046
printf 'FAILED: %s\n' "$1" >&2
4147
}
4248

49+
json_string() {
50+
local value="$1"
51+
value="${value//\\/\\\\}"
52+
value="${value//\"/\\\"}"
53+
printf '"%s"' "$value"
54+
}
55+
56+
print_json_summary() {
57+
local status="pass"
58+
if [ "$fail_count" -gt 0 ]; then
59+
status="fail"
60+
fi
61+
62+
printf '{"audit":'
63+
json_string "personal-security"
64+
printf ',"status":'
65+
json_string "$status"
66+
printf ',"failed":%s,"warnings":%s,"user":' "$fail_count" "$warn_count"
67+
json_string "$USER"
68+
printf '}\n'
69+
}
70+
4371
mode_of() {
4472
stat -f '%Lp' "$1"
4573
}
@@ -107,6 +135,10 @@ while [ "$#" -gt 0 ]; do
107135
expected_admin_users="${2:-}"
108136
shift 2
109137
;;
138+
--json)
139+
json_output=1
140+
shift
141+
;;
110142
--help|-h)
111143
usage
112144
exit 0
@@ -127,7 +159,15 @@ else
127159
fail_check "current shell exports OP_SERVICE_ACCOUNT_TOKEN"
128160
fi
129161

130-
if zsh -lic 'test -z "${OP_SERVICE_ACCOUNT_TOKEN+x}"'; then
162+
if [ "$json_output" -eq 1 ]; then
163+
zsh_login_has_no_token="$(zsh -lic 'test -z "${OP_SERVICE_ACCOUNT_TOKEN+x}"' >/dev/null 2>&1; printf '%s' "$?")"
164+
elif zsh -lic 'test -z "${OP_SERVICE_ACCOUNT_TOKEN+x}"'; then
165+
zsh_login_has_no_token=0
166+
else
167+
zsh_login_has_no_token=1
168+
fi
169+
170+
if [ "$zsh_login_has_no_token" = "0" ]; then
131171
ok "login shell does not export OP_SERVICE_ACCOUNT_TOKEN"
132172
else
133173
fail_check "login shell exports OP_SERVICE_ACCOUNT_TOKEN"
@@ -254,7 +294,11 @@ else
254294
warn "tailscale CLI is missing"
255295
fi
256296

257-
printf '\npersonal security audit summary: %s failed, %s warnings\n' "$fail_count" "$warn_count"
297+
if [ "$json_output" -eq 1 ]; then
298+
print_json_summary
299+
else
300+
printf '\npersonal security audit summary: %s failed, %s warnings\n' "$fail_count" "$warn_count"
301+
fi
258302

259303
if [ "$fail_count" -gt 0 ]; then
260304
exit 1

0 commit comments

Comments
 (0)