semctx runs locally against repositories that may be untrusted. It:
- reads source files, docs and migrations under the configured root;
- shells out to
git(forverify diff) and, only when explicitly configured, toccc(the optional CocoIndex provider) — both with fixed argument lists, never a shell string; - stores results in a local SQLite file (
.semctx/semctx.db) using bound parameters; - performs no network I/O in its deterministic core.
Please report suspected vulnerabilities privately by opening a
GitHub Security Advisory
on the repository, or by emailing the maintainers listed in package.json. Do not open a
public issue for an unpatched vulnerability.
We aim to acknowledge reports within a few business days and to ship a fix or mitigation as quickly as the severity warrants.
- External command arguments are passed as arrays (
Bun.spawnSync([...])), so task text or file contents cannot inject shell commands. - All SQL uses parameter binding; no value is concatenated into a query string.
- Config and task files are parsed with
JSON.parseand validated with Zod at the boundary.
- GitHub Action (
packages/github-action): uses the standardpull_requesttrigger, neverpull_request_target. Requirespermissions: contents: readonly — no write token, no secret. It never comments on PRs. All user-controlled inputs are routed through the stepenv:and referenced as shell variables, so the${{ }}template engine never interpolates a value into a run script (no Actions injection). It runs a fixed set ofsemctxcommands plus a Node adapter — it does not execute arbitrary PR scripts. - Claude Code guarded hook (
plugins/claude-code): advisory (never blocks) by default. When a project opts in, it gates onlygit commit/git push, keyed on a diff hash — it runs no analysis and parses the command structurally (argv tokens, never a shell eval). It is strictly disableable withSEMCTX_GUARD=off. The verification-state file is git-ignored and written atomically. This is a cooperative workflow gate, not anti-tamper enforcement: the same principal can forge the state, disable the hook, bypass recognized Bash shapes, or run Git outside Claude. - Shadow lifecycle hook (both plugins): never blocks. Every path exits with success and it
writes nothing to stdout, so no output can be read as a decision. It parses the host envelope
that arrives on stdin and uses exactly four fields (
hook_event_name,session_id,cwd,tool_name); every other field the host sends — prompt,transcript_path,tool_input,tool_response, model, permission mode — is not retained, used, or reproduced. It never opens the transcript file and never reads repository source. Its ledger is git-ignored, holds canonical stage ids only, and is keyed by a SHA-256 digest so the host session id never reaches the disk. It is strictly disableable withSEMCTX_LIFECYCLE=off. Like the guard, this is a cooperative advisory surface: the same principal can edit or delete the ledger, and its report is presence evidence, not proof that a stage did its job. - Authored semantic state (
.semctx/semantic/*.sem) is also cooperative trust. Normal CLI/MCP APIs require proved evidence before resolving an unknown and a fresh composedVERIFIEDverdict before closing a change as verified, but a co-located principal can still edit the files directly.