fix(inputs): add ownership check to saved-input args read endpoint (#10194)

`get_args_from_history_or_saved_input` looked up saved-input rows by
`id + workspace_id` only, without checking ownership or `is_public`. The
`input` table has no RLS policy, so any authenticated workspace member
could read another member's private saved-input args (which may hold
credentials) just by knowing the UUID, even though `inputs/list` already
scopes to `is_public OR created_by = caller`.

Add `AND (is_public IS true OR created_by = $4)` to both `input`-table
queries in the handler (the `?input=true` single-query branch and the
default `v2_job UNION ALL input` branch), matching the predicate already
used by `list_saved_inputs`.

The `v2_job` branches are already covered by the table's RLS policies
enforced via `SET LOCAL ROLE windmill_user` in `UserDB::begin()`, so they
are left unchanged.

Fixes WIN-2202

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-07-20 07:31:02 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 60a7a23a04
commit f32d7702bc
3 changed files with 14 additions and 10 deletions
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM input WHERE id = $1 AND workspace_id = $2",
"query": "SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM input WHERE id = $1 AND workspace_id = $2 AND (is_public IS true OR created_by = $4)",
"describe": {
"columns": [
{
@@ -13,12 +13,13 @@
"Left": [
"Uuid",
"Text",
"Bool"
"Bool",
"Text"
]
},
"nullable": [
null
]
},
"hash": "a22146c0a924e5a1a99bc72775399a8c3db31b57ae9ded5bff55f44321c3f3c2"
"hash": "cb26dc8e09c0525963c3d9d316e6cf0437c4efcdeae45fba0f240fc1175bf003"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM v2_job WHERE id = $1 AND workspace_id = $2\n UNION ALL\n SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM input WHERE id = $1 AND workspace_id = $2",
"query": "SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM v2_job WHERE id = $1 AND workspace_id = $2\n UNION ALL\n SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM input WHERE id = $1 AND workspace_id = $2 AND (is_public IS true OR created_by = $4)",
"describe": {
"columns": [
{
@@ -13,12 +13,13 @@
"Left": [
"Uuid",
"Text",
"Bool"
"Bool",
"Text"
]
},
"nullable": [
null
]
},
"hash": "55002cccf17e32af5b077a17707122cfeeeebf5a9392c6798a486274d8d233d5"
"hash": "ef57c1622cc57e4485ffe086dd55f62df8b7f510b4c478818ff8ab1a11d7d5ea"
}
+6 -4
View File
@@ -246,10 +246,11 @@ async fn get_args_from_history_or_saved_input(
let result_o = if let Some(input) = g.input {
if input {
sqlx::query_scalar!(
"SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM input WHERE id = $1 AND workspace_id = $2",
"SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM input WHERE id = $1 AND workspace_id = $2 AND (is_public IS true OR created_by = $4)",
job_or_input_id,
w_id,
g.allow_large.unwrap_or(true)
g.allow_large.unwrap_or(true),
authed.username
)
.fetch_optional(&mut *tx)
.await?
@@ -267,10 +268,11 @@ async fn get_args_from_history_or_saved_input(
sqlx::query_scalar!(
"SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM v2_job WHERE id = $1 AND workspace_id = $2
UNION ALL
SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM input WHERE id = $1 AND workspace_id = $2",
SELECT CASE WHEN pg_column_size(args) < 40000 OR $3 THEN args ELSE '\"WINDMILL_TOO_BIG\"'::jsonb END as args FROM input WHERE id = $1 AND workspace_id = $2 AND (is_public IS true OR created_by = $4)",
job_or_input_id,
w_id,
g.allow_large.unwrap_or(true)
g.allow_large.unwrap_or(true),
authed.username
)
.fetch_optional(&mut *tx)
.await?