Skip to content

Commit 985c0ad

Browse files
authored
chore: rework handling of submission attachment under 100 bytes (#1171)
1 parent 404a658 commit 985c0ad

File tree

2 files changed

+59
-49
lines changed

2 files changed

+59
-49
lines changed

lambda-code/reliability/src/lib/s3FileInput.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import {
88
} from "@aws-sdk/client-s3";
99
import { NodeJsClient } from "@smithy/types";
1010

11+
export class FileSizeUnder100BytesException extends Error {
12+
constructor() {
13+
super("FileSizeUnder100BytesException");
14+
Object.setPrototypeOf(this, FileSizeUnder100BytesException.prototype);
15+
}
16+
}
17+
1118
const s3Client = new S3Client({
1219
region: process.env.REGION ?? "ca-central-1",
1320
forcePathStyle: true,
@@ -124,22 +131,6 @@ export const getFileTags = async (filePath: string) => {
124131
}
125132
};
126133

127-
export const getFileSize = async (filePath: string) => {
128-
const response = await s3Client
129-
.send(
130-
new HeadObjectCommand({
131-
Bucket: reliabilityBucketName,
132-
Key: filePath,
133-
})
134-
)
135-
.catch((error) => {
136-
console.error(error);
137-
throw new Error(`Failed to retrieve size for file: ${filePath}`);
138-
});
139-
140-
return Number(response.ContentLength ?? "0");
141-
};
142-
143134
export const getFileMetaData = async (filePath: string) => {
144135
const response = await s3Client
145136
.send(
@@ -165,19 +156,28 @@ export const getFileMetaData = async (filePath: string) => {
165156
export async function getObjectFirst100BytesInReliabilityBucket(
166157
objectKey: string
167158
): Promise<Uint8Array<ArrayBufferLike>> {
168-
const response = await s3Client.send(
169-
new GetObjectCommand({
170-
Bucket: reliabilityBucketName,
171-
Key: objectKey,
172-
Range: "bytes=0-99",
173-
})
174-
);
175-
176-
const bytes = await response.Body?.transformToByteArray();
177-
178-
if (bytes === undefined) {
179-
throw new Error("Object first 100 bytes failed to be retrieved");
180-
}
159+
try {
160+
const response = await s3Client.send(
161+
new GetObjectCommand({
162+
Bucket: reliabilityBucketName,
163+
Key: objectKey,
164+
Range: "bytes=0-99",
165+
})
166+
);
181167

182-
return bytes;
168+
const bytes = await response.Body?.transformToByteArray();
169+
170+
if (bytes === undefined || bytes.length < 100) {
171+
throw new FileSizeUnder100BytesException();
172+
}
173+
174+
return bytes;
175+
} catch (error) {
176+
// AWS S3 will throw the exception only if the file size is 0 bytes.
177+
if ((error as Error).name === "InvalidRange") {
178+
throw new FileSizeUnder100BytesException();
179+
}
180+
181+
throw error;
182+
}
183183
}

lambda-code/reliability/src/lib/vaultProcessing.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {} from "./file_scanning.js";
44
import { isFileValid } from "./fileValidation.js";
55
import {
66
copyFilesFromReliabilityToVaultStorage,
7-
getFileSize,
7+
FileSizeUnder100BytesException,
88
getObjectFirst100BytesInReliabilityBucket,
99
removeFilesFromReliabilityStorage,
1010
} from "./s3FileInput.js";
@@ -93,29 +93,39 @@ async function verifyAndFlagMaliciousSubmissionAttachments(
9393
): Promise<SubmissionAttachmentInformation[]> {
9494
return Promise.all(
9595
submissionAttachmentsWithScanStatuses.map(async (item) => {
96-
const fileSize = await getFileSize(item.attachmentPath);
96+
try {
97+
const attachmentFirst100Bytes = await getObjectFirst100BytesInReliabilityBucket(
98+
item.attachmentPath
99+
);
100+
101+
const isFileValidResult = isFileValid(item.attachmentPath, attachmentFirst100Bytes);
102+
103+
// If we flagged the file as invalid we return the same scan status value AWS Guard Duty offers to avoid breaking Data Retrieval API service integration
104+
const scanStatus = isFileValidResult ? item.scanStatus : "THREATS_FOUND";
97105

98-
if (fileSize < 100) {
99-
// File size is too small to test and contains no content that can be of value to the end user
100106
return {
101107
...item,
102-
scanStatus: "THREATS_FOUND",
108+
scanStatus: scanStatus,
103109
};
110+
} catch (error) {
111+
switch ((error as Error).constructor) {
112+
case FileSizeUnder100BytesException:
113+
console.warn(
114+
JSON.stringify({
115+
level: "warn",
116+
msg: `Will not try to validate file as its size is under 100 bytes and probably contains no content that can be of value to the end user. Flagging ${item.attachmentPath} as potentially malicious.`,
117+
})
118+
);
119+
120+
return {
121+
...item,
122+
scanStatus: "THREATS_FOUND",
123+
};
124+
default: {
125+
throw new Error(`Failed to verify submission attachment ${item.attachmentPath}`);
126+
}
127+
}
104128
}
105-
106-
const attachmentFirst100Bytes = await getObjectFirst100BytesInReliabilityBucket(
107-
item.attachmentPath
108-
);
109-
110-
const isFileValidResult = isFileValid(item.attachmentPath, attachmentFirst100Bytes);
111-
112-
// If we flagged the file as invalid we return the same scan status value AWS Guard Duty offers to avoid breaking Data Retrieval API service integration
113-
const scanStatus = isFileValidResult ? item.scanStatus : "THREATS_FOUND";
114-
115-
return {
116-
...item,
117-
scanStatus: scanStatus,
118-
};
119129
})
120130
);
121131
}

0 commit comments

Comments
 (0)