ailock
Version:
AI-Proof File Guard - Protect sensitive files from accidental AI modifications
176 lines • 5.95 kB
JavaScript
import { readFile } from 'fs/promises';
import { existsSync, statSync } from 'fs';
import path from 'path';
import glob from 'fast-glob';
import { getRepoRoot } from './git.js';
/**
* Parse .ailock file content using gitignore-style syntax
*/
export function parseAilockContent(content) {
return content
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0 && !line.startsWith('#'))
.filter(line => !line.startsWith('!')); // TODO: Handle negation patterns in future
}
/**
* Parse .gitignore file content and extract potentially sensitive patterns
*/
export function parseGitignoreContent(content) {
const lines = content
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0 && !line.startsWith('#'))
.filter(line => !line.startsWith('!')); // Skip negation patterns for now
// Smart filtering: only include patterns that likely contain sensitive data
const sensitivePatterns = [
// Environment files
/\.env/i,
/environment/i,
// Secret and credential files
/secret/i,
/credential/i,
/password/i,
/token/i,
/key/i,
/cert/i,
/\.pem$/i,
/\.p12$/i,
/\.pfx$/i,
/\.crt$/i,
// Configuration files that might contain secrets
/config.*\.json$/i,
/config.*\.yaml$/i,
/config.*\.yml$/i,
// Database files
/\.db$/i,
/\.sqlite$/i,
// Private keys and certificates
/private/i,
/\.rsa$/i,
/\.dsa$/i,
];
return lines.filter(line => sensitivePatterns.some(pattern => pattern.test(line)));
}
/**
* Find .gitignore file in the git repository root
*/
export async function findGitignoreFile(startDir = process.cwd()) {
try {
const repoRoot = await getRepoRoot(startDir);
if (!repoRoot) {
return null;
}
const gitignorePath = path.join(repoRoot, '.gitignore');
if (existsSync(gitignorePath)) {
// Check if it's actually a file, not a directory
try {
const stats = statSync(gitignorePath);
if (stats.isFile()) {
return gitignorePath;
}
}
catch {
// If we can't stat it, skip it
}
}
return null;
}
catch {
return null;
}
}
/**
* Find .ailock file in current directory or parent directories
*/
export async function findAilockFile(startDir = process.cwd()) {
let currentDir = path.resolve(startDir);
const root = path.parse(currentDir).root;
while (currentDir !== root) {
const ailockPath = path.join(currentDir, '.ailock');
if (existsSync(ailockPath)) {
// Check if it's actually a file, not a directory
try {
const stats = statSync(ailockPath);
if (stats.isFile()) {
return ailockPath;
}
}
catch {
// If we can't stat it, skip it
}
}
currentDir = path.dirname(currentDir);
}
return null;
}
/**
* Load and parse .ailock configuration
*/
export async function loadConfig(startDir, options) {
const ailockPath = await findAilockFile(startDir);
const workingDir = startDir || process.cwd();
// Load gitignore patterns if requested
let gitIgnorePatterns = [];
if (options?.includeGitignored) {
const gitignorePath = await findGitignoreFile(workingDir);
if (gitignorePath) {
try {
const gitignoreContent = await readFile(gitignorePath, 'utf-8');
gitIgnorePatterns = parseGitignoreContent(gitignoreContent);
}
catch (error) {
// Silently ignore gitignore parsing errors
console.warn(`Warning: Could not parse .gitignore file: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
if (!ailockPath) {
// Return default configuration if no .ailock file found
const defaultPatterns = ['.env', '.env.*', '**/*.key', '**/*.pem', '**/secrets.json'];
const allPatterns = options?.includeGitignored
? [...defaultPatterns, ...gitIgnorePatterns]
: defaultPatterns;
return {
patterns: [...new Set(allPatterns)], // Remove duplicates
rootDir: workingDir,
includeGitignored: options?.includeGitignored,
gitIgnorePatterns: options?.includeGitignored ? gitIgnorePatterns : []
};
}
const content = await readFile(ailockPath, 'utf-8');
const ailockPatterns = parseAilockContent(content);
const rootDir = path.dirname(ailockPath);
// Merge .ailock patterns with gitignore patterns if requested
const allPatterns = options?.includeGitignored
? [...ailockPatterns, ...gitIgnorePatterns]
: ailockPatterns;
return {
patterns: [...new Set(allPatterns)], // Remove duplicates
rootDir,
includeGitignored: options?.includeGitignored,
gitIgnorePatterns: options?.includeGitignored ? gitIgnorePatterns : []
};
}
/**
* Find all files matching the protection patterns
*/
export async function findProtectedFiles(config) {
if (config.patterns.length === 0) {
return [];
}
try {
const files = await glob(config.patterns, {
cwd: config.rootDir,
absolute: true,
ignore: ['node_modules/**', '.git/**', ...(config.ignore || [])],
onlyFiles: true,
followSymbolicLinks: false
});
return files;
}
catch (error) {
throw new Error(`Failed to find protected files: ${error instanceof Error ? error.message : String(error)}`);
}
}
//# sourceMappingURL=config.js.map