Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions frontend/server/handlers/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
findFileOnPodVolume,
parseJSONString,
isAllowedResourceName,
isAllowedObjectKey,
} from '../utils';
import { createMinioClient, getObjectStream } from '../minio-helper';
import * as serverInfo from '../helpers/server-info';
Expand Down Expand Up @@ -124,6 +125,10 @@ export function getArtifactsHandler({
res.status(500).send('Storage key is missing from artifact request');
return;
}
if (!isAllowedObjectKey(key)) {
res.status(500).send('Invalid object key');
return;
}
console.log(`Getting storage artifact at: ${source}: ${bucket}/${key}`);

let client: MinioClient;
Expand Down
29 changes: 29 additions & 0 deletions frontend/server/integration-tests/artifact-get.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,35 @@ describe('/artifacts', () => {
.get(`/artifacts/get?source=volume&bucket=artifact&key=subartifact/notxist.csv`)
.expect(500, 'Failed to open volume.', done);
});

it('rejects keys with encoded XSS payloads', done => {
const configs = loadConfigs(argv, {
AWS_ACCESS_KEY_ID: 'aws123',
AWS_SECRET_ACCESS_KEY: 'awsSecret123',
});
app = new UIServer(configs);
const request = requests(app.start());
request
.get(
'/artifacts/get?source=s3&namespace=test&peek=256&bucket=ml-pipeline&key=%3Cscript%3Ealert(1)%3C%2Fscript%3E',
)
.expect(500, 'Invalid object key', done);
});

it('rejects keys with repeated unsafe characters', done => {
const configs = loadConfigs(argv, {
AWS_ACCESS_KEY_ID: 'aws123',
AWS_SECRET_ACCESS_KEY: 'awsSecret123',
});
app = new UIServer(configs);
const request = requests(app.start());
request
.get(
'/artifacts/get?source=s3&namespace=test&peek=256&bucket=ml-pipeline&key=' +
'%2b['.repeat(300),
)
.expect(500, 'Invalid object key', done);
});
});

describe('/:source/:bucket/:key', () => {
Expand Down
4 changes: 4 additions & 0 deletions frontend/server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,7 @@ function parseK8sError(error: any): ErrorDetails | undefined {
export function isAllowedResourceName(name: string): boolean {
return name.length > 0 && name.length <= 63 && /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(name);
}

export function isAllowedObjectKey(key: string): boolean {
return key.length > 0 && key.length <= 1024 && /^[a-zA-Z0-9\/\-_.]+$/.test(key);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you validate that this doesn't inadvertently exclude characters that are permitted by S3 / GC / ABS? Thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth looking into idiomatic typescript implementations around guarding against XSS. This might not be something that you have to define yourself.

}
Loading