Skip to content

Commit 31a3d9a

Browse files
authored
fix: url poisoning via user-controlled input (take 4) (#700)
1 parent a7e733a commit 31a3d9a

File tree

15 files changed

+160
-41
lines changed

15 files changed

+160
-41
lines changed

blocks/browse/da-browse/da-browse.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { LitElement, html, nothing } from 'da-lit';
22
import { DA_ORIGIN } from '../../shared/constants.js';
33
import { daFetch, getFirstSheet } from '../../shared/utils.js';
4-
import { getNx } from '../../../scripts/utils.js';
4+
import { getNx, sanitizePathParts } from '../../../scripts/utils.js';
55

66
// Components
77
import '../da-breadcrumbs/da-breadcrumbs.js';
@@ -52,7 +52,7 @@ export default class DaBrowse extends LitElement {
5252
if ((e.metaKey || e.ctrlKey) && e.altKey && e.code === 'KeyT') {
5353
e.preventDefault();
5454
const { fullpath } = this.details;
55-
const [, ...split] = fullpath.split('/');
55+
const [...split] = sanitizePathParts(fullpath);
5656
if (split.length < 2) return;
5757

5858
if (split[2] === '.trash') {

blocks/browse/da-list/da-list.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { LitElement, html, repeat, nothing } from 'da-lit';
22
import { DA_ORIGIN } from '../../shared/constants.js';
3-
import { getNx } from '../../../scripts/utils.js';
3+
import { getNx, sanitizePathParts } from '../../../scripts/utils.js';
44
import { daFetch, aemAdmin } from '../../shared/utils.js';
55

66
import '../da-list-item/da-list-item.js';
@@ -307,7 +307,7 @@ export default class DaList extends LitElement {
307307
this._itemsRemaining = this._selectedItems.length;
308308

309309
const callback = async (item) => {
310-
const [, org, site, ...rest] = item.path.split('/');
310+
const [org, site, ...rest] = sanitizePathParts(item.path);
311311

312312
// If already in trash or not in a site, its a direct delete
313313
const directDelete = item.path.includes('/.trash/') || rest.length === 0;

blocks/browse/da-list/helpers/utils.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { SUPPORTED_FILES, DA_ORIGIN } from '../../../shared/constants.js';
2+
import { sanitizePath, sanitizePathParts } from '../../../../scripts/utils.js';
23
import { daFetch } from '../../../shared/utils.js';
34

45
const MAX_DEPTH = 1000;
@@ -81,12 +82,6 @@ export async function getFullEntryList(entries) {
8182
return files.filter((file) => file);
8283
}
8384

84-
export function sanitizePath(path) {
85-
const pathArray = path.split('/');
86-
const sanitizedArray = pathArray.map((element) => element.replaceAll(/[^a-zA-Z0-9.]/g, '-').toLowerCase());
87-
return [...sanitizedArray].join('/');
88-
}
89-
9085
export async function handleUpload(list, fullpath, file) {
9186
const { data, path } = file;
9287
const formData = new FormData();
@@ -122,8 +117,7 @@ export async function handleUpload(list, fullpath, file) {
122117
export function items2Clipboard(items) {
123118
const aemUrls = items.reduce((acc, item) => {
124119
if (item.ext) {
125-
const path = item.path.replace('.html', '');
126-
const [org, repo, ...pathParts] = path.substring(1).split('/');
120+
const [org, repo, ...pathParts] = sanitizePathParts(item.path.replace('.html', ''));
127121
const pageName = pathParts.pop();
128122
pathParts.push(pageName === 'index' ? '' : pageName);
129123

blocks/browse/da-sites/da-sites.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { LitElement, html, nothing } from 'da-lit';
22
import getSheet from '../../shared/sheet.js';
3+
import { sanitizeName } from '../../../scripts/utils.js';
34

45
const sheet = await getSheet('/blocks/browse/da-sites/da-sites.css');
56

@@ -94,7 +95,7 @@ export default class DaSites extends LitElement {
9495
// eslint-disable-next-line no-unused-vars
9596
const [_, repo, org] = helixString.split('--');
9697
if (!repo || !org) return null;
97-
return `#/${org}/${repo}`;
98+
return `#/${sanitizeName(org, false)}/${sanitizeName(repo, false)}`;
9899
} catch (_) {
99100
return null;
100101
}

blocks/edit/da-library/da-library.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
ref,
1010
nothing,
1111
} from 'da-lit';
12-
import { getNx } from '../../../scripts/utils.js';
12+
import { getNx, sanitizePathParts } from '../../../scripts/utils.js';
1313
import { getBlocks, getBlockVariants } from './helpers/index.js';
1414
import getSheet from '../../shared/sheet.js';
1515
import inlinesvg from '../../shared/inlinesvg.js';
@@ -291,7 +291,7 @@ class DaLibrary extends LitElement {
291291

292292
getParts() {
293293
const view = 'edit';
294-
const [org, repo, ...path] = window.location.hash.replace('#/', '').split('/');
294+
const [org, repo, ...path] = sanitizePathParts(window.location.hash.substring(1));
295295
return { view, org, repo, ref: 'main', path: `/${path.join('/')}` };
296296
}
297297

blocks/edit/da-library/helpers/helpers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import getPathDetails from '../../../shared/pathDetails.js';
55
import { daFetch, getFirstSheet } from '../../../shared/utils.js';
66
import { getConfKey, openAssets } from '../../da-assets/da-assets.js';
77
import { fetchKeyAutocompleteData } from '../../prose/plugins/slashMenu/keyAutocomplete.js';
8-
import { sanitiseRef } from '../../../../scripts/utils.js';
8+
import { sanitizeName } from '../../../../scripts/utils.js';
99

1010
const DA_ORIGIN = getDaAdmin();
1111
const REPLACE_CONTENT = '<content>';
@@ -18,7 +18,7 @@ const DA_PLUGINS = [
1818
'placeholders',
1919
];
2020

21-
const ref = sanitiseRef(new URLSearchParams(window.location.search).get('ref')) || 'main';
21+
const ref = sanitizeName(new URLSearchParams(window.location.search).get('ref'), false) || 'main';
2222

2323
export function parseDom(dom) {
2424
const { schema } = window.view.state;

blocks/edit/utils/helpers.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AEM_ORIGIN, DA_ORIGIN } from '../../shared/constants.js';
2+
import { sanitizePathParts } from '../../../../scripts/utils.js';
23
import prose2aem from '../../shared/prose2aem.js';
34
import { daFetch } from '../../shared/utils.js';
45

@@ -154,7 +155,7 @@ function parseAemError(xError) {
154155
}
155156

156157
export async function getCdnConfig(path) {
157-
const [org, site] = path.slice(1).toLowerCase().split('/');
158+
const [org, site] = sanitizePathParts(path);
158159
const resp = await daFetch(`${AEM_ORIGIN}/config/${org}/sites/${site}.json`);
159160
if (!resp.ok) {
160161
// eslint-disable-next-line no-console

blocks/shared/pathDetails.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CON_ORIGIN, DA_ORIGIN } from './constants.js';
2+
import { sanitizePathParts } from '../../scripts/utils.js';
23

34
let currpath;
45
let currhash;
@@ -119,7 +120,7 @@ export default function getPathDetails(loc) {
119120
if (!fullpath || fullpath.startsWith('old_hash') || fullpath.startsWith('access_token')) return null;
120121

121122
// Split everything up so it can be later used for both DA & AEM
122-
const pathParts = fullpath.slice(1).toLowerCase().split('/');
123+
const pathParts = sanitizePathParts(fullpath);
123124

124125
// Redirect JSON files from edit view to sheet view
125126
if (editor === 'edit' && fullpath.endsWith('.json')) {

blocks/start/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getNx } from '../../scripts/utils.js';
1+
import { getNx, sanitizePathParts } from '../../scripts/utils.js';
22
import { DA_ORIGIN } from '../shared/constants.js';
33
import { daFetch } from '../shared/utils.js';
44

@@ -32,7 +32,7 @@ async function getBlob(path) {
3232

3333
async function bulkAemAdmin(org, site, files) {
3434
const paths = files.map((file) => {
35-
const [, , ...parts] = file.path.slice(1).split('/');
35+
const [, , ...parts] = sanitizePathParts(file.path);
3636
return `/${parts.join('/')}`.replace('.html', '');
3737
});
3838

blocks/start/start.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getDaAdmin } from '../shared/constants.js';
33
import getSheet from '../shared/sheet.js';
44
import { daFetch } from '../shared/utils.js';
55
import { copyConfig, copyContent, previewContent } from './index.js';
6+
import { sanitizePathParts } from '../../scripts/utils.js';
67

78
const sheet = await getSheet('/blocks/start/start.css');
89

@@ -124,7 +125,7 @@ class DaStart extends LitElement {
124125
try {
125126
const { origin, pathname } = new URL(e.target.value);
126127
if (origin !== 'https://github.com') throw Error('Not github');
127-
const [, org, site] = pathname.toLowerCase().trim().split('/');
128+
const [org, site] = sanitizePathParts(pathname);
128129
if (!(org && site)) throw Error('No org or site');
129130
this.org = org;
130131
this.site = site;

0 commit comments

Comments
 (0)