Date: 2026-08-11
Repository: https://github.com/gastownhall/beads
Version: v1.1.2 + 2 weeks of unrelease commits
Commit SHA: 18c24a3ac3e737c34cb478d58ecf3a805a99e996
Scope: Full security audit of a Go CLI tool for issue tracking with Dolt/SQLite backends, git sync, and AI agent integration.
Methodology: glm-5.2-max investigation covering 300+ Go source files, dependency manifests, CI pipelines, and install scripts, searching for unintentional and malicous security issues.
The 7 malicious-intent categories (backdoors, data exfiltration, obfuscation, supply chain attacks, credential harvesting, trojan functionality, anti-analysis) all returned clean. No backdoors, hidden endpoints, encoded payloads, data exfiltration, credential harvesting, persistence mechanisms, or environment-aware behavior changes were found.
| # | Category | File:Line | Issue | Recommendation |
|---|---|---|---|---|
| M1 | SQL Injection | cmd/bd/doctor/fix/metadata.go:286 |
dbName from SHOW DATABASES interpolated without backtick escaping. Same file at line 449 has the correct pattern. |
Apply strings.ReplaceAll(dbName, "", "``")` like line 449 |
| M2 | SQL Injection | internal/doltserver/doltserver.go:1595,1605 |
dbName from SHOW DATABASES interpolated in SELECT and USE without escaping. //nolint:gosec comment incorrectly claims it is constant. |
Escape backticks or validate with isValidIdentifier |
| M3 | SQL Injection | cmd/bd/doctor/dolt.go:679 |
Same unescaped dbName pattern in doctor probes. |
Same fix as M1 |
| M4 | Secrets | cmd/bd/federation.go:137 |
--password/-p flag accepts password as CLI arg, visible in process list. The serve command deliberately avoids this pattern. |
Consider deprecating --password in favor of interactive prompt only, or env var |
| # | Category | File:Line | Issue |
|---|---|---|---|
| L1 | Secrets | internal/configfile/credentials.go:42 |
Plaintext credentials file (standard pattern like ~/.pgpass, with permission warning) |
| L2 | Secrets | internal/storage/dolt/credentials.go:62 |
AES encryption key stored alongside encrypted data in .beads/ (standard for local CLI tools, 0600 perms, gitignored) |
| L3 | Secrets | internal/ado/types.go:101 |
SecretString type only used for ADO PAT, not other integration tokens (mitigated by masking/redaction) |
| L4 | TLS | internal/configfile/external_dolt_config.go:112 |
InsecureSkipVerify available via opt-in TLSSkipVerify config flag (gated, gosec-suppressed, testing only) |
| L5 | SQL Injection | internal/storage/dolt/federation.go:285 |
AS OF ref interpolation after strict regex validation (^[a-zA-Z0-9_./-]+$) -- Dolt syntax does not support bind params here |
| L6 | Path Traversal | internal/beads/beads.go:134 |
FollowRedirect uses filepath.Join without .. sanitization (mitigated by isPathInSafeBoundary downstream) |
- 5 hidden cobra commands: all legitimate internal infrastructure (db-proxy-child, codex-hook, cursor-hook, send-metrics, migrate-issues alias)
- 15 hidden flags: all CLI ergonomics aliases or backwards-compat shims
- No magic strings, date triggers, username-based triggers, or auth bypasses
- All HTTP API routes defined in single route table (
routes.go:170-733), spec-parity enforced by tests - No conditionally-registered or undocumented endpoints
- Complete URL inventory: only expected destinations (GitHub, Linear, Jira, GitLab, Notion, ADO, Anthropic, PyPI, beads' own telemetry endpoint)
- Telemetry sends only command names + HMAC'd machine ID -- never issue content
- No DNS-based exfiltration (
net.LookupHost/TXT/MXnot used) - Telemetry endpoint hardened against project-level redirect attacks (flusher child drops inherited env vars)
- All integration sync is user-initiated and expected
- No hardcoded base64/hex strings decoding to URLs, commands, or code
- All base64 usage: standard auth headers and cursor pagination
- All hex usage: hash output encoding, test fixtures, CI SHA pins
reflectusage: 100% in test files (*_test.go), no production dynamic dispatchunsafeusage: 5 instances, all syscall argument passing (ioctl/fcntl) with gosec annotations- No
plugin.Open, nogo:linkname, noimport "C", no string obfuscation
- No typosquatting, no homograph attacks, no suspicious dependencies
- Zero
replaceorretractdirectives in go.mod - No vendored code, no local path replacements
- go.sum: all checksums present and consistent
- CI: all GitHub Actions pinned with SHA hashes
- Install scripts verify SHA256 checksums before installing
- Release pipeline includes SBOM generation and release attestation
- Every integration token flows only to its expected API endpoint via Authorization headers
- No tokens in URL query strings, request bodies to unexpected hosts, or error messages
- Telemetry args thoroughly scrubbed (passwords, DSN credentials, secret flags)
- Secret keys stored in config.yaml (not Dolt DB), excluded from
dolt push CheckSecretKeyGitSafety()refuses to write secrets to git-tracked config- HTTP API settings endpoint redacts secret values via
config.IsSecretKey() - No
InsecureSkipVerifyin production paths (only opt-in testing config)
- No system file modification (no writes to
/etc,/usr,/var, shell configs, SSH keys) - No persistence mechanisms (no LaunchAgents, cron jobs, services, self-copying)
- No sensitive file reads (
~/.ssh/id_rsa,~/.aws/credentials,~/.kube/config-- zero matches) - All process kills target beads' own spawned processes with PID revalidation
- No network infrastructure modification (no
/etc/hosts, firewall, DNS, or proxy changes) - All data collection (hostname, machine ID, git user.name) serves legitimate functions
- No debugger detection (no ptrace, TracerPid, parent process checks)
- CI detection (
CI=true) only used for non-interactive mode -- standard CLI pattern - No analysis tool detection (no strace/gdb/lldb/valgrind/wireshark checks)
- No timing-based anti-analysis (all
time.Sleepcalls are legitimate retries/polling) machineiduses HMAC-protected app-scoped ID, not raw hardware ID; opt-out available- All
/procreads are for lock diagnostics and orphan cleanup, not tool detection
- Every
exec.Commandcall uses separate args, never shell string interpolation - Only one
sh -cin production code: credential-process helper (by-design, env-var-sourced) - Git operations consistently disable hooks (
core.hooksPath=) and templates (GIT_TEMPLATE_DIR=) BEADS_DIRvalidated againstisPathInSafeBoundary()with symlink resolution
isPathInSafeBoundary(SEC-003) rejects system directories and other users' homessafeWorkspacePathusesfilepath.Relto verify containment- Atomic file writes via
os.CreateTemp(random names, no predictability) - Workspace gate rejects symlinked directories and uses
O_EXCLfor TOCTOU prevention - CLI-context file opens (user-supplied
@filepaths) are acceptable -- user already has FS access
- User data values consistently passed as
?bind parameters across all filter/search/query code - Dynamic table/column names sourced from hardcoded constants or validated with strict regex
sqlStringLiteralproperly escapes'and\ValidateRefandValidateConflictTablestrictly validate before interpolation
- No hardcoded secrets found
- Comprehensive token masking before display and telemetry scrubbing
- AES-256-GCM encryption for federation peer passwords
- HTTP API never echoes presented credentials
- ADO
SecretStringtype prevents accidental serialization of PATs
- Parameterized queries consistently used for all user data values across SQL layer
- Constant-time auth comparison (
subtle.ConstantTimeCompare) for HTTP API tokens - SHA-256 digests (not plaintext) for auth token storage in memory
- Structural secret isolation: secrets in YAML config, not Dolt DB; excluded from sync
- Telemetry endpoint pinning: flusher child drops inherited env to prevent hijack
- Spec-parity tests for all HTTP API routes
- Comprehensive telemetry scrubbing with dedicated test coverage
- AES-256-GCM encryption for federation credentials with random keys
- SHA-pinned CI actions and checksum-verified install scripts
- Git hooks disabled in all internal git operations
- M1 --
cmd/bd/doctor/fix/metadata.go:286: Escape backticks indbName(same file already has the correct pattern at line 449) - M2 --
internal/doltserver/doltserver.go:1595,1605: Escape backticks indbNamefromSHOW DATABASES - M3 --
cmd/bd/doctor/dolt.go:679: Same backtick escaping fix - M4 --
cmd/bd/federation.go:137: Consider deprecating--passwordflag in favor of interactive prompt only
All four are medium/low severity with limited practical attack surface (the SQL injection findings require a malicious Dolt database name from a synced remote, and the password flag requires local process list access). No critical or high-severity findings.