Fixes security issue: potential remote Path Traversal via File Upload

This commit is contained in:
2026-07-19 05:55:58 +00:00
parent 4a65c7562f
commit 39b3e633a6
10 changed files with 211 additions and 145 deletions
@@ -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;