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;
-11
View File
@@ -8,16 +8,6 @@ export interface MobileBuildDefaults {
stream_metadata_url: string;
}
export interface MobileBuildUploadedFile {
id: number;
request_id: number;
platform: 'android' | 'ios';
file_kind: string;
original_name: string;
size: number;
uploaded_at: string;
}
export interface MobileBuildArtifact {
id: number;
request_id: number;
@@ -49,7 +39,6 @@ export interface MobileBuildRequest {
updated_at: string;
started_at: string | null;
completed_at: string | null;
files: MobileBuildUploadedFile[];
artifacts: MobileBuildArtifact[];
}
+6 -3
View File
@@ -8,7 +8,6 @@ import {
MobileBuildDefaults,
MobileBuildPreflightResult,
MobileBuildRequest,
MobileBuildUploadedFile,
ThemeStatus,
} from '../interfaces/mobile-build';
@@ -35,19 +34,23 @@ export class MobileBuildService {
return this.http.post<MobileBuildRequest>(`${this.baseUrl}/requests`, payload);
}
deleteRequest(id: number): Observable<void> {
return this.http.delete<void>(`${this.baseUrl}/requests/${id}`);
}
uploadFile(
requestId: number,
platform: 'android' | 'ios',
fileKind: string,
file: File,
): Observable<MobileBuildUploadedFile> {
): Observable<{ platform: string; file_kind: string; display_name: string; sha256: string; size: number }> {
const form = new FormData();
form.append('file', file);
const params = new HttpParams()
.set('platform', platform)
.set('file_kind', fileKind);
return this.http.post<MobileBuildUploadedFile>(
return this.http.post<{ platform: string; file_kind: string; display_name: string; sha256: string; size: number }>(
`${this.baseUrl}/requests/${requestId}/files`,
form,
{ params },