mirror of
https://github.com/aaronsb/obsidian-mcp-plugin.git
synced 2026-07-22 06:45:14 +00:00
docs(adr): ADR-103 — TLS cert/key storage strategy for localhost HTTPS (#7)
Records the decision to keep the per-instance, non-synced, auto-generated self-signed cert/key and NOT move the private key into the synced vault, and to dissolve the recurring "store the key in the vault?" question by documenting the HTTPS scope (opt-in, loopback-only, asserts no identity; zero-config already delivered by the HTTP-on-localhost default). Rationale: localhost self-signed cert is low-sensitivity; the API key (#135) is the real secret and already syncs; vault-shared key is a defense-in-depth regression for shared/team vaults for marginal gain. ADR only — no code change; certificate-manager.ts behaviour unchanged. Independent of the accepted "Direct Filesystem Access" finding. Refs #7
This commit is contained in:
parent
b1a8ffe150
commit
92bd69013f
2 changed files with 116 additions and 0 deletions
|
|
@ -23,6 +23,7 @@ _Server architecture, transport, connection handling, plugin lifecycle_
|
|||
| [ADR-100](./core/ADR-100-remove-concurrent-mode-toggle-and-simplify-connection-setup.md) | Remove concurrent mode toggle and simplify connection setup | Accepted |
|
||||
| [ADR-101](./core/ADR-101-tree-based-mcp-tool-visibility-gating.md) | Tree-based MCP tool visibility gating | Draft |
|
||||
| [ADR-102](./core/ADR-102-ship-mcpb-bundle-as-primary-claude-desktop-onboarding.md) | Ship MCPB bundle as primary Claude Desktop onboarding | Draft |
|
||||
| [ADR-103](./core/ADR-103-tls-certificate-and-key-storage-strategy-for-localhost-https.md) | TLS certificate and key storage strategy for localhost HTTPS | Accepted |
|
||||
|
||||
## Tools & API
|
||||
_MCP tool design, semantic operations, graph operations, formatters_
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
status: Accepted
|
||||
date: 2026-05-16
|
||||
deciders:
|
||||
- aaronsb
|
||||
- claude
|
||||
related: []
|
||||
---
|
||||
|
||||
# ADR-103: TLS certificate and key storage strategy for localhost HTTPS
|
||||
|
||||
## Context
|
||||
|
||||
The optional HTTPS server uses a self-signed certificate. When none is
|
||||
configured, `certificate-manager.ts` generates one and writes the cert +
|
||||
private key with `fs.writeFileSync` to:
|
||||
|
||||
```
|
||||
<vault base path>/<configDir>/plugins/semantic-vault-mcp/certificates/default.{crt,key}
|
||||
```
|
||||
|
||||
`configDir` is `.obsidian` by default. This is the plugin's data directory,
|
||||
**not** the synced note tree, and Obsidian's vault sync does not carry it by
|
||||
default. The key is therefore **per-instance**: each machine that enables
|
||||
HTTPS generates and keeps its own.
|
||||
|
||||
A recurring design question keeps resurfacing — stated plainly as: *"if the
|
||||
plugin already has vault access, would it be acceptable to keep the TLS key
|
||||
in the vault?"* Storing it in the synced tree would make the opt-in HTTPS
|
||||
path zero-config on every synced instance — generate once, it travels — at
|
||||
the cost of **one shared private key** across all of a user's (or a team's)
|
||||
machines. The pull is real enough that it needs a recorded decision, not
|
||||
another ad-hoc re-litigation.
|
||||
|
||||
This intersects the "Direct Filesystem Access" review finding (the `fs`
|
||||
write here), which is already analysed and **accepted** as load-bearing in
|
||||
`CLAUDE.md` — this ADR is about *where the key lives*, not whether `fs` is
|
||||
used.
|
||||
|
||||
## Decision
|
||||
|
||||
**Keep the status quo: a per-instance, auto-generated, non-synced
|
||||
self-signed cert/key. Do not move the private key into the synced vault.**
|
||||
|
||||
And resolve the recurring question by **documenting the HTTPS scope** so it
|
||||
stops being re-opened: HTTPS here is *opt-in, loopback-only, and
|
||||
identity-asserting-nothing*; the zero-config property it seems to want
|
||||
already exists elsewhere (see below). The question dissolves once the scope
|
||||
is written down.
|
||||
|
||||
Rationale:
|
||||
|
||||
1. **A localhost self-signed cert asserts no identity.** Its only job is to
|
||||
encrypt loopback traffic; `CN=localhost` is trusted by nothing and
|
||||
proves nothing. Its sensitivity is far below that of an identity
|
||||
certificate — the threat a shared key would worsen is small to begin
|
||||
with.
|
||||
2. **The API key is the real secret, and it already syncs.** The bearer
|
||||
token in the plugin's synced settings is what actually gates access, and
|
||||
it already travels wherever plugin settings are synced. Secret-hygiene
|
||||
effort belongs there (tracked separately: env-var API key, #135), not on
|
||||
a loopback TLS key. Optimising the low-value secret while the high-value
|
||||
one is the open lever is misdirected energy.
|
||||
3. **Zero-config already exists — via the HTTP default, not a synced key.**
|
||||
The server listens on `127.0.0.1` over HTTP by default and works on any
|
||||
instance with no setup. HTTPS is the *opt-in* path; syncing its key
|
||||
would help only the minority who both enable HTTPS *and* run multiple
|
||||
synced instances.
|
||||
4. **Defense-in-depth, specifically for shared/team vaults.** A per-instance
|
||||
key contains a compromise to one machine and is regenerated locally for
|
||||
free. A vault-shared key is a single point of failure that cannot be
|
||||
revoked per instance and would silently propagate to every member of a
|
||||
shared vault — eroding isolation exactly where it matters most, to buy
|
||||
convenience that point 3 shows is largely already delivered.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- Compromise of one machine's loopback TLS key does not implicate any other
|
||||
instance; rotation is local and costless.
|
||||
- Shared/team vaults do not silently distribute a private key.
|
||||
- The HTTPS scope is now written down; the question stops recurring.
|
||||
|
||||
### Negative
|
||||
|
||||
- A user running HTTPS across several synced instances must accept each
|
||||
instance's own self-signed cert (or supply their own). This is the
|
||||
intended trade and is mild given the HTTP default already needs no setup.
|
||||
|
||||
### Neutral
|
||||
|
||||
- No code change. `certificate-manager.ts` behaviour is unchanged; this ADR
|
||||
ratifies and documents it.
|
||||
- Independent of, and does not alter, the accepted "Direct Filesystem
|
||||
Access" finding.
|
||||
- Relates to #135 (env-var API key) as the place the real secret-hygiene
|
||||
work lives.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **(b) Vault-shared key (the intrusive thought).** Solo-convenient:
|
||||
generate once, every synced instance gets HTTPS with no setup. Rejected —
|
||||
one non-revocable private key shared across machines and shared-vault
|
||||
members is a defense-in-depth regression, and the convenience it buys is
|
||||
marginal because the HTTP-on-localhost default already provides
|
||||
zero-config; only the opt-in HTTPS minority benefits.
|
||||
- **Per-instance key, but sync only the public cert** (so clients could
|
||||
pin). Rejected as overkill: localhost self-signed clients
|
||||
trust-on-first-use or disable verification anyway; syncing a public cert
|
||||
adds moving parts for negligible gain.
|
||||
- **Leave it undocumented and decide case-by-case.** Rejected — the
|
||||
question has already resurfaced more than once; the cost here is the
|
||||
absence of a written scope, and an ADR is exactly the friction that fixes
|
||||
that.
|
||||
Loading…
Reference in a new issue