Skip to content

Commit 18d66c0

Browse files
committed
feat(helix-loader): allow managing trust from configuration
This allows managing trust via the `config.toml` via the `paths` vector in the `[editor.trust]` section. The path of the workspace is matched against the entries in the `path` array while expanding globbing (a `*` matches a single unspecified path component, a `**` matches all subdirectories) and supporting a leading `~/` as shorthand for the home directory. Prefixing a path with `!` can be used to deny trust. The last matching entry wins. For the following example: ```toml [editor.trust] paths = [ "~/repos/helix", "~/repos/foo/*", "~/repos/bar/**", "!~/repos/bar/untrusted" ] ``` This would result in the following trust levels assuming the home directory `/home/user`: | Path | Decision | |:----------------------------------------------- |:------------------- | | `/home/user/foobar` | undecided | | `/home/user/repos/helix` | trusted | | `/home/other/repos/helix` | undecided | | `/home/user/repos/helix/branch_a` | undecided | | `/home/user/repos/foo` | undecided | | `/home/user/repos/foo/branch_a` | trusted | | `/home/user/repos/foo/remote_a/branch_a` | undecided | | `/home/user/repos/bar/branch_a` | trusted | | `/home/user/repos/bar/remote_a/branch_a` | trusted | | `/home/user/repos/bar/untrusted` | untrusted | In case the configuration does not decide to trust or untrust a given working directory, the existing trust mechanism to prompt the user for each new working directory and storing the decision in an internal database is used. In other words: Users that don't want to make use of the new interface and only use the old model can just leave the `config.toml` unchanged and will never be bothered with this. On the other hand users only wanting to use the configuration can do so using: ```toml [editor.trust] paths = [ "!**", "~/repos/helix", ... ] ``` Since the first item (`!**`) matches every work space and denies trust, this configuration will deny trust to all workspaces unless later items override this. The `insecure` configuration from the editor has been removed, as the same result can be achieved with: ```toml [editor.trust] paths= [ "**" ] ```
1 parent ad6a0db commit 18d66c0

13 files changed

Lines changed: 302 additions & 43 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

book/src/workspace-trust.md

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,63 @@
11
# Workspace trust
22

3-
Helix has a number of potentially dangerous features, namely LSP and ability to use local to workspace configurations. Those features can lead to unexpected code execution. To protect against code execution in dangerous contexts, Helix has a workspace trust protection, which will prevent these potentially dangerous features from running automatically.
3+
Helix has a number of potentially dangerous features, namely LSP and ability to use workspace-local configuration files. Those features can lead to unexpected code execution. To protect against code execution in dangerous contexts, Helix has a workspace trust protection, which will prevent these potentially dangerous features from running automatically.
44

55
Helix will not trust any workspace by default.
66

7-
By default, it will prompt about trust when you open new file in a workspace where you didn't make a decision about trust yet.
7+
By default, it will prompt about trust when you open a new file in a workspace where you didn't make a decision about trust yet.
88

9-
If you decide not to trust a workspace and don't want to be prompted about trust every time you start a new session in it, you can exclude the workspace by choosing `Never` option in trust selection window.
9+
If you decide not to trust a workspace and don't want to be prompted about trust each session, you can exclude the workspace by choosing `Never` option in trust selection window.
1010

1111
You can always make current workspace trusted by running `:workspace-trust` command, and untrust it with `:workspace-untrust`.
1212

13-
Lists of trusted and excluded workspaces, delimited by newline characters, are stored in `~/.local/share/helix/trusted_workspaces` and `~/.local/share/helix/excluded_workspaces` correspondingly.
13+
The list of trusted and permanently untrusted workspaces, delimited by newline characters, are stored in `~/.local/share/helix/trusted_workspaces` and `~/.local/share/helix/excluded_workspaces` respectively.
1414
<!-- TODO: Windows paths -->
1515

1616
# Configuration
1717

18-
You can return to the old behaviour of loading every local `.helix/config.toml` and `.helix/languages.toml` and starting LSP's without an explicit permission by setting following option:
18+
You can return to the old behaviour to accept the risk of loading every local `.helix/config.toml` and `.helix/languages.toml` and starting LSPs without an explicit permission by setting the following option:
1919

2020
```toml
21-
[editor]
22-
insecure = true
21+
[editor.trust]
22+
paths = [ "**" ]
2323
```
24+
25+
In addition to trusting any workspaces with the wildcard glob `**`, it is possible to configure trust fine-grained with a `.gitignore` like syntax. E.g. consider the following:
26+
27+
```toml
28+
[editor.trust]
29+
paths = [
30+
"~/repos/helix",
31+
"~/repos/foo/*",
32+
"~/repos/bar/**",
33+
"!~/repos/bar/untrusted"
34+
]
35+
```
36+
37+
This would result in the following trust levels assuming the home directory `/home/user`:
38+
39+
| Path | Decision |
40+
|:----------------------------------------------- |:------------------- |
41+
| `/home/user/foobar` | undecided |
42+
| `/home/user/repos/helix` | trusted |
43+
| `/home/other/repos/helix` | undecided |
44+
| `/home/user/repos/helix/branch_a` | undecided |
45+
| `/home/user/repos/foo` | undecided |
46+
| `/home/user/repos/foo/branch_a` | trusted |
47+
| `/home/user/repos/foo/remote_a/branch_a` | undecided |
48+
| `/home/user/repos/bar/branch_a` | trusted |
49+
| `/home/user/repos/bar/remote_a/branch_a` | trusted |
50+
| `/home/user/repos/bar/untrusted` | untrusted |
51+
52+
Specifically, the paths are processed one entry at a time by expanding a leading `~/` to the user's home directory, expanding a `*` to any single path segment, and expanding `**` to any number of path segments. An entry prefixed with `!` is a negated entry, instead of granting trust to a matching workspace it will deny it. After processing the full list, the most recently matched entry "wins". If no entry matched (e.g. because the default configuration with an empty list of `paths` was used), the user will be prompted as described in the beginning.
53+
54+
A secure configuration that will never prompt would be:
55+
56+
```toml
57+
paths = [
58+
"!**",
59+
"~/repos/helix",
60+
...
61+
```
62+
63+
Above configuration will

helix-core/src/config.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ impl std::fmt::Display for LanguageLoaderError {
3737
impl std::error::Error for LanguageLoaderError {}
3838

3939
/// Language configuration based on user configured languages.toml.
40-
pub fn user_lang_config(insecure: bool) -> Result<Configuration, toml::de::Error> {
41-
helix_loader::config::user_lang_config(insecure)?.try_into()
40+
pub fn user_lang_config(config: &helix_loader::workspace_trust::Config) -> Result<Configuration, toml::de::Error> {
41+
helix_loader::config::user_lang_config(config)?.try_into()
4242
}
4343

4444
/// Language configuration loader based on user configured languages.toml.
45-
pub fn user_lang_loader(insecure: bool) -> Result<Loader, LanguageLoaderError> {
46-
let config_val = helix_loader::config::user_lang_config(insecure)
45+
pub fn user_lang_loader(config: &helix_loader::workspace_trust::Config) -> Result<Loader, LanguageLoaderError> {
46+
let config_val = helix_loader::config::user_lang_config(config)
4747
.map_err(LanguageLoaderError::DeserializeError)?;
4848
let config = config_val.clone().try_into().map_err(|e| {
4949
if let Some(languages) = config_val.get("language").and_then(|v| v.as_array()) {

helix-loader/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ rust-version.workspace = true
99
categories.workspace = true
1010
repository.workspace = true
1111
homepage.workspace = true
12+
globset.workspace = true
1213

1314
[[bin]]
1415
name = "hx-loader"
@@ -33,3 +34,5 @@ threadpool = { version = "1.0" }
3334
tempfile.workspace = true
3435

3536
tree-house.workspace = true
37+
globset.workspace = true
38+
arc-swap.workspace = true

helix-loader/src/config.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::str::from_utf8;
22

3-
use crate::workspace_trust::{quick_query_workspace, TrustStatus};
3+
use crate::workspace_trust::{self, quick_query_workspace, TrustStatus};
44

55
/// Default built-in languages.toml.
66
pub fn default_lang_config() -> toml::Value {
@@ -10,11 +10,11 @@ pub fn default_lang_config() -> toml::Value {
1010
}
1111

1212
/// User configured languages.toml file, merged with the default config.
13-
pub fn user_lang_config(insecure: bool) -> Result<toml::Value, toml::de::Error> {
13+
pub fn user_lang_config(config: &workspace_trust::Config) -> Result<toml::Value, toml::de::Error> {
1414
let global_config = crate::lang_config_file();
1515
let workspace_config = crate::workspace_lang_config_file();
1616

17-
let files = if let TrustStatus::Trusted = quick_query_workspace(insecure) {
17+
let files = if let TrustStatus::Trusted = quick_query_workspace(config) {
1818
vec![global_config, workspace_config]
1919
} else {
2020
vec![global_config]

helix-loader/src/grammar.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ pub fn build_grammars(target: Option<String>) -> Result<()> {
195195
// merged. The `grammar_selection` key of the config is then used to filter
196196
// down all grammars into a subset of the user's choosing.
197197
fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
198-
let config: Configuration = crate::config::user_lang_config(false)
198+
let config: Configuration = crate::config::user_lang_config(&crate::workspace_trust::Config::default())
199199
.context("Could not parse languages.toml")?
200200
.try_into()?;
201201

@@ -217,7 +217,7 @@ fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
217217
}
218218

219219
pub fn get_grammar_names() -> Result<Option<HashSet<String>>> {
220-
let config: Configuration = crate::config::user_lang_config(false)
220+
let config: Configuration = crate::config::user_lang_config(&crate::workspace_trust::Config::default())
221221
.context("Could not parse languages.toml")?
222222
.try_into()?;
223223

0 commit comments

Comments
 (0)