mcp-secrets-vault
Version:
MCP-compatible tool for secure secret management without exposure
1,331 lines (1,318 loc) • 103 kB
JavaScript
#!/usr/bin/env node
// src/utils/package-info.ts
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
var __filename = fileURLToPath(import.meta.url);
var __dirname = dirname(__filename);
function findPackageJson() {
const possiblePaths = [
join(__dirname, "..", "..", "package.json"),
// From src/utils
join(__dirname, "..", "package.json"),
// From dist
join(process.cwd(), "package.json")
// From current working directory
];
for (const path3 of possiblePaths) {
try {
return JSON.parse(readFileSync(path3, "utf-8"));
} catch {
}
}
return { version: "0.1.1", name: "mcp-secrets-vault" };
}
var packageJson = findPackageJson();
var PACKAGE_VERSION = packageJson.version;
var PACKAGE_NAME = packageJson.name;
// src/constants/config-constants.ts
var CONFIG = {
// Version
VERSION: PACKAGE_VERSION,
// Server settings
SERVER_NAME: PACKAGE_NAME,
SERVER_VERSION: PACKAGE_VERSION,
PROTOCOL_VERSION: "1.0.0",
// HTTP settings
HTTP_TIMEOUT_MS: 3e4,
HTTP_MAX_REDIRECTS: 5,
HTTP_DEFAULT_USER_AGENT: `MCP-Secrets-Vault/${PACKAGE_VERSION}`,
// Rate limiting
DEFAULT_RATE_LIMIT_REQUESTS: 100,
DEFAULT_RATE_LIMIT_WINDOW_SECONDS: 3600,
RATE_LIMIT_CLEANUP_INTERVAL_MS: 6e4,
MILLISECONDS_PER_SECOND: 1e3,
RATE_LIMIT_WINDOW_MULTIPLIER: 2,
// Audit settings
AUDIT_FILE_PREFIX: "audit",
AUDIT_FILE_EXTENSION: ".jsonl",
AUDIT_MAX_FILE_SIZE_MB: 100,
AUDIT_MAX_FILE_AGE_DAYS: 30,
AUDIT_ROTATION_CHECK_INTERVAL_MS: 36e5,
AUDIT_DEFAULT_PAGE_SIZE: 50,
AUDIT_MAX_PAGE_SIZE: 500,
// Response limits
RESPONSE_MAX_BODY_LENGTH: 1e4,
RESPONSE_TRUNCATION_MESSAGE: "... [truncated]",
// Sanitization - Comprehensive patterns for defense in depth
SANITIZE_AUTH_HEADER_PATTERN: /authorization/i,
SANITIZE_SECRET_PATTERN: /\b(api[_-]?key|token|password|auth|bearer|credential|private[_-]?key|access[_-]?key)\b/i,
SANITIZE_REPLACEMENT: "[REDACTED]",
// Additional redaction patterns for security hardening
REDACT_URL_AUTH_PATTERN: /https?:\/\/[^:]+:[^@]+@[^\s]+/gi,
REDACT_JWT_PATTERN: /eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
REDACT_BEARER_TOKEN_PATTERN: /bearer\s+[a-zA-Z0-9\-._~+\/]+=*/gi,
REDACT_API_KEY_PATTERNS: [
/\b[a-zA-Z0-9]{32,}\b/g,
// Generic long tokens
/sk[-_]test[-_][a-zA-Z0-9]{24,}/gi,
// Stripe test keys
/sk[-_]live[-_][a-zA-Z0-9]{24,}/gi,
// Stripe live keys
/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi,
// UUIDs
/ghp_[a-zA-Z0-9]{36}/g,
// GitHub personal access tokens
/gho_[a-zA-Z0-9]{36}/g
// GitHub OAuth tokens
],
REDACT_ENV_VAR_PATTERN: /\b[A-Z][A-Z0-9_]*_(KEY|SECRET|TOKEN|PASSWORD|API|CREDENTIAL)\b/g,
REDACT_KEY_VALUE_PATTERN: /(api[_-]?key|secret|token|password|auth|bearer|credential|private[_-]?key)\s*[:=]\s*[^\s,;}]+/gi,
// Validation
MAX_SECRET_ID_LENGTH: 100,
MAX_DOMAIN_LENGTH: 253,
MAX_ACTION_LENGTH: 50,
MAX_REASON_LENGTH: 500,
MAX_URL_LENGTH: 2048,
MAX_HEADER_NAME_LENGTH: 100,
MAX_HEADER_VALUE_LENGTH: 8192,
SECRET_ID_REGEX: /^[a-zA-Z0-9_-]+$/,
ENV_VAR_REGEX: /^[A-Z][A-Z0-9_]*$/,
MIN_SECRET_ID_LENGTH: 1,
MIN_ENV_VAR_LENGTH: 1,
MIN_DOMAIN_LENGTH: 3,
DOMAIN_REGEX: /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i,
ACTION_REGEX: /^[a-z_]+$/,
URL_REGEX: /^https?:\/\/([\w\-]+\.)+[\w\-]+(:\d+)?(\/[\w\-._~:/?#[\]@!$&'()*+,;=]*)?$/,
HEADER_NAME_REGEX: /^[a-zA-Z0-9\-_]+$/,
// File paths
DEFAULT_CONFIG_FILE: "vault.config.json",
ENV_VAULT_CONFIG: "VAULT_CONFIG",
DEFAULT_POLICIES_DIR: "policies",
DEFAULT_POLICIES_FILE: "policies.json",
DEFAULT_MAPPINGS_FILE: "mappings.json",
DEFAULT_AUDIT_DIR: "audit",
// Log levels
LOG_LEVEL_ERROR: "ERROR",
LOG_LEVEL_WARN: "WARN",
LOG_LEVEL_INFO: "INFO",
LOG_LEVEL_DEBUG: "DEBUG",
DEFAULT_LOG_LEVEL: "INFO",
// Error codes
ERROR_CODE_UNKNOWN_SECRET: "unknown_secret",
ERROR_CODE_FORBIDDEN_DOMAIN: "forbidden_domain",
ERROR_CODE_FORBIDDEN_ACTION: "forbidden_action",
ERROR_CODE_TTL_EXPIRED: "ttl_expired",
ERROR_CODE_RATE_LIMITED: "rate_limited",
ERROR_CODE_PAYLOAD_TOO_LARGE: "payload_too_large",
ERROR_CODE_MISSING_ENV: "missing_env",
ERROR_CODE_TIMEOUT: "timeout",
ERROR_CODE_INVALID_REQUEST: "invalid_request",
ERROR_CODE_INVALID_POLICY: "invalid_policy",
ERROR_CODE_POLICY_EXPIRED: "policy_expired",
ERROR_CODE_NO_POLICY: "no_policy",
ERROR_CODE_POLICIES_NOT_LOADED: "policies_not_loaded",
ERROR_CODE_UNKNOWN_TOOL: "unknown_tool",
ERROR_CODE_DUPLICATE_TOOL: "duplicate_tool",
ERROR_CODE_INVALID_METHOD: "invalid_method",
ERROR_CODE_INVALID_INJECTION_TYPE: "invalid_injection_type",
ERROR_CODE_INVALID_URL: "invalid_url",
ERROR_CODE_INVALID_HEADERS: "invalid_headers",
ERROR_CODE_EXECUTION_FAILED: "execution_failed",
// Pagination
DEFAULT_PAGE_NUMBER: 1,
DEFAULT_PAGE_SIZE: 50,
// Function size limits (for code quality)
MAX_FUNCTION_LINES: 20,
// Cache settings
CACHE_TTL_MS: 3e5,
// 5 minutes
// Header allowlist for responses
ALLOWED_RESPONSE_HEADERS: [
"content-type",
"content-length",
"date",
"etag",
"cache-control",
"x-request-id",
"x-rate-limit-remaining",
"x-rate-limit-reset"
],
// Supported HTTP methods
SUPPORTED_HTTP_METHODS: ["GET", "POST"],
// Supported actions for policies
SUPPORTED_ACTIONS: ["http_get", "http_post"],
// HTTP header names
HEADER_AUTHORIZATION: "authorization",
HEADER_USER_AGENT: "user-agent",
HEADER_CONTENT_TYPE: "content-type",
// HTTP fetch options
FETCH_REDIRECT_MODE: "manual",
// Default values
DEFAULT_TIMEZONE: "UTC",
DEFAULT_ENCODING: "utf-8",
// Security defaults (deny-by-default posture)
DEFAULT_ALLOW_DOMAINS: [],
DEFAULT_ALLOW_ACTIONS: [],
DEFAULT_MAX_REQUEST_SIZE: 1024 * 1024,
// 1MB
DEFAULT_REQUIRE_HTTPS: true,
DEFAULT_ALLOW_REDIRECTS: false,
DEFAULT_SANITIZE_RESPONSES: true,
DEFAULT_AUDIT_ALL_REQUESTS: true,
// Environment variable prefix
ENV_PREFIX: "MCP_VAULT_",
// Exit codes
EXIT_CODE_SUCCESS: 0,
EXIT_CODE_ERROR: 1,
EXIT_CODE_INVALID_CONFIG: 2,
EXIT_CODE_MISSING_DEPENDENCY: 3,
// File system error codes
FS_ERROR_ENOENT: "ENOENT",
// Unit conversion constants
BYTES_PER_MB: 1024 * 1024,
MS_PER_DAY: 24 * 60 * 60 * 1e3,
// Numeric defaults
ZERO_COUNT: 0,
// Line ending pattern
LINE_ENDING_PATTERN: /\r?\n/,
// Process signals
SIGNAL_INT: "SIGINT",
SIGNAL_TERM: "SIGTERM",
// Process arguments
PROCESS_ARGV_FILE_INDEX: 1,
// Environment variables
ENV_TEST_SECRET_MAPPINGS: "TEST_SECRET_MAPPINGS",
// URL schemes
FILE_URL_SCHEME: "file://",
INDEX_JS_SUFFIX: "/index.js",
CLI_DOCTOR_MODULE: "./cli/doctor.js",
// Logging patterns
STACK_TRACE_PATTERN: "\n at ",
EMPTY_STRING_FALLBACK: "",
// Exception field names
EXCEPTION_FIELD_NAMES: ["description", "environment"],
// Sensitive key patterns for logging
SENSITIVE_KEY_PATTERNS: ["SECRET", "KEY", "TOKEN", "PASSWORD"],
// JSON Schema metadata
JSON_SCHEMA_DRAFT: "http://json-schema.org/draft-07/schema#",
JSON_SCHEMA_ID_PREFIX: "mcp-secrets-vault",
JSON_SCHEMA_VERSION: "v1.0.0",
JSON_SCHEMA_FILENAME: "vault.config.schema.json",
JSON_SCHEMA_NAME: "VaultConfig",
// ANSI color codes for terminal output
ANSI_RESET: "\x1B[0m",
ANSI_RED: "\x1B[31m",
ANSI_GREEN: "\x1B[32m",
ANSI_YELLOW: "\x1B[33m",
ANSI_BLUE: "\x1B[34m",
ANSI_CYAN: "\x1B[36m",
ANSI_GRAY: "\x1B[90m",
// CLI formatting
CLI_SEPARATOR_LINE: "\u2550".repeat(60),
CLI_SEPARATOR_LINE_THIN: "\u2501".repeat(60),
// CLI status values
CLI_STATUS_OK: "OK",
CLI_STATUS_WARN: "WARN",
CLI_STATUS_ERROR: "ERROR",
// CLI arguments
CLI_ARG_HELP_LONG: "--help",
CLI_ARG_HELP_SHORT: "-h",
// Content types
CONTENT_TYPE_JSON: "application/json",
// File encoding
UTF8_ENCODING: "utf-8",
// JSON formatting
JSON_INDENT_SIZE: 2,
// Zod schema configuration
ZOD_REF_STRATEGY_NONE: "none",
// Redaction patterns for logging
REDACT_ENV_PATTERN: /\b[A-Z][A-Z0-9_]*_(PASSWORD|SECRET|KEY|TOKEN|CREDENTIAL|API)\b/g,
REDACT_GENERIC_TOKEN: /\b[a-zA-Z0-9_-]{32,}\b/g,
REDACT_CONTROL_CHARS: /[\x00-\x1F\x7F]/g,
// Sensitive field names for logging
SENSITIVE_FIELD_NAMES: [
"password",
"secret",
"token",
"key",
"auth",
"authorization",
"apikey",
"api_key",
"access_token",
"refresh_token",
"private_key",
"client_secret",
"webhook_secret",
"signing_key",
"encryption_key",
"database_url",
"connection_string",
"env",
"envvar",
"environment"
],
// Doctor CLI thresholds
DOCTOR_RATE_LIMIT_MIN_REQUESTS: 10,
DOCTOR_RATE_LIMIT_MAX_REQUESTS: 1e4,
DOCTOR_RATE_LIMIT_MIN_WINDOW: 60,
DOCTOR_RATE_LIMIT_MAX_WINDOW: 86400,
DOCTOR_FILE_SIZE_WARN_MB: 500,
DOCTOR_FILE_AGE_WARN_DAYS: 90,
DOCTOR_POLICY_EXPIRING_SOON_DAYS: 7,
DOCTOR_MIN_DOMAIN_COUNT: 1,
DOCTOR_MAX_DOMAIN_COUNT: 100,
DOCTOR_SUSPICIOUS_DOMAIN_PATTERNS: ["localhost", "127.0.0.1", "0.0.0.0", "test.com", "example.com"]
};
// src/constants/text-constants.ts
var TEXT = {
// Error messages
ERROR_UNKNOWN_SECRET: "Secret not found",
ERROR_FORBIDDEN_DOMAIN: "Domain not allowed by policy",
ERROR_FORBIDDEN_ACTION: "Action not allowed by policy",
ERROR_TTL_EXPIRED: "Secret has expired",
ERROR_RATE_LIMITED: "Rate limit exceeded",
ERROR_PAYLOAD_TOO_LARGE: "Payload size exceeds limit",
ERROR_MISSING_ENV: "Environment variable not set",
ERROR_TIMEOUT: "Request timed out",
ERROR_INVALID_REQUEST: "Invalid request format",
ERROR_POLICY_NOT_FOUND: "Policy not found for secret",
ERROR_POLICY_EXPIRED: "Policy has expired",
ERROR_NO_MAPPING: "No mapping found for secret",
ERROR_INVALID_ACTION: "Invalid action format",
ERROR_INVALID_DOMAIN: "Invalid domain format",
ERROR_INVALID_CONFIG: "Invalid configuration",
ERROR_DUPLICATE_SECRET_ID: "Duplicate secret ID",
ERROR_INVALID_SECRET_ID_FORMAT: "Invalid secret ID format",
ERROR_INVALID_ENV_VAR_FORMAT: "Invalid environment variable format",
ERROR_SECRET_ID_TOO_LONG: "Secret ID exceeds maximum length",
ERROR_SECRET_ID_TOO_SHORT: "Secret ID is too short",
ERROR_ENV_VAR_TOO_SHORT: "Environment variable name is too short",
ERROR_INVALID_POLICY_STRUCTURE: "Invalid policy structure",
ERROR_MISSING_POLICY_FIELD: "Required policy field missing",
ERROR_INVALID_ALLOWED_ACTIONS: "Invalid allowed actions",
ERROR_INVALID_ALLOWED_DOMAINS: "Invalid allowed domains",
ERROR_INVALID_RATE_LIMIT: "Invalid rate limit configuration",
ERROR_INVALID_EXPIRATION: "Invalid expiration date",
ERROR_EMPTY_ALLOWED_ACTIONS: "Allowed actions cannot be empty",
ERROR_EMPTY_ALLOWED_DOMAINS: "Allowed domains cannot be empty",
ERROR_DUPLICATE_POLICY: "Duplicate policy for secret ID",
ERROR_UNSUPPORTED_ACTION: "Unsupported action",
ERROR_POLICIES_NOT_LOADED: "Policies not loaded",
ERROR_NETWORK_ERROR: "Network request failed",
ERROR_EMPTY_HEADER_NAME: "Header name cannot be empty",
ERROR_UNKNOWN_TOOL: "Unknown tool requested",
ERROR_TOOL_EXECUTION_FAILED: "Tool execution failed",
ERROR_DUPLICATE_TOOL: "Duplicate tool registration",
ERROR_INVALID_METHOD: "Invalid HTTP method",
ERROR_INVALID_INJECTION_TYPE: "Invalid injection type",
ERROR_INVALID_URL: "Invalid URL format",
ERROR_INVALID_HEADERS: "Invalid headers format",
ERROR_EXECUTION_FAILED: "Execution failed",
ERROR_DOMAIN_TOO_SHORT: "Domain name is too short",
ERROR_DOMAIN_TOO_LONG: "Domain name exceeds maximum length",
ERROR_INVALID_DOMAIN_FORMAT: "Invalid domain format",
ERROR_URL_TOO_LONG: "URL exceeds maximum length",
ERROR_HTTPS_REQUIRED: "HTTPS is required for security",
ERROR_INVALID_URL_FORMAT: "Invalid URL format",
ERROR_ACTION_TOO_LONG: "Action exceeds maximum length",
ERROR_INVALID_ACTION_FORMAT: "Invalid action format",
ERROR_HEADER_NAME_TOO_LONG: "Header name exceeds maximum length",
ERROR_INVALID_HEADER_NAME_FORMAT: "Invalid header name format",
ERROR_HEADER_VALUE_TOO_LONG: "Header value exceeds maximum length",
// Success messages
SUCCESS_REQUEST_COMPLETED: "Request completed successfully",
SUCCESS_POLICY_LOADED: "Policy loaded successfully",
SUCCESS_AUDIT_WRITTEN: "Audit entry written",
// Field names
FIELD_SECRET_ID: "secretId",
FIELD_ACTION: "action",
FIELD_TYPE: "type",
FIELD_URL: "url",
FIELD_INJECTION_TYPE_LOWER: "injectionType",
FIELD_DOMAIN: "domain",
FIELD_TIMESTAMP: "timestamp",
FIELD_OUTCOME: "outcome",
FIELD_REASON: "reason",
FIELD_ENV_VAR: "envVar",
FIELD_ALLOWED_ACTIONS: "allowedActions",
FIELD_ALLOWED_DOMAINS: "allowedDomains",
FIELD_RATE_LIMIT: "rateLimit",
FIELD_EXPIRES_AT: "expiresAt",
FIELD_REQUESTS: "requests",
FIELD_WINDOW_SECONDS: "windowSeconds",
FIELD_METHOD: "method",
FIELD_PAGE: "page",
FIELD_PAGE_SIZE: "pageSize",
FIELD_TOTAL_COUNT: "totalCount",
FIELD_HAS_MORE: "hasMore",
FIELD_START_TIME: "startTime",
FIELD_END_TIME: "endTime",
FIELD_SECRETS: "secrets",
FIELD_AVAILABLE: "available",
FIELD_DESCRIPTION: "description",
FIELD_ENTRIES: "entries",
FIELD_ERROR: "error",
FIELD_CODE: "code",
FIELD_MESSAGE: "message",
FIELD_HEADER_NAME: "headerName",
FIELD_HEADER_VALUE: "headerValue",
// Log messages
LOG_SERVER_STARTED: "MCP server started",
LOG_SERVER_STOPPED: "MCP server stopped",
LOG_SHUTDOWN_HANDLER_ERROR: "Shutdown handler error",
LOG_PROCESSING_REQUEST: "Processing request",
LOG_REQUEST_DENIED: "Request denied",
LOG_REQUEST_ALLOWED: "Request allowed",
LOG_LOADING_POLICIES: "Loading policies",
LOG_LOADING_MAPPINGS: "Loading ENV mappings",
// Audit outcomes
AUDIT_OUTCOME_SUCCESS: "success",
AUDIT_OUTCOME_DENIED: "denied",
AUDIT_OUTCOME_ERROR: "error",
// HTTP methods
HTTP_METHOD_GET: "http_get",
HTTP_METHOD_POST: "http_post",
HTTP_VERB_GET: "GET",
HTTP_VERB_POST: "POST",
// Injection types
INJECTION_TYPE_BEARER: "bearer",
INJECTION_TYPE_HEADER: "header",
// Header names
AUTHORIZATION_HEADER: "Authorization",
SECRET_HEADER_NAME: "X-Secret",
// Response messages
RESPONSE_NO_SECRETS: "No secrets configured",
RESPONSE_POLICY_DESCRIPTION: "Policy for secret",
RESPONSE_AUDIT_ENTRIES: "Audit entries",
// Validation messages
VALIDATION_REQUIRED_FIELD: "Required field missing",
VALIDATION_INVALID_TYPE: "Invalid field type",
VALIDATION_INVALID_FORMAT: "Invalid format",
// Tool names
TOOL_DISCOVER: "discover_secrets",
TOOL_DESCRIBE: "describe_policy",
TOOL_USE: "use_secret",
TOOL_AUDIT: "query_audit",
// Tool descriptions
TOOL_DESC_DISCOVER: "List available secrets",
TOOL_DESC_DESCRIBE: "Get policy details for a secret",
TOOL_DESC_USE: "Use a secret to perform an action",
TOOL_DESC_AUDIT: "Query audit log entries",
TOOL_DISCOVER_DESCRIPTION: "List all available secret identifiers and their metadata",
TOOL_AUDIT_DESCRIPTION: "Query audit log entries with filtering and pagination",
// Input field descriptions
INPUT_DESC_SECRET_ID: "The ID of the secret to describe policy for",
INPUT_DESC_USE_SECRET_ID: "The ID of the secret to use",
INPUT_DESC_ACTION: "The action to perform with the secret",
INPUT_DESC_ACTION_TYPE: "The type of action to perform",
INPUT_DESC_ACTION_URL: "The URL to make the request to",
INPUT_DESC_ACTION_HEADERS: "Optional headers for the request",
INPUT_DESC_ACTION_BODY: "Optional body for POST requests",
INPUT_DESC_INJECTION_TYPE: "How to inject the secret (bearer or header)",
// Schema types
SCHEMA_TYPE_OBJECT: "object",
SCHEMA_TYPE_STRING: "string",
// Schema field names (JSON Schema standard keywords)
SCHEMA_PROPERTIES: "properties",
SCHEMA_REQUIRED: "required",
SCHEMA_TYPE: "type",
// Schema required arrays
SCHEMA_REQUIRED_ACTION: ["type", "url"],
SCHEMA_REQUIRED_USE_SECRET: ["secretId", "action"],
// Field values
FIELD_VALUE_UNKNOWN: "unknown",
FIELD_VALUE_INVALID: "invalid",
// Error message defaults
ERROR_UNKNOWN: "Unknown error",
// MCP Test messages
TEST_MCP_HANDSHAKE: "Testing MCP protocol handshake",
TEST_MCP_TOOL_DISCOVERY: "Testing MCP tool discovery",
TEST_MCP_TOOL_EXECUTION: "Testing MCP tool execution",
TEST_MCP_ERROR_HANDLING: "Testing MCP error handling",
TEST_MCP_CONCURRENT_REQUESTS: "Testing MCP concurrent requests",
TEST_MCP_PAYLOAD_SNAPSHOT: "Testing MCP payload snapshot",
TEST_MCP_PROTOCOL_FLOW: "Testing MCP protocol flow",
TEST_MCP_RATE_LIMITING: "Testing MCP rate limiting",
TEST_MCP_AUDIT_TRAIL: "Testing MCP audit trail verification",
// Test descriptions for snapshots
SNAPSHOT_DESC_TOOL_METADATA: "Tool metadata snapshot",
SNAPSHOT_DESC_SUCCESS_RESPONSE: "Successful response snapshot",
SNAPSHOT_DESC_ERROR_RESPONSE: "Error response snapshot",
SNAPSHOT_DESC_EMPTY_RESPONSE: "Empty response snapshot",
SNAPSHOT_DESC_PAGINATION: "Pagination snapshot",
SNAPSHOT_DESC_COMPLEX_OBJECT: "Complex object snapshot",
// Schema generation messages
SCHEMA_GENERATION_SUCCESS: "JSON Schema generated successfully",
SCHEMA_GENERATION_FAILED: "Failed to generate JSON Schema",
SCHEMA_REMINDER_EXACT_FQDN: "Remember: All domains must be exact FQDNs - wildcards are not allowed",
SCHEMA_TITLE: "MCP Secrets Vault Configuration",
SCHEMA_DESCRIPTION: "Configuration schema for MCP Secrets Vault. All domains must be exact FQDNs - wildcards are not allowed.",
SCHEMA_DOMAIN_ITEM_DESC: 'Exact FQDN only (e.g., "api.example.com"). Wildcards (*, ?, []) are NOT allowed.',
SCHEMA_DOMAIN_LIST_DESC: "List of exact FQDNs that can be accessed with this secret. No wildcards allowed.",
// Doctor CLI messages
DOCTOR_HEADER: "MCP Secrets Vault - System Diagnostics",
DOCTOR_CHECKING_CONFIG: "Checking configuration...",
DOCTOR_CONFIG_VALID: "Configuration is valid",
DOCTOR_CONFIG_INVALID: "Configuration is invalid",
DOCTOR_CONFIG_NOT_FOUND: "Configuration file not found",
DOCTOR_CHECKING_ENV_VARS: "Checking environment variables...",
DOCTOR_ENV_VAR_SET: "Environment variable is set",
DOCTOR_ENV_VAR_NOT_SET: "Environment variable is not set",
DOCTOR_CHECKING_DOMAINS: "Checking domain configurations...",
DOCTOR_DOMAIN_VALID: "Domain configuration is valid",
DOCTOR_DOMAIN_DUPLICATE: "Duplicate domain detected",
DOCTOR_DOMAIN_SUSPICIOUS: "Suspicious domain pattern detected",
DOCTOR_CHECKING_LIMITS: "Checking rate limits...",
DOCTOR_LIMIT_REASONABLE: "Rate limit is reasonable",
DOCTOR_LIMIT_TOO_HIGH: "Rate limit may be too high",
DOCTOR_LIMIT_TOO_LOW: "Rate limit may be too low",
DOCTOR_CHECK_PASSED: "All checks passed",
DOCTOR_CHECK_WARNINGS: "Checks completed with warnings",
DOCTOR_CHECK_ERRORS: "Checks completed with errors",
DOCTOR_STATUS_OK: "OK",
DOCTOR_STATUS_WARN: "WARN",
DOCTOR_STATUS_ERROR: "ERROR",
DOCTOR_SECRET_NOT_IN_ENV: "Secret ID mapped but ENV variable not set",
DOCTOR_POLICY_WITHOUT_MAPPING: "Policy exists but no mapping found",
DOCTOR_EXPIRED_POLICY: "Policy has already expired",
DOCTOR_EXPIRING_SOON: "Policy expires soon",
DOCTOR_NO_POLICIES: "No policies defined for mapped secrets",
DOCTOR_AUDIT_DIR_NOT_WRITABLE: "Audit directory is not writable",
DOCTOR_AUDIT_DIR_CREATED: "Audit directory created",
DOCTOR_FILE_SIZE_WARNING: "Max file size may be too large",
DOCTOR_FILE_AGE_WARNING: "Max file age may be too long",
DOCTOR_SUMMARY_HEADER: "Diagnostic Summary",
DOCTOR_TOTAL_CHECKS: "Total checks",
DOCTOR_PASSED_CHECKS: "Passed",
DOCTOR_WARNINGS: "Warnings",
DOCTOR_ERRORS: "Errors",
DOCTOR_RUNNING_CHECK: "Running check",
DOCTOR_HELP_TEXT: "Use this tool to diagnose configuration and setup issues",
// Index.ts
DOCTOR_CLI_FAILED: "Doctor CLI failed",
// Doctor check names
DOCTOR_CHECK_CONFIG_SCHEMA: "Configuration Schema",
DOCTOR_CHECK_ENV_VARS: "Environment Variables",
DOCTOR_CHECK_DOMAINS: "Domain Configuration",
DOCTOR_CHECK_RATE_LIMITS: "Rate Limits",
DOCTOR_CHECK_AUDIT_DIR: "Audit Directory",
DOCTOR_CHECK_POLICY_STATUS: "Policy Status",
// Doctor messages (ALL literals must be replaced)
DOCTOR_ENV_ALL_SET: "All {count} secrets have configured environment variables",
DOCTOR_ENV_NONE_SET: "No environment variables are set",
DOCTOR_DOMAIN_HAS_ISSUES: "Domain configuration has issues",
DOCTOR_LIMITS_NEED_ADJUSTMENT: "Some rate limits may need adjustment",
DOCTOR_AUDIT_WRITABLE_WITH_WARNINGS: "Audit directory is writable but has warnings",
DOCTOR_AUDIT_WRITABLE: "Audit directory is writable: {path}",
DOCTOR_POLICIES_ALL_VALID: "All policies are valid and properly mapped",
DOCTOR_POLICIES_NEED_ATTENTION: "Policy configuration needs attention",
DOCTOR_DOMAIN_COUNT_INFO: "Total unique domains: {count}",
DOCTOR_LIMIT_WINDOW_SHORT: "Window too short ({seconds}s)",
DOCTOR_LIMIT_WINDOW_LONG: "Window too long ({seconds}s)",
DOCTOR_NO_DOMAINS_CONFIGURED: "{secretId}: No domains configured",
DOCTOR_TOO_MANY_DOMAINS: "{secretId}: Too many domains ({count})",
DOCTOR_ANALYZING: "Analyzing: {path}",
DOCTOR_VERSION_INFO: "Version: {version}",
DOCTOR_MAPPINGS_INFO: "Mappings: {count}",
DOCTOR_POLICIES_INFO: "Policies: {count}",
// Config validator messages
CONFIG_VALIDATOR_VERSION_MUST_BE: "Must be {version} (current schema version)",
CONFIG_VALIDATOR_WILDCARDS_NOT_ALLOWED: "Wildcards not allowed. Use exact FQDNs only (e.g., 'api.example.com')",
CONFIG_VALIDATOR_VALIDATION_FAILED: "Configuration validation failed:",
CONFIG_VALIDATOR_DUPLICATE_SECRET: "Duplicate secret ID found: {id}",
CONFIG_VALIDATOR_DUPLICATE_POLICY: "Duplicate policy for secret ID: {id}",
CONFIG_VALIDATOR_DOMAIN_MUST_BE_FQDN: "Domain must be an exact FQDN (no wildcards). Example: api.example.com",
CONFIG_VALIDATOR_INVALID_ACTION: "Invalid action. Supported actions: {actions}",
CONFIG_VALIDATOR_NOTE_DOMAINS: "Note: All domains must be exact FQDNs. Wildcards (*, ?, []) are not allowed.",
CONFIG_VALIDATOR_ENV_VAR_FORMAT: "Environment variable must be uppercase with underscores",
// Validate-config messages (if needed for consistency)
VALIDATE_HEADER: "MCP Secrets Vault - Configuration Validator",
VALIDATE_CHECKING_FILE: "Validating: {path}",
VALIDATE_FILE_EXISTS: "File exists",
VALIDATE_FILE_NOT_FOUND: "File not found",
VALIDATE_LOADING: "Loading configuration...",
VALIDATE_VALIDATING: "Validating structure...",
VALIDATE_STRUCTURE_VALID: "Configuration structure is valid",
// CLI status icons
CLI_ICON_SUCCESS: "\u2705",
CLI_ICON_WARNING: "\u26A0\uFE0F ",
CLI_ICON_ERROR: "\u274C",
// Index.ts messages
INDEX_CONFIG_LOADED: "Configuration loaded: {mappings} mappings, {policies} policies",
INDEX_CONFIG_LOAD_FAILED: "Failed to load configuration: {error}",
INDEX_SERVER_START_FAILED: "Failed to start server",
// CLI commands
CLI_COMMAND_DOCTOR: "doctor",
// Doctor CLI help text
DOCTOR_HELP_HEADER: "MCP Secrets Vault - Doctor CLI",
DOCTOR_HELP_DESCRIPTION: "Description:",
DOCTOR_HELP_USAGE: "Usage:",
DOCTOR_HELP_ARGUMENTS: "Arguments:",
DOCTOR_HELP_EXAMPLES: "Examples:",
DOCTOR_HELP_EXIT_CODES: "Exit Codes:",
DOCTOR_HELP_CHECKS: "Checks Performed:",
DOCTOR_HELP_CONFIG_ARG: "config-file Path to configuration file (default: vault.config.json)",
DOCTOR_HELP_EXAMPLE_DEFAULT: "doctor # Check default vault.config.json",
DOCTOR_HELP_EXAMPLE_CUSTOM: "doctor my-config.json # Check specific file",
DOCTOR_HELP_EXAMPLE_HELP: "doctor --help # Show this help message",
DOCTOR_HELP_EXIT_0: "0 All checks passed or only warnings",
DOCTOR_HELP_EXIT_2: "2 Critical errors found",
DOCTOR_HELP_CHECK_LIST: "\u2022 Configuration schema validity\n \u2022 Environment variable existence\n \u2022 Domain configuration coherence\n \u2022 Rate limit reasonableness\n \u2022 Audit directory accessibility\n \u2022 Policy expiration status",
DOCTOR_FILE_NOT_FOUND: "File not found: {path}",
DOCTOR_FATAL_ERROR: "Fatal error:",
// Validate-config CLI messages
VALIDATE_CONFIG_HEADER: "MCP Secrets Vault - Configuration Validator",
VALIDATE_CONFIG_VALIDATING: "Validating:",
VALIDATE_CONFIG_FILE_EXISTS: "File exists",
VALIDATE_CONFIG_FILE_NOT_FOUND: "File not found",
VALIDATE_CONFIG_CREATE_TIP: "Tip:",
VALIDATE_CONFIG_CREATE_FILE_MSG: "Create a vault.config.json file with the following structure:",
VALIDATE_CONFIG_LOADING: "Loading configuration...",
VALIDATE_CONFIG_VALIDATING_STRUCTURE: "Validating structure...",
VALIDATE_CONFIG_STRUCTURE_VALID: "Configuration structure is valid",
VALIDATE_CONFIG_SUMMARY_HEADER: "Configuration Summary",
VALIDATE_CONFIG_VERSION_LABEL: "Version",
VALIDATE_CONFIG_MAPPINGS_LABEL: "Mappings",
VALIDATE_CONFIG_POLICIES_LABEL: "Policies",
VALIDATE_CONFIG_SECRET_MAPPINGS_HEADER: "Secret Mappings:",
VALIDATE_CONFIG_ACCESS_POLICIES_HEADER: "Access Policies:",
VALIDATE_CONFIG_ACTIONS_LABEL: "Actions:",
VALIDATE_CONFIG_DOMAINS_LABEL: "Domains:",
VALIDATE_CONFIG_EXACT_FQDNS_SUFFIX: "exact FQDNs",
VALIDATE_CONFIG_RATE_LIMIT_FORMAT: "{requests} requests per {windowSeconds}s",
VALIDATE_CONFIG_RATE_LIMIT_LABEL: "Rate Limit:",
VALIDATE_CONFIG_EXPIRES_LABEL: "Expires:",
VALIDATE_CONFIG_CHECKING_DUPLICATES: "Checking for duplicates...",
VALIDATE_CONFIG_NO_DUPLICATES: "No duplicate secret IDs found",
VALIDATE_CONFIG_SETTINGS_HEADER: "Settings:",
VALIDATE_CONFIG_AUDIT_DIR_LABEL: "Audit Directory",
VALIDATE_CONFIG_MAX_FILE_SIZE_LABEL: "Max File Size",
VALIDATE_CONFIG_MAX_FILE_SIZE_SUFFIX: "MB",
VALIDATE_CONFIG_MAX_FILE_AGE_LABEL: "Max File Age",
VALIDATE_CONFIG_MAX_FILE_AGE_SUFFIX: "days",
VALIDATE_CONFIG_DEFAULT_RATE_LIMIT_LABEL: "Default Rate Limit",
VALIDATE_CONFIG_SECURITY_NOTES_HEADER: "Security Notes",
VALIDATE_CONFIG_IMPORTANT_LABEL: "\u26A0\uFE0F Important:",
VALIDATE_CONFIG_NOTE_EXACT_FQDNS: "\u2022 All domains must be exact FQDNs (no wildcards)",
VALIDATE_CONFIG_NOTE_ENV_NOT_CHECKED: "\u2022 Environment variables are NOT checked by this validator",
VALIDATE_CONFIG_NOTE_USE_DOCTOR: "\u2022 Use the doctor CLI (Task-17) to verify environment setup",
VALIDATE_CONFIG_NOTE_DENY_BY_DEFAULT: "\u2022 Configuration follows deny-by-default security posture",
VALIDATE_CONFIG_SUCCESS: "Configuration is valid!",
VALIDATE_CONFIG_VALIDATION_FAILED: "Validation failed:",
VALIDATE_CONFIG_WILDCARDS_TIP: "List each domain explicitly. For example:",
VALIDATE_CONFIG_WILDCARDS_INSTEAD_OF: 'Instead of: "*.example.com"',
VALIDATE_CONFIG_WILDCARDS_USE: 'Use: ["api.example.com", "www.example.com", "app.example.com"]',
VALIDATE_CONFIG_JSON_TIP: "Check your JSON syntax with a JSON validator",
VALIDATE_CONFIG_HELP_HEADER: "MCP Secrets Vault - Configuration Validator",
VALIDATE_CONFIG_HELP_USAGE_LABEL: "Usage:",
VALIDATE_CONFIG_HELP_USAGE: "validate-config [config-file]",
VALIDATE_CONFIG_HELP_ARGUMENTS_LABEL: "Arguments:",
VALIDATE_CONFIG_HELP_CONFIG_FILE: "config-file Path to configuration file (default: vault.config.json)",
VALIDATE_CONFIG_HELP_EXAMPLES_LABEL: "Examples:",
VALIDATE_CONFIG_HELP_EXAMPLE_DEFAULT: "validate-config # Validate default vault.config.json",
VALIDATE_CONFIG_HELP_EXAMPLE_CUSTOM: "validate-config my-config.json # Validate specific file",
VALIDATE_CONFIG_HELP_EXAMPLE_HELP: "validate-config --help # Show this help message",
VALIDATE_CONFIG_HELP_EXIT_CODES_LABEL: "Exit Codes:",
VALIDATE_CONFIG_HELP_EXIT_0: "0 Configuration is valid",
VALIDATE_CONFIG_HELP_EXIT_2: "2 Configuration is invalid",
VALIDATE_CONFIG_JSON_EXAMPLE: '{\n "version": "1.0.0",\n "mappings": [],\n "policies": [],\n "settings": {}\n}',
VALIDATE_CONFIG_SECURITY_NOTE: "Security Notes:\n \u2022 Never commit actual secrets to version control\n \u2022 Use environment variables for sensitive values\n \u2022 Restrict file permissions appropriately",
VALIDATE_CONFIG_HELP_TIP: "Tip: Run with DEBUG=* for detailed validation output",
VALIDATE_CONFIG_JSON_ERROR: ": Invalid JSON in {path}",
// Tool query audit descriptions
TOOL_QUERY_AUDIT_SECRET_DESC: "Optional: Filter by secret ID",
TOOL_QUERY_AUDIT_DOMAIN_DESC: "Optional: Filter by domain",
TOOL_QUERY_AUDIT_OUTCOME_DESC: "Optional: Filter by outcome (success, denied, error)",
TOOL_QUERY_AUDIT_START_DESC: "Optional: Start time (ISO 8601)",
TOOL_QUERY_AUDIT_END_DESC: "Optional: End time (ISO 8601)",
TOOL_QUERY_AUDIT_PAGE_DESC: "Optional: Page number (default: 1)",
TOOL_QUERY_AUDIT_PAGE_SIZE_DESC: "Optional: Page size (default: 50, max: 500)",
// Validation messages
VALIDATION_NON_EMPTY_STRING: "{fieldName} must be a non-empty string"
};
// src/utils/format.ts
function fmt(template, params) {
return template.replace(
/{(\w+)}/g,
(_, key) => params[key]?.toString() || `{${key}}`
);
}
// src/utils/security.ts
function truncateText(text, maxLength) {
if (text.length <= maxLength) {
return text;
}
return text.substring(0, maxLength) + CONFIG.RESPONSE_TRUNCATION_MESSAGE;
}
function applyBasicRedaction(text) {
return text.replace(CONFIG.REDACT_URL_AUTH_PATTERN, CONFIG.SANITIZE_REPLACEMENT).replace(CONFIG.REDACT_JWT_PATTERN, CONFIG.SANITIZE_REPLACEMENT).replace(CONFIG.REDACT_BEARER_TOKEN_PATTERN, CONFIG.SANITIZE_REPLACEMENT).replace(CONFIG.REDACT_ENV_VAR_PATTERN, CONFIG.SANITIZE_REPLACEMENT).replace(CONFIG.REDACT_KEY_VALUE_PATTERN, (_match, key) => `${key}=${CONFIG.SANITIZE_REPLACEMENT}`);
}
function applyApiKeyRedaction(text) {
let result = text;
for (const pattern of CONFIG.REDACT_API_KEY_PATTERNS) {
result = result.replace(pattern, CONFIG.SANITIZE_REPLACEMENT);
}
return result;
}
function applyTokenRedaction(text) {
return text.replace(/\b[a-zA-Z0-9_-]{32,}\b/g, (match) => {
if (/^[a-zA-Z]+$/.test(match)) return match;
if (/[a-zA-Z]/.test(match) && /[0-9_-]/.test(match)) {
return CONFIG.SANITIZE_REPLACEMENT;
}
return match;
});
}
function redactSensitiveValue(value) {
if (typeof value !== "string") {
return String(value);
}
let redacted = applyBasicRedaction(value);
redacted = applyApiKeyRedaction(redacted);
redacted = applyTokenRedaction(redacted);
return redacted;
}
function sanitizeForOutput(text, maxLength = CONFIG.RESPONSE_MAX_BODY_LENGTH) {
const truncated = truncateText(text, maxLength);
return redactSensitiveValue(truncated);
}
function sanitizeUrl(url) {
try {
const urlObj = new URL(url);
urlObj.username = "";
urlObj.password = "";
const params = urlObj.searchParams;
for (const key of Array.from(params.keys())) {
if (CONFIG.SANITIZE_SECRET_PATTERN.test(key)) {
params.set(key, CONFIG.SANITIZE_REPLACEMENT);
}
}
return urlObj.toString();
} catch {
return redactSensitiveValue(url);
}
}
function isSensitiveKey(key) {
const lowerKey = key.toLowerCase();
if (lowerKey === "secretid" || lowerKey === "secret_id" || lowerKey === "secrets") {
return false;
}
return CONFIG.SENSITIVE_FIELD_NAMES.some(
(sensitive) => lowerKey === sensitive || lowerKey.includes(sensitive)
);
}
function sanitizeObjectProperties(obj, maxDepth) {
const sanitized = {};
for (const [key, value] of Object.entries(obj)) {
if (isSensitiveKey(key)) {
sanitized[key] = CONFIG.SANITIZE_REPLACEMENT;
} else if (key === "url" && typeof value === "string") {
sanitized[key] = sanitizeUrl(value);
} else {
sanitized[key] = deepSanitizeObject(value, maxDepth - 1);
}
}
return sanitized;
}
function deepSanitizeObject(obj, maxDepth = 10) {
if (maxDepth <= 0 || obj === null || obj === void 0) {
return maxDepth <= 0 ? CONFIG.SANITIZE_REPLACEMENT : obj;
}
if (typeof obj === "string") {
return redactSensitiveValue(obj);
}
if (typeof obj === "number" || typeof obj === "boolean") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => deepSanitizeObject(item, maxDepth - 1));
}
if (typeof obj === "object") {
return sanitizeObjectProperties(obj, maxDepth);
}
return CONFIG.SANITIZE_REPLACEMENT;
}
function deepFreeze(obj) {
if (obj === null || obj === void 0) {
return obj;
}
if (typeof obj !== "object") {
return obj;
}
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach((prop) => {
const value = obj[prop];
if (value && typeof value === "object") {
deepFreeze(value);
}
});
return obj;
}
function sanitizeError(error) {
const err = error;
if (err?.stack) {
const firstLine = String(err.stack).split("\n")[0] || "";
return redactSensitiveValue(firstLine);
}
if (err?.message && typeof err.message === "string") {
return redactSensitiveValue(err.message);
}
if (typeof error === "string") {
return redactSensitiveValue(error);
}
return CONFIG.SANITIZE_REPLACEMENT;
}
function sanitizeHeaders(headers, allowedHeaders) {
const sanitized = {};
const processHeader = (value, key) => {
const normalizedKey = key.toLowerCase();
if (allowedHeaders.has(normalizedKey)) {
sanitized[normalizedKey] = redactSensitiveValue(value);
}
};
const h = headers;
if (h?.forEach && typeof h.forEach === "function") {
h.forEach(processHeader);
} else {
Object.entries(headers).forEach(([key, value]) => {
processHeader(value, key);
});
}
return sanitized;
}
function isEmptyOrWhitespace(value) {
return !value || value.trim().length === 0;
}
function sanitizeResponse(response) {
const sanitized = deepSanitizeObject(response);
return deepFreeze(sanitized);
}
// src/utils/errors.ts
var VaultError = class _VaultError extends Error {
code;
context;
constructor(code, message, context) {
const sanitizedMessage = typeof message === "string" ? message.replace(CONFIG.SANITIZE_SECRET_PATTERN, CONFIG.SANITIZE_REPLACEMENT) : String(message);
super(sanitizedMessage);
this.name = "VaultError";
this.code = code;
if (context) {
this.context = deepFreeze(deepSanitizeObject(context));
}
Object.setPrototypeOf(this, _VaultError.prototype);
}
toJSON() {
const json = {
name: this.name,
code: this.code,
message: this.message,
context: this.context
};
return deepFreeze(json);
}
};
var ValidationError = class _ValidationError extends VaultError {
constructor(code, message, field) {
const context = field ? { field } : void 0;
super(code, message, context);
this.name = "ValidationError";
Object.setPrototypeOf(this, _ValidationError.prototype);
}
};
var ConfigurationError = class _ConfigurationError extends VaultError {
constructor(message, field) {
const context = field ? { field } : void 0;
super(CONFIG.ERROR_CODE_INVALID_REQUEST, message, context);
this.name = "ConfigurationError";
Object.setPrototypeOf(this, _ConfigurationError.prototype);
}
};
var ToolError = class _ToolError extends VaultError {
constructor(message, code) {
super(code, message);
this.name = "ToolError";
Object.setPrototypeOf(this, _ToolError.prototype);
}
};
function sanitizeUnknownError(error) {
if (error instanceof VaultError) {
return error;
}
if (error instanceof Error) {
return new VaultError(
CONFIG.ERROR_CODE_EXECUTION_FAILED,
TEXT.ERROR_EXECUTION_FAILED,
{ errorType: error.name }
);
}
return new VaultError(
CONFIG.ERROR_CODE_EXECUTION_FAILED,
TEXT.ERROR_EXECUTION_FAILED
);
}
// src/utils/validation.ts
function isNonEmptyString(value) {
return typeof value === "string" && value.trim().length > 0;
}
function validateSecretId(secretId) {
const trimmed = secretId.trim();
if (trimmed.length < CONFIG.MIN_SECRET_ID_LENGTH) {
throw new ValidationError(
CONFIG.ERROR_CODE_INVALID_REQUEST,
TEXT.ERROR_SECRET_ID_TOO_SHORT,
TEXT.FIELD_SECRET_ID
);
}
if (trimmed.length > CONFIG.MAX_SECRET_ID_LENGTH) {
throw new ValidationError(
CONFIG.ERROR_CODE_INVALID_REQUEST,
TEXT.ERROR_SECRET_ID_TOO_LONG,
TEXT.FIELD_SECRET_ID
);
}
if (!CONFIG.SECRET_ID_REGEX.test(trimmed)) {
throw new ValidationError(
CONFIG.ERROR_CODE_INVALID_REQUEST,
TEXT.ERROR_INVALID_SECRET_ID_FORMAT,
TEXT.FIELD_SECRET_ID
);
}
return trimmed;
}
function validateEnvVar(envVar) {
const trimmed = envVar.trim();
if (trimmed.length < CONFIG.MIN_ENV_VAR_LENGTH) {
throw new ValidationError(
CONFIG.ERROR_CODE_INVALID_REQUEST,
TEXT.ERROR_ENV_VAR_TOO_SHORT,
TEXT.FIELD_ENV_VAR
);
}
if (!CONFIG.ENV_VAR_REGEX.test(trimmed)) {
throw new ValidationError(
CONFIG.ERROR_CODE_INVALID_REQUEST,
TEXT.ERROR_INVALID_ENV_VAR_FORMAT,
TEXT.FIELD_ENV_VAR
);
}
return trimmed;
}
// src/services/env-secret-provider.ts
var EnvSecretProvider = class {
mappings;
secretIds;
constructor(mappings) {
const validated = this.validateAndNormalizeMappings(mappings);
this.mappings = Object.freeze(new Map(validated));
this.secretIds = Object.freeze(Array.from(this.mappings.keys()).sort());
}
validateAndNormalizeMappings(mappings) {
const seen = /* @__PURE__ */ new Set();
const result = [];
for (const mapping of mappings) {
const secretId = validateSecretId(mapping.secretId);
const envVar = validateEnvVar(mapping.envVar);
if (seen.has(secretId)) {
throw new ConfigurationError(
TEXT.ERROR_DUPLICATE_SECRET_ID,
TEXT.FIELD_SECRET_ID
);
}
seen.add(secretId);
const validated = Object.freeze({
secretId,
envVar,
description: mapping.description?.trim()
});
result.push([secretId, validated]);
}
return result;
}
listSecretIds() {
return this.secretIds;
}
isSecretAvailable(secretId) {
const normalizedId = secretId.trim();
const mapping = this.mappings.get(normalizedId);
if (!mapping) {
return false;
}
const value = process.env[mapping.envVar];
return isNonEmptyString(value);
}
getSecretInfo(secretId) {
const normalizedId = secretId.trim();
const mapping = this.mappings.get(normalizedId);
if (!mapping) {
return void 0;
}
return Object.freeze({
secretId: mapping.secretId,
available: this.isSecretAvailable(normalizedId),
description: mapping.description
});
}
getSecretValue(secretId) {
const normalizedId = secretId.trim();
const mapping = this.mappings.get(normalizedId);
if (!mapping) {
return void 0;
}
const value = process.env[mapping.envVar];
return isNonEmptyString(value) ? value : void 0;
}
};
// src/services/policy-loader.service.ts
import { promises as fs } from "fs";
import path from "path";
var PolicyLoaderService = class {
constructor(policiesPath = path.join(
CONFIG.DEFAULT_POLICIES_DIR,
CONFIG.DEFAULT_POLICIES_FILE
)) {
this.policiesPath = policiesPath;
}
async loadPolicies() {
try {
const content = await fs.readFile(this.policiesPath, CONFIG.DEFAULT_ENCODING);
const data = JSON.parse(content);
if (!Array.isArray(data)) {
throw new Error(TEXT.ERROR_INVALID_CONFIG);
}
return data.map((policy) => this.freezePolicy(policy));
} catch (error) {
if (error.code === CONFIG.FS_ERROR_ENOENT) {
return [];
}
if (error instanceof SyntaxError) {
throw new Error(TEXT.ERROR_INVALID_CONFIG);
}
throw error;
}
}
normalizeList(items) {
if (!Array.isArray(items)) return Object.freeze([]);
const normalized = /* @__PURE__ */ new Set();
for (const item of items) {
const trimmed = item?.toString()?.trim()?.toLowerCase();
if (trimmed) normalized.add(trimmed);
}
return Object.freeze(Array.from(normalized).sort());
}
freezePolicy(policy) {
const frozen = {
secretId: policy.secretId?.trim(),
allowedActions: this.normalizeList(policy.allowedActions),
allowedDomains: this.normalizeList(policy.allowedDomains)
};
if (policy.rateLimit) {
frozen.rateLimit = Object.freeze({
requests: policy.rateLimit.requests,
windowSeconds: policy.rateLimit.windowSeconds
});
}
if (policy.expiresAt) frozen.expiresAt = policy.expiresAt?.trim();
return Object.freeze(frozen);
}
};
// src/services/policy-validator.service.ts
import { z } from "zod";
var RateLimitSchema = z.object({
requests: z.number().positive(),
windowSeconds: z.number().positive()
});
var PolicyStructureSchema = z.object({}).passthrough();
var PolicyValidatorService = class {
seenSecretIds = /* @__PURE__ */ new Set();
validate(policy) {
this.validateStructure(policy);
this.validateSecretId(policy.secretId);
this.validateAllowedActions(policy.allowedActions);
this.validateAllowedDomains(policy.allowedDomains);
if (policy.rateLimit) {
this.validateRateLimit(policy.rateLimit);
}
if (policy.expiresAt) {
this.validateExpiration(policy.expiresAt);
}
}
validateAll(policies) {
this.seenSecretIds.clear();
for (const policy of policies) {
this.validate(policy);
const trimmedId = policy.secretId?.trim();
if (trimmedId && this.seenSecretIds.has(trimmedId)) {
throw new ToolError(TEXT.ERROR_DUPLICATE_POLICY, CONFIG.ERROR_CODE_INVALID_POLICY);
}
if (trimmedId) {
this.seenSecretIds.add(trimmedId);
}
}
}
validateStructure(policy) {
try {
PolicyStructureSchema.parse(policy);
} catch {
throw new ToolError(TEXT.ERROR_INVALID_POLICY_STRUCTURE, CONFIG.ERROR_CODE_INVALID_POLICY);
}
const requiredFields = [
TEXT.FIELD_SECRET_ID,
TEXT.FIELD_ALLOWED_ACTIONS,
TEXT.FIELD_ALLOWED_DOMAINS
];
for (const field of requiredFields) {
if (!(field in policy)) {
throw new ToolError(
`${TEXT.ERROR_MISSING_POLICY_FIELD}: ${field}`,
CONFIG.ERROR_CODE_INVALID_POLICY
);
}
}
}
validateSecretId(secretId) {
const trimmed = secretId?.trim();
if (!trimmed) {
throw new ToolError(TEXT.ERROR_SECRET_ID_TOO_SHORT, CONFIG.ERROR_CODE_INVALID_POLICY);
}
if (trimmed.length > CONFIG.MAX_SECRET_ID_LENGTH) {
throw new ToolError(TEXT.ERROR_SECRET_ID_TOO_LONG, CONFIG.ERROR_CODE_INVALID_POLICY);
}
if (!CONFIG.SECRET_ID_REGEX.test(trimmed)) {
throw new ToolError(TEXT.ERROR_INVALID_SECRET_ID_FORMAT, CONFIG.ERROR_CODE_INVALID_POLICY);
}
}
validateAllowedActions(actions) {
if (!Array.isArray(actions)) {
throw new ToolError(TEXT.ERROR_INVALID_ALLOWED_ACTIONS, CONFIG.ERROR_CODE_INVALID_POLICY);
}
if (actions.length === 0) {
throw new ToolError(TEXT.ERROR_EMPTY_ALLOWED_ACTIONS, CONFIG.ERROR_CODE_INVALID_POLICY);
}
for (const action of actions) {
const trimmed = action?.trim();
if (!trimmed) {
throw new ToolError(TEXT.ERROR_INVALID_ACTION, CONFIG.ERROR_CODE_INVALID_POLICY);
}
if (!CONFIG.ACTION_REGEX.test(trimmed)) {
throw new ToolError(TEXT.ERROR_INVALID_ACTION, CONFIG.ERROR_CODE_INVALID_POLICY);
}
const supportedActions = CONFIG.SUPPORTED_ACTIONS;
if (!supportedActions.includes(trimmed)) {
throw new ToolError(`${TEXT.ERROR_UNSUPPORTED_ACTION}: ${trimmed}`, CONFIG.ERROR_CODE_INVALID_POLICY);
}
}
}
validateAllowedDomains(domains) {
if (!Array.isArray(domains)) {
throw new ToolError(TEXT.ERROR_INVALID_ALLOWED_DOMAINS, CONFIG.ERROR_CODE_INVALID_POLICY);
}
if (domains.length === 0) {
throw new ToolError(TEXT.ERROR_EMPTY_ALLOWED_DOMAINS, CONFIG.ERROR_CODE_INVALID_POLICY);
}
for (const domain of domains) {
const trimmed = domain?.trim();
if (!trimmed) {
throw new ToolError(TEXT.ERROR_INVALID_DOMAIN, CONFIG.ERROR_CODE_INVALID_POLICY);
}
if (trimmed.length > CONFIG.MAX_DOMAIN_LENGTH) {
throw new ToolError(TEXT.ERROR_INVALID_DOMAIN, CONFIG.ERROR_CODE_INVALID_POLICY);
}
if (trimmed.endsWith(".") || /\s/.test(trimmed)) {
throw new ToolError(`${TEXT.ERROR_INVALID_DOMAIN}: ${trimmed}`, CONFIG.ERROR_CODE_INVALID_POLICY);
}
if (!CONFIG.DOMAIN_REGEX.test(trimmed)) {
throw new ToolError(`${TEXT.ERROR_INVALID_DOMAIN}: ${trimmed}`, CONFIG.ERROR_CODE_INVALID_POLICY);
}
}
}
validateRateLimit(rateLimit) {
try {
RateLimitSchema.parse(rateLimit);
} catch {
throw new ToolError(TEXT.ERROR_INVALID_RATE_LIMIT, CONFIG.ERROR_CODE_INVALID_POLICY);
}
}
validateExpiration(expiresAt) {
const trimmed = expiresAt?.trim();
if (!trimmed) {
throw new ToolError(TEXT.ERROR_INVALID_EXPIRATION, CONFIG.ERROR_CODE_INVALID_POLICY);
}
const date = new Date(trimmed);
if (isNaN(date.getTime())) {
throw new ToolError(TEXT.ERROR_INVALID_EXPIRATION, CONFIG.ERROR_CODE_INVALID_POLICY);
}
}
};
// src/services/policy-evaluator.service.ts
var PolicyEvaluatorService = class {
policies = /* @__PURE__ */ new Map();
constructor(policies = []) {
for (const policy of policies) {
const trimmedKey = policy.secretId?.trim();
if (trimmedKey) {
this.policies.set(trimmedKey, policy);
}
}
}
validateInput(secretId, action, domain) {
const trimmedSecretId = secretId?.trim();
const trimmedDomain = domain?.trim()?.toLowerCase();
const normalizedAction = action?.trim()?.toLowerCase();
if (!trimmedSecretId || !normalizedAction || !trimmedDomain) {
return { valid: false, error: {
allowed: false,
code: CONFIG.ERROR_CODE_INVALID_REQUEST,
message: TEXT.ERROR_INVALID_REQUEST
} };
}
return { valid: true, trimmedSecretId, normalizedAction, trimmedDomain };
}
validateAction(action) {
const supportedActions = CONFIG.SUPPORTED_ACTIONS;
if (!supportedActions.includes(action)) {
return {
allowed: false,
code: CONFIG.ERROR_CODE_FORBIDDEN_ACTION,
message: TEXT.ERROR_UNSUPPORTED_ACTION
};
}
return null;
}
validateExpiration(policy) {
if (policy.expiresAt) {
const expirationDate = new Date(policy.expiresAt);
if (expirationDate <= /* @__PURE__ */ new Date()) {
return {
allowed: false,
code: CONFIG.ERROR_CODE_POLICY_EXPIRED,
message: TEXT.ERROR_POLICY_EXPIRED
};
}
}
return null;
}
validatePolicyPermissions(policy, action, domain) {
if (!policy.allowedActions.includes(action)) {
return {
allowed: false,
code: CONFIG.ERROR_CODE_FORBIDDEN_ACTION,
message: TEXT.ERROR_FORBIDDEN_ACTION
};
}
const domainAllowed = policy.allowedDomains.some(
(allowedDomain) => allowedDomain.toLowerCase() === domain
);
if (!domainAllowed) {
return {
allowed: false,
code: CONFIG.ERROR_CODE_FORBIDDEN_DOMAIN,
message: TEXT.ERROR_FORBIDDEN_DOMAIN
};
}
return { allowed: true };
}
evaluate(secretId, action, domain) {
const validation = this.validateInput(secretId, action, domain);
if (!validation.valid) return validation.error;
const actionError = this.validateAction(validation.normalizedAction);
if (actionError) return actionError;
const policy = this.policies.get(validation.trimmedSecretId);
if (!policy) {
return {
allowed: false,
code: CONFIG.ERROR_CODE_NO_POLICY,
message: TEXT.ERROR_POLICY_NOT_FOUND
};
}
const expirationError = this.validateExpiration(policy);
if (expirationError) return expirationError;
return this.validatePolicyPermissions(policy, validation.normalizedAction, validation.trimmedDomain);
}
getPolicy(secretId) {
const trimmed = secretId?.trim();
return trimmed ? this.policies.get(trimmed) : void 0;
}
hasPolicy(secretId) {
const trimmed = secretId?.trim();
return trimmed ? this.policies.has(trimmed) : false;
}
};
// src/services/policy-provider.service.ts
var PolicyProviderService = class {
loader;
validator;
evaluator = null;
constructor(policiesPath, loader, validator) {
this.loader = loader || new PolicyLoaderService(policiesPath);
this.validator = validator || new PolicyValidatorService();
}
async loadPolicies() {
const policies = await this.loader.loadPolicies();
this.validator.validateAll(policies);
this.evaluator = new PolicyEvaluatorService(policies);
}
async loadPoliciesFromConfig(policies) {
this.validator.validateAll(policies);
this.evaluator = new PolicyEvaluatorService(policies);
}
evaluate(secretId, action, domain) {
if (!this.evaluator) {
return {
allowed: false,
code: CONFIG.ERROR_CODE_POLICIES_NOT_LOADED,
message: TEXT.ERROR_POLICIES_NOT_LOADED
};
}
return this.evaluator.evaluate(secretId, action, domain);
}
getPolicy(secretId) {
if (!this.evaluator) {
return void 0;
}
return this.evaluator.getPolicy(secretId);
}
hasPolicy(secretId) {
if (!this.evaluator) {
return false;
}
return this.evaluator.hasPolicy(secretId);
}
};
// src/services/http-action-executor.service.ts
var HttpActionExecutor = class {
allowedHeaders;
constructor() {
this.allowedHeaders = new Set(
CONFIG.ALLOWED_RESPONSE_HEADERS.map((h) => h.toLowerCase())
);
}
/**
* Execute an HTTP action with secret injection
*/
buildFetchOptions(request, headers, controller) {
const options = {
method: request.method,
headers,
signal: controller.signal,
redirect: CONFIG.FETCH_REDIRECT_MODE
};
if (request.method === "POST" && request.body) {
options.body = JSON.stringify(request.body);
headers[CONFIG.HEADER_CONTENT_TYPE] = CONFIG.CONTENT_TYPE_JSON;
}
return options;
}
handleExecutionError(error) {
if (