Some checks failed
Publish Docker image / Build (push) Has been cancelled
Lint / pnpm_install (push) Has been cancelled
Test (backend) / unit (22.11.0) (push) Has been cancelled
Test (production install and build) / production (22.11.0) (push) Has been cancelled
Lint / lint (backend) (push) Has been cancelled
Lint / lint (frontend) (push) Has been cancelled
Lint / lint (frontend-embed) (push) Has been cancelled
Lint / lint (frontend-shared) (push) Has been cancelled
Lint / lint (misskey-bubble-game) (push) Has been cancelled
Lint / lint (misskey-js) (push) Has been cancelled
Lint / lint (misskey-reversi) (push) Has been cancelled
Lint / lint (sw) (push) Has been cancelled
Lint / typecheck (backend) (push) Has been cancelled
Lint / typecheck (misskey-js) (push) Has been cancelled
Lint / typecheck (sw) (push) Has been cancelled
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
110 lines
3 KiB
TypeScript
110 lines
3 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
import { createHash } from 'crypto';
|
|
import { readFileSync } from 'fs';
|
|
import { URL } from 'url';
|
|
|
|
export type CSPHashed = {
|
|
content: string,
|
|
integrity: string,
|
|
};
|
|
|
|
export function generateCSP(hashedMap: Map<string, CSPHashed>, options: {
|
|
url: string | URL,
|
|
mediaProxy?: string,
|
|
script_src?: string[],
|
|
append?: { [key: string]: string | string[] },
|
|
}) {
|
|
const parsedUrl = new URL(options.url);
|
|
|
|
const localPath = (path: string, scheme?: string) => {
|
|
const finalUrl = new URL(path, parsedUrl.origin);
|
|
if (scheme) {
|
|
finalUrl.protocol = scheme;
|
|
}
|
|
return finalUrl.toString();
|
|
};
|
|
|
|
const keys = Array.from(hashedMap.keys());
|
|
const scripts = keys
|
|
.filter(name => name.endsWith('.js'))
|
|
.map(name => `'${hashedMap.get(name)!.integrity}'`);
|
|
const styles = keys
|
|
.filter(name => name.endsWith('.css'))
|
|
.map(name => `'${hashedMap.get(name)!.integrity}'`);
|
|
|
|
const cpolicy = [
|
|
['default-src', ['\'self\'']],
|
|
['img-src',
|
|
[
|
|
'\'self\'',
|
|
'data:',
|
|
'blob:',
|
|
// 'https://avatars.githubusercontent.com', // uncomment this for contributor avatars to work
|
|
options.mediaProxy,
|
|
].filter(Boolean)],
|
|
['media-src', ['\'self\'', 'data:', options.mediaProxy].filter(Boolean)],
|
|
['font-src', ['\'self\'']],
|
|
['style-src', ['\'self\'', ...styles]],
|
|
// remove below line if you do not use code highlighting.
|
|
// shiki is not designed to work with CSP, so we need to allow unsafe-inline for now.
|
|
//
|
|
// Allowing inline style attributes is a very small risk, so I will allow by default.
|
|
// Since you can not write CSS selectors or cascading rules in the inline style attributes.
|
|
//
|
|
// ref: https://github.com/shikijs/shiki/issues/671
|
|
['style-src-attr', ['\'self\'', '\'unsafe-inline\'']],
|
|
['script-src', [
|
|
...(options.script_src ?? ['\'self\'']),
|
|
'\'wasm-unsafe-eval\'',
|
|
...scripts,
|
|
]],
|
|
['object-src', ['\'none\'']],
|
|
['base-uri', ['\'none\'']],
|
|
['form-action', ['\'self\'']],
|
|
['child-src', ['\'self\'']],
|
|
['manifest-src', ['\'self\'']],
|
|
...(process.env.NODE_ENV === 'production' ?
|
|
[
|
|
['upgrade-insecure-requests', []],
|
|
] : []),
|
|
] as [string, string[]][];
|
|
|
|
if (options.append) {
|
|
for (const [name, values] of Object.entries(options.append)) {
|
|
if (!values) {
|
|
continue;
|
|
}
|
|
const found = cpolicy.find(([n]) => n === name);
|
|
if (found) {
|
|
found[1].push(...(Array.isArray(values) ? values : [values]));
|
|
} else {
|
|
cpolicy.push([name, Array.isArray(values) ? values : [values]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return cpolicy
|
|
.map(([name, values]) => {
|
|
return `${name} ${values.join(' ')}`;
|
|
}).join('; ');
|
|
}
|
|
|
|
export function hashResource(res: string): CSPHashed {
|
|
const sha256 = createHash('sha256');
|
|
|
|
sha256.update(res);
|
|
|
|
const content = res;
|
|
const integrity = `sha256-${sha256.digest('base64')}`;
|
|
return { content, integrity };
|
|
}
|
|
|
|
export function hashSourceFile(file: string): CSPHashed {
|
|
const content = readFileSync(file, 'utf8');
|
|
|
|
return hashResource(content);
|
|
}
|