Report a vulnerability privately. Do not open a public issue for a defect that an attacker can use.
Use GitHub private vulnerability reporting on this repository: https://github.com/sanlee-ys/telltale/security/advisories/new. The report stays private between you and the maintainer until an advisory is published.
If that page does not show a report form, open a normal issue that says only that you have a security report. Do not put the defect in the issue. The maintainer then opens a private advisory thread, and you send the details there.
What to expect. One person maintains this project. The response is best effort. There is no service level agreement, no bounty, and no guaranteed response time. You get an acknowledgement when the maintainer reads the report, and a decision after that. A fix ships in a normal release.
Which versions get fixes. The most recent release gets fixes. Older tags do not. Build from source, or install the most recent release, before you report a defect.
Read this section first. It tells you which behavior is a defect and which
behavior is the design. docs/design.md records the same boundary in more
detail, and CLAUDE.md states it as a rule for contributors.
The gauges read local files. They do not call the network. telltale statusline, telltale hud and telltale snapshot read the session files that
the vendor CLIs already write on this machine. They make no network calls. They
read no credentials. No keybinding changes vendor state.
Three bounded write exceptions exist. All three write under ~/.telltale/,
and all three write numbers and keys only, never session content. telltale council writes council/room.json (session ids and the workspace path). The
statusline writes quota/<vendor>.json (the rate-limit windows it just
rendered). The relays write usage/<vendor>.json (per-turn token totals).
telltale hook cursor and telltale otel grok are the two writers of that last
file. The OTLP listener binds the loopback interface only, and the vendor pushes
to it, so the gauges still make no network calls of their own. A test pins the
serialized form of each of the three files to keys and numbers.
The event sink is different, and it says so. telltale events stores hook
payloads verbatim under ~/.telltale/events/, so that directory holds content,
not just numbers and keys. Scope contains it rather than redaction: the operator
starts it as its own foreground mode, the server binds the loopback interface
only, and no gauge reads or renders those files.
telltale council starts vendor CLIs. It runs the vendor binaries that the
operator already installed and already trusts, in the operator's own workspace,
under the operator's own vendor credentials. telltale adds no account and holds
no token of its own. A seat can write to the workspace, and the room shows which
sandbox posture each seat runs under.
Two adapters meet credential material, and both refuse it by construction.
The Cursor adapter reads a store that holds OAuth and refresh tokens in the same
SQLite file as session state, so a read allowlist limits it, and a test plants
credential-shaped strings in fixtures and asserts that none reaches a surface the
HUD can display. The Cursor hook receives a payload that carries the model reply
text and the user email address, so the destination struct is the allowlist:
encoding/json drops every field with no destination, and a test asserts that no
planted marker survives the parser or the cache file.
A report against any of the above is in scope. Examples: credential-shaped
content that reaches a rendered surface or a cache file; a gauge that makes a
network call; a write outside ~/.telltale/; session content in a file that the
rules above limit to numbers and keys; a council gate that approves a command it
should hold.
No release artifact carries a signature of its own. No archive and no binary
holds an Authenticode signature, a codesign signature, or a notarization ticket.
The release workflow builds and stages the artifacts, and it adds none of these.
You can check this: .goreleaser.yaml declares no signs block, and
.github/workflows/release.yml holds no signing secret.
Read this together with the provenance section below. A release from the next tag forward carries a signed provenance attestation, which is a separate signed document about an archive. It is not a signature on an archive, and it does not change any statement in this section.
The consequences, per platform:
- Windows. The binary carries no Authenticode signature.
scoopandwingetinstall that same unsigned binary. A direct download through a browser can raise a Microsoft Defender SmartScreen prompt, because SmartScreen weighs the signature and the download reputation. This project has not measured that prompt. - macOS. The
darwin_amd64anddarwin_arm64archives are unsigned and not notarized. macOS applies thecom.apple.quarantineattribute to a file that a browser downloads, and Gatekeeper then refuses to run an unsigned, un-notarized binary that carries that attribute. This project has now walked that path, on 2026-08-17, on an Intel MBP on macOS 26.5.2, against the publishedv0.2.0darwin_amd64archive. macOS killed the binary. The terminal reportedKilled: 9and exit status 137, the binary printed nothing, and a dialog read"telltale" Not OpenedoverApple could not verify "telltale" is free of malware that may harm your Mac or compromise your privacy., with the buttonsMove to TrashandDone.xattr -d com.apple.quarantinecleared the attribute, and the binary then ran to completion at exit 0. That walk used no browser.curlfetched the archive, and the operator wrote thecom.apple.quarantineattribute by hand to reproduce what a browser marks, so the gate and the remedy are measured and the download itself is reproduced. A real browser download is still owed, andPARITY.mdrecords it as owed. - Linux. The archive is unsigned. Linux applies no equivalent gate, so the archive runs after you unpack it.
Verify the checksum. Every release attaches checksums.txt with a SHA-256
for each archive. That file tells you the archive is the one the release workflow
produced. It does not tell you who produced it. scoop verifies the SHA-256
itself from the manifest.
Signing is not planned work with a date. It needs a certificate or an Apple
Developer account that the owner holds, plus release secrets, so it is an owner
decision rather than a contributor task. docs/design.md §8 records it as one.
This section applies from the next tag forward. The releases published today carry neither of these. Nothing adds them to a release that already exists.
Each archive gets a signed provenance attestation. The release workflow uses
actions/attest-build-provenance. Verify one with the GitHub CLI:
gh attestation verify telltale_<version>_windows_amd64.zip --repo sanlee-ys/telltale
What that proves, and what it does not. A verified attestation proves that
the release workflow of this repository built that exact archive, from a named
commit, on a runner that GitHub hosts. It closes the gap that checksums.txt
leaves open, because a checksum proves only that two files match. It does not
prove the identity of the owner, and it does not prove that the owner vouches for
the content. A code-signing certificate proves that, and the section above says
why this project does not hold one.
The attestation needs no secret from the owner. GitHub mints a short-lived token for each run, and that token is the identity. This is the reason provenance exists here while signing does not: signing is blocked on a long-lived credential and provenance needs none.
Each archive also gets an SBOM. syft writes one SPDX-JSON document per
archive, and the release attaches it. It lists the Go modules that the build
used. It does not describe the vendor CLI programs that telltale council
starts, because the operator installs those and this project ships none of them.
Two scans run on this repository, and both also run on a weekly schedule. The schedule is the part that matters: it catches a vulnerability that becomes public after the code merged.
govulncheckreads the Go vulnerability database and reports the known vulnerabilities that this code can actually reach. It fails on a finding.- CodeQL runs GitHub's static analysis with the default query suite. Findings appear in this repository's Security tab.
A scan that fails does not mean a release is withheld. It means the maintainer
has to answer it. docs/design.md §8 records what each scan covers, and it
records the one file that each scan cannot see.