mirror of
https://github.com/Twigpine/node.git
synced 2026-11-08 15:39:37 -10:00
fix(storage): address CodeRabbit round — key backslash rejection, lock-poll error path, config doc
- validate_key: reject backslashes outright — keys are forward-slash delimited, and on Windows PathBuf::join treats '\' as a separator, letting a key smuggle path components past the segment checks. - LockedConn::acquire: when the pg_try_advisory_lock poll itself errors, close the pinned connection deliberately with an accurate rationale instead of falling through to Drop's dropped-holder warning. Closing (not repooling) is intentional: a lost response means the session may hold the lock, and repooling would strand it. - config.rs: storage_backend doc no longer claims ipfs is auto-selected. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
f17fee06b4
commit
d30af0d20b
4 files changed
+27
-6
No files matched your search
@@ -137,8 +137,9 @@ pub struct Config {
|
||||
|
||||
/// Object-storage backend: `s3` (any S3-compatible service), `fs` (local
|
||||
/// directory), or `ipfs` (Kubo MFS). Empty = auto-detect: `s3` when a bucket
|
||||
/// is set, else `fs` when a storage dir is set, else `ipfs` when an IPFS API
|
||||
/// is set, else local-only.
|
||||
/// is set, else `fs` when a storage dir is set, else local-only. `ipfs` is
|
||||
/// never auto-selected (`GITLAWB_IPFS_API` alone keeps its pinning-only
|
||||
/// meaning) — set this explicitly to `ipfs` to opt in.
|
||||
#[arg(long, env = "GITLAWB_STORAGE_BACKEND", default_value = "")]
|
||||
pub storage_backend: String,
|
||||
|
||||
|
||||
@@ -477,11 +477,25 @@ impl LockedConn {
|
||||
};
|
||||
for attempt in 0..LOCK_ACQUIRE_TIMEOUT_SECS {
|
||||
let conn = this.conn.as_mut().expect("connection present until unlock");
|
||||
let row: (bool,) = sqlx::query_as("SELECT pg_try_advisory_lock($1)")
|
||||
let row: (bool,) = match sqlx::query_as("SELECT pg_try_advisory_lock($1)")
|
||||
.bind(lock_key)
|
||||
.fetch_one(&mut **conn)
|
||||
.await
|
||||
.context("trying advisory lock")?;
|
||||
{
|
||||
Ok(row) => row,
|
||||
Err(e) => {
|
||||
// The poll itself failed, so the lock's server-side state
|
||||
// is unknown: if the query executed but the response was
|
||||
// lost, this session HOLDS the lock, and repooling the
|
||||
// connection would strand it. Close the connection
|
||||
// deliberately (freeing any lock it may hold) instead of
|
||||
// going through Drop's generic dropped-holder warning.
|
||||
if let Some(conn) = this.conn.take() {
|
||||
drop(conn.detach());
|
||||
}
|
||||
return Err(e).context("trying advisory lock");
|
||||
}
|
||||
};
|
||||
if row.0 {
|
||||
return Ok(this);
|
||||
}
|
||||
|
||||
@@ -200,6 +200,9 @@ mod tests {
|
||||
let store = FsBlobStore::new(dir.path()).unwrap();
|
||||
assert!(store.get("../escape").await.is_err());
|
||||
assert!(store.put("a/../../etc/passwd", Bytes::new()).await.is_err());
|
||||
// Backslashes are separators on Windows — must be rejected as keys.
|
||||
assert!(store.get("a\\..\\escape").await.is_err());
|
||||
assert!(store.put("repos\\v1\\x", Bytes::new()).await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -151,8 +151,11 @@ pub(crate) fn validate_key(key: &str) -> Result<()> {
|
||||
if key.split('/').any(|seg| seg == ".." || seg == ".") {
|
||||
anyhow::bail!("blob key contains traversal segment: {key}");
|
||||
}
|
||||
if key.starts_with('/') || key.contains('\0') {
|
||||
anyhow::bail!("blob key is absolute or contains null byte: {key}");
|
||||
// Backslash is rejected outright: keys are forward-slash-delimited, and on
|
||||
// Windows `PathBuf::join` treats `\` as a separator, which would let a key
|
||||
// smuggle path components past the segment checks above.
|
||||
if key.starts_with('/') || key.contains('\\') || key.contains('\0') {
|
||||
anyhow::bail!("blob key is absolute, contains '\\', or contains null byte: {key}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in new issue
Block a user