mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@qinglong/local-command-file",
|
||||
"version": "3.0.0-alpha.0",
|
||||
"private": true,
|
||||
"description": "QingLong 3.0 bounded private durable command-file protocol",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=24.18.0 <25"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"require": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*.js",
|
||||
"dist/**/*.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"check": "node ../../scripts/ql3-build-package-closure.cjs && tsc -p tsconfig.json --noEmit",
|
||||
"test": "node ../../scripts/ql3-build-package-closure.cjs && node --test test/*.test.cjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.13.3",
|
||||
"typescript": "5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
MAX_PRIVATE_LOCAL_JSON_FILE_BYTES,
|
||||
PrivateLocalCommandFileError,
|
||||
readPrivateLocalCommandFile,
|
||||
readPrivateLocalJsonFile,
|
||||
type ReadPrivateLocalJsonFileOptions,
|
||||
} from './protocol/privateLocalCommandFile';
|
||||
@@ -0,0 +1,160 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const MAX_COMMAND_FILE_BYTES = 16 * 1024;
|
||||
export const MAX_PRIVATE_LOCAL_JSON_FILE_BYTES = 1024 * 1024;
|
||||
const MAX_PATH_BYTES = 4096;
|
||||
|
||||
export interface ReadPrivateLocalJsonFileOptions {
|
||||
readonly maxBytes: number;
|
||||
}
|
||||
|
||||
export class PrivateLocalCommandFileError extends TypeError {
|
||||
readonly code = 'PRIVATE_LOCAL_COMMAND_FILE_INVALID';
|
||||
|
||||
constructor(message: string, readonly cause?: unknown) {
|
||||
super(`Private local command file is invalid: ${message}`);
|
||||
this.name = 'PrivateLocalCommandFileError';
|
||||
}
|
||||
}
|
||||
|
||||
function currentUid(): number {
|
||||
if (
|
||||
typeof process.getuid !== 'function' ||
|
||||
typeof process.geteuid !== 'function'
|
||||
) {
|
||||
throw new PrivateLocalCommandFileError(
|
||||
'POSIX user identity is unavailable',
|
||||
);
|
||||
}
|
||||
const uid = process.getuid();
|
||||
const effectiveUid = process.geteuid();
|
||||
if (
|
||||
!Number.isSafeInteger(uid) ||
|
||||
uid < 0 ||
|
||||
!Number.isSafeInteger(effectiveUid) ||
|
||||
effectiveUid < 0 ||
|
||||
uid !== effectiveUid
|
||||
) {
|
||||
throw new PrivateLocalCommandFileError(
|
||||
'real and effective POSIX users must match',
|
||||
);
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
|
||||
function commandPath(value: string): string {
|
||||
if (
|
||||
typeof value !== 'string' ||
|
||||
!path.isAbsolute(value) ||
|
||||
Buffer.byteLength(value) < 1 ||
|
||||
Buffer.byteLength(value) > MAX_PATH_BYTES ||
|
||||
value.includes('\0') ||
|
||||
path.normalize(value) !== value
|
||||
) {
|
||||
throw new PrivateLocalCommandFileError(
|
||||
'path must be normalized, bounded and absolute',
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function readLimit(value: ReadPrivateLocalJsonFileOptions): number {
|
||||
if (
|
||||
!value ||
|
||||
typeof value !== 'object' ||
|
||||
Array.isArray(value) ||
|
||||
Object.keys(value).length !== 1 ||
|
||||
!Number.isSafeInteger(value.maxBytes) ||
|
||||
value.maxBytes < 1 ||
|
||||
value.maxBytes > MAX_PRIVATE_LOCAL_JSON_FILE_BYTES
|
||||
) {
|
||||
throw new PrivateLocalCommandFileError('read options are invalid');
|
||||
}
|
||||
return value.maxBytes;
|
||||
}
|
||||
|
||||
export function readPrivateLocalJsonFile(
|
||||
candidatePath: string,
|
||||
options: ReadPrivateLocalJsonFileOptions,
|
||||
): unknown {
|
||||
const filePath = commandPath(candidatePath);
|
||||
const maxBytes = readLimit(options);
|
||||
const uid = currentUid();
|
||||
let descriptor: number | undefined;
|
||||
let material: Buffer | undefined;
|
||||
try {
|
||||
const before = fs.lstatSync(filePath, { bigint: true });
|
||||
if (
|
||||
!before.isFile() ||
|
||||
before.isSymbolicLink() ||
|
||||
Number(before.uid) !== uid ||
|
||||
(Number(before.mode) & 0o777) !== 0o600 ||
|
||||
before.size < 1n ||
|
||||
before.size > BigInt(maxBytes)
|
||||
) {
|
||||
throw new PrivateLocalCommandFileError(
|
||||
'file must be a bounded private regular file',
|
||||
);
|
||||
}
|
||||
descriptor = fs.openSync(
|
||||
filePath,
|
||||
fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0),
|
||||
);
|
||||
const opened = fs.fstatSync(descriptor, { bigint: true });
|
||||
if (
|
||||
!opened.isFile() ||
|
||||
opened.dev !== before.dev ||
|
||||
opened.ino !== before.ino ||
|
||||
opened.size !== before.size ||
|
||||
Number(opened.uid) !== uid ||
|
||||
(Number(opened.mode) & 0o777) !== 0o600
|
||||
) {
|
||||
throw new PrivateLocalCommandFileError(
|
||||
'file identity changed while opening',
|
||||
);
|
||||
}
|
||||
material = Buffer.allocUnsafe(Number(opened.size) + 1);
|
||||
let offset = 0;
|
||||
while (offset < material.byteLength) {
|
||||
const bytesRead = fs.readSync(
|
||||
descriptor,
|
||||
material,
|
||||
offset,
|
||||
material.byteLength - offset,
|
||||
null,
|
||||
);
|
||||
if (bytesRead === 0) break;
|
||||
offset += bytesRead;
|
||||
}
|
||||
const after = fs.fstatSync(descriptor, { bigint: true });
|
||||
if (
|
||||
offset !== Number(opened.size) ||
|
||||
after.dev !== opened.dev ||
|
||||
after.ino !== opened.ino ||
|
||||
after.size !== opened.size ||
|
||||
Number(after.uid) !== uid ||
|
||||
(Number(after.mode) & 0o777) !== 0o600
|
||||
) {
|
||||
throw new PrivateLocalCommandFileError(
|
||||
'file identity changed while reading',
|
||||
);
|
||||
}
|
||||
material = material.subarray(0, offset);
|
||||
return JSON.parse(
|
||||
new TextDecoder('utf-8', { fatal: true }).decode(material),
|
||||
) as unknown;
|
||||
} catch (error) {
|
||||
if (error instanceof PrivateLocalCommandFileError) throw error;
|
||||
throw new PrivateLocalCommandFileError('file cannot be read', error);
|
||||
} finally {
|
||||
material?.fill(0);
|
||||
if (descriptor !== undefined) fs.closeSync(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
export function readPrivateLocalCommandFile(candidatePath: string): unknown {
|
||||
return readPrivateLocalJsonFile(candidatePath, {
|
||||
maxBytes: MAX_COMMAND_FILE_BYTES,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { test } = require('node:test');
|
||||
const {
|
||||
MAX_PRIVATE_LOCAL_JSON_FILE_BYTES,
|
||||
PrivateLocalCommandFileError,
|
||||
readPrivateLocalCommandFile,
|
||||
readPrivateLocalJsonFile,
|
||||
} = require('../dist');
|
||||
|
||||
function fixture(t, value = { schemaVersion: 1, operation: 'test' }) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-command-file-'));
|
||||
const filePath = path.join(root, 'command.json');
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
return { root, filePath };
|
||||
}
|
||||
|
||||
test('reads one bounded private JSON intent', (t) => {
|
||||
const value = { schemaVersion: 1, operation: 'test', mutationId: 'stable' };
|
||||
const { filePath } = fixture(t, value);
|
||||
assert.deepEqual(readPrivateLocalCommandFile(filePath), value);
|
||||
});
|
||||
|
||||
test('rejects relative, broad, symlinked, oversized and malformed files', (t) => {
|
||||
assert.throws(
|
||||
() => readPrivateLocalCommandFile('command.json'),
|
||||
PrivateLocalCommandFileError,
|
||||
);
|
||||
const broad = fixture(t);
|
||||
fs.chmodSync(broad.filePath, 0o644);
|
||||
assert.throws(
|
||||
() => readPrivateLocalCommandFile(broad.filePath),
|
||||
PrivateLocalCommandFileError,
|
||||
);
|
||||
const linked = fixture(t);
|
||||
const linkPath = path.join(linked.root, 'link.json');
|
||||
fs.symlinkSync(linked.filePath, linkPath);
|
||||
assert.throws(
|
||||
() => readPrivateLocalCommandFile(linkPath),
|
||||
PrivateLocalCommandFileError,
|
||||
);
|
||||
const oversized = fixture(t);
|
||||
fs.writeFileSync(oversized.filePath, 'x'.repeat(16 * 1024 + 1), {
|
||||
mode: 0o600,
|
||||
});
|
||||
assert.throws(
|
||||
() => readPrivateLocalCommandFile(oversized.filePath),
|
||||
PrivateLocalCommandFileError,
|
||||
);
|
||||
const malformed = fixture(t);
|
||||
fs.writeFileSync(malformed.filePath, '{', { mode: 0o600 });
|
||||
assert.throws(
|
||||
() => readPrivateLocalCommandFile(malformed.filePath),
|
||||
PrivateLocalCommandFileError,
|
||||
);
|
||||
const invalidUtf8 = fixture(t);
|
||||
fs.writeFileSync(
|
||||
invalidUtf8.filePath,
|
||||
Buffer.from([0x7b, 0x22, 0x78, 0x22, 0x3a, 0x22, 0xff, 0x22, 0x7d]),
|
||||
{ mode: 0o600 },
|
||||
);
|
||||
assert.throws(
|
||||
() => readPrivateLocalCommandFile(invalidUtf8.filePath),
|
||||
PrivateLocalCommandFileError,
|
||||
);
|
||||
});
|
||||
|
||||
test('reuses the private descriptor protocol for explicitly bounded JSON', (t) => {
|
||||
const value = { payload: 'x'.repeat(32 * 1024) };
|
||||
const { filePath } = fixture(t, value);
|
||||
assert.deepEqual(
|
||||
readPrivateLocalJsonFile(filePath, { maxBytes: 64 * 1024 }),
|
||||
value,
|
||||
);
|
||||
assert.throws(
|
||||
() => readPrivateLocalCommandFile(filePath),
|
||||
PrivateLocalCommandFileError,
|
||||
);
|
||||
for (const options of [
|
||||
{},
|
||||
{ maxBytes: 0 },
|
||||
{ maxBytes: MAX_PRIVATE_LOCAL_JSON_FILE_BYTES + 1 },
|
||||
{ maxBytes: 64 * 1024, widened: true },
|
||||
]) {
|
||||
assert.throws(
|
||||
() => readPrivateLocalJsonFile(filePath, options),
|
||||
PrivateLocalCommandFileError,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user