main
tag: launch-2026-06-01
tag: magicmirror-v0.1.1
tag: magicmirror-v0.3.0
tag: mnw-cli-v0.1.2
tag: mnw-cli-v0.1.3
tag: mnw-cli-v0.1.4
tag: pom-v0.4.1
tag: pom-v0.4.2
tag: pom-v0.4.3
tag: pom-v0.4.4
tag: pom-v0.4.5
tag: wam-v0.3.0
tag: wam-v0.3.1
Files
Commits
Tags
Notes
Issues
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.
6 files changed,
+20 insertions,
-9 deletions
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
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?
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
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)
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 {
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(
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>