@dollhousemcp/mcp-server
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
582 lines • 79.6 kB
JavaScript
/**
* Secure GitHub token management and validation
*/
import { logger } from '../utils/logger.js';
import { RateLimiter } from '../utils/RateLimiter.js';
import { SecurityError } from './errors.js';
import * as crypto from 'crypto';
import * as path from 'path';
import { homedir } from 'os';
import { SecurityMonitor } from './securityMonitor.js';
import { UnicodeValidator } from './validators/unicodeValidator.js';
/**
* Secure GitHub token manager with validation and protection
*/
export class TokenManager {
static tokenLoggedOnce = false;
/** Reset static flags for test isolation. */
static resetStaticState() {
TokenManager.tokenLoggedOnce = false;
}
static GITHUB_TOKEN_PATTERNS = {
// More flexible patterns - accept any content after the prefix
PERSONAL_ACCESS_TOKEN: /^ghp_.+$/, // Personal access tokens
FINE_GRAINED_PAT: /^github_pat_.+$/, // Fine-grained personal access tokens
INSTALLATION_TOKEN: /^ghs_.+$/, // GitHub App installation tokens
USER_ACCESS_TOKEN: /^ghu_.+$/, // GitHub App user-to-server tokens
REFRESH_TOKEN: /^ghr_.+$/, // Refresh tokens
OAUTH_ACCESS_TOKEN: /^gho_.+$/, // OAuth device flow tokens
// Generic pattern to catch ALL GitHub tokens (gh + any letter + underscore + anything)
GENERIC_GITHUB_TOKEN: /^gh[a-z]_.+$/i // Catch-all for any gh*_ pattern
};
// Secure storage configuration
static TOKEN_DIR = path.join(homedir(), '.dollhouse', '.auth');
static TOKEN_FILE = 'github_token.enc';
static ALGORITHM = 'aes-256-gcm';
static KEY_LENGTH = 32;
static IV_LENGTH = 16;
static TAG_LENGTH = 16;
static SALT_LENGTH = 32;
static ITERATIONS = 100000;
// Rate limiter for token validation operations - prevents brute force attacks
tokenValidationLimiter = null;
// File operations service for secure file access (injected via constructor)
fileOperations;
constructor(fileOperations) {
this.fileOperations = fileOperations;
}
/**
* Get or create the token validation rate limiter
* Prevents brute force token validation attacks
*/
getTokenValidationLimiter() {
if (!this.tokenValidationLimiter) {
this.tokenValidationLimiter = this.createTokenValidationLimiter();
}
return this.tokenValidationLimiter;
}
/**
* Create a rate limiter specifically for token validation
* Conservative limits to prevent abuse while allowing legitimate usage
*/
createTokenValidationLimiter() {
return new RateLimiter({
maxRequests: 10, // 10 validation attempts
windowMs: 60 * 60 * 1000, // per hour
minDelayMs: 5 * 1000 // 5 seconds minimum between attempts
});
}
/**
* Reset the token validation rate limiter
* Useful for testing or manual intervention
*/
resetTokenValidationLimiter() {
this.tokenValidationLimiter?.reset();
}
/**
* Validate GitHub token format
*/
validateTokenFormat(token) {
if (!token || typeof token !== 'string') {
return false;
}
// Check against all known GitHub token patterns
return Object.values(TokenManager.GITHUB_TOKEN_PATTERNS).some(pattern => pattern.test(token));
}
/**
* Get GitHub token from environment with validation
*
* Supports backward compatibility with old variable names:
* - GITHUB_TOKEN (canonical)
* - TEST_GITHUB_TOKEN (deprecated)
* - GITHUB_TEST_TOKEN (deprecated)
*/
getGitHubToken() {
// Try canonical name first
let token = process.env.GITHUB_TOKEN;
// Backward compatibility: Check for old variable names
if (!token && process.env.TEST_GITHUB_TOKEN) {
logger.warn('DEPRECATED: TEST_GITHUB_TOKEN is deprecated. Use GITHUB_TOKEN with .env file or GITHUB_TEST_TOKEN for tests.');
token = process.env.TEST_GITHUB_TOKEN;
}
if (!token && process.env.GITHUB_TEST_TOKEN) {
logger.warn('DEPRECATED: GITHUB_TEST_TOKEN found but should be used by test code only. Production code should use GITHUB_TOKEN.');
token = process.env.GITHUB_TEST_TOKEN;
}
if (!token) {
logger.debug('No GitHub token found in environment');
return null;
}
if (!this.validateTokenFormat(token)) {
// codeql[js/clear-text-logging] — Only the first 4 chars (tokenPrefix) and length are logged, never the full token
logger.warn('Invalid GitHub token format detected', {
tokenPrefix: this.getTokenPrefix(token),
length: token.length
});
return null;
}
// codeql[js/clear-text-logging] — Only token type name and first 4 chars (tokenPrefix) are logged, never the full token
if (!TokenManager.tokenLoggedOnce) {
logger.debug('Valid GitHub token found', {
tokenType: this.getTokenType(token),
tokenPrefix: this.getTokenPrefix(token)
});
TokenManager.tokenLoggedOnce = true;
}
return token;
}
/**
* Redact token for safe logging
*/
redactToken(token) {
if (!token || token.length < 8) {
return '[REDACTED]';
}
return token.substring(0, 4) + '...' + token.substring(token.length - 4);
}
/**
* Get token type from format
*/
getTokenType(token) {
// Check fine-grained PAT first since it's more specific
if (TokenManager.GITHUB_TOKEN_PATTERNS.FINE_GRAINED_PAT.test(token)) {
return 'Fine-grained Personal Access Token';
}
if (TokenManager.GITHUB_TOKEN_PATTERNS.PERSONAL_ACCESS_TOKEN.test(token)) {
return 'Personal Access Token';
}
if (TokenManager.GITHUB_TOKEN_PATTERNS.INSTALLATION_TOKEN.test(token)) {
return 'Installation Token';
}
if (TokenManager.GITHUB_TOKEN_PATTERNS.USER_ACCESS_TOKEN.test(token)) {
return 'User Access Token';
}
if (TokenManager.GITHUB_TOKEN_PATTERNS.REFRESH_TOKEN.test(token)) {
return 'Refresh Token';
}
if (TokenManager.GITHUB_TOKEN_PATTERNS.OAUTH_ACCESS_TOKEN.test(token)) {
return 'OAuth Access Token';
}
// Check generic pattern last
if (TokenManager.GITHUB_TOKEN_PATTERNS.GENERIC_GITHUB_TOKEN.test(token)) {
return 'GitHub Token';
}
return 'Unknown';
}
/**
* Get safe token prefix for logging
*/
getTokenPrefix(token) {
if (!token || token.length < 4) {
return '[INVALID]';
}
return token.substring(0, 4) + '...';
}
/**
* Validate token scopes via GitHub API
*/
async validateTokenScopes(token, requiredScopes) {
// Validate token format before consuming rate limit
if (!this.validateTokenFormat(token)) {
return {
isValid: false,
error: 'Invalid token format'
};
}
// Check rate limit before making API call
const rateLimiter = this.getTokenValidationLimiter();
const rateLimitStatus = rateLimiter.checkLimit();
if (!rateLimitStatus.allowed) {
logger.warn('Token validation rate limit exceeded', {
tokenPrefix: this.getTokenPrefix(token),
retryAfterMs: rateLimitStatus.retryAfterMs,
remainingTokens: rateLimitStatus.remainingTokens
});
throw new SecurityError(`Token validation rate limit exceeded. Please retry in ${Math.ceil((rateLimitStatus.retryAfterMs || 0) / 1000)} seconds.`, 'RATE_LIMIT_EXCEEDED');
}
try {
// Consume rate limit token for this validation attempt
rateLimiter.consumeToken();
// Make a test API call to check token validity and scopes
const response = await fetch('https://api.github.com/user', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'DollhouseMCP/1.0'
}
});
const rateLimitRemaining = Number.parseInt(response.headers.get('x-ratelimit-remaining') || '0');
const rateLimitReset = Number.parseInt(response.headers.get('x-ratelimit-reset') || '0');
if (!response.ok) {
const error = `GitHub API error: ${response.status} ${response.statusText}`;
logger.warn('Token validation failed', {
status: response.status,
tokenPrefix: this.getTokenPrefix(token)
});
return {
isValid: false,
error: error
};
}
// Extract scopes from response headers
const scopesHeader = response.headers.get('x-oauth-scopes') || '';
const tokenScopes = scopesHeader.split(',').map(s => s.trim()).filter(s => s);
// Check if required scopes are present
const hasRequiredScopes = requiredScopes.required.every(scope => tokenScopes.includes(scope));
if (!hasRequiredScopes) {
const missingScopes = requiredScopes.required.filter(scope => !tokenScopes.includes(scope));
logger.warn('Token missing required scopes', {
tokenPrefix: this.getTokenPrefix(token),
missingScopes: missingScopes,
currentScopes: tokenScopes
});
return {
isValid: false,
scopes: tokenScopes,
error: `Missing required scopes: ${missingScopes.join(', ')}`
};
}
logger.info('Token validation successful', {
tokenType: this.getTokenType(token),
tokenPrefix: this.getTokenPrefix(token),
scopes: tokenScopes,
rateLimitRemaining: rateLimitRemaining
});
return {
isValid: true,
scopes: tokenScopes,
rateLimit: {
remaining: rateLimitRemaining,
resetTime: new Date(rateLimitReset * 1000)
}
};
}
catch (error) {
// Handle SecurityError (including rate limit errors) separately
if (error instanceof SecurityError && error.code === 'RATE_LIMIT_EXCEEDED') {
const currentStatus = rateLimiter.checkLimit();
return {
isValid: false,
rateLimitExceeded: true,
retryAfterMs: currentStatus.retryAfterMs,
error: error.message
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.error('Token validation error', {
error: errorMessage,
tokenPrefix: this.getTokenPrefix(token)
});
return {
isValid: false,
error: `Validation error: ${errorMessage}`
};
}
}
/**
* Create safe error message without token exposure
*/
createSafeErrorMessage(error, token) {
// Remove any potential token data from error messages
// Using word boundaries to avoid over-matching
let safeMessage = error
.replaceAll(/\bghp_\S+/g, '[REDACTED_PAT]')
.replaceAll(/\bgithub_pat_\S+/g, '[REDACTED_FINE_PAT]')
.replaceAll(/\bghs_\S+/g, '[REDACTED_INSTALL]')
.replaceAll(/\bghu_\S+/g, '[REDACTED_USER]')
.replaceAll(/\bghr_\S+/g, '[REDACTED_REFRESH]')
.replaceAll(/\bgho_\S+/g, '[REDACTED_OAUTH]')
.replaceAll(/\bgh[a-z]_\S+/gi, '[REDACTED_TOKEN]'); // Catch any other gh*_ pattern
if (token) {
const tokenPrefix = this.getTokenPrefix(token);
safeMessage += ` (Token: ${tokenPrefix})`;
}
return safeMessage;
}
/**
* Get minimum required scopes for different operations
*
* NOTE: The 'marketplace' scope identifier is kept for backward compatibility
* with existing token validations. This is an internal scope name and does not
* affect user-facing functionality. (PR #280)
*/
getRequiredScopes(operation) {
switch (operation) {
case 'read':
return {
required: ['public_repo'], // OAuth tokens use 'public_repo' not 'repo'
optional: ['user:email']
};
case 'write':
return {
required: ['public_repo'], // OAuth tokens use 'public_repo' not 'repo'
optional: ['user:email']
};
case 'marketplace': // Internal scope name kept for compatibility (PR #280)
case 'collection': // New preferred name
return {
required: ['public_repo'], // OAuth tokens use 'public_repo' not 'repo'
optional: ['user:email']
};
case 'gist':
return {
required: ['gist'],
optional: ['user:email']
};
default:
return {
required: ['public_repo'] // OAuth tokens use 'public_repo' not 'repo'
};
}
}
/**
* Check if token has sufficient permissions for operation
*
* NOTE: The 'marketplace' operation type is kept for backward compatibility.
* This is called internally when accessing collection features. (PR #280)
*/
async ensureTokenPermissions(operation) {
const token = this.getGitHubToken();
if (!token) {
return {
isValid: false,
error: 'No GitHub token available'
};
}
const requiredScopes = this.getRequiredScopes(operation);
return this.validateTokenScopes(token, requiredScopes);
}
/**
* Derive encryption key from a passphrase
*/
deriveKey(passphrase, salt) {
return crypto.pbkdf2Sync(passphrase, salt, TokenManager.ITERATIONS, TokenManager.KEY_LENGTH, 'sha256');
}
/**
* Get passphrase for token encryption.
*
* Priority: DOLLHOUSE_TOKEN_SECRET env var → machine-derived passphrase (fallback).
* The machine-derived passphrase uses homedir + USER which is predictable (#1735).
* Set DOLLHOUSE_TOKEN_SECRET for stronger protection.
*/
getPassphrase() {
if (process.env.DOLLHOUSE_TOKEN_SECRET) {
return process.env.DOLLHOUSE_TOKEN_SECRET;
}
return this.getMachinePassphrase();
}
/**
* Machine-derived passphrase — fallback when DOLLHOUSE_TOKEN_SECRET is not
* set, and migration path for tokens encrypted before that env var existed.
* Not deprecated; still the default for installations that haven't opted in
* to an explicit secret.
*/
getMachinePassphrase() {
// codeql[js/insufficient-password-hashing] — These are NOT password hashes.
// SHA-256 is used to derive a stable machine fingerprint from system identifiers
// (home directory path, OS username). The actual token encryption uses pbkdf2Sync
// with ITERATIONS rounds (see deriveKey method above).
const hostname = crypto.createHash('sha256').update(homedir()).digest('hex').substring(0, 16);
const username = crypto.createHash('sha256').update(process.env.USER || 'default').digest('hex').substring(0, 16);
const appId = 'DollhouseMCP-TokenStore-v1';
return `${appId}-${hostname}-${username}`;
}
/**
* Attempt decryption with the primary passphrase, then fall back to the
* machine-derived passphrase for backward compatibility (#1735).
*/
decryptWithFallback(salt, iv, tag, encrypted) {
const primaryPassphrase = this.getPassphrase();
try {
return this.decryptToken(primaryPassphrase, salt, iv, tag, encrypted);
}
catch {
// If primary passphrase differs from machine passphrase, try fallback
const machinePassphrase = this.getMachinePassphrase();
if (machinePassphrase !== primaryPassphrase) {
logger.info('Primary passphrase failed, trying machine passphrase migration path');
return this.decryptToken(machinePassphrase, salt, iv, tag, encrypted);
}
throw new SecurityError('Token decryption failed');
}
}
decryptToken(passphrase, salt, iv, tag, encrypted) {
const key = this.deriveKey(passphrase, salt);
const decipher = crypto.createDecipheriv(TokenManager.ALGORITHM, key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
}
/**
* Store GitHub token securely to file
*/
async storeGitHubToken(token) {
try {
// Validate token format first
if (!this.validateTokenFormat(token)) {
throw new SecurityError('Invalid token format');
}
// Normalize and validate token
const validation = UnicodeValidator.normalize(token);
if (!validation.isValid) {
throw new SecurityError('Token contains invalid characters');
}
// Ensure directory exists
await this.fileOperations.createDirectory(TokenManager.TOKEN_DIR);
await this.fileOperations.chmod(TokenManager.TOKEN_DIR, 0o700, {
source: 'TokenManager.storeGitHubToken'
});
// Generate encryption components
const salt = crypto.randomBytes(TokenManager.SALT_LENGTH);
const iv = crypto.randomBytes(TokenManager.IV_LENGTH);
const passphrase = this.getPassphrase();
const key = this.deriveKey(passphrase, salt);
// Encrypt token
const cipher = crypto.createCipheriv(TokenManager.ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(validation.normalizedContent, 'utf8'),
cipher.final()
]);
const tag = cipher.getAuthTag();
// Create storage format: salt + iv + tag + encrypted
const stored = Buffer.concat([salt, iv, tag, encrypted]);
// Write to file with restricted permissions
const tokenPath = path.join(TokenManager.TOKEN_DIR, TokenManager.TOKEN_FILE);
// Write the binary content to the file (need to convert Buffer to string for FileOperationsService)
// Since we're writing binary data, we'll write as base64 for safe storage
await this.fileOperations.writeFile(tokenPath, stored.toString('base64'), {
source: 'TokenManager.storeGitHubToken'
});
await this.fileOperations.chmod(tokenPath, 0o600, {
source: 'TokenManager.storeGitHubToken'
});
// Log security event
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_SUCCESS',
severity: 'LOW',
source: 'TokenManager.storeGitHubToken',
details: 'GitHub token stored securely',
metadata: {
tokenType: this.getTokenType(token),
tokenPrefix: this.getTokenPrefix(token)
}
});
logger.info('GitHub token stored securely');
}
catch (error) {
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_FAILURE',
severity: 'MEDIUM',
source: 'TokenManager.storeGitHubToken',
details: `Failed to store GitHub token: ${error instanceof Error ? error.message : 'Unknown error'}`
});
throw new SecurityError(`Failed to store token: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Retrieve GitHub token from secure storage
*/
async retrieveGitHubToken() {
try {
const tokenPath = path.join(TokenManager.TOKEN_DIR, TokenManager.TOKEN_FILE);
// Check if file exists
const exists = await this.fileOperations.exists(tokenPath);
if (!exists) {
// No stored token
return null;
}
// Read encrypted data (stored as base64)
const base64Content = await this.fileOperations.readFile(tokenPath, {
source: 'TokenManager.retrieveGitHubToken'
});
const stored = Buffer.from(base64Content, 'base64');
// Extract components
const salt = stored.subarray(0, TokenManager.SALT_LENGTH);
const iv = stored.subarray(TokenManager.SALT_LENGTH, TokenManager.SALT_LENGTH + TokenManager.IV_LENGTH);
const tag = stored.subarray(TokenManager.SALT_LENGTH + TokenManager.IV_LENGTH, TokenManager.SALT_LENGTH + TokenManager.IV_LENGTH + TokenManager.TAG_LENGTH);
const encrypted = stored.subarray(TokenManager.SALT_LENGTH + TokenManager.IV_LENGTH + TokenManager.TAG_LENGTH);
// Decrypt with primary passphrase; fall back to machine passphrase for
// backward compatibility with tokens stored before DOLLHOUSE_TOKEN_SECRET (#1735)
const decrypted = this.decryptWithFallback(salt, iv, tag, encrypted);
// Validate decrypted token
if (!this.validateTokenFormat(decrypted)) {
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_FAILURE',
severity: 'HIGH',
source: 'TokenManager.retrieveGitHubToken',
details: 'Decrypted token has invalid format'
});
return null;
}
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_SUCCESS',
severity: 'LOW',
source: 'TokenManager.retrieveGitHubToken',
details: 'GitHub token retrieved from secure storage',
metadata: {
tokenType: this.getTokenType(decrypted),
tokenPrefix: this.getTokenPrefix(decrypted)
}
});
return decrypted;
}
catch (error) {
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_FAILURE',
severity: 'MEDIUM',
source: 'TokenManager.retrieveGitHubToken',
details: `Failed to retrieve GitHub token: ${error instanceof Error ? error.message : 'Unknown error'}`
});
logger.debug('Failed to retrieve stored token', { error });
return null;
}
}
/**
* Remove stored GitHub token
*/
async removeStoredToken() {
try {
const tokenPath = path.join(TokenManager.TOKEN_DIR, TokenManager.TOKEN_FILE);
// Check if file exists before attempting deletion
const exists = await this.fileOperations.exists(tokenPath);
if (exists) {
await this.fileOperations.deleteFile(tokenPath, undefined, {
source: 'TokenManager.removeStoredToken'
});
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_CACHE_CLEARED',
severity: 'LOW',
source: 'TokenManager.removeStoredToken',
details: 'GitHub token removed from secure storage'
});
logger.info('Stored GitHub token removed');
}
else {
// File doesn't exist
logger.debug('No stored token to remove');
}
}
catch (error) {
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_CACHE_CLEARED',
severity: 'LOW',
source: 'TokenManager.removeStoredToken',
details: `Failed to remove stored token: ${error instanceof Error ? error.message : 'Unknown error'}`
});
logger.warn('Failed to remove stored token', { error });
}
}
/**
* Get GitHub token from environment or secure storage
* Updated to check secure storage if environment variable not set
*/
async getGitHubTokenAsync() {
// First check environment variable
const envToken = this.getGitHubToken();
if (envToken) {
return envToken;
}
// Fall back to secure storage
return this.retrieveGitHubToken();
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW5NYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlY3VyaXR5L3Rva2VuTWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDdEQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUM1QyxPQUFPLEtBQUssTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUNqQyxPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUM3QixPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQzdCLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN2RCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQW9CcEU7O0dBRUc7QUFDSCxNQUFNLE9BQU8sWUFBWTtJQUNmLE1BQU0sQ0FBQyxlQUFlLEdBQUcsS0FBSyxDQUFDO0lBRXZDLDZDQUE2QztJQUM3QyxNQUFNLENBQUMsZ0JBQWdCO1FBQ3JCLFlBQVksQ0FBQyxlQUFlLEdBQUcsS0FBSyxDQUFDO0lBQ3ZDLENBQUM7SUFFTyxNQUFNLENBQVUscUJBQXFCLEdBQUc7UUFDOUMsK0RBQStEO1FBQy9ELHFCQUFxQixFQUFFLFVBQVUsRUFBTyx5QkFBeUI7UUFDakUsZ0JBQWdCLEVBQUUsaUJBQWlCLEVBQUssc0NBQXNDO1FBQzlFLGtCQUFrQixFQUFFLFVBQVUsRUFBVSxpQ0FBaUM7UUFDekUsaUJBQWlCLEVBQUUsVUFBVSxFQUFXLG1DQUFtQztRQUMzRSxhQUFhLEVBQUUsVUFBVSxFQUFlLGlCQUFpQjtRQUN6RCxrQkFBa0IsRUFBRSxVQUFVLEVBQVUsMkJBQTJCO1FBQ25FLHVGQUF1RjtRQUN2RixvQkFBb0IsRUFBRSxlQUFl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SUFBSSxDQUFDLG1CQUFtQixFQUFFLENBQUM7SUFDcEMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogU2VjdXJlIEdpdEh1YiB0b2tlbiBtYW5hZ2VtZW50IGFuZCB2YWxpZGF0aW9uXG4gKi9cblxuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IFJhdGVMaW1pdGVyIH0gZnJvbSAnLi4vdXRpbHMvUmF0ZUxpbWl0ZXIuanMnO1xuaW1wb3J0IHsgU2VjdXJpdHlFcnJvciB9IGZyb20gJy4vZXJyb3JzLmpzJztcbmltcG9ydCAqIGFzIGNyeXB0byBmcm9tICdjcnlwdG8nO1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB7IGhvbWVkaXIgfSBmcm9tICdvcyc7XG5pbXBvcnQgeyBTZWN1cml0eU1vbml0b3IgfSBmcm9tICcuL3NlY3VyaXR5TW9uaXRvci5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi92YWxpZGF0b3JzL3VuaWNvZGVWYWxpZGF0b3IuanMnO1xuaW1wb3J0IHsgSUZpbGVPcGVyYXRpb25zU2VydmljZSB9IGZyb20gJy4uL3NlcnZpY2VzL0ZpbGVPcGVyYXRpb25zU2VydmljZS5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgVG9rZW5TY29wZXMge1xuICByZXF1aXJlZDogc3RyaW5nW107XG4gIG9wdGlvbmFsPzogc3RyaW5nW107XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgVG9rZW5WYWxpZGF0aW9uUmVzdWx0IHtcbiAgaXNWYWxpZDogYm9vbGVhbjtcbiAgc2NvcGVzPzogc3RyaW5nW107XG4gIHJhdGVMaW1pdD86IHtcbiAgICByZW1haW5pbmc6IG51bWJlcjtcbiAgICByZXNldFRpbWU6IERhdGU7XG4gIH07XG4gIHJhdGVMaW1pdEV4Y2VlZGVkPzogYm9vbGVhbjtcbiAgcmV0cnlBZnRlck1zPzogbnVtYmVyO1xuICBlcnJvcj86IHN0cmluZztcbn1cblxuLyoqXG4gKiBTZWN1cmUgR2l0SHViIHRva2VuIG1hbmFnZXIgd2l0aCB2YWxpZGF0aW9uIGFuZCBwcm90ZWN0aW9uXG4gKi9cbmV4cG9ydCBjbGFzcyBUb2tlbk1hbmFnZXIge1xuICBwcml2YXRlIHN0YXRpYyB0b2tlbkxvZ2dlZE9uY2UgPSBmYWxzZTtcblxuICAvKiogUmVzZXQgc3RhdGljIGZsYWdzIGZvciB0ZXN0IGlzb2xhdGlvbi4gKi9cbiAgc3RhdGljIHJlc2V0U3RhdGljU3RhdGUoKTogdm9pZCB7XG4gICAgVG9rZW5NYW5hZ2VyLnRva2VuTG9nZ2VkT25jZSA9IGZhbHNlO1xuICB9XG5cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgR0lUSFVCX1RPS0VOX1BBVFRFUk5TID0ge1xuICAgIC8vIE1vcmUgZmxleGlibGUgcGF0dGVybnMgLSBhY2NlcHQgYW55IGNvbnRlbnQgYWZ0ZXIgdGhlIHByZWZpeFxuICAgIFBFUlNPTkFMX0FDQ0VTU19UT0tFTjogL15naHBfLiskLywgICAgICAvLyBQZXJzb25hbCBhY2Nlc3MgdG9rZW5zXG4gICAgRklORV9HUkFJTkVEX1BBVDogL15naXRodWJfcGF0Xy4rJC8sICAgIC8vIEZpbmUtZ3JhaW5lZCBwZXJzb25hbCBhY2Nlc3MgdG9rZW5zXG4gICAgSU5TVEFMTEFUSU9OX1RPS0VOOiAvXmdoc18uKyQvLCAgICAgICAgIC8vIEdpdEh1YiBBcHAgaW5zdGFsbGF0aW9uIHRva2Vuc1xuICAgIFVTRVJfQUNDRVNTX1RPS0VOOiAvXmdodV8uKyQvLCAgICAgICAgICAvLyBHaXRIdWIgQXBwIHVzZXItdG8tc2VydmVyIHRva2Vuc1xuICAgIFJFRlJFU0hfVE9LRU46IC9eZ2hyXy4rJC8sICAgICAgICAgICAgICAvLyBSZWZyZXNoIHRva2Vuc1xuICAgIE9BVVRIX0FDQ0VTU19UT0tFTjogL15naG9fLiskLywgICAgICAgICAvLyBPQXV0aCBkZXZpY2UgZmxvdyB0b2tlbnNcbiAgICAvLyBHZW5lcmljIHBhdHRlcm4gdG8gY2F0Y2ggQUxMIEdpdEh1YiB0b2tlbnMgKGdoICsgYW55IGxldHRlciArIHVuZGVyc2NvcmUgKyBhbnl0aGluZylcbiAgICBHRU5FUklDX0dJVEhVQl9UT0tFTjogL15naFthLXpdXy4rJC9pICAgLy8gQ2F0Y2gtYWxsIGZvciBhbnkgZ2gqXyBwYXR0ZXJuXG4gIH07XG5cbiAgLy8gU2VjdXJlIHN0b3JhZ2UgY29uZmlndXJhdGlvblxuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBUT0tFTl9ESVIgPSBwYXRoLmpvaW4oaG9tZWRpcigpLCAnLmRvbGxob3VzZScsICcuYXV0aCcpO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBUT0tFTl9GSUxFID0gJ2dpdGh1Yl90b2tlbi5lbmMnO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBBTEdPUklUSE0gPSAnYWVzLTI1Ni1nY20nO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBLRVlfTEVOR1RIID0gMzI7XG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IElWX0xFTkdUSCA9IDE2O1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBUQUdfTEVOR1RIID0gMTY7XG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IFNBTFRfTEVOR1RIID0gMzI7XG4gIHByaXZhdGUgc3RhdGljIHJlYWRvbmx5IElURVJ