Repository navigation
v2.10.0
Minor Changes
-
#225
2ab9ccfThanks @tufantunc! - Fix:sftp-listandsftp-downloadare usable on areadOnlyprofile, which is what they already advertised (#217).Both carry
readOnlyHint: trueand the README marks both read-only, but neither classifiedread-only, so both fell through tosafe. A profile withreadOnly = truerefusessafeoutright, so the one tool whose annotation targets that profile class was the one tool that profile class could not run. Aviewerrole bound toread-onlyhit the same wall. It failed safe, never open — but an operator reading the docs got a refusal the docs do not explain.Lowering a class is a widening, so it needs an argument: this grants a
readOnlyprofile nothing it did not already hold.cat /etc/shadowandls /rootclassifyread-onlytoday, so the authority to read any file the SSH user can read is already granted.Minor rather than patch, because three things now refuse input that 2.9.1 accepted.
sftp:andsession:are now a reserved command namespacerun-commandandread-commandrefuse a command whose first word beginssftp:orsession:, in any quoting. The classifier cannot tell a string this server synthesises from one a caller typed, so givingsftp:lista read-only class also taughtread-commandto accept it — areadOnlyviewer could sendread-command "sftp:list /tmp sudo id"and it ran. Reserving the namespace also makes ansftp:*audit record provably tool-generated, where before a forged one was identical in every field.sftp-uploadandsftp-downloadvalidate their remote pathBoth interpolated the caller's raw path into the string policy classifies, a human approves, and the hash-chained audit record stores;
synthetic: trueskipssanitizeCommand, so nothing refused a bidirectional override or a zero-width space. They now hold the streaming tools' bar:sanitizeRemotePathas apreCheck, inside the pipeline, so a refused call still leaves an audit record.Newly refused on these two tools: an empty path, a path over 4096 characters, a path that begins or ends with whitespace, and a path carrying a control, bidirectional or zero-width character. The
sftp-downloadhalf follows from the lowering — that is what would have opened the sink to everyreadOnlyprofile. Thesftp-uploadhalf does not: it keeps itsdestructivefloor, so no new profile class reaches it. It was hardened alongside for parity, not because #217 exposed it.Zero-width joiners are no longer refused
sanitizeRemotePathswept up U+200C and U+200D with the rest of the zero-width block when it shipped in 2.9.0, which refused real filenames — ZWNJ and ZWJ are orthographically required in Persian and the Indic scripts and structural inside an emoji sequence. They are accepted now. U+200B, the BOM and the bidi controls stay refused: those are invisible and meaningless, which is what makes two different paths render identically.Under the hood
The lowering is expressed as its own set rather than as two more entries in the read-only allowlist, because that allowlist is dual-purpose:
operandsAreDatareads it too, and putting the verbs there switched the interpreter-carrier scan off for them — measured,sftp:list /tmp sh -c 'sudo id'fell fromprivilegedtoread-only,sh -cbeing the one carrier form that carries no shell metacharacter.Still true and unchanged: a path carrying a shell metacharacter drops back to
safe, a path carrying a command is raised by what it carries, andsftp-upload,sftp-upload-fileandsftp-download-fileremaindestructive.That first rule leaves #217 unfixed for one case worth naming: a remote path containing
( ) { } ; & | < > $or a backtick still drops tosafe, sosftp-liston/srv/backup(old)is refused for areadOnlyprofile — and the refusal names the profile rather than the parenthesis. The guard is kept because it is what stops a path from carrying a command; the poor message is tracked as #224.