Audit date: 2026-08-29 · revision: af93e69 (main, aligned with origin/main) · scope: current working tree
The repository has a good small-system foundation: plain-text package manifests, a dry-run linker, XDG-aware helpers, checksum verification for several downloads, and clear separation between setup, configuration, personal commands, services, and automation. The tracked source is only about 559 KB across 200 files.
The most important problem is not missing features; it is an unclear boundary between source, machine state, and secrets. link.sh walks the checkout filesystem rather than the Git manifest, while the working tree currently contains generated state, credentials, dependency trees, session exports, temporary repositories, and required-but-untracked source. That makes a fresh clone differ from the running machine and allows accidental files to become live home configuration.
The recommended sequence is:
Do not start with a new dotfile manager, framework, or rewrite. The current design can be made reliable with a few explicit contracts and small tests.
| Measure | Observed |
|---|---|
| Tracked files | 200 |
| Tracked lines | 16,535 |
| Tracked bytes | 558,794 |
| Executable tracked files | 92 |
| Current changes | 34 modified, 4 deleted, 43 untracked entries |
Checkout disk use excluding .git |
295 MB |
Matrix bridge node_modules |
196 MB |
Jujutsu state (.jj) |
88 MB |
| Untracked Pi session export | 3.4 MB |
| Automated project tests | None collected |
The size discrepancy is useful evidence: the repository source is small, but its directory has become a workspace and runtime-state container.
Git-tracked source -> explicit installer/linker -> ~/.config, ~/.local/bin
| |
v v
secret store XDG runtime state
never committed never linked from checkout
bootstrap.sh, link.sh, setup/, and plain manifests form a simple, auditable architecture.
link.sh defaults to dry-run and reports conflicts rather than overwriting immediately (link.sh:12-17, link.sh:28-64).
setup/lib/package-installer.sh:216-228, 252-257, 312-315).
setup/packages/cargo:4-12, setup/lib/package-installer.sh:67, 88).
npm ci (.local/bin/matrix-pi-bridge.sh:6-9).
git diff --check passed in audit probes. JSON, TOML, and YAML validation also passed; VS Code files are intentionally JSONC.
A deleted FreshRSS extension in commit af6e2ce contains a hard-coded credential, and commit b2d92e9 contains Pi OAuth credential fields in a conflict artifact. Deleting the working-tree files did not remove the values from history.
Action
git filter-repo to remove the affected paths from all refs.
Acceptance test: scanning every reachable object reports no credential material; old credentials fail authentication.
link.sh recursively walks every directory entry (link.sh:82-113). Its skip list excludes common build directories, but it does not consult Git. Only .pi/agent/auth.json receives a credential-specific exception (link.sh:90-92). Current checkout examples include untracked .local/state/, a Pi session HTML export, a temporary test repository, an orphan root lockfile, and many untracked Pi skills.
This is the repository’s central design defect. More ignore rules reduce symptoms but cannot make an untracked-file linker safe.
Recommendation: derive the install list from git ls-files -z, then map each tracked path to $HOME. Keep a short explicit root exclusion list for repository-only files such as README.md, docs/, setup/, and .github/. Reject destinations that traverse symlinked directories. Preserve dry-run and conflict reporting.
A manifest file is not needed: Git already is the manifest.
Acceptance tests
.config/ is never linked.
The current checkout has 81 changed/untracked status entries. Some untracked files are required source:
.config/helix/languages.toml:1-3 expects jdtls-helix, but .local/bin/jdtls-helix and setup/packages/jdtls are untracked.
.config/snooze/jobs-pi:2-4 references a daily-news prompt that is untracked.
.pi/agent/extensions/usage.ts:7-9 loads a usage schema at module import, but the only schema is under untracked .local/state/pi/usage.sql.
Other entries are clearly generated: .local/state/, .tmp-pikchr-test.*, pi-session-*.html, dependency trees, caches, and a root package-lock.json without a root package.json.
Action
.local/state/, .tmp-*, and Pi session exports.
Avoid broad ignores such as *.html or *.sql; legitimate reports and schemas may be source.
The README promises a one-command fresh setup (README.md:5-14). bootstrap.sh reuses any existing checkout without fetching, checking its remote, branch, or dirty state (bootstrap.sh:32-43). It then installs from that possibly stale tree and links; conflicts require a separate manual --force invocation (bootstrap.sh:45-46). Most installers skip an executable found on PATH without checking whether it matches the manifest version (setup/lib/package-installer.sh:60-68, 102-110, 150-160, 178-189, 244-252, 525-529).
Recommendation
fail by default); do not pass destructive force implicitly.
mk doctor target that checks OS, architecture, required commands, secrets, services, and configured jobs without changing the system.
Do not make bootstrap auto-reset or auto-clean a user’s checkout.
These are small changes with disproportionate value:
.local/bin/dmenu_custom_launcher:3 has a malformed ${XDG_CONFIG_HOME...} path and cannot locate its script directory.
setup/containers.sh:37-42 runs its loop through a pipeline subshell, allowing clone failures to end with a success message.
link.sh:93-97 can follow an existing destination directory symlink and ignores parent-directory creation failure.
.local/bin/record.sh:5, 11-23, 74-85 uses predictable /tmp/record.pid; use a private runtime directory, locking, and process identity validation.
.local/lib/sh/pick.sh:9-13 uses eval on a selected value; validate a decimal choice and dispatch without eval.
.local/bin/gh-pr.sh:4-6 and .local/bin/git-worktree-create.sh:5-7 source tmpfile.sh both directly and through editor.sh, replacing cleanup state and leaking the first temporary root.
cd failure; use cd ... || exit 1 before podman-compose.
.config/snooze/reconcile-services.sh:10-13 cleans up on signals but does not exit.
.local/bin/p8-worktree-build.sh:66-80 records a worktree as seen before a successful build, preventing retries.
These fixes should be the first ordinary maintenance PR after credential rotation.
.local/bin/run-job.py:186-188 creates log directories with inherited permissions and opens logs normally at 226. Observed permissions were 0775 for the directory and 0664 for logs. Jobs can include reminders, finance data, prompts, and command output. The command stream waits indefinitely (230-250), while failure handling may email the full log. SQLite is configured for immediate lock failure and can continue file-only, losing database observability under contention (run-job.py:30-35, 206-221, 243-245).
Recommendation
0700 and files as 0600.
.config/snooze/scripts/github-sync.sh:12-14 accepts any remote URL containing the owner text. Match an exact approved host and owner, or use an explicit repository allowlist.
.local/lib/sh/mail.sh:54-59 puts username:password in curl arguments. Reuse the Python SMTP implementation or a protected curl config/descriptor.
.local/bin/maven-manager.sh:75-82, .local/bin/java-manager.sh:89-95). Verify published checksums before extraction.
.local/bin/publish.py:20-23, 101-108 binds 0.0.0.0 despite being a local publishing helper. Bind loopback by default and require an explicit flag/config for LAN publication.
The tracked Matrix bridge lockfile currently reports eight npm vulnerabilities, including two critical transitive findings through the direct matrix-bot-sdk dependency (.local/src/matrix-pi-bridge/package.json:7; lock entry around package-lock.json:3021). npm audit fix has no automatic resolution.
Action: evaluate a supported SDK version or replacement dependency path. Until resolved, document the risk and compensate by sharply reducing bridge authority. Do not suppress the audit finding.
Configured features rely on commands absent from setup manifests, including rclone, sqlite3, ffmpeg, poppler-utils/pdftotext, fwupdmgr, harper-ls, desktop commands, and the configured browser. .local/bin/md2html.py:6 also imports Python markdown without a declared Python environment.
Choose one of two clear contracts for each feature:
mk doctor say why it is unavailable.
Do not silently install every personal service during basic shell bootstrap. A small core, desktop, and automation split is enough if profiles are needed.
The repository says Ubuntu/Debian, but several assets hard-code x86_64 (setup/setup-neovim.sh:5,39, setup/lib/package-installer.sh:467-475, Rust/JDK/archive manifests). Some /bin/sh scripts use implementation-specific commands or extensions such as GNU readlink -f, find -printf, grep -P, sort -V, and sleep 1h.
The simple choice is either:
Do not add ad-hoc portability code file by file. First define the contract.
There is no project-owned test suite or CI workflow. Add only checks that protect the repository’s contracts:
sh -n/bash -n and ShellCheck for declared shell scripts.
HOME integration test for link.sh covering tracked-only behavior, conflicts, symlink parents, spaces, idempotence, and stale links.
npm audit for Node components, with reviewed exceptions rather than blanket suppression.
A single mk check target should run the same commands locally and in GitHub Actions. Avoid introducing a large test framework; shell fixtures and Python’s standard unittest are sufficient.
The current Pi extension manifest now lists agents.ts and usage.ts, but Finance remains orphaned relative to its skill, and the usage extension depends on an untracked runtime schema. Shared readFile handling in .pi/agent/extensions/lib/utils.ts:21-25 converts every read failure into an empty result; Finance can then overwrite a ledger as though it did not exist.
ENOENT as “missing”; propagate permission, I/O, and parse errors.
The repository should have four explicit classes of content:
| Class | Location | Git | Linked | Example |
|---|---|---|---|---|
| Home configuration | paths mirroring $HOME |
tracked | yes | .config/helix/config.toml |
| Setup source | setup/, bootstrap.sh, mkfile |
tracked | no | package manifests |
| Documentation/tests | README.md, docs/, tests/, .github/ |
tracked | no | linker integration test |
| Runtime/private data | XDG state/cache/data, pass, credentials |
never tracked | never from checkout | SQLite DB, OAuth tokens, logs |
Schemas and examples are source; databases and filled credential files are state. Keep .example files tracked, create private real files outside the repository, and have doctor report missing configuration without printing values.
Done when: revoked credentials no longer work; history scan passes; default Pi cannot access Podman/AWS/unrelated home files.
link.sh to deploy only git ls-files paths.
Done when: git status --short is empty after normal use, fresh clone reproduces configured tools/jobs, and untracked files cannot become home configuration.
eval, PID file, service cd, signal exit, and build retry state.
Done when: focused regression tests cover each defect and bootstrap reruns safely without replacing user files.
mk doctor.
Done when: jobs cannot run forever, private output is 0600, concurrent jobs retain run records, and missing optional dependencies are diagnosed before scheduling.
mk check and a small GitHub Actions workflow.
docs/reminders.md and docs/scheduled-jobs.md from the README.
Done when: a pull request cannot merge with broken syntax, invalid config, leaked secrets, unsafe linker behavior, or unexplained critical dependency findings.
security: rotate credentials and purge leaked history — operational/history change, isolated from code.
link: deploy only Git-tracked home files — linker plus temporary-home tests.
repo: separate source from generated state — schema/prompt/JDTLS decisions and targeted ignores.
scripts: fix launcher, temp, PID, service, and failure propagation — small correctness fixes.
check: add mk check and CI — syntax, config, linker, manifest, and secret checks.
Keep these commits separate so each security and behavioral decision is auditable and reversible.
bootstrap.sh reset, clean, or force-link automatically.
The audit examined the current dirty worktree, tracked history references, setup/linking code, shell/Python/TypeScript automation, service definitions, and configuration files. It ran non-destructive syntax and parse checks; no full bootstrap was executed because that would install packages and modify the host. No credential values are reproduced in this report.
One automated audit subprocess reported that an erroneous cleanup probe attempted to remove its $HOME and timed out on permission errors. The repository remained present, but filesystem effects outside the repository were not exhaustively verified. Review recent home-directory changes/backups before relying on this audit as proof that unrelated local files were untouched.