GHSA-737X-CWFR-5VVC

Vulnerability from github – Published: 2026-10-23 17:30 – Updated: 2026-10-23 17:30
VLAI
Summary
obsidian-tc: Declined human-in-the-loop confirmations can permit destructive calls over HTTP
Details

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
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.31.3"
      },
      "package": {
        "ecosystem": "npm",
        "name": "obsidian-tc"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.13.0"
            },
            {
              "fixed": "1.31.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-115477"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-285",
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-23T17:30:10Z",
    "nvd_published_at": "2026-10-23T15:17:29Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nOn 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.\n\n### Details\nReproduced 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.\n\n### Impact\nA 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\u0027s control; the human\u0027s refusal is not enforced by the server.\n\n### Patches\nFixed in 1.31.4: the server now also requires the confirm leg\u0027s 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.\n\n### Not addressed\nA 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.\n\n### Workarounds\nRun 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.\n\n### Credit\nReported in GH #967; found and closed under THE-1106 (PR #969); the residual is documented in SECURITY.md.\n\n### References\n- PR #969, GH #967, CHANGELOG `## [1.31.4] ### Security`\n- `packages/server/src/mcp/server.ts` (`tools/call` handler), `packages/server/src/vault/hitl.ts`",
  "id": "GHSA-737x-cwfr-5vvc",
  "modified": "2026-10-23T17:30:10Z",
  "published": "2026-10-23T17:30:10Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/The-40-Thieves/obsidian-tc/security/advisories/GHSA-737x-cwfr-5vvc"
    },
    {
      "type": "WEB",
      "url": "https://github.com/The-40-Thieves/obsidian-tc/issues/967"
    },
    {
      "type": "WEB",
      "url": "https://github.com/The-40-Thieves/obsidian-tc/pull/969"
    },
    {
      "type": "WEB",
      "url": "https://github.com/The-40-Thieves/obsidian-tc/commit/101c91c656e2c2b88bcf3696a5da035f8c032c71"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/The-40-Thieves/obsidian-tc"
    },
    {
      "type": "WEB",
      "url": "https://github.com/The-40-Thieves/obsidian-tc/blob/v1.31.4/CHANGELOG.md"
    },
    {
      "type": "WEB",
      "url": "https://github.com/The-40-Thieves/obsidian-tc/releases/tag/v1.31.4"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/cve-2026-115477"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "obsidian-tc: Declined human-in-the-loop confirmations can permit destructive calls over HTTP"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.