mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 21:52:54 +00:00
This pull request introduces several improvements and security enhancements to the documentation build scripts in `apps/docs/scripts/fetch-remote-content.mjs`, as well as updates to the test configuration in `apps/docs/package.json` and `apps/docs/project.json`. The main focus is on enabling local development workflows, improving safety when handling refs and file paths, and making the codebase more robust against injection and traversal attacks. **Security and validation improvements:** * Added `safeLog` and `isValidRef` helper functions to sanitize logs and validate refs, preventing log and command injection vulnerabilities. * Updated logic for reading local files and external content to ensure paths are securely resolved and checked, preventing directory traversal and unauthorized file access. **Local development and workflow enhancements:** * Refactored content fetching logic to support copying local content and public assets when the source ref matches the local branch, enabling easier local testing and development. [[1]](diffhunk://#diff-0d1ade7f3d86ad72c66b573c31b0bc34d318dbf964198164719822d300d397d1L70-R202) [[2]](diffhunk://#diff-0d1ade7f3d86ad72c66b573c31b0bc34d318dbf964198164719822d300d397d1R230-L145) [[3]](diffhunk://#diff-0d1ade7f3d86ad72c66b573c31b0bc34d318dbf964198164719822d300d397d1L155-R250) * Improved handling of external files (like `defaults.yaml` and `setup/steps.yaml`) in local and remote workflows, including secure copying and conditional downloading. [[1]](diffhunk://#diff-0d1ade7f3d86ad72c66b573c31b0bc34d318dbf964198164719822d300d397d1L70-R202) [[2]](diffhunk://#diff-0d1ade7f3d86ad72c66b573c31b0bc34d318dbf964198164719822d300d397d1R266-R364) * Enhanced relative import fixing logic to robustly rewrite paths and ensure external files are downloaded or copied as needed, with clearer separation of cases for content, public, and external assets. (F768070bL263R373, [[1]](diffhunk://#diff-0d1ade7f3d86ad72c66b573c31b0bc34d318dbf964198164719822d300d397d1L310-R398) [[2]](diffhunk://#diff-0d1ade7f3d86ad72c66b573c31b0bc34d318dbf964198164719822d300d397d1L345-L349) [[3]](diffhunk://#diff-0d1ade7f3d86ad72c66b573c31b0bc34d318dbf964198164719822d300d397d1L360-R425) **Testing and configuration updates:** * Added a new `test:scripts` npm script for running script-level tests, and updated the Nx test command to use this script for the docs app. [[1]](diffhunk://#diff-d03d2ce97fad5059ad6248fca5f5919994b09bca360f883318c03f1f817ae677R19) [[2]](diffhunk://#diff-92d9997980c0871325f1a46a6d363cc28da597bd19ed327e979774d9e7838cabL216-R216) These changes improve the reliability, security, and developer experience of the documentation build process.
150 lines
5.4 KiB
JavaScript
150 lines
5.4 KiB
JavaScript
import { test, describe, beforeEach, afterEach, mock } from 'node:test';
|
|
import assert from 'node:assert';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { getCurrentRef, resetCache, downloadFileContent, isValidRef, safeLog } from './fetch-remote-content.mjs';
|
|
|
|
const TEST_TMP_DIR = path.join(process.cwd(), '.test-tmp');
|
|
const MOCK_REPO_ROOT = path.join(TEST_TMP_DIR, 'mock-repo');
|
|
|
|
describe('fetch-remote-content', () => {
|
|
|
|
// --- Setup & Teardown ---
|
|
const originalEnv = process.env;
|
|
|
|
beforeEach(() => {
|
|
process.env = { ...originalEnv };
|
|
resetCache();
|
|
|
|
// Set up a mock repo structure
|
|
if (fs.existsSync(TEST_TMP_DIR)) {
|
|
fs.rmSync(TEST_TMP_DIR, { recursive: true, force: true });
|
|
}
|
|
fs.mkdirSync(MOCK_REPO_ROOT, { recursive: true });
|
|
|
|
// Create some dummy content
|
|
fs.writeFileSync(path.join(MOCK_REPO_ROOT, 'README.md'), '# Mock Repo');
|
|
fs.mkdirSync(path.join(MOCK_REPO_ROOT, 'secrets'), { recursive: true });
|
|
fs.writeFileSync(path.join(MOCK_REPO_ROOT, 'secrets/passwd'), 'super_secret');
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.env = originalEnv;
|
|
mock.restoreAll();
|
|
resetCache();
|
|
if (fs.existsSync(TEST_TMP_DIR)) {
|
|
fs.rmSync(TEST_TMP_DIR, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
// --- Helper Tests ---
|
|
describe('isValidRef', () => {
|
|
test('accepts valid alphanumeric refs', () => {
|
|
assert.strictEqual(isValidRef('main'), true);
|
|
assert.strictEqual(isValidRef('v1.0.0'), true);
|
|
assert.strictEqual(isValidRef('feature/new-docs'), true);
|
|
assert.strictEqual(isValidRef('fix_bug-123'), true);
|
|
assert.strictEqual(isValidRef('dependabot/npm_and_yarn/@types/react'), true);
|
|
assert.strictEqual(isValidRef('build+test'), true);
|
|
});
|
|
|
|
test('rejects malicious refs', () => {
|
|
assert.strictEqual(isValidRef('; rm -rf'), false);
|
|
assert.strictEqual(isValidRef('main && echo pwned'), false);
|
|
assert.strictEqual(isValidRef('../parent'), false); // contains .. which isn't allowed in regex
|
|
});
|
|
});
|
|
|
|
describe('safeLog', () => {
|
|
test('strips newlines', () => {
|
|
assert.strictEqual(safeLog('valid'), 'valid');
|
|
assert.strictEqual(safeLog('malicious\nlog'), 'maliciouslog');
|
|
assert.strictEqual(safeLog('malicious\r\nlog'), 'maliciouslog');
|
|
});
|
|
});
|
|
|
|
// --- getCurrentRef Tests ---
|
|
describe('getCurrentRef', () => {
|
|
test('returns VERCEL_GIT_COMMIT_REF if set and valid', () => {
|
|
process.env.VERCEL_GIT_COMMIT_REF = 'vercel-branch';
|
|
delete process.env.GITHUB_REF_NAME;
|
|
assert.strictEqual(getCurrentRef(), 'vercel-branch');
|
|
});
|
|
|
|
test('ignores invalid VERCEL_GIT_COMMIT_REF', () => {
|
|
// Should fall through to GITHUB or git command
|
|
process.env.VERCEL_GIT_COMMIT_REF = 'invalid;cmd';
|
|
process.env.GITHUB_REF_NAME = 'valid-github';
|
|
assert.strictEqual(getCurrentRef(), 'valid-github');
|
|
});
|
|
|
|
test('returns GITHUB_REF_NAME if set and valid', () => {
|
|
delete process.env.VERCEL_GIT_COMMIT_REF;
|
|
process.env.GITHUB_REF_NAME = 'github-branch';
|
|
assert.strictEqual(getCurrentRef(), 'github-branch');
|
|
});
|
|
});
|
|
|
|
// --- downloadFileContent Tests ---
|
|
describe('downloadFileContent', () => {
|
|
|
|
test('refuses traversal attempts (..)', async () => {
|
|
// We expect this to return null and log a warning
|
|
const content = await downloadFileContent('main', '../secrets/passwd');
|
|
assert.strictEqual(content, null);
|
|
});
|
|
|
|
test('refuses absolute paths', async () => {
|
|
const content = await downloadFileContent('main', '/etc/passwd');
|
|
assert.strictEqual(content, null);
|
|
});
|
|
|
|
test('refuses encoded traversal attempts', async () => {
|
|
// %2e%2e is ..
|
|
const content = await downloadFileContent('main', '%2e%2e/secrets/passwd');
|
|
assert.strictEqual(content, null);
|
|
});
|
|
|
|
test('reads valid local file (README.md)', async () => {
|
|
// Mock getCurrentRef to return 'main' and pass 'main' to simulate local
|
|
process.env.VERCEL_GIT_COMMIT_REF = 'main';
|
|
|
|
const content = await downloadFileContent('main', 'README.md');
|
|
// It should find the real README.md of the project since the script resolves from __dirname
|
|
assert.ok(content);
|
|
assert.ok(content.length > 0);
|
|
});
|
|
|
|
test('refuses invalid ref strings', async () => {
|
|
// Validation check for command injection protection
|
|
const content = await downloadFileContent('invalid; rm -rf', 'README.md');
|
|
assert.strictEqual(content, null);
|
|
});
|
|
});
|
|
|
|
// --- filterVersions Tests ---
|
|
describe('filterVersions', () => {
|
|
test('filters versions older than CUTOFF (4.10.0)', async () => {
|
|
const { filterVersions } = await import('./fetch-remote-content.mjs');
|
|
const tags = [
|
|
{ name: 'v4.9.0' },
|
|
{ name: 'v4.10.0' }, // Matches cutoff strictly > 4.10.0? No, gt is strict.
|
|
{ name: 'v4.11.0' },
|
|
{ name: 'v5.0.0-alpha' },
|
|
{ name: 'invalid' }
|
|
];
|
|
|
|
const result = filterVersions(tags);
|
|
// Expect only v4.11.0 and v5.0.0-alpha, but slice(0, 3) logic applies
|
|
// If gt(4.10.0), then 4.10.0 is excluded.
|
|
// sort is rcompare (descending).
|
|
// 5.0.0-alpha, 4.11.0
|
|
|
|
assert.ok(result.includes('v4.11.0'));
|
|
assert.ok(!result.includes('v4.10.0'));
|
|
assert.ok(!result.includes('v4.9.0'));
|
|
});
|
|
});
|
|
|
|
});
|