Skip to content

Commit

Permalink
Ensure that source files only contain ASCII
Browse files Browse the repository at this point in the history
This change should have no user impact. This is a defense-in-depth
against someone opening a malicious patch that has non-ASCII
characters.
  • Loading branch information
EvanHahn committed Apr 28, 2024
1 parent 6475da1 commit a1c7466
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 10 deletions.
35 changes: 27 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -5,6 +5,7 @@
"@rollup/plugin-typescript": "^11.1.6",
"@types/connect": "^3.4.38",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.7",
"@types/node-zopfli": "^2.0.5",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^7.7.1",
Expand Down
4 changes: 2 additions & 2 deletions test/content-security-policy.test.ts
Expand Up @@ -297,12 +297,12 @@ describe("Content-Security-Policy middleware", () => {
const invalidNames = [
"",
";",
"á",
"\u00e1",
"default src",
"default;src",
"default,src",
"default!src",
"defáult-src",
"def\u00e1ult-src",
"default_src",
"__proto__",
];
Expand Down
64 changes: 64 additions & 0 deletions test/source-files.test.ts
@@ -0,0 +1,64 @@
import { promisify } from "node:util";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as childProcess from "node:child_process";
import { ReadableStream } from "node:stream/web";

const EXTNAMES_THAT_DONT_HAVE_TO_BE_ASCII: ReadonlySet<string> = new Set([
".md",
]);
const NEWLINE = "\n".charCodeAt(0);
const SPACE = " ".charCodeAt(0);
const TILDE = "~".charCodeAt(0);

const exec = promisify(childProcess.exec);
const root = path.resolve(__dirname, "..");

describe("source files", () => {
it('only has "normal" ASCII characters in the source files', async () => {
for await (const { sourceFile, chunk, index } of getSourceFileChunks()) {
const abnormalByte = chunk.find((byte) => !isNormalAsciiByte(byte));
if (typeof abnormalByte === "number") {
throw new Error(
`${sourceFile} must only contain "normal" ASCII characters but contained 0x${abnormalByte.toString(16)} at index ${index + chunk.indexOf(abnormalByte)}`,
);
}
}
});
});

const getSourceFileChunks = (): AsyncIterable<{
sourceFile: string;
chunk: Uint8Array;
index: number;
}> =>
new ReadableStream({
async start(controller) {
await Promise.all(
(await getSourceFiles()).map(async (sourceFile) => {
const handle = await fs.open(sourceFile);
let index = 0;
for await (const chunk of handle.readableWebStream({
type: "bytes",
})) {
controller.enqueue({ sourceFile, chunk, index });
index += chunk.byteLength;
}
await handle.close();
}),
);
controller.close();
},
});

const getSourceFiles = async (): Promise<Array<string>> =>
(await exec("git ls-files", { cwd: root, env: {} })).stdout
.split(/\r?\n/g)
.filter(
(file) => !EXTNAMES_THAT_DONT_HAVE_TO_BE_ASCII.has(path.extname(file)),
)
.filter(Boolean)
.map((line) => path.resolve(root, line));

const isNormalAsciiByte = (byte: number): boolean =>
byte === NEWLINE || (byte >= SPACE && byte <= TILDE);

0 comments on commit a1c7466

Please sign in to comment.