Fixes security issue: potential remote Path Traversal via File Upload
This commit is contained in:
@@ -163,6 +163,41 @@ export class AdminMobileBuildsTabComponent implements OnInit {
|
||||
return 'iOS requires both certificate and provisioning profile files.';
|
||||
}
|
||||
|
||||
// Validate file extensions before hitting the backend
|
||||
const extChecks: Array<{ file: File | null; label: string; exts: string[] }> = [];
|
||||
if (this.buildAndroid) {
|
||||
extChecks.push({
|
||||
file: this.androidKeystoreFile,
|
||||
label: 'Android keystore',
|
||||
exts: ['.jks', '.keystore', '.p12'],
|
||||
});
|
||||
extChecks.push({
|
||||
file: this.androidDescriptorFile,
|
||||
label: 'Android descriptor',
|
||||
exts: ['.json'],
|
||||
});
|
||||
}
|
||||
if (this.buildIos) {
|
||||
extChecks.push({
|
||||
file: this.iosCertificateFile,
|
||||
label: 'iOS certificate',
|
||||
exts: ['.p12'],
|
||||
});
|
||||
extChecks.push({
|
||||
file: this.iosProfileFile,
|
||||
label: 'iOS provisioning profile',
|
||||
exts: ['.mobileprovision'],
|
||||
});
|
||||
}
|
||||
|
||||
for (const { file, label, exts } of extChecks) {
|
||||
if (!file) continue;
|
||||
const ext = '.' + file.name.split('.').pop()?.toLowerCase();
|
||||
if (!exts.includes(ext)) {
|
||||
return `${label} must have one of: ${exts.join(', ')}`;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -176,6 +211,7 @@ export class AdminMobileBuildsTabComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.mobileBuildBusy = true;
|
||||
let createdRequestId: number | null = null;
|
||||
try {
|
||||
const request = await firstValueFrom(this.mobileBuildService.createRequest({
|
||||
platform_android: this.buildAndroid,
|
||||
@@ -186,6 +222,7 @@ export class AdminMobileBuildsTabComponent implements OnInit {
|
||||
build_number: this.buildNumber.trim(),
|
||||
release_profile: this.buildReleaseProfile.trim() || 'release',
|
||||
}));
|
||||
createdRequestId = request.id;
|
||||
|
||||
if (this.buildAndroid && this.androidKeystoreFile && this.androidDescriptorFile) {
|
||||
await firstValueFrom(this.mobileBuildService.uploadFile(request.id, 'android', 'keystore', this.androidKeystoreFile));
|
||||
@@ -201,6 +238,13 @@ export class AdminMobileBuildsTabComponent implements OnInit {
|
||||
await this.reloadMobileBuilds();
|
||||
this.onMobileBuildSelected(request.id);
|
||||
} catch (err) {
|
||||
// Clean up orphaned request if files couldn't all be uploaded
|
||||
if (createdRequestId !== null) {
|
||||
await firstValueFrom(
|
||||
this.mobileBuildService.deleteRequest(createdRequestId),
|
||||
).catch(() => {}); // best-effort cleanup
|
||||
await this.reloadMobileBuilds().catch(() => {});
|
||||
}
|
||||
this.mobileBuildErrorMessage = this.getErrorMessage(err, 'Failed to create build request.');
|
||||
} finally {
|
||||
this.mobileBuildBusy = false;
|
||||
|
||||
Reference in New Issue
Block a user