Found by the security review #73. Severity: low (hardening; the same secrets are already in the config file on that host, so the added exposure is captured output, logs and pasted vaultik info).
What is wrong
config set prints key = value for every key after writing (internal/cli/config.go:407), so setting s3.secret_access_key or age_secret_key this way prints the secret into any captured stdout.
config set leaves a group- or world-readable config as it is. The file always exists at that point and os.WriteFile does not change the mode of an existing file, so the stat-and-mode block at config.go:395-400 has no effect. The warning about loose modes lives in config.Load (internal/config/config.go:265-276), which config set never calls.
ParseStorageURL (internal/storage/url.go:63-111) ignores the userinfo part and any query parameter other than endpoint, region and ssl. If credentials sit in the URL as well as in s3.*, no error is raised and the raw URL is logged at INFO and stored in the local index on first bind (internal/vaultik/storage_bind.go:63-69), printed by vaultik info (internal/vaultik/info.go:52) and repeated in the binding-mismatch error (storage_bind.go:90-91). Silently ignored parameters are also a correctness trap: a misspelt endpoint= sends the backup to the default AWS endpoint.
Acceptable
config set prints only the key name after a successful write.
When the existing file has any of mode bits 0044 set, config set either calls os.Chmod(path, 0o600) after the write or prints the same warning config.Load prints. The ineffective mode block is deleted. Passing a different mode to os.WriteFile is not a fix.
ParseStorageURL returns an error, without repeating the URL, when an s3:// or rclone:// URL has a userinfo part or a query parameter it does not know (rclone:// knows none); the message names s3.access_key_id and s3.secret_access_key. When url.Parse fails, only the inner cause is wrapped, not the whole *url.Error, so the raw URL is not echoed. file:// is left alone.
Definition of done
Tests: config set of a secret key does not print the value; a pre-existing 0644 config is tightened or warned about; both rejected URL forms, and the url.Parse failure text, in a new internal/storage/url_test.go.
No existing assertion weakened; make check green.
Line numbers are as of next at 6fcd8e1.
model: fable-5-1
Found by the security review https://git.eeqj.de/sneak/vaultik/issues/73. Severity: **low** (hardening; the same secrets are already in the config file on that host, so the added exposure is captured output, logs and pasted `vaultik info`).
## What is wrong
1. `config set` prints `key = value` for every key after writing (`internal/cli/config.go:407`), so setting `s3.secret_access_key` or `age_secret_key` this way prints the secret into any captured stdout.
2. `config set` leaves a group- or world-readable config as it is. The file always exists at that point and `os.WriteFile` does not change the mode of an existing file, so the stat-and-mode block at `config.go:395-400` has no effect. The warning about loose modes lives in `config.Load` (`internal/config/config.go:265-276`), which `config set` never calls.
3. `ParseStorageURL` (`internal/storage/url.go:63-111`) ignores the userinfo part and any query parameter other than `endpoint`, `region` and `ssl`. If credentials sit in the URL as well as in `s3.*`, no error is raised and the raw URL is logged at INFO and stored in the local index on first bind (`internal/vaultik/storage_bind.go:63-69`), printed by `vaultik info` (`internal/vaultik/info.go:52`) and repeated in the binding-mismatch error (`storage_bind.go:90-91`). Silently ignored parameters are also a correctness trap: a misspelt `endpoint=` sends the backup to the default AWS endpoint.
## Acceptable
- `config set` prints only the key name after a successful write.
- When the existing file has any of mode bits 0044 set, `config set` either calls `os.Chmod(path, 0o600)` after the write or prints the same warning `config.Load` prints. The ineffective mode block is deleted. Passing a different mode to `os.WriteFile` is not a fix.
- `ParseStorageURL` returns an error, without repeating the URL, when an `s3://` or `rclone://` URL has a userinfo part or a query parameter it does not know (`rclone://` knows none); the message names `s3.access_key_id` and `s3.secret_access_key`. When `url.Parse` fails, only the inner cause is wrapped, not the whole `*url.Error`, so the raw URL is not echoed. `file://` is left alone.
## Definition of done
1. Tests: `config set` of a secret key does not print the value; a pre-existing 0644 config is tightened or warned about; both rejected URL forms, and the `url.Parse` failure text, in a new `internal/storage/url_test.go`.
2. No existing assertion weakened; `make check` green.
Line numbers are as of `next` at `6fcd8e1`.
model: fable-5-1
config set now prints only the key name (never the value), and tightens a pre-existing group- or world-readable config to 0600 after writing (the old mode-preserving block was dead code and is removed). ParseStorageURL rejects s3:// and rclone:// URLs carrying credentials in the userinfo or an unknown query parameter, naming s3.access_key_id and s3.secret_access_key; rclone:// accepts no parameters, so a misspelt one is caught. A url.Parse failure wraps only the inner cause, so the raw URL is not echoed. file:// is unchanged. Tests cover all of these; make check is green.
Minor deviation: the new URL tests went into the existing internal/storage/url_parse_test.go rather than a separate url_test.go.
Model: opus-4-8
PR: https://git.eeqj.de/sneak/vaultik/pulls/184
`config set` now prints only the key name (never the value), and tightens a pre-existing group- or world-readable config to 0600 after writing (the old mode-preserving block was dead code and is removed). `ParseStorageURL` rejects `s3://` and `rclone://` URLs carrying credentials in the userinfo or an unknown query parameter, naming `s3.access_key_id` and `s3.secret_access_key`; `rclone://` accepts no parameters, so a misspelt one is caught. A `url.Parse` failure wraps only the inner cause, so the raw URL is not echoed. `file://` is unchanged. Tests cover all of these; `make check` is green.
Minor deviation: the new URL tests went into the existing `internal/storage/url_parse_test.go` rather than a separate `url_test.go`.
Model: opus-4-8
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the security review #73. Severity: low (hardening; the same secrets are already in the config file on that host, so the added exposure is captured output, logs and pasted
vaultik info).What is wrong
config setprintskey = valuefor every key after writing (internal/cli/config.go:407), so settings3.secret_access_keyorage_secret_keythis way prints the secret into any captured stdout.config setleaves a group- or world-readable config as it is. The file always exists at that point andos.WriteFiledoes not change the mode of an existing file, so the stat-and-mode block atconfig.go:395-400has no effect. The warning about loose modes lives inconfig.Load(internal/config/config.go:265-276), whichconfig setnever calls.ParseStorageURL(internal/storage/url.go:63-111) ignores the userinfo part and any query parameter other thanendpoint,regionandssl. If credentials sit in the URL as well as ins3.*, no error is raised and the raw URL is logged at INFO and stored in the local index on first bind (internal/vaultik/storage_bind.go:63-69), printed byvaultik info(internal/vaultik/info.go:52) and repeated in the binding-mismatch error (storage_bind.go:90-91). Silently ignored parameters are also a correctness trap: a misspeltendpoint=sends the backup to the default AWS endpoint.Acceptable
config setprints only the key name after a successful write.config seteither callsos.Chmod(path, 0o600)after the write or prints the same warningconfig.Loadprints. The ineffective mode block is deleted. Passing a different mode toos.WriteFileis not a fix.ParseStorageURLreturns an error, without repeating the URL, when ans3://orrclone://URL has a userinfo part or a query parameter it does not know (rclone://knows none); the message namess3.access_key_idands3.secret_access_key. Whenurl.Parsefails, only the inner cause is wrapped, not the whole*url.Error, so the raw URL is not echoed.file://is left alone.Definition of done
config setof a secret key does not print the value; a pre-existing 0644 config is tightened or warned about; both rejected URL forms, and theurl.Parsefailure text, in a newinternal/storage/url_test.go.make checkgreen.Line numbers are as of
nextat6fcd8e1.model: fable-5-1
PR: #184
config setnow prints only the key name (never the value), and tightens a pre-existing group- or world-readable config to 0600 after writing (the old mode-preserving block was dead code and is removed).ParseStorageURLrejectss3://andrclone://URLs carrying credentials in the userinfo or an unknown query parameter, namings3.access_key_idands3.secret_access_key;rclone://accepts no parameters, so a misspelt one is caught. Aurl.Parsefailure wraps only the inner cause, so the raw URL is not echoed.file://is unchanged. Tests cover all of these;make checkis green.Minor deviation: the new URL tests went into the existing
internal/storage/url_parse_test.gorather than a separateurl_test.go.Model: opus-4-8