mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-app.git
synced 2026-05-05 04:54:53 +00:00
security: medium-severity fixes (proxies, ssrf, uploads, lnurl, github_data)
- Trust the Forge reverse proxy and force https URLs in production so generated absolute URLs match the actual TLS termination. - Reject Nostr profile photo URLs that aren't http(s) or that resolve to loopback / private (RFC1918) addresses to close an SSRF vector in FetchNostrProfileJob. - Tighten image upload validation across meetup, course, and lecturer create/edit components: explicit mimes whitelist (jpeg, png, webp), max 5 MiB, and dimension cap of 4000x4000. - Replace the silent "skip if exists" branch in LnurlAuthController with updateOrCreate so concurrent callers cannot race on the k1 record. - Validate github_data on Meetup edit, decoding the JSON, and keep only the whitelisted keys (top, left, state) with strict type coercion to prevent storing arbitrary attacker-controlled JSON.
This commit is contained in:
@@ -14,7 +14,7 @@ class extends Component {
|
||||
use WithFileUploads;
|
||||
use SeoTrait;
|
||||
|
||||
#[Validate('image|max:10240')] // 10MB Max
|
||||
#[Validate('image|mimes:jpeg,png,webp|max:5120|dimensions:max_width=4000,max_height=4000')]
|
||||
public $logo;
|
||||
|
||||
public string $name = '';
|
||||
|
||||
@@ -16,7 +16,7 @@ class extends Component {
|
||||
use WithFileUploads;
|
||||
use SeoTrait;
|
||||
|
||||
#[Validate('image|max:10240')] // 10MB Max
|
||||
#[Validate('image|mimes:jpeg,png,webp|max:5120|dimensions:max_width=4000,max_height=4000')]
|
||||
public $logo;
|
||||
|
||||
public Course $course;
|
||||
|
||||
@@ -13,7 +13,7 @@ class extends Component {
|
||||
use WithFileUploads;
|
||||
use SeoTrait;
|
||||
|
||||
#[Validate('image|max:10240')] // 10MB Max
|
||||
#[Validate('image|mimes:jpeg,png,webp|max:5120|dimensions:max_width=4000,max_height=4000')]
|
||||
public $avatar;
|
||||
|
||||
public string $name = '';
|
||||
|
||||
@@ -15,7 +15,7 @@ class extends Component {
|
||||
use WithFileUploads;
|
||||
use SeoTrait;
|
||||
|
||||
#[Validate('image|max:10240')] // 10MB Max
|
||||
#[Validate('image|mimes:jpeg,png,webp|max:5120|dimensions:max_width=4000,max_height=4000')]
|
||||
public $avatar;
|
||||
|
||||
public Lecturer $lecturer;
|
||||
|
||||
@@ -15,7 +15,7 @@ class extends Component {
|
||||
use WithFileUploads;
|
||||
use SeoTrait;
|
||||
|
||||
#[Validate('image|max:10240')] // 10MB Max
|
||||
#[Validate('image|mimes:jpeg,png,webp|max:5120|dimensions:max_width=4000,max_height=4000')]
|
||||
public $logo;
|
||||
|
||||
// Basic Information
|
||||
|
||||
@@ -17,7 +17,7 @@ class extends Component {
|
||||
use WithFileUploads;
|
||||
use SeoTrait;
|
||||
|
||||
#[Validate('image|max:10240')] // 10MB Max
|
||||
#[Validate('image|mimes:jpeg,png,webp|max:5120|dimensions:max_width=4000,max_height=4000')]
|
||||
public $logo;
|
||||
|
||||
public Meetup $meetup;
|
||||
@@ -90,6 +90,35 @@ class extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitelist the keys allowed inside github_data and coerce types so a
|
||||
* tampered payload cannot smuggle arbitrary keys into the stored JSON.
|
||||
*/
|
||||
protected function sanitizeGithubData(?string $raw): ?array
|
||||
{
|
||||
if (empty($raw)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
if (!is_array($decoded)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$clean = [];
|
||||
if (array_key_exists('top', $decoded) && (is_string($decoded['top']) || is_numeric($decoded['top']))) {
|
||||
$clean['top'] = (string) $decoded['top'];
|
||||
}
|
||||
if (array_key_exists('left', $decoded) && (is_string($decoded['left']) || is_numeric($decoded['left']))) {
|
||||
$clean['left'] = (string) $decoded['left'];
|
||||
}
|
||||
if (array_key_exists('state', $decoded) && is_string($decoded['state'])) {
|
||||
$clean['state'] = mb_substr($decoded['state'], 0, 64);
|
||||
}
|
||||
|
||||
return $clean === [] ? null : $clean;
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->authorizeAccess();
|
||||
@@ -140,19 +169,10 @@ class extends Component {
|
||||
'simplex' => ['nullable', 'string', 'max:255'],
|
||||
'signal' => ['nullable', 'string', 'max:255'],
|
||||
'community' => ['required', 'string', 'max:255'],
|
||||
'github_data' => ['nullable', 'json'],
|
||||
]);
|
||||
|
||||
// Convert github_data string back to array if provided
|
||||
if (!empty($validated['github_data'])) {
|
||||
$decoded = json_decode($validated['github_data'], true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$validated['github_data'] = $decoded;
|
||||
} else {
|
||||
$validated['github_data'] = null;
|
||||
}
|
||||
} else {
|
||||
$validated['github_data'] = null;
|
||||
}
|
||||
$validated['github_data'] = $this->sanitizeGithubData($validated['github_data'] ?? null);
|
||||
|
||||
$this->meetup->update($validated);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user