Skip to content

fix: readable onepassword:// resources and security hardening (v5.0.0) - #33

Merged
CakeRepository merged 5 commits into
masterfrom
claude/upbeat-gates-8362ef
Oct 4, 2026
Merged

CakeRepository merged 5 commits into
masterfrom
claude/upbeat-gates-8362ef

Conversation

@CakeRepository

Copy link
Copy Markdown
Owner

Summary

One release, 5.0.0, combining two pieces of work. Supersedes #32, which can be closed once this merges. 4.0.4 is never published; its notes are folded into the 5.0.0 CHANGELOG entry.

1. Resources fix (breaking URI change). The three MCP resources couldn't be read by real clients:

  • The SDK parses every resources/read URI with new URL(), and 1password:// isn't a valid scheme, because a scheme can't start with a digit. Every read failed with -32602 Resource URI … is invalid.
  • …/vaults/{vaultId}/items was registered as a static URI, so no concrete vault URI could match it.

Now the URIs use onepassword://. I ruled out op:// because it collides with 1Password secret references. The per-vault items resource is a ResourceTemplate, and vaultId comes from the template variables, percent-decoded.

2. Security hardening from #32, merged in unchanged as d9905bd:

  • Deny-by-default item_get masking.
  • A server-wide vault allow-list (src/vault-access.ts).
  • op_run fixes for redaction, the output cap, and timeouts.
  • Hardened CI and publish workflows.
  • An absolute path for the Keychain lookup, and a startup warning when the token is passed on the command line.

Breaking and behavior changes

  • Resource URIs change from 1password://… to onepassword://…. The old ones never resolved.
  • The vault allow-list (OP_MCP_ALLOWED_VAULTS) now applies to every tool and resource. Tools that take a vaultId need the vault's ID, not its name.
  • item_get hides SSH keys, OTP seeds, and card numbers unless you pass reveal: true.

How the two fit together

  • src/resources/index.ts merged cleanly. filterAllowedVaults() filters onepassword://vaults, and assertVaultIdAllowed() runs on the decoded vaultId, the same value items.list() gets.
  • fix(security): harden item_get, vault allow-list, op_run, and CI (v4.0.4) #32's nine resource tests in tests/vault-allowlist.test.ts called the callbacks with the old signatures. The merge commit updates them.
  • buildServer() moved to src/server.ts (still re-exported from src/index.ts), so tests can build the real server without starting stdio.

Testing

  • New tests/resources.e2e.test.ts: a real @modelcontextprotocol/client (new dev dependency, same version as the server package) reads every advertised resource from serveStdio(() => buildServer()) over InMemoryTransport, in both the 2025 and 2026-07-28 protocol eras. It also covers the allow-list, including a check that percent-encoding can't bypass it. Run against master's code, it fails with exactly the reported error.
  • Mutation checks: removing the allow-list wiring, or checking the encoded vaultId instead of the decoded one, makes the new tests fail.
  • Full suite (npm run clean && npm ci && npm run build && npm run lint && npm test): 414 passed, 1 skipped (a POSIX-only op_run test, skipped on Windows).
  • Publish build job variant (npm ci --ignore-scripts && npm run build && npm test): passes. Versions are aligned at 5.0.0 in package.json, package-lock.json, server.json, and SERVER_VERSION.
  • Smoke test: the built server works over real stdio in both protocol eras.

Commits

🤖 Generated with Claude Code

CakeRepository and others added 5 commits October 4, 2026 03:33
…0.4)

Security release from an internal review.

- item_get: deny-by-default field masking. SSH private keys, TOTP seeds,
  and card numbers were returned in plaintext without reveal.
- Vault allow-list (OP_MCP_ALLOWED_VAULTS) is now enforced by every tool
  and resource, and op:// references are also checked by the vault they
  resolve to, not only as written (new src/vault-access.ts).
- op_run redaction (new src/redaction.ts): single-pass masking over the
  original output, so overlapping secrets no longer leak each other's
  remainder; also masks base64 (any alignment), JSON/URL-escaped, and
  multi-line/CRLF forms.
- op_run: output cap enforced while the command runs (memory-exhaustion
  DoS), timeouts kill the whole process tree and always return, the
  credential env scrub is case-insensitive, and the tool description no
  longer overclaims.
- CI: remove the leftover issue_comment-triggered mcp-v2-migration.yml
  (contents: write, triggerable by any GitHub user). publish.yml passes
  the release tag via env, does not persist credentials, and splits
  build/test/pack (npm ci --ignore-scripts, no OIDC) from a publish-only
  job that alone holds id-token: write.
- macOS Keychain lookup runs /usr/bin/security; startup warning when the
  token is passed on the command line.
- Docs and CHANGELOG updated; version bumped to 4.0.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0.0)

Every resources/read failed with -32602 "Resource URI ... is invalid":
the SDK parses the URI with new URL() before dispatching, and a scheme
cannot start with a digit (RFC 3986 3.1), so no 1password:// URI ever
reached a handler.

- Resource URIs now use the onepassword:// scheme. Breaking for anything
  that hard-coded the old URIs, hence 5.0.0.
- onepassword://vaults/{vaultId}/items is a ResourceTemplate and reads
  vaultId from the percent-decoded template variables. It was a static
  resource whose URI was the literal template string, so no concrete
  vault URI could match it.
- buildServer() moved to src/server.ts (re-exported from index.ts) so
  tests can build the real server without starting stdio.
- tests/resources.e2e.test.ts: a real MCP client reads every advertised
  resource from serveStdio(() => buildServer()) over an in-memory
  transport, in both the 2025 and 2026-07-28 protocol eras. Adds the
  @modelcontextprotocol/client dev dependency.
- README, agents.md, CONTRIBUTING, and CHANGELOG updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Combine origin/claude/security-vulnerabilities-review-5d20ad (d9905bd)
with the onepassword:// resources fix.

Conflict resolution:
- package.json, package-lock.json, server.json, SERVER_VERSION: 5.0.0.
- CHANGELOG.md: keep both entries, 5.0.0 above 4.0.4.
- README.md: onepassword:// URIs with #32's allow-list note on the vaults
  row, and a changelog pointer that names both releases. The allow-list
  section's `1password://vaults` line merged without a conflict; it now
  reads onepassword://vaults and also names the items template.

Semantic conflict:
- tests/vault-allowlist.test.ts called the resource callbacks with the
  old signatures (no URI, or a 1password:// string with no template
  variables). They now pass a URL and, for vault-items, { vaultId }.

src/resources/index.ts merged cleanly: filterAllowedVaults() filters
onepassword://vaults, and assertVaultIdAllowed() runs on the decoded
vaultId from the template variables, the same value items.list() gets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read onepassword://vaults and the items template through the real MCP
client with OP_MCP_ALLOWED_VAULTS set, in both protocol eras. The vault
listing is filtered, items of a vault outside the list are refused
before items.list() runs, and the check sees the percent-decoded vaultId,
so an encoded ID can't slip past it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The security hardening from #32 ships in the same release as the
resource URI change, so 4.0.4 is never published. Move its Security and
Changed notes into the 5.0.0 entry, name the items template in the
allow-list note, and point the README and agents.md at 5.0.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CakeRepository
CakeRepository merged commit 9287e77 into master Oct 4, 2026
3 checks passed
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