Security

September 19, 2026 · View on GitHub

Reporting

Please report vulnerabilities through GitHub's private security advisories for nrdz-labs/fast-jev-opencode: https://github.com/nrdz-labs/fast-jev-opencode/security/advisories/new. Do not open a public issue for a vulnerability. Credential leaks are the most likely class here.

What this plugin touches

  • Reads the OpenCode context hook payload (the request about to be sent) and may rewrite tool-call / tool-result parts in the outgoing request only. It never mutates the stored session history, and never touches the compaction path.
  • Reads config.json from its own plugin folder. It does not write it; the file is local configuration you own.
  • Writes log.jsonl (rotated to log.1.jsonl), and — only when debugDumpMessages: truedebug/, inside its own plugin folder. log.jsonl holds only operational fields: counts, character sizes, timings, decision probabilities, the run's model label (providerID/id) and the configured model string, the configured host, the session id, and tool-call identifiers (tool names and tool-call ids). It never holds prompts, tool results, tool inputs, file contents, the configured goal, the full baseUrl or raw error text. debug/ holds the raw conversation verbatim: leave the flag off outside a deliberate fixture capture, and delete debug/ afterwards.
  • Persists per-call decisions (tool-call ids + probabilities) in the plugin storage OpenCode provides for its location.
  • Reads TYPESAFE_API_KEY from the environment, then from ~/.config/opencode/.env. The key is sent only to the configured baseUrl (default https://api.typesafe.ai/v1/systemone). The default is HTTPS; if you point baseUrl at a plain-HTTP endpoint the key travels in cleartext, so keep the default or another HTTPS endpoint you trust.
  • Fails open: a missing key, timeout, transport error, HTTP error, malformed answer, invalid answer, malformed tool payload or unreadable config.json leaves the request untouched. Errors are logged as a fixed category only (for example jev-http (500) or internal); the plugin never stringifies an arbitrary caught error, so a body, URL or cause cannot leak into the log.

What is sent to Jev

The transport contract is separate from the log contract: Jev receives more than the log does. Each ask posts { model, state, questions } to the configured endpoint. The state is built by the vendored state.ts:

  • goal: the configured goal verbatim, or, when unset, the last three real user prompts joined together, each truncated to 500 chars.
  • history: one entry per message with its role and text. Message text — the latest real prompt included — is sent in full until the state is fitted to maxStateTokens; shrinking then abridges texts oldest-first (a 400-char head plus a 150-char tail) and finally collapses or omits older entries.
  • per tool call: its id, tool name, a JSON excerpt of the arguments (truncated to 1000, then 200, then 60 chars, or omitted), and the result note ok|error, N chars (omitted). Tool-result bodies are not included — only that indicator and size.

Non-text, non-tool content such as media/file attachments is skipped, so attachments are not forwarded as attachments. Ordinary message text and tool arguments are sent within those limits and can contain user code, paths, prose or serialised file content. Treat the Jev endpoint as receiving that material, the same way your model provider does. Provider-executed parts are skipped.