---
id: "CVE-2026-115477"
kind: "vulnerability"
aliases: ["CVE-2026-115477", "GHSA-737x-cwfr-5vvc"]
title: "obsidian-tc: Declined human-in-the-loop confirmations can permit destructive calls over HTTP"
severity: "high"
cvss: 7.1
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "obsidian-tc", "fixed": ["1.31.4"], "affected": ">= 1.13.0, <= 1.31.3", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["cwe-285", "cwe-863", "mcp", "obsidian-tc", "package", "self-hosted", "streamable-http"]
not_affected_if: ["jwt-secret-unset", "managed-hosting", "stdio-transport", "version-1.31.4-or-later"]
published_at: "2026-10-23T15:17:29+00:00"
first_seen_at: "2026-10-23T19:11:31+00:00"
updated_at: "2026-10-23T21:57:34+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-737x-cwfr-5vvc", "first_seen_at": "2026-10-23T19:11:31+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-115477", "first_seen_at": "2026-10-23T21:57:34+00:00"}]
---

# obsidian-tc: Declined human-in-the-loop confirmations can permit destructive calls over HTTP

obsidian-tc (npm, versions 1.13.0 through 1.31.3) lets a client that declined a human-in-the-loop confirmation still run destructive vault tools such as delete and move, on Streamable HTTP deployments with auth.jwtSecret set. An attacker controlling the client, or a non-compliant client, can replay the echoed requestState to execute operations the human refused.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:obsidian-tc affected >= 1.13.0, <= 1.31.3 fixed 1.31.4

## How to check

Check the installed obsidian-tc version with npm ls obsidian-tc; affected if between 1.13.0 and 1.31.3 inclusive.
Check whether the server runs Streamable HTTP with auth.jwtSecret configured in its config.

## Fix

Upgrade obsidian-tc to 1.31.4.
As an alternative, run over stdio or leave auth.jwtSecret unset, per the advisory workarounds.

## Timeline

- 2026-10-23T19:11:31+00:00 ghsa https://github.com/advisories/GHSA-737x-cwfr-5vvc
- 2026-10-23T21:57:34+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-115477

## From the sources

### Summary
On a Streamable HTTP deployment with `auth.jwtSecret` configured, serving a 2026-07-28-era client that declared `elicitation.form` support, a call to a dispatch-gated tool (`destructive: true`, or a scope with an HITL floor) that was offered an `inputRequired` confirmation could still complete after the human declined, cancelled, or the elicitation response was omitted or malformed, provided the client echoed the validly bound `requestState` on resubmission. The server trusted an authentic, call-bound state as proof of approval; the state is minted before the elicitation leg is asked, so it proves only that the resubmission belongs to the same call.

### Details
Reproduced directly: a `destructive: true` tool call, offered `inputRequired`, answered with `{action: "decline"}` on the `elicitation/create` leg, still wrote. Present since the 2026-07-28 round trip shipped in v1.13.0 (THE-583). Only dispatch-gated tools were reachable; the sixteen handler-side conditionally gated tools (`write_note` overwrite, `move_note`, and others) did not read the elicitation state on the modern wire at all, so they neither let a decline nor an approval through.

### Impact
A client that echoes state after a decline can execute destructive vault operations (delete, cross-folder move, frontmatter replace, link rewrite) that the human explicitly refused. The precondition is a compliant client behaving incorrectly or a client under an attacker's control; the human's refusal is not enforced by the server.

### Patches
Fixed in 1.31.4: the server now also requires the confirm leg's own response (`inputResponse`) to be `{action: "accept", content: {approve: true}}` before trusting the echoed state. A declined, cancelled or not-approved round renders the ordinary `elicit_required` error and is never offered a second round for the same declined confirmation. The same release adds server-initiated in-band confirmation on stdio (legacy era), so stdio agents no longer need to mint `elicit_token` themselves.

### Not addressed
A malicious or non-compliant client can still answer its own `elicitation/create` leg with an approval regardless of what the human said. The server cannot verify that the client rendered the form or that a human saw it; this is inherent to the client-driven half of the 2026-07-28 protocol. The legacy shim, where the server sends and owns the leg, does not share this gap.

### Workarounds
Run affected deployments over stdio, or leave `auth.jwtSecret` unset so the `requestState` codec (and therefore `inputRequired`) is never offered; both fall back to the CLI `elicit_token` path.

### Credit
Reported in GH #967; found and closed under THE-1106 (PR #969); the residual is documented in SECURITY.md.

### References
- PR #969, GH #967, CHANGELOG `## [1.31.4] ### Security`
- `packages/server/src/mcp/server.ts` (`tools/call` handler), `packages/server/src/vault/hitl.ts`

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
