Skip to main content

max / makenotwork

server: clear five Phase 1 caps from Ultra Fuzz Run #4 - creator-tier forms now render the CSRF token (the page handler already threaded csrf_token; only the template was missing the hidden input). Without this, every authenticated click on Monthly/Annual 403'd. - git_ssh.rs dispatch validates repo_name via validate_git_repo_name before the DB lookup or git-shell command reconstruction. Same check added at the db::git_repos::create_repo backstop so the HTTP smart- protocol and CI internal-API call sites are covered without per-caller duplication. - lockout just_locked changes from `>= max_attempts` to `= max_attempts` so the lockout email fires only on the crossing attempt, not on every attempt at or above the threshold. - cancel_pending_item_checkout moves from Skip("Phase 1 todo: ...") to post_csrf. The purchase-page form already renders _csrf. - promo use_count over-release: scheduler/cleanup dedupes promo_code_ids via HashSet before iterating release_use_count. Cart checkouts produce N pending-tx rows sharing one promo_code_id; the loop was releasing N times per reservation. Remaining Phase 1 (scanner streaming, scan_jobs retention, scanner pool permit, broadcast bounded fan-out) have detailed plan docs at _private/docs/mnw/server-docs/plans/ — referenced from server/todo.md.
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-27 05:36 UTC
Commit: c4113a78c7fe8cd3dedaecf5bf71cc4bf7f71ca8
Parent: a8f9880
6 files changed, +20 insertions, -9 deletions
@@ -38,7 +38,7 @@ pub async fn increment_failed_login(
38 38 ELSE locked_until
39 39 END
40 40 WHERE id = $1
41 - RETURNING failed_login_attempts, (failed_login_attempts >= $2) AS just_locked
41 + RETURNING failed_login_attempts, (failed_login_attempts = $2) AS just_locked
42 42 "#,
43 43 )
44 44 .bind(user_id)
@@ -17,8 +17,13 @@ pub struct PublicRepoWithOwner {
17 17 }
18 18
19 19 /// Register a new git repository for a user (default visibility: public).
20 + ///
21 + /// Validates `name` against `validate_git_repo_name` as a defense-in-depth
22 + /// backstop — the SSH dispatch path and HTTP smart-protocol path both call
23 + /// this with names supplied by untrusted remote git clients.
20 24 #[tracing::instrument(skip_all)]
21 25 pub async fn create_repo(pool: &PgPool, user_id: UserId, name: &str) -> Result<DbGitRepo> {
26 + crate::validation::validate_git_repo_name(name)?;
22 27 let repo = sqlx::query_as::<_, DbGitRepo>(
23 28 r#"
24 29 INSERT INTO git_repos (user_id, name)
@@ -7,6 +7,7 @@
7 7 use sqlx::PgPool;
8 8
9 9 use crate::db::{self, UserId, Username};
10 + use crate::validation::validate_git_repo_name;
10 11
11 12 // ── Constants ──
12 13
@@ -76,12 +77,14 @@ async fn exec_git_operation(
76 77 let (operation, repo_path) = parse_ssh_command(original_cmd)?;
77 78 let (owner, repo_name) = parse_repo_path(&repo_path)?;
78 79
79 - // Validate the SSH-supplied owner string before any DB lookup or shell
80 - // reconstruction. `parse_repo_path` is a path-shape check, not a Username
81 - // syntax check — without this, a malformed owner could reach the DB layer
80 + // Validate the SSH-supplied owner and repo name before any DB lookup or
81 + // shell reconstruction. `parse_repo_path` is a path-shape check, not a
82 + // syntax check — without this, a malformed name could reach the DB layer
82 83 // or end up embedded in the `git-shell -c` argument below.
83 84 let owner_username = Username::new(owner)
84 85 .map_err(|_| anyhow::anyhow!("repository not found"))?;
86 + validate_git_repo_name(repo_name)
87 + .map_err(|_| anyhow::anyhow!("repository not found"))?;
85 88
86 89 let owner_user = db::users::get_user_by_username(pool, &owner_username)
87 90 .await?
@@ -39,10 +39,7 @@ pub fn stripe_routes() -> CsrfRouter<AppState> {
39 39 .route("/stripe/billing-portal", post_csrf(checkout::open_billing_portal))
40 40 .route("/stripe/creator-tier", post_csrf(checkout::create_creator_tier_checkout))
41 41 .route("/stripe/checkout/{item_id}", post_csrf_skip(STRIPE_SESSION_SKIP, checkout::create_checkout))
42 - // cancel-pending was in the old allowlist; behavior-preserving Skip.
43 - // The Phase 1 entry "cancel_pending_item_checkout CSRF gap" tracks
44 - // moving this to `post_csrf` once the form renders the token.
45 - .route("/stripe/checkout/{item_id}/cancel-pending", post_csrf_skip("Phase 1 todo: tighten to post_csrf", checkout::cancel_pending_item_checkout))
42 + .route("/stripe/checkout/{item_id}/cancel-pending", post_csrf(checkout::cancel_pending_item_checkout))
46 43 .route("/stripe/checkout/project/{project_id}", post_csrf_skip(STRIPE_SESSION_SKIP, checkout::create_project_checkout))
47 44 .route("/stripe/subscribe/{tier_id}", post_csrf_skip(STRIPE_SESSION_SKIP, checkout::create_subscription_checkout))
48 45 .route("/stripe/checkout/tip/{recipient_id}", post_csrf_manual(
@@ -176,8 +176,12 @@ pub(super) async fn cleanup_stale_pending_transactions(state: &AppState) {
176 176 }
177 177 };
178 178
179 + // Cart checkouts produce N pending-tx rows that share a promo_code_id;
180 + // release once per reservation, not once per row.
181 + let unique_promo_ids: std::collections::HashSet<_> = promo_ids.into_iter().flatten().collect();
182 +
179 183 let mut released = 0i64;
180 - for pc_id in promo_ids.into_iter().flatten() {
184 + for pc_id in unique_promo_ids {
181 185 if let Err(e) = db::promo_codes::release_use_count(&state.db, pc_id).await {
182 186 tracing::warn!(promo_code_id = %pc_id, error = ?e, "failed to release promo code use count");
183 187 } else {
@@ -78,11 +78,13 @@
78 78 <div class="meta creator-tier-storage mb-3">{{ tier_storage }}</div>
79 79 <div class="creator-tier-buttons">
80 80 <form method="post" action="/stripe/creator-tier" class="m-0">
81 + {% if let Some(token) = csrf_token %}<input type="hidden" name="_csrf" value="{{ token }}">{% endif %}
81 82 <input type="hidden" name="tier" value="{{ tier_key }}">
82 83 <input type="hidden" name="interval" value="monthly">
83 84 <button type="submit" class="btn-primary creator-tier-btn" data-loading-text="Redirecting to Stripe...">Monthly</button>
84 85 </form>
85 86 <form method="post" action="/stripe/creator-tier" class="m-0">
87 + {% if let Some(token) = csrf_token %}<input type="hidden" name="_csrf" value="{{ token }}">{% endif %}
86 88 <input type="hidden" name="tier" value="{{ tier_key }}">
87 89 <input type="hidden" name="interval" value="annual">
88 90 <button type="submit" class="btn-secondary creator-tier-btn" data-loading-text="Redirecting to Stripe...">Annual (save 10%)</button>