Helix is designed with a "trust but verify" model. The LLM output is treated as untrusted input. Every shell command, git action, and package installation passes through strict structural validators before reaching the OS. Dangerous operations require explicit typed confirmations (e.g., "YES, FORCE PUSH").
Helix is designed as a defensive cybersecurity platform and an AI-powered productivity shell.
While it includes reconnaissance engines (/scan) and exploit references (via Exploit-DB integration), these are strictly for:
- Authorized penetration testing and red team operations.
- Defensive threat intelligence and detection engineering.
- Educational purposes in controlled environments.
Unauthorized scanning, exploitation, or malicious use of Helix is strictly prohibited.
Helix treats AI-generated output as untrusted. To prevent catastrophic accidents, the following safety layers are enforced:
- Hard Blocks: Destructive patterns like
rm -rf /,mkfs,curl | sh,eval, and redirection onto a raw block device (> /dev/sda,>> /dev/nvme0n1, and thehd*,vd*,disk*,rdisk*families) are blocked at the parser level. Reading a device and writing to/dev/nullare untouched. - Risk Tiering: Medium-risk commands (e.g.,
sed -i,chmod,chown, and any redirection that writes) require explicit user confirmation. Spacing does not change the tier:echo x > f,echo x >fandcat a 2>err.logare all Medium. A descriptor duplication (2>&1) writes no file and stays Low. - Directory Sandbox: Write and delete operations are confined to the current working directory and its subdirectories. Absolute paths outside the sandbox are rejected.
Both of the guarantees above were false until 2026-09-08, and are recorded here rather than quietly corrected. The hard-block rule against device writes was written
>:\\s*/dev/sd[a-z], where a Go raw string makes\\sa literal backslash and ans— so it demanded the text>:\and matched nothing a shell produces.cat /dev/zero > /dev/sdapassed it. Separately, the risk tier for redirection tested for" > "with spaces on both sides, soecho x >/dev/sdawas classified Low — the tier that does not ask — andecho key >~/.ssh/authorized_keyswith it. Both rules are now asserted by behaviour rather than by pattern text, including a test that the old expression really was inert, so a future rewrite cannot make them ornamental again.
- Typed Confirmations: Dangerous Git operations (
push --force,reset --hard,clean -fdx) require the user to type an exact confirmation phrase. - Critical Package Protection: Helix blocks the removal of critical system packages (e.g.,
libc6,systemd,bash) to prevent OS corruption.
- The
/scanengine requires explicit target authorization with a written scope/reason before executingnmapormasscan. - Dangerous flags (e.g.,
masscan --rate 1000000) are blocked to prevent network floods.
Retrieved knowledge is untrusted data with zero authority. Planner context is
built only from sanitized structured fields inside authority="data-only"
fences; a per-request canary detects context echo; a fail-closed critic validates any plan that exhibits unsolicited network
egress (external URLs absent from the user request); clean local operations
execute without extra review, keeping Helix fast for legitimate red-team work. See docs/threat_model.md.
In strict mode (/sandbox strict), write and delete operations outside the
jail root are denied by the OS kernel (Seatbelt on macOS; bubblewrap or
Landlock on Linux). This closes the gap left by advisory path validation:
even a confused or injected child process cannot write outside the sandbox.
Where no kernel backend exists, Helix warns and degrades to advisory
confinement.
Helix extracts exactly one third-party archive: the standalone piper speech
binary, fetched over the network and then executed. Extraction writes through
an os.Root opened on the destination, so every path is resolved inside
that directory by the kernel. Entry names are additionally required to be local
(filepath.IsLocal, after slash conversion), which rejects absolute paths,
.. traversal, and Windows reserved device names.
The os.Root is the load-bearing half, not the name check. A name-based guard
can only judge the path an archive declares; it cannot know what the
filesystem will do with it. ~/.helix/piper is reused across installs and
upgrades, so if anything in that tree is a symlink pointing out of it, an entry
named piper/lib/x is perfectly local by every string test and a checked
filepath.Join still follows the link straight out. A regression test extracts
exactly that archive into exactly that tree and asserts the file outside is
untouched.
The self-updater's extraction is safe by a different route: it never uses an entry's path at all, matching the binary by base name and always writing to a filename of its own choosing.
Sandbox validation resolves symlinks, which costs a chain of lstat calls per
path. ValidateCommand therefore bounds the work it will do for one command:
results are memoised per path, and the number of distinct paths one command
may make it resolve is capped. Passing the cap refuses the command — the
sandbox never permits a path it declined to check.
This is a denial-of-service control, not an access-control one. Commands reach the validator from a model, so their length is not under the user's control, and the storage this runs on at the edge is slow. A fuzzer found the original version resolving every absolute-looking word in every command — including read-only ones, which discarded the answer — at a cost that grew without limit alongside the input.
The harness gained a file tool (read, list, glob, grep, edit, write). It does
not carry its own confinement. Every path goes through the same
DirectorySandbox.ValidateSafePath that shell commands get, passed in as a
resolver, and the tool package refuses to run at all when no resolver is
configured rather than defaulting to "anywhere".
That is deliberate and is the main security decision in the port. The upstream
implementation this came from confines paths with its own prefix check against
the working directory. Helix already has a stronger one — it resolves symlinks
on both sides, folds case for macOS and Windows, handles a target that does not
exist yet by validating its parent, and rejects a sibling whose name merely
extends the root (/tmp/jail-x against root /tmp/jail). A second, weaker copy
of a confinement rule is how a jail grows a door, so there is one copy and the
file tool consults it.
Two further properties:
- Writes are atomic and preserve the existing mode. A temp file in the same directory and one rename. A 0600 file does not become 0644 because something rewrote it, and an interrupted write leaves the original intact.
- Content a file tool returns is data, never instruction. It reaches the
planner through the same
authority="data-only"execution report as command output. A file in a repository was written by whoever wrote that repository — precisely the provenance the Instruction Firewall exists for — so areadof an attacker-authored source file cannot direct the next plan.
Local policy hooks see file steps too, on the pre-file / post-file events,
with the match subject "<action> <path>" so a rule can gate an action, a path,
or both. A blocking pre-file hook denies the step, and it runs after the risk
tiers have already approved it — hooks subtract permission, never grant it.
Two install policies exist deliberately, and the boundary between them is the moment of consent.
First boot asks. The system-package stage detects the host's package manager and requests a separate yes for each dependency, showing the exact command first. This is where you decide what Helix may put on your machine.
/blackbox setup does not ask again. Choosing a speech chain is the
decision, so what that chain needs — a runtime, a model file, a voice server, or
a host package such as Python, git or cargo — is installed and started without a
further prompt. Being asked whether you want the file the chain cannot start
without has one sensible answer, and declining leaves a configuration Helix has
just recommended.
Stated plainly, because it is a real escalation: on Linux those package
commands carry sudo, and they run after one selection rather than one
selection per package. The controls that remain are the ones that were always
doing the work: Helix never runs a guessed package name — a manager with no
verified entry in the catalogue produces guidance, not a command — the command
is printed before it runs, and installation is confined to the catalogue in
internal/deps plus the specific sidecars in the wizard's own table. It will
not install a container runtime, and it will not accept a model licence.
The licence gate is walked, not crossed. For CSM's gated weights Helix
installs the Hugging Face CLI, opens the model's terms page in a browser and
runs huggingface-cli login — and stops there. None of those three is the
consent: a pip package is not a decision, showing you a page is not agreeing to
it, and a login prompt is not a token. The token you paste is the consent, and
it goes to the Hugging Face CLI's own credential store, not to Helix.
Installers inherit stdin, so a command that needs to ask can. sudo on a
host without a cached password, and huggingface-cli login waiting for a token,
both prompt at the terminal instead of failing with the reason off-screen. This
is visible privilege escalation rather than hidden: the command is printed
first, and the password goes to sudo, never through Helix.
The one third-party archive Helix downloads is checksum-pinned and extracted
under os.Root (§5a). Sources it builds — currently only csm.rs — are cloned
from a pinned URL into ~/.helix and compiled there; the build is ordinary
cargo, with no privilege beyond the user's.
~/.helix/config.json is written to a temporary file beside it, fsynced, and
renamed into place. A rename within one directory is atomic, so the previous
file survives every failure before it.
This is not theoretical tidiness. The previous implementation used
os.WriteFile, which opens with O_TRUNC — it empties the file and then
writes — and the way that fails in practice is a full disk, which is also the
moment several other things fail at once and nobody is reading carefully. It was
observed one step from destroying a live configuration:
✘ preferences could not be saved: open ~/.helix/config.json: no space left on device
Losing that file loses the provider, the voice chain and every preference in it.
The temporary file is created in the config's own directory rather than
TMPDIR, because a rename cannot cross a filesystem and would silently degrade
to a copy.
Multi-gigabyte work is refused before it starts. Helix measures free space ahead of the CSM build and the model download and declines with the numbers, rather than compiling for twenty minutes and dying at 95%. A filesystem that cannot be measured is allowed through: guessing that a disk is too small where the syscall failed would block a machine that is probably fine.
Crash reports are written ONLY to local disk (0600), contain redacted
environment values (*_KEY, *_TOKEN, *_SECRET, *_PASSWORD), are never
transmitted, are capped at 5, and are removable via /purge. The diagnostics
package is provably network-free via a CI-enforced import grep test.
The same contract now covers everything else Helix writes about a session:
internal/journal (the daemon interaction journal and the opt-in voice
transcript log) and internal/metrics (latency and liveness samples) each carry
their own CI-enforced grep test proving they import no networking. Code that
writes down what you said or did cannot send it anywhere. All of it is 0600
inside a 0700 directory, size-rotated, and wiped by /purge.
/reboot writes one short-lived file under the same contract, minus the
rotation it does not need: ~/.helix/reboot.json carries the state a restart
needs — mode, working directory, provider and model, and in-progress tasks — plus,
for a typed restart only, a 240-character excerpt of the last thing you
typed. It is not a second copy of the conversation: duplicating session.json
would put everything you said on disk twice, in a file /memory clear does not
govern. It is deleted the moment the restarted shell reads it rather than
rotated, discarded unread past 12 hours, and wiped by /purge. It stores
provider and model names, never a key.
Speech is an untrusted input channel, not a convenient keyboard. Transcribed audio arrives with user authority, so a television, a podcast, or a person in the room becomes text that could otherwise plan and execute. The controls are structural rather than advisory:
-
Risk is capped at Medium. A high-risk command is unreachable from voice whatever the phrasing, and the refusal is spoken as well as printed.
-
Typed confirmations stay typed. Force push, hard reset, worktree clean, deleting a main branch: the voice prompter refuses these outright, so voice cannot satisfy them even with a perfect impersonation. In a full-duplex session Helix goes further and deafens the session for the duration —
session.input_audio.mute, verified to stop transcription and turn-taking outright — so nothing in the room can speak the phrase while you read the prompt. If that mute is refused, the confirmation is refused with it. -
Ending the machine is High risk.
shutdown,reboot,halt,poweroffand the macOSosascriptspelling are unreachable from voice and blocked at the default posture. They analysed as Low until 2026-09-13 — which underaskmeans they run with no confirmation — and the Medium voice cap does not reach a Low command either, so neither guard applied. Found when a planner offered to reboot the machine in answer to "reboot yourself". -
Voice may restart the SHELL, and nothing else in the danger category.
/rebootis reachable by voice because it destroys nothing: the continuity record is written before the process ends, so the worst a misheard "reboot" costs is a few seconds, after which the same mode, directory and conversation are back./purge,/rag-reset,/commit,/config,/hooks,/init,/scan,/setupand/stealth— all nine — remain unreachable, and the distinction is data loss, not severity of sound. -
A spoken restart MAY install software, by owner decision.
/rebootself-updates and does so automatically, from the microphone as well as the keyboard. The reasoning is that the release comes from a repository the owner controls and tags deliberately, so publishing it IS the authorization. The consequence is stated rather than hidden: whoever can publish to the configured repo can replace the binary with no human present, and a misheard "reboot" can trigger that. What still holds is everything in ADR-019 — mandatory checksum, pinned host, build-info proof — plus automatic rollback when the new binary cannot start.update.check: falseturns the check off entirely. -
A spoken restart writes nothing you said.
/rebootis voice-reachable, and the continuity record it leaves omits conversation content entirely on the spoken path — so the rule below survives without an exception. -
Confirmations fail closed. Silence, timeout, or an unintelligible answer counts as "no". A pending confirmation also remembers what it asked about, so a question about stopping listening cannot be answered by a later phrase that means something else.
-
The microphone is open at an idle prompt on a fresh install. This changed on 2026-09-09 by owner decision and is stated here rather than left to be discovered: wake listening (
/blackbox wake on|off) defaults to on, so a new install holds the recorder open while the shell sits idle and enters live mode on any sound. What holds it up: nothing is transcribed while it waits (the detector scores chunks and discards them), the state is announced once per session and shown continuously by the standby HUD, it arms only where a recorder, a transcriber and keystroke readiness all exist, turning it on is typed-only while turning it off always works by voice, and one command closes the microphone entirely. Setspeech.wake_word.enabled: falsein~/.helix/config.jsonto decline it — that value is honoured and never overridden by the default. Unix-only; Windows reports unavailable. The residual risk — someone who installs Helix and reads no banner — is recorded as threat V2b indocs/threat_model_voice.md. -
Once woken, the microphone stays open for the conversation. This is the largest exposure in this document and it changed on 2026-09-11. A wake used to gate every turn; it now opens a conversation in which every utterance is transcribed, sent to an STT provider and planned, with no further gate. Three states, and only one of them closes the microphone:
microphone ends by STANDBY (startup default) open, wake detection only, nothing transcribed any sound → AWAKE AWAKE open and transcribing every turn a stop phrase, /blackbox off, or 10 minutes of silenceMANUAL closed /blackbox wake on— typed onlyWhat holds AWAKE up: an inactivity stand-down (
speech.wake_word.awake_idle_stand_down_s, default 600s) which is the only control that works with nobody present — setting it to0removes the bound and should be read as doing exactly that; spoken stop phrases in two strengths, one of which closes the microphone and persists it; a confirmation round for phrases that could plausibly be part of an ordinary sentence; and the rule below, enforced inside the state transition rather than at a command handler, that voice can never reopen a closed microphone. Recorded as threats V2c, V2d and V2e indocs/threat_model_voice.md. -
Typing during a capture discards the audio. The keyboard is live while Helix listens; a keystroke cancels the capture and the partial clip is thrown away without being transcribed, so audio recorded up to that moment never reaches a provider. There is a test behind that claim rather than a comment.
-
Outside a conversation the microphone opens only for a turn — unless you ask otherwise. Enabling sentence-boundary barge-in (
/config barge-in on) lets Helix sample the mic in the pause between its own spoken sentences, so it can be interrupted by voice. That clip follows the same path as every capture — the recorder writes a temp WAV, which is deleted the moment it is read — and then only its loudness is computed. It is never transcribed, never sent to a provider, never logged, and never enters the conversational context. Off by default, scoped to live mode, and shown on the INTERRUPT row of/blackbox status. -
Spoken input never takes the shell fast path. A confidently classified command line runs as typed when typed; from the microphone it always goes to the planner, because the classifier decides on the first token and ordinary sentences begin with command names.
-
Default-deny command surface. A command is reachable by voice only if the registry marks it so; nine remain unreachable by design (data destruction, scanning, history writes, posture and privacy switches, key entry). One DANGER ZONE command is reachable —
/reboot— because it destroys nothing; the criterion is data loss, not how alarming the command sounds. -
Voice may reduce what is collected, never increase it. "Turn off your eyes" closes the camera and
/blackbox log offstops transcript recording, both by voice; opening the camera is an explicit announced act and starting a transcript log must be typed. The rule has no exceptions:/rebootis the case that tested it, and the feature was shaped to fit — a spoken restart stores no conversation content — rather than the rule being amended.Two enforcement points were added on 2026-09-11, both places where a spoken word could otherwise have widened something silently:
- Voice cannot reopen a closed microphone. Moving out of MANUAL is refused for any non-typed cause, and the refusal lives inside the state transition rather than in a command handler — so it holds for every door into that state, not just the one anybody remembered to guard.
- Spoken
exportdoes not persist. Typed at the prompt,export FOO=barnow changes the session. Spoken, it runs in a subshell and says that it did not stick — an exportedPATHis silent and lasts the session, which makes it a way to redirect every later command without appearing in any of them.
Full model, including the residual risk accepted for a voice-first assistant:
docs/threat_model_voice.md.
Helix updates itself through /reboot. What is verified, and what is not, stated
plainly because an updater is the highest-consequence code in the project:
- Verified. The download's SHA-256 against the checksums file published with that release, matched by filename; the URL and every redirect against a pinned set of GitHub hosts; and the payload's own Go build info, proving it is a Helix binary for this machine before it is installed. Any of these failing is a refusal, never a warning.
- Not verified. The Sigstore signatures the release pipeline produces.
Keyless verification with the wrong identity and issuer constraints reports
success while proving nothing, under a label that stops anyone looking further
— worse than an honest checksum. Helix prints the
cosign verify-blobcommand instead of pretending. See ADR-019. - Reversible. The previous binary is kept, and restored automatically if the new one exits non-zero within ten seconds of starting — the failure a checksum cannot catch, which is an authentic release that does not run on this machine.
- Automatic, by owner decision. Checking is on by default and installing
needs no confirmation, on the typed and spoken paths alike. This is the one
place Helix trades a prompt for convenience, and it does so because the
publisher and the operator are the same person. Turn the whole thing off with
update.check: false, or use/reboot nowto restart without checking.
- A restart is the only thing that puts an excerpt of your words on disk
without an opt-in, and only when typed.
/rebootstores 240 characters of the last typed message in~/.helix/reboot.jsonso the resumed shell can say what it was doing; the file is 0600, consumed on read, and expires in 12 hours. The spoken path stores none of it. - Camera frames are memory-only, always. One frame at a time, downscaled, held in RAM, never written to disk — enforced by a filesystem-snapshot test. Only metadata (provider, count, timestamp) reaches the journal.
- Vision is off by default and opens on an explicit, announced act.
/blackbox statuswill not claim the camera is working until a frame has actually arrived. - Nothing you say is stored unless you ask.
/blackbox log onstarts a local transcript log; with it off there is no directory and no file. It records text and metadata only — never audio, because captured clips are deleted the moment they are read. - Conversational context retention is opt-in and never reaches disk. A
context-conditioned voice (Sesame CSM-1B) needs to hear the last few turns, so
enabling
speech.tts.context_turnsmakes Helix hold recent audio in memory for longer than the turn that produced it. That retention is memory only — the store imports no filesystem or networking API, enforced by a test — bounded by both turn count and total bytes, and dropped when live mode ends. The audio was already in memory a moment earlier for transcription; what changes is how long, which is why the bounds are the control. Off by default. It is visible while it is happening: theCONTEXTrow of/blackbox statusreports how many turns and how much audio are being held, and says retained, unused when turns are being kept that no configured voice can actually consume — retention with a cost and no benefit is the case worth surfacing, not hiding.
- API keys are stored in
~/.helix/secrets.jsonwith strict0600file permissions. - Helix preferentially reads secrets from environment variables to avoid disk persistence in ephemeral environments.
- No secrets are ever logged, even when
HELIX_DEBUG=1is enabled. - Speech keys are namespaced (
stt.*/tts.*) in the same store, and a saved key is verified and reused rather than requested again. - Misdirected-key guard. A pasted key whose prefix unambiguously belongs to
another vendor (
sk-ant-,xai-,gsk_) is flagged before it is stored, because GroqCloud and xAI are different companies one letter apart and the mistake otherwise surfaces later as an auth failure on every transcription. The check is negative-only: it never asserts what a valid key looks like, since vendors change formats and a positive rule would start rejecting good keys.
A key is read without echo. commands.AskSecret reads the terminal in raw
mode and prints nothing back, so the value does not reach the screen, the
scrollback or a screenshot.
This was not always true, and the gap was not theoretical. Every key prompt used
AskLine — the same reader that asks which provider you want, which echoes by
design — so a pasted key was printed in full. It was found when a user sent a
screenshot of the Windows setup wizard with a live sk-proj-… key visible in
it; the key had to be revoked. Nothing about the bug was Windows-specific. It
had echoed on every platform since keys were first asked for, and Windows was
simply the first time anyone photographed it.
The prompt itself says what it is about to do with the value. Before anything is typed it names the provider, the page that issues its keys, the file the key lands in with its mode, and the environment variable that avoids the disk entirely:
API KEY
────────────────────────────────────────────────────────────────
│ PROVIDER openai
│ GET ONE https://platform.openai.com/api-keys
│ STORED ~/.helix/secrets.json (0600, this machine only)
│ OR SET $OPENAI_API_KEY (never written to disk)
│
│ Nothing is echoed as you type. The key is never logged, never
│ passed as a command-line argument, and goes nowhere but openai.
On a terminal that cannot suppress echo the last paragraph is replaced, not softened. It says plainly that the key will be visible on screen and in the scrollback, and suggests pasting it elsewhere. Promising hiding that will not happen is worse than saying nothing, because the reader pastes a credential on the strength of it — which is how a live key ended up in a screenshot.
A provider with no single key-issuing page (custom) gets no URL rather than a
plausible one. A wrong link in a security prompt is worse than no link: the
reader follows it and then has to work out that Helix, not their memory, was
wrong.
Two further properties are worth stating because they are deliberate:
- Not routed through
Prompter. ADR-005 puts/setupon the voice-denied list precisely because it "would have you dictate API keys aloud". A secret that travelled through the same abstraction as an ordinary question would be one refactor away from reaching the voice prompter, soAskSecretreads the terminal directly and there is no channel to misroute. - A terminal that cannot hide input says so. Some emulators — MSYS2's MINGW64 among them — hand a Go binary a pipe rather than a console, and echo there belongs to the emulator, not to Helix. Rather than reading silently and looking fixed, the prompt states that input is not hidden, so the choice to paste a key is an informed one.
make delete-secrets removes every credential Helix stores and nothing else:
secrets.json (all provider keys), daemon.conn.json (the daemon's per-start
auth token), and voice_log/ if the opt-in transcript log was ever enabled.
It is deliberately not part of make clean. Cleaning a build must not cost
you your API keys — clean used to delete them through a blanket *.json
sweep, which is a bug rather than a policy — but revoking what is on a machine
is a real thing to want on its own: handing a laptop on, attaching a transcript
to a bug report, or after a key has leaked.
Two things it deliberately does not do, and says so when it runs:
- The Hugging Face token is named, not deleted. It lives in a shared cache
that other tools authenticate against, and removing the file leaves a
half-revoked login.
hf auth logoutis the command that actually revokes it. - Keys set in the environment are not files and survive it. A target called
"delete secrets" invites the opposite assumption, so it prints the check
(
env | grep -iE 'API_KEY|_TOKEN') rather than letting you infer it.
/purge remains the broader wipe — it reaches models, caches and the stores
outside ~/.helix, with sizes, and asks about each group.
Every official Helix release is built with a verified supply chain. We generate a Software Bill of Materials (SBOM) in SPDX format and cryptographically sign every release artifact using Sigstore keyless signing.
You can verify the integrity and provenance of any downloaded Helix binary or archive using cosign and syft.
1. Install the tools:
2. Verify the signature (Sigstore Keyless):
cosign verify-blob \
--certificate Helix_Linux_x86_64.tar.gz.pem \
--signature Helix_Linux_x86_64.tar.gz.sig \
--certificate-identity-regexp "https://github.com/Nibir1/Helix/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
Helix_Linux_x86_64.tar.gz3. Inspect the SBOM:
syft helix_Linux_x86_64.tar.gzEvery commit and pull request is automatically scanned for known vulnerabilities in Go dependencies using govulncheck and for static application security testing (SAST) using GitHub CodeQL. See .github/workflows/security.yml for details. You can run these checks locally via make sec-scan.
This gate has fired. It held the v1.5.0 release for GO-2026-5942 — a panic
parsing a malformed SVCB or HTTPS DNS record in golang.org/x/net, fixed by
upgrading to v0.56.0. It is recorded here because a scanner that has never
blocked anything is indistinguishable from one that is not running:
govulncheck reports by reachability, and this one was reachable from
Helix's own code rather than merely present in the module graph — through the
full-duplex path, live.Session.negotiate → SetLocalDescription →
dnsmessage.Message.Unpack. The same scan reported 25 other advisories in
required modules that Helix does not call, and correctly did not fail on them.
One toolchain across all three workflows (Go 1.27). govulncheck reports against the
standard library of the Go it runs under, so scanning with an older toolchain than the one
that builds the release answers a question nobody asked. CI, the release build and the
security scan are pinned to the same version for that reason; go.mod's go 1.25.1 is the
language floor the source is written against, which is a different thing. The pin moved off
1.26 because the fuzzing coordinator there could fail a green run at its own deadline
(go.dev/issue/75804) — see .github/workflows/ci.yml.
The linter pin moves with it. golangci-lint embeds a type-checker built against a
specific Go release and cannot read export data from a newer one, so a Go bump alone fails
every job before a line of Helix is checked — with errors naming the standard library
inside the toolchain cache rather than any file in this repository. That has happened twice
now (v1.59.1 against Go 1.25, v2.5.0 against Go 1.27), so the version is pinned to
v2.13.2 — built with go1.27.0 — in .github/workflows/ci.yml, the Makefile's
install hint and .golangci.yml's header, and scripts/check-workflows.sh fails if those
three ever disagree or if the workflows stop agreeing about Go. A version string copied into
three files needs a guard, not a convention.
If you discover a security vulnerability in Helix (e.g., a sandbox escape, a planner injection flaw, or a bypass of the safety pipeline), please report it responsibly.
Do not open a public GitHub issue for security vulnerabilities.
Instead, please email the maintainer or use the GitHub Security Advisories feature to report the issue privately. We will acknowledge receipt within 48 hours and work on a patch.