All files / core security.ts

100% Statements 20/20
100% Branches 6/6
100% Functions 5/5
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63      14x           14x       22x 22x 22x           14x 21x                       14x   14x 20x           14x 12x     14x 14x         14x 58x 33x   25x 18x   7x    
// Copyright 2026 ForgeKit Contributors
// SPDX-License-Identifier: Apache-2.0
// https://github.com/SubhanshuMG/ForgeKit
import * as path from 'path';
 
/**
 * Validates that a resolved path stays within the allowed root directory.
 * Prevents directory traversal attacks in template file writing.
 */
export function validatePathContainment(
  targetRoot: string,
  filePath: string
): boolean {
  const resolvedRoot = path.resolve(targetRoot);
  const resolvedFile = path.resolve(targetRoot, filePath);
  return resolvedFile.startsWith(resolvedRoot + path.sep) || resolvedFile === resolvedRoot;
}
 
/**
 * Sanitizes a project name to be safe for use as a directory name.
 */
export function sanitizeProjectName(name: string): string {
  return name
    .toLowerCase()
    .replace(/[^a-z0-9-_]/g, '-')
    .replace(/^-+|-+$/g, '')
    .replace(/-+/g, '-')
    .slice(0, 214); // npm package name max length
}
 
/**
 * Validates that a hook command is in the allowed list.
 * Prevents arbitrary code execution from malicious templates.
 */
const ALLOWED_HOOK_COMMANDS = ['npm', 'npx', 'yarn', 'pnpm', 'pip', 'pip3', 'python', 'python3'];
 
export function validateHookCommand(command: string): boolean {
  return ALLOWED_HOOK_COMMANDS.includes(command);
}
 
/**
 * Validates a template ID is safe (no path traversal).
 */
export function validateTemplateId(id: string): boolean {
  return /^[a-z0-9-_]+$/.test(id) && !id.includes('..');
}
 
const GITHUB_ID_PATTERN = /^github:[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+(#[a-zA-Z0-9_./-]+)?$/;
const NPM_ID_PATTERN = /^npm:(@[a-z0-9-]+\/)?[a-z0-9-]+$/;
 
/**
 * Validates an external template ID with github: or npm: prefix.
 */
export function validateExternalTemplateId(id: string): boolean {
  if (id.startsWith('github:')) {
    return GITHUB_ID_PATTERN.test(id);
  }
  if (id.startsWith('npm:')) {
    return NPM_ID_PATTERN.test(id);
  }
  return false;
}