Skip to content

Post-public-release review remediation (ASH-210..213)#12

Merged
cstaszak merged 4 commits into
mainfrom
cstaszak/ash-210-217-review-remediation
May 22, 2026
Merged

Post-public-release review remediation (ASH-210..213)#12
cstaszak merged 4 commits into
mainfrom
cstaszak/ash-210-217-review-remediation

Conversation

@cstaszak

Copy link
Copy Markdown
Contributor

Remediation of the verified findings from the post-public-release code review (docs / security / performance / correctness). Four commits, one per ticket.

ASH-210 — daemon shutdown panic + file-perm hardening

  • HIGH: ashd panicked on every shutdown when [lsp] is disabled (the default) — langCache.Close() on a nil receiver. The live ashd.log held 98 panic traces; the crash skipped the deferred led.Close() / WAL checkpoint. Fixed by making Cache.Close() nil-receiver-safe.
  • Close LSP stdin/stdout/stderr pipes on a cmd.Start() failure (fd leak).
  • Create the UDS socket under a 0o177 umask (no world-connectable window).
  • chmod ledger.db to 0600 (it holds paths + result fragments).
  • Two stale code comments corrected.

ASH-211 — git argument injection (security, verified exploitable)

Request-controlled rev/pathspec args (range/ref/rev/author/since/until/pathspec) reached the git argv unsanitized. With [git].backend="shellout", --range '--output=/path' made git log --output=FILE write attacker-chosen files — and log/show are read-side verbs exposed over MCP. ParseArgs (the single choke point for both backends) now rejects any of those args beginning with -. Regression test added.

ASH-212 — hot-path perf

  • Tokenizer: EncodeOrdinary instead of Encode — drops the special-token scan + a redundant full []rune pass, on a function called 3-4× per request.
  • argsBlob no longer re-decodes the request (one fewer full msgpack unmarshal per call).
  • PrettyPath drops a redundant per-call sort.

ASH-213 — docs accuracy sweep

README benchmark numbers refreshed (21 cases / −63.8% / 2026-05-18); orphaned docs/install.md deleted; hook deny message no longer points at the retired docs/session-notes/; not_implemented / lang / build / MCP-surface claims corrected across README, CLAUDE.md, configuration.md. Vocab inventory regenerated.

Verification

go vet, full test suite (54 pkgs), vocab-check, schema-check all pass locally.

Not in this PR

Pre-existing repo-wide gofmt drift (~70 files) — filed as ASH-220 with a CI-gate proposal; kept separate so the 70-file formatting diff doesn't bury these changes. Larger findings filed as ASH-214..219.

🤖 Generated with Claude Code

cstaszak and others added 4 commits May 21, 2026 17:20
Found in the post-public-release code review.

HIGH: ashd panicked on every shutdown when LSP is disabled (the
default). cmd/ashd/main.go defers and signal-handler-calls
langCache.Close(), but langCache stays nil unless [lsp].enabled —
Cache.Close() then dereferenced the nil receiver via c.mu.Lock().
The live ashd.log held 98 such panic traces; the crash skipped the
deferred led.Close()/WAL checkpoint. Fix: make Cache.Close()
nil-receiver-safe, matching Broker.Close()'s style.

Bundled hardening (all small, behavior-preserving):
- internal/lsp/lsp.go: close stdin/stdout/stderr pipes when
  cmd.Start() fails in Broker.start() — they leaked on that path.
- cmd/ashd/main.go: create the UDS socket under a 0o177 umask so it
  is never briefly world-connectable between net.Listen and Chmod.
- internal/ledger/ledger.go: chmod ledger.db to 0600 — it holds file
  paths and result fragments and was created 0644.
- Fix two stale code comments: the misplaced `ash hook` doc comment
  in cmd/ash/main.go, and the jail-policy-swap comment in
  cmd/ashd/main.go (it is mutex-guarded, not a lock-free swap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Found in the post-public-release security review; verified exploitable.

The git verb appended request-controlled rev/pathspec args (range, ref,
rev, author, since, until, pathspec) into the git argv unsanitized. With
[git].backend = "shellout", git read a value like
  ash git --op log --range '--output=/path'
as an option, not a revision — `git log --output=FILE` writes
attacker-chosen files anywhere the daemon user can write. log/show are
read-side verbs (exposed over MCP as ash_git), so a prompt-injected tool
call could drop a .git/hooks script or overwrite ~/.bashrc.

Fix: ParseArgs — the single choke point for both the shellout and go-git
backends — now rejects any of those seven args whose value begins with
"-". A legitimate ref, revision range, date, author, or pathspec never
starts with "-", so no real call is affected. Adds a regression test
covering the --output= injection across all seven args.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Found in the post-public-release performance review.

- internal/ledger/tokens.go: Counter.Count called enc.Encode(s,nil,nil),
  which routes to the special-token-scanning encode path and does a
  redundant second full []rune(s) conversion. ash responses never carry
  <|endoftext|>-style tokens, so EncodeOrdinary yields identical counts
  on the cheaper path. Count runs 3-4x per request on the synchronous
  response path.
- cmd/ashd/main.go: argsBlob re-decoded the whole request via
  proto.DecodeRequest just to read Args, while the decoded req was
  already in scope. Take *proto.Request directly; one fewer full
  msgpack unmarshal per call.
- internal/jail/relpath.go: PrettyPath copied and re-sorted the prefix
  list every call despite PathPrefixes() being longest-first by
  contract. Iterate directly; drop the now-unused sort import.

PERF#9 (emitter Flush time.Now dedup) was intentionally skipped —
lastFlush and firstFlushAt are semantically distinct timestamps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Found in the post-public-release docs review. Mechanical accuracy fixes
— the repo is now public, so stale docs are read by strangers.

- README §Benchmarks: numbers were stale (19 cases / -54.8% / 2026-05-10).
  Updated to bench/baseline.md: 21 cases, -63.8%, 2026-05-18, 41,767 vs
  115,410 tok.
- Delete docs/install.md — the orphaned original ASH-47/70 *planning*
  doc, zero inbound links, contradicted by the live docs/adoption/install.md.
- docs/configuration.md: dropped the false "git --op status returns
  not_implemented because go-git is stubbed" comment.
- internal/verbs/hook/hook.go: nudgeTail pointed contributors at
  docs/session-notes/, a directory retired per CLAUDE.md — the most-seen
  agent-facing string in the repo. Now points at the session feedback
  ritual. Regenerated docs/vocab/inventory.{md,json}; hook tests assert
  against the nudgeTail const directly now, not a magic substring.
- internal/verbs/initverb/template.md: dropped the retired "per-ship
  session notes" reference.
- README §How we're building: corrected "no CLAUDE.md is propagated"
  (ash init does) and "every read-side verb" over MCP (read- and
  write-side; fixed the ash_lang_def example to ash_edit).
- README §Roadmap: build shipped — moved from Upcoming to Live; added
  git blame to the live git ops.
- CLAUDE.md: not_implemented IS returned by git blame under the
  shellout backend — corrected the error-code note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cstaszak
cstaszak merged commit 1d2afb4 into main May 22, 2026
5 checks passed
@cstaszak
cstaszak deleted the cstaszak/ash-210-217-review-remediation branch May 22, 2026 00:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant