Repository navigation
Commit 29beea0
authored
sec(api): scope ladder config writes to the caller's allowed_accounts (#1770)
* sec(api): scope ladder config writes to the caller's allowed_accounts
upsertLadderConfig gated only on update:config and took cloud_account_id
straight from the request body, so a caller scoped to one account could
write the commitment-laddering config of any other. The read side of the
same feature has filtered on allowed_accounts since migration 000088
opened view:config to scoped users; the write side never did, so the row
was also filtered back out of the caller's own GET afterwards.
Reachable by any principal holding update:config with a restricted
allowed_accounts. No seeded group grants that pairing -- migration 000088
deliberately withheld update:config from the non-admin groups -- so it
needs an operator-created custom group, which is why this is severity/high
rather than critical. A plain admin is unaffected either way: admin:*
carries allowed_accounts ARRAY['*'], so the new check passes for it.
What it buys, concretely: the ladder engine only executes configs whose
account matches the deployment's own AWS account (the single-account gate
in internal/server/handler_ladder.go), and for that account mode
auto_approve with a nil max_hourly_commit_per_run means uncapped
auto-purchase. Because the store upserts on UNIQUE(cloud_account_id,
provider), an out-of-scope caller could also overwrite an admin's existing
row for that account -- raise the cap, flip the mode, or set enabled:false
to silently stop a running ladder.
validateLadderAccountProvider becomes requireLadderAccountAccess and
delegates the lookup to requireAccountAccess, which already fetches and
nil-checks the account, so the account is fetched once rather than twice.
The rename is deliberate: the function now enforces authorization, not
just validation.
Behaviour change: a cloud_account_id that does not resolve now returns
errNotFound (404) instead of 400 "cloud account %q does not exist".
requireAccountAccess answers "no such account" and "not yours"
identically on purpose -- a distinguishable 400 would let a scoped caller
enumerate the account table by probing ids.
Deliberately NOT added: the requirePermissionConstraints call the issue's
fix direction suggests. requirePermissionConstraintsAction is hardcoded to
"execute" (handler.go), with a comment saying a genuinely new action
should add a real parameter back rather than resurrect an unused one.
Calling it here would evaluate the caller's execute permission on config,
which is not the verb being exercised and would fail closed for anyone
holding only update:config. The allowed_accounts check is what closes the
reported bypass.
Tests cover both directions -- out-of-scope refused, in-scope still
allowed -- plus an unrestricted session writing the same account the
scoped one was refused, which pins that the refusal is driven by the
caller's scope and not by anything about the account.
Closes #1539
* fix(api): avoid shadowing err in upsertLadderConfig
Capturing the session (session, err := h.requirePermission) introduced a
function-scoped err, which the four subsequent `if err := ...` statements
in the same function then shadowed. govet's shadow check flags all four,
failing the Lint job. The findings are a side effect of this branch: the
previous `if _, err := ...` bound err inside the if-statement, so no
function-scoped err existed to shadow.
Bind the permission error as permErr instead of err. That leaves the four
body-parsing and validation statements untouched -- they are unrelated to
this change and rewriting them to `err = ...` would enlarge the diff for
no behavioural gain -- and matches the naming already used elsewhere in
the package for exactly this situation (planErr, apiKeyErr, awsIDErr).
golangci-lint v2.10.1 (the CI-pinned version) now exits 0 with a genuine
"0 issues." line. Verified by re-running the linter, not inferred: the
earlier run in this worktree reported exit 3 with no findings and the
message "parallel golangci-lint is running", which is lock contention
rather than a clean result and must not be read as one.
No behaviour change: the rename does not touch the guard, and the ten
ladder handler tests still pass.1 parent 88f16ec commit 29beea0
2 files changed
Lines changed: 135 additions & 19 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
76 | 87 | | |
77 | | - | |
78 | | - | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
79 | 94 | | |
80 | 95 | | |
81 | 96 | | |
| |||
108 | 123 | | |
109 | 124 | | |
110 | 125 | | |
111 | | - | |
| 126 | + | |
112 | 127 | | |
113 | 128 | | |
114 | 129 | | |
| |||
120 | 135 | | |
121 | 136 | | |
122 | 137 | | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
130 | 153 | | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
| 154 | + | |
135 | 155 | | |
136 | 156 | | |
137 | 157 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
154 | | - | |
155 | | - | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
156 | 162 | | |
157 | 163 | | |
158 | 164 | | |
| |||
162 | 168 | | |
163 | 169 | | |
164 | 170 | | |
165 | | - | |
166 | | - | |
167 | | - | |
| 171 | + | |
168 | 172 | | |
169 | 173 | | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
0 commit comments