Please report security vulnerabilities privately rather than opening a public issue. Use GitHub's "Report a vulnerability" (Security Advisories) on the repository, or contact the maintainer directly. We aim to acknowledge reports within a few days.
kritya is beta software. The properties below are implemented and covered by
automated checks (npm audit, Dependabot), but have not had a third-party
security audit or penetration test — treat them as defense-in-depth, not a
certified guarantee.
kritya runs an autonomous agent that can read and modify files and run shell commands in the workspace you launch it in. Some safety properties to be aware of:
- Permission prompts gate every mutating tool (
write_file,edit_file,shell) unless you allowlist them insettings.json. - Deny rules (
denyinsettings.json) block matching tool calls outright and cannot be overridden by an allow rule or an "always allow" choice. - Destructive-command detection forces a warning prompt for commands like
rm -rf,git push --force, andcurl | sh, even when allowlisted. This is a regex-based backstop, not a guarantee — it can be evaded (e.g. long-form flags like--recursive --force, or obfuscation via$(...),eval, or a base64-encoded payload). Don't rely on it as the sole safeguard for a blanketshell(*)allow rule. - Sandboxed execution (
sandboxExecin config — see Sandboxed execution) adds an OS-enforced backstop for the case above: shell commands run underbwrap(Linux) orsandbox-exec(macOS) with writes confined to the workspace, so evading the regex no longer means unrestricted write access to the rest of the machine. Default is"auto", which sandboxes every command on Linux/macOS when the required binary is present, and (since there's no sandbox binary to fall back to) only commands flagged as dangerous on Windows;"always"and"strict"sandbox every command on every platform. It does not confine reads or network access — treat it as raising the cost of an evasion, not eliminating one."auto"and"always"fall back to an unsandboxed run (with a warning) if the sandbox binary isn't available;"strict"refuses to run the command at all in that case instead. - File access is confined to the workspace root — including via a symlink
inside the workspace that points outside it — and paths that look like
secrets (
.env*,.git/config,*credentials*,*secret*, private keys) are blocked from being read or written by tools, regardless of allowlist rules. Theshelltool gets the same filename-based check: a command whose text references a sensitive path (cat .env,grep foo .env) is refused rather than relying on output redaction alone. That check can only see the literal filename in the command text, so shell expansion or unusual quoting can slip past it. - Secret scanning on write. Filename checks only cover secrets kept in
files that look sensitive; they do nothing to stop the model writing a
real key it saw in some tool output into an ordinary file like
README.md. Sowrite_fileandedit_filealso scan the content being written for known key formats (AWS, GitHub, GitLab, Slack, Stripe, Google, OpenAI, Anthropic, private key blocks, …) and high-entropy secret-shaped assignments, and block the write. Shell and background-process output is redacted with the same patterns before it reaches the transcript. Both are heuristics — they can miss a novel key format and can false-positive on random-looking fixtures. - SSRF guard.
fetch_urland the MCP HTTP transport share one host check that refuses private, loopback, link-local, and carrier-grade-NAT ranges, so neither can be steered into your internal network or a cloud metadata endpoint. - Workspace trust. A repository's own
.kritya/settings.jsonallow rules, hooks,.env, custom commands,.mcp.json, and.kritya/plugins/are all inert until you trust that workspace, so cloning a hostile repo can't self-grant permissions or run code just by being opened. Trust is keyed on a hash of the gated content, so changing it re-prompts. In headless/CI mode this is off unless--trustis passed, since CI often checks out untrusted branches and PRs. - Per-server MCP trust. Every MCP server is arbitrary code (stdio) or a
remote endpoint (HTTP) running with your credentials, so each one also
gets its own first-use confirmation on top of workspace trust — approving a
repo's
.mcp.jsononce does not blanket-approve servers a later commit adds. The fingerprint covers the declared, pre-expansion config (never expanded env/header values, which may hold live secrets); if a server's command, args, cwd, url, tool filter, or env/header key names change, it counts as new and re-prompts./mcp trustlists approvals and/mcp trust revoke <name>withdraws them. - Agent Plugins are covered by both gates: a workspace plugin is only discovered once the workspace is trusted, and any MCP server it declares still needs its own per-server approval. See Agent Plugins.
- Untrusted content from web search and MCP tools is wrapped in explicit
markers, and the system prompt instructs the model to treat all tool output
as data, never as instructions. Prompt injection via file/command/web content
is nonetheless a real risk with any LLM agent — review changes before trusting
them, and use plan mode (
/plan) for unfamiliar repositories. - Subagents inherit these limits rather than escaping them. Read-only subagents get no write, edit, or shell tool at all; write-capable subagents are isolated on their own git worktree/branch and have destructive commands blocked outright, since there's no one present to confirm a prompt.
kritya is an AI agent: you launch it by name and it reads/writes files and
runs shell commands via a model you configure, so there is no disguise to
disclose — Article 50(1)'s "obvious from the circumstances" exemption
applies. On first interactive run in a workspace, kritya prints a one-time
notice to that effect, recording the acknowledgment (with a timestamp, not
just a boolean) in ~/.kritya/ai-disclosure.json, keyed by workspace path —
so it shows again in a new project rather than being silently suppressed
machine-wide. By default, /commit appends a
Generated-By: kritya (<provider>/<model>) trailer to the commit message it
writes, disclosing that the change was AI-assisted; set commitAttribution
to false in config to opt out.
Headless/CI mode. kritya --prompt never shows the interactive notice —
this is a deliberate scope decision, not an oversight. There is no TTY to
show a notice to and no human present to read one; the workspace's own
.kritya/settings.json allow rules, hooks, .env, and custom commands stay
inert there unless --trust is passed explicitly (see "Scope and design
notes" above), and that explicit flag is itself the informed, deliberate
invocation Article 50(1)'s "obvious from the circumstances" exemption turns
on.
Scope. kritya's own disclosure covers kritya as the AI system under Article 50(1) — it does not, and cannot, discharge whatever separate obligations your chosen model provider (NVIDIA, OpenAI, Anthropic, etc.) has as a GPAI model provider under Title VIII. Those are a different party's responsibility under the Act.
kritya collects no telemetry by default. It talks only to the model
provider you configure (and to Tavily if you use web search). The one opt-in
exception is KRITYA_OTEL_ENDPOINT: if explicitly set, tracing/metrics spans
are exported to the OpenTelemetry Collector endpoint you configure — nothing
leaves the machine unless you set that yourself. See the README's
Privacy section,
Audit log & telemetry, and
docs/observability.md for the local collector setup.
Everything kritya keeps — config, session transcripts, the audit log, MCP
OAuth tokens, and trust manifests — lives under ~/.kritya/.
- Those files are created
0600inside0700directories. Because POSIX mode bits are a no-op on NTFS, on Windows kritya additionally strips inherited ACEs from~/.kritya/and grants full control to only the current user and SYSTEM, so the same owner-only isolation applies there. - Session transcripts, audit logs, and telemetry files are auto-deleted after
15 days by default (
retentionDaysin config, orKRITYA_RETENTION_DAYS). Set it to0to keep everything indefinitely. See Audit log & telemetry.
Note that the audit log is a local, user-owned record rather than telemetry — it is never transmitted anywhere.