Skip to content

feat: project-scoped secrets (MVP)#11

Open
tashian wants to merge 3 commits into
mainfrom
feat/project-scope
Open

feat: project-scoped secrets (MVP)#11
tashian wants to merge 3 commits into
mainfrom
feat/project-scope

Conversation

@tashian

@tashian tashian commented May 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds scope ("global" | "project") and roots: []string to Secret. Backward-compatible decode; no envelope bump.
  • Daemon resolves peer cwd via proc_pidinfo(PROC_PIDVNODEPATHINFO) (PeerCwd.resolve) — the CLI never claims its own cwd.
  • tsm add --here / tsm add --project <path> (repeatable) for project binding.
  • tsm list defaults to in-scope filter; --all reveals everything.
  • tsm get returns secret_out_of_scope (RPC code -32010) when the calling cwd is outside any bound root, with a CLI hint.
  • Skill update + design doc (design doc is local-only per repo convention — docs/plans/ is gitignored).

Deferred to follow-ups: tsm run auto-inject, tsm edit --add-root/--remove-root, recent-projects cache, tsm project noun.

Closes #7 (MVP — see PR body for deferred items)

Test plan

  • cd tsmd && swift test passes (incl. PeerCwdTests, scope validation, cwd resolution)
  • go test ./... passes (incl. cmd/add_test.go --here, cmd/list_test.go --all, cmd/get_test.go out-of-scope hint)
  • Manual: tsm add --here --name foo --no-input from /tmp/proj then cd /tmp/other && tsm get foo returns the hint

tashian added 3 commits May 4, 2026 19:16
…oping

Adds the daemon half of project-scoped secrets (issue #7 MVP).

- Secret/SecretMetadata gain `scope` ("global" | "project") and
  `roots: [String]`. Backward-compatible decode via custom Secret decoder
  defaults (`scope="global"`, `roots=[]`); no envelope-version bump.
- New `PeerCwd.resolve(peerPID:proc:)` queries the kernel via
  `proc_pidinfo(PROC_PIDVNODEPATHINFO)` for the connecting peer's cwd.
  Closure-injected `proc` lets the test suite fake the syscall layer,
  mirroring `PeerSession.resolveDurableSessionID`'s pattern.
- `Vault.add` validates scope+roots: scope must be in {global, project};
  project requires non-empty, absolute, normalized roots, capped at 16.
- `Vault.get` and `Vault.list` consult `peerCwd` and filter project-scoped
  secrets accordingly. New `VaultError.secretOutOfScope(name:roots:)`
  surfaces as wire error code -32010 with structured `{name, roots}` data
  so the CLI can render an actionable hint.
- `SocketServer` resolves cwd per-RPC (not at accept) so a long-lived
  connection sees each `cd`. peerPID is captured once at accept.
CLI half of project-scoped secrets (issue #7 MVP).

- `tsm add --here` resolves cwd via os.Getwd + filepath.EvalSymlinks and
  binds the new secret to that path. `--project /abs/path` (repeatable)
  lets the user bind to additional roots. Both flags imply scope=project
  and dedupe roots before sending. Relative paths are rejected client-side.
- `tsm list` defaults to the cwd-filtered view. `--all` opts out by
  passing `include_all=true` to the daemon. List output gains a
  "scope: project (root1, root2)" line for project-scoped rows.
- `tsm get` maps the new daemon error code (-32010, secret_out_of_scope)
  to a hint that names the secret, lists the bound root(s), and points
  the user at `tsm list --all`.
- `internal/jsonrpc` exposes `CodeSecretOutOfScope = -32010` for callers.
- `runAdd` and `runList` were refactored into `runAddWith` / `runListWith`
  so tests can inject mock callers and stub Getwd / EvalSymlinks; the
  previously untested commands now have coverage.
Adds a "Project-scoped secrets" section to credential-usage SKILL.md
between §1 and §2:
- Default `tsm list` is cwd-filtered; trust it.
- Out-of-scope secret should not trigger a `cd` workaround — explain to
  the user and offer `tsm list --all` instead.
- How to save a project-bound secret (`pbpaste | tsm add --here ...`).
- Project scope is a UX boundary, not a security one.

The local design doc at docs/plans/2026-05-04-project-scoped-tokens-design.md
captures the eight design decisions and the threat-model paragraph; per
.gitignore convention, plans live locally and are not committed.
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.

project-scoped (cwd) tokens

1 participant