UNPKG

@samiyev/guardian

Version:

Research-backed code quality guardian for AI-assisted development. Detects hardcodes, secrets, circular deps, framework leaks, entity exposure, and 9 architecture violations. Enforces Clean Architecture/DDD principles. Works with GitHub Copilot, Cursor, W

377 lines 16.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HardcodedValue = void 0; const ValueObject_1 = require("./ValueObject"); const rules_1 = require("../../shared/constants/rules"); const Suggestions_1 = require("../constants/Suggestions"); /** * Represents a hardcoded value found in source code */ class HardcodedValue extends ValueObject_1.ValueObject { constructor(props) { super(props); } static create(value, type, line, column, context, valueType, duplicateLocations, withinFileUsageCount) { return new HardcodedValue({ value, type, valueType, line, column, context, duplicateLocations, withinFileUsageCount, }); } get value() { return this.props.value; } get type() { return this.props.type; } get line() { return this.props.line; } get column() { return this.props.column; } get context() { return this.props.context; } get valueType() { return this.props.valueType; } get duplicateLocations() { return this.props.duplicateLocations; } get withinFileUsageCount() { return this.props.withinFileUsageCount; } hasDuplicates() { return (this.props.duplicateLocations !== undefined && this.props.duplicateLocations.length > 0); } isAlmostConstant() { return this.props.withinFileUsageCount !== undefined && this.props.withinFileUsageCount >= 2; } isMagicNumber() { return this.props.type === rules_1.HARDCODE_TYPES.MAGIC_NUMBER; } isMagicString() { return this.props.type === rules_1.HARDCODE_TYPES.MAGIC_STRING; } suggestConstantName() { if (this.isMagicNumber()) { return this.suggestNumberConstantName(); } if (this.isMagicString()) { return this.suggestStringConstantName(); } return Suggestions_1.CONSTANT_NAMES.UNKNOWN_CONSTANT; } suggestNumberConstantName() { const value = this.props.value; const context = this.props.context.toLowerCase(); if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.TIMEOUT)) { return Suggestions_1.CONSTANT_NAMES.TIMEOUT_MS; } if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.RETRY) || context.includes(Suggestions_1.SUGGESTION_KEYWORDS.ATTEMPT)) { return Suggestions_1.CONSTANT_NAMES.MAX_RETRIES; } if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.LIMIT) || context.includes(Suggestions_1.SUGGESTION_KEYWORDS.MAX)) { return Suggestions_1.CONSTANT_NAMES.MAX_LIMIT; } if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.PORT)) { return Suggestions_1.CONSTANT_NAMES.DEFAULT_PORT; } if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.DELAY)) { return Suggestions_1.CONSTANT_NAMES.DELAY_MS; } return `${Suggestions_1.CONSTANT_NAMES.MAGIC_NUMBER}_${String(value)}`; } // eslint-disable-next-line complexity, max-lines-per-function suggestStringConstantName() { const value = String(this.props.value); const context = this.props.context.toLowerCase(); const valueType = this.props.valueType; if (valueType === "email") { if (context.includes(Suggestions_1.EMAIL_CONTEXT_KEYWORDS.ADMIN)) { return Suggestions_1.CONSTANT_NAMES.ADMIN_EMAIL; } if (context.includes(Suggestions_1.EMAIL_CONTEXT_KEYWORDS.SUPPORT)) { return Suggestions_1.CONSTANT_NAMES.SUPPORT_EMAIL; } if (context.includes(Suggestions_1.EMAIL_CONTEXT_KEYWORDS.NOREPLY) || context.includes(Suggestions_1.EMAIL_CONTEXT_KEYWORDS.NO_REPLY)) { return Suggestions_1.CONSTANT_NAMES.NOREPLY_EMAIL; } return Suggestions_1.CONSTANT_NAMES.DEFAULT_EMAIL; } if (valueType === "api_key") { if (context.includes(Suggestions_1.API_KEY_CONTEXT_KEYWORDS.SECRET)) { return Suggestions_1.CONSTANT_NAMES.API_SECRET_KEY; } if (context.includes(Suggestions_1.API_KEY_CONTEXT_KEYWORDS.PUBLIC)) { return Suggestions_1.CONSTANT_NAMES.API_PUBLIC_KEY; } return Suggestions_1.CONSTANT_NAMES.API_KEY; } if (valueType === "url") { if (context.includes(Suggestions_1.URL_CONTEXT_KEYWORDS.API)) { return Suggestions_1.CONSTANT_NAMES.API_BASE_URL; } if (context.includes(Suggestions_1.URL_CONTEXT_KEYWORDS.DATABASE) || context.includes(Suggestions_1.URL_CONTEXT_KEYWORDS.DB)) { return Suggestions_1.CONSTANT_NAMES.DATABASE_URL; } if (context.includes(Suggestions_1.URL_CONTEXT_KEYWORDS.MONGO)) { return Suggestions_1.CONSTANT_NAMES.MONGODB_CONNECTION_STRING; } if (context.includes(Suggestions_1.URL_CONTEXT_KEYWORDS.POSTGRES) || context.includes(Suggestions_1.URL_CONTEXT_KEYWORDS.PG)) { return Suggestions_1.CONSTANT_NAMES.POSTGRES_URL; } return Suggestions_1.CONSTANT_NAMES.BASE_URL; } if (valueType === "ip_address") { if (context.includes(Suggestions_1.IP_CONTEXT_KEYWORDS.SERVER)) { return Suggestions_1.CONSTANT_NAMES.SERVER_IP; } if (context.includes(Suggestions_1.URL_CONTEXT_KEYWORDS.DATABASE) || context.includes(Suggestions_1.URL_CONTEXT_KEYWORDS.DB)) { return Suggestions_1.CONSTANT_NAMES.DATABASE_HOST; } if (context.includes(Suggestions_1.IP_CONTEXT_KEYWORDS.REDIS)) { return Suggestions_1.CONSTANT_NAMES.REDIS_HOST; } return Suggestions_1.CONSTANT_NAMES.HOST_IP; } if (valueType === "file_path") { if (context.includes(Suggestions_1.FILE_PATH_CONTEXT_KEYWORDS.LOG)) { return Suggestions_1.CONSTANT_NAMES.LOG_FILE_PATH; } if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.CONFIG)) { return Suggestions_1.CONSTANT_NAMES.CONFIG_FILE_PATH; } if (context.includes(Suggestions_1.FILE_PATH_CONTEXT_KEYWORDS.DATA)) { return Suggestions_1.CONSTANT_NAMES.DATA_DIR_PATH; } if (context.includes(Suggestions_1.FILE_PATH_CONTEXT_KEYWORDS.TEMP)) { return Suggestions_1.CONSTANT_NAMES.TEMP_DIR_PATH; } return Suggestions_1.CONSTANT_NAMES.FILE_PATH; } if (valueType === "date") { if (context.includes(Suggestions_1.DATE_CONTEXT_KEYWORDS.DEADLINE)) { return Suggestions_1.CONSTANT_NAMES.DEADLINE; } if (context.includes(Suggestions_1.DATE_CONTEXT_KEYWORDS.START)) { return Suggestions_1.CONSTANT_NAMES.START_DATE; } if (context.includes(Suggestions_1.DATE_CONTEXT_KEYWORDS.END)) { return Suggestions_1.CONSTANT_NAMES.END_DATE; } if (context.includes(Suggestions_1.DATE_CONTEXT_KEYWORDS.EXPIR)) { return Suggestions_1.CONSTANT_NAMES.EXPIRATION_DATE; } return Suggestions_1.CONSTANT_NAMES.DEFAULT_DATE; } if (valueType === "uuid") { if (context.includes(Suggestions_1.UUID_CONTEXT_KEYWORDS.ID) || context.includes(Suggestions_1.UUID_CONTEXT_KEYWORDS.IDENTIFIER)) { return Suggestions_1.CONSTANT_NAMES.DEFAULT_ID; } if (context.includes(Suggestions_1.UUID_CONTEXT_KEYWORDS.REQUEST)) { return Suggestions_1.CONSTANT_NAMES.REQUEST_ID; } if (context.includes(Suggestions_1.UUID_CONTEXT_KEYWORDS.SESSION)) { return Suggestions_1.CONSTANT_NAMES.SESSION_ID; } return Suggestions_1.CONSTANT_NAMES.UUID_CONSTANT; } if (valueType === "version") { if (context.includes(Suggestions_1.URL_CONTEXT_KEYWORDS.API)) { return Suggestions_1.CONSTANT_NAMES.API_VERSION; } if (context.includes(Suggestions_1.VERSION_CONTEXT_KEYWORDS.APP)) { return Suggestions_1.CONSTANT_NAMES.APP_VERSION; } return Suggestions_1.CONSTANT_NAMES.VERSION; } if (valueType === "color") { if (context.includes(Suggestions_1.COLOR_CONTEXT_KEYWORDS.PRIMARY)) { return Suggestions_1.CONSTANT_NAMES.PRIMARY_COLOR; } if (context.includes(Suggestions_1.COLOR_CONTEXT_KEYWORDS.SECONDARY)) { return Suggestions_1.CONSTANT_NAMES.SECONDARY_COLOR; } if (context.includes(Suggestions_1.COLOR_CONTEXT_KEYWORDS.BACKGROUND)) { return Suggestions_1.CONSTANT_NAMES.BACKGROUND_COLOR; } return Suggestions_1.CONSTANT_NAMES.COLOR_CONSTANT; } if (valueType === "mac_address") { return Suggestions_1.CONSTANT_NAMES.MAC_ADDRESS; } if (valueType === "base64") { if (context.includes(Suggestions_1.BASE64_CONTEXT_KEYWORDS.TOKEN)) { return Suggestions_1.CONSTANT_NAMES.ENCODED_TOKEN; } if (context.includes(Suggestions_1.BASE64_CONTEXT_KEYWORDS.KEY)) { return Suggestions_1.CONSTANT_NAMES.ENCODED_KEY; } return Suggestions_1.CONSTANT_NAMES.BASE64_VALUE; } if (valueType === "config") { if (context.includes(Suggestions_1.CONFIG_CONTEXT_KEYWORDS.ENDPOINT)) { return Suggestions_1.CONSTANT_NAMES.API_ENDPOINT; } if (context.includes(Suggestions_1.CONFIG_CONTEXT_KEYWORDS.ROUTE)) { return Suggestions_1.CONSTANT_NAMES.ROUTE_PATH; } if (context.includes(Suggestions_1.CONFIG_CONTEXT_KEYWORDS.CONNECTION)) { return Suggestions_1.CONSTANT_NAMES.CONNECTION_STRING; } return Suggestions_1.CONSTANT_NAMES.CONFIG_VALUE; } if (value.includes(Suggestions_1.SUGGESTION_KEYWORDS.HTTP)) { return Suggestions_1.CONSTANT_NAMES.API_BASE_URL; } if (value.includes(".") && !value.includes(" ")) { if (value.includes("/")) { return Suggestions_1.CONSTANT_NAMES.DEFAULT_PATH; } return Suggestions_1.CONSTANT_NAMES.DEFAULT_DOMAIN; } if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.ERROR) || context.includes(Suggestions_1.SUGGESTION_KEYWORDS.MESSAGE)) { return Suggestions_1.CONSTANT_NAMES.ERROR_MESSAGE; } if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.DEFAULT)) { return Suggestions_1.CONSTANT_NAMES.DEFAULT_VALUE; } return Suggestions_1.CONSTANT_NAMES.MAGIC_STRING; } suggestLocation(currentLayer) { if (!currentLayer) { return Suggestions_1.LOCATIONS.SHARED_CONSTANTS; } const context = this.props.context.toLowerCase(); const valueType = this.props.valueType; if (valueType === "api_key" || valueType === "url" || valueType === "ip_address") { return Suggestions_1.LOCATIONS.CONFIG_ENVIRONMENT; } if (valueType === "email") { return Suggestions_1.LOCATIONS.CONFIG_CONTACTS; } if (valueType === "file_path") { return Suggestions_1.LOCATIONS.CONFIG_PATHS; } if (valueType === "date") { return Suggestions_1.LOCATIONS.CONFIG_DATES; } if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.ENTITY) || context.includes(Suggestions_1.SUGGESTION_KEYWORDS.AGGREGATE) || context.includes(Suggestions_1.SUGGESTION_KEYWORDS.DOMAIN)) { return currentLayer ? `${currentLayer}/constants` : Suggestions_1.LOCATIONS.DOMAIN_CONSTANTS; } if (context.includes(Suggestions_1.SUGGESTION_KEYWORDS.CONFIG) || context.includes(Suggestions_1.SUGGESTION_KEYWORDS.ENV)) { return Suggestions_1.LOCATIONS.INFRASTRUCTURE_CONFIG; } return Suggestions_1.LOCATIONS.SHARED_CONSTANTS; } getDetailedSuggestion(currentLayer) { const constantName = this.suggestConstantName(); const location = this.suggestLocation(currentLayer); const valueTypeLabel = this.valueType ? ` (${this.valueType})` : ""; let suggestion = `Extract${valueTypeLabel} to constant ${constantName} in ${location}`; if (this.isAlmostConstant() && this.withinFileUsageCount) { suggestion += `. This value appears ${String(this.withinFileUsageCount)} times in this file`; } if (this.hasDuplicates() && this.duplicateLocations) { const count = this.duplicateLocations.length; const fileList = this.duplicateLocations .slice(0, 3) .map((loc) => `${loc.file}:${String(loc.line)}`) .join(", "); const more = count > 3 ? ` and ${String(count - 3)} more` : ""; suggestion += `. Also duplicated in ${String(count)} other file(s): ${fileList}${more}`; } return suggestion; } /** * Analyzes variable name and context to determine importance */ getImportance() { const context = this.props.context.toLowerCase(); const valueType = this.props.valueType; if (valueType === "api_key") { return "critical"; } const criticalKeywords = [ ...rules_1.DETECTION_PATTERNS.SENSITIVE_KEYWORDS, ...rules_1.DETECTION_PATTERNS.BUSINESS_KEYWORDS, "key", "age", ]; if (criticalKeywords.some((keyword) => context.includes(keyword))) { return "critical"; } const highKeywords = [...rules_1.DETECTION_PATTERNS.TECHNICAL_KEYWORDS, "db", "api"]; if (highKeywords.some((keyword) => context.includes(keyword))) { return "high"; } if (valueType === "url" || valueType === "ip_address" || valueType === "email") { return "high"; } const mediumKeywords = rules_1.DETECTION_PATTERNS.MEDIUM_KEYWORDS; if (mediumKeywords.some((keyword) => context.includes(keyword))) { return "medium"; } const lowKeywords = rules_1.DETECTION_PATTERNS.UI_KEYWORDS; if (lowKeywords.some((keyword) => context.includes(keyword))) { return "low"; } return "medium"; } /** * Checks if this violation should be skipped based on layer strictness * * Different layers have different tolerance levels: * - domain: strictest (no hardcoded values allowed) * - application: strict (only low importance allowed) * - infrastructure: moderate (low and some medium allowed) * - cli: lenient (UI constants allowed) */ shouldSkip(layer) { if (!layer) { return false; } const importance = this.getImportance(); if (layer === "domain") { return false; } if (layer === "application") { return false; } if (layer === "infrastructure") { return importance === "low" && this.isUIConstant(); } if (layer === "cli") { return importance === "low" && this.isUIConstant(); } return false; } /** * Checks if this value is a UI-related constant */ isUIConstant() { const context = this.props.context.toLowerCase(); const uiKeywords = rules_1.DETECTION_PATTERNS.UI_KEYWORDS; return uiKeywords.some((keyword) => context.includes(keyword)); } } exports.HardcodedValue = HardcodedValue; //# sourceMappingURL=HardcodedValue.js.map