fix(local-agent): derive deterministic local_agent_id + harden install path - #161
Merged
jeff-r2026 merged 1 commit intoJul 8, 2026
Merged
Conversation
…l path The HTTP local-agent generated local_agent_id via crypto.randomBytes, so the id changed on every hook fire (env-only path) or per reinstall/machine — the backend saw a fresh random agent each time instead of a stable one. Reuse the existing deterministic deriveLocalAgentId(agentType, machineId, installPath): the tool is auto-detected from the hook --tool flag, so claude/codebuddy/ workbuddy each get a stable, distinct id. localAgentId is no longer stored in config.json (kept optional for backward-compatible loads). Also harden the install/sync command path (backend commands are untrusted): - validateSlug() in commandSlug() rejects path-traversal slugs (../, /, \, abs) - downloadResource() drops file:// / local-path copy, allows only http(s), and follows redirects manually so each hop's scheme is re-validated - writeTokenFile() writes the credential file with owner-only 0o600 perms Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5 tasks
jeff-r2026
added a commit
that referenced
this pull request
Jul 8, 2026
…r slug (#162) * fix(local-agent): name HTTP-synced skills by SKILL.md name, not server slug When skills are delivered over the HTTP local-agent path, the directory was named after the server-provided skill_slug. But the SKILL.md `name:` frontmatter is what the AI tool uses to identify a skill, and the two can differ — so a skill synced as slug "skillsaaa" landed at ~/.codebuddy/skills/skillsaaa and was unrecognizable. The git-native path already fixed this in skill-command.ts (#144); this brings the HTTP path (local-agent.ts) in line. - resolveSkillDirName() reads the SKILL.md name after extraction and uses it as the on-disk directory name when it differs from the slug, passing path-safety validation; falls back to the slug when the name is missing/equal/unsafe. - The manifest records dir_name (keyed by slug) so uninstall_skill can locate and remove the SKILL.md-name directory when the backend only knows the slug. Not a regression from #161 — verified by building the prior internal release and reproducing identical slug-based naming; this is a pre-existing gap in the HTTP path since it was introduced. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(local-agent): wrap zip body in Buffer for BodyInit type compat CI's @types/node types `zipSync`'s return as `Uint8Array<ArrayBufferLike>`, which is not assignable to `BodyInit` in `new Response(...)`. Wrap it in `Buffer.from()` (a valid BodyInit) so `tsc --noEmit` passes across type versions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The HTTP local-agent generated
local_agent_idviacrypto.randomBytes(8), which is non-deterministic:loadLocalAgentConfigwith onlyTEAMAI_HTTP_ENDPOINTset): a fresh random id was minted on every hook fire and never persisted — sosession_start/prompt_submit/ each tool-use reported a different id.init --httppath: random once then persisted, but a new random id on every reinstall (config removed) or on a different machine.Either way the backend saw a churn of unrelated random agents instead of one stable agent per machine/tool. This regressed the design contract already documented in
src/machine-id.ts:local_agent_id = sha1(agent_type + machine_id + path_hash)[:16], stable for the same machine + install dir + agent_type.Fix
Reuse the existing deterministic
deriveLocalAgentId(agentType, machineId, installPath):resolveLocalAgentId(context)derives the id at runtime. The tool is auto-detected from the hook's--toolflag (context.tool), soclaude/codebuddy/workbuddyeach get a stable, distinct id.localAgentIdis no longer stored inconfig.json(field kept optional for backward-compatible loads of old config files).TEAMAI_LOCAL_AGENT_IDstill overrides for explicit pinning.Security hardening (install/sync command path)
Backend sync commands are untrusted input; three issues flagged by an automated security review, all pre-existing in the HTTP local-agent module, fixed here since they live in the same file:
validateSlug()incommandSlug()rejects slugs containing/,\,.., or absolute paths before they reachpath.join.downloadResource()drops thefile://and absolute-local-path copy branches, allows onlyhttp(s)schemes, and follows redirects manually so each hop's scheme is re-validated. (Kepthttpallowed so internal/self-hosted endpoints keep working; no private-IP block by design.)writeTokenFile()writes the token with owner-only0o600andchmods an already-existing token file to tighten it.Test Plan
npx tsc --noEmitnpx vitest run src/__tests__/local-agent.test.ts src/__tests__/machine-id.test.ts— 16 passed (incl. 4 new security tests)npm run buildhook-dispatchwith--tool claude|codebuddy|workbuddyagainst a mock server — same tool → same id across calls; different tools → different ids; report & sync ids match;TEAMAI_LOCAL_AGENT_IDoverride honoured.hook-dispatchwhere the mock server returns malicious commands — slug../../evil→ ackfailed: Invalid resource slug, no file escapes the repo;file:///etc/passwddownload_url → ackfailed: Unsupported download URL scheme: file:.🤖 Generated with Claude Code