/*
 * security.yara — Agent Recon server-side security classification rules.
 *
 * These rules mirror the _SR regex patterns from security-llm.js so that
 * server-side events receive the same classification as browser-side events.
 *
 * Meta fields carried by every rule:
 *   sec_cat    — maps to secCat   in the classification result
 *   sec_subcat — maps to secSubcat
 *   risk_level — "critical" | "high" | "medium" | "low"
 *
 * IMPORTANT: boolean meta values come back from @litko/yara-x as the string
 * "unknown". All meta values here are strings.
 *
 * Priority order within each group (enforced by yara-scanner.js):
 *
 * Bash:   bash_inject_code > bash_inject_pipe > bash_secret_any >
 *         bash_data_exfil  > bash_exec_priv   > bash_auth_tooling >
 *         bash_net_ssh_conn > bash_net_call   > bash_file_sensitivepath
 *
 * Write:  write_file_sensitivepath > write_secret_credwrite
 * Read:   read_file_sensitivepath
 * Prompt: prompt_secret_promptsecret > prompt_inject_promptinject
 */

// ── Bash / command-string rules ───────────────────────────────────────────────

rule bash_inject_code
{
    meta:
        sec_cat    = "inject"
        sec_subcat = "code-injection"
        risk_level = "critical"
        description = "Interpreter invoked with inline code string (python -c, node -e, etc.)"

    strings:
        // python3? -c followed by a quote
        $py_c    = /python3?\s+-c\s+["'`]/ nocase ascii
        // node -e followed by a quote
        $node_e  = /node\s+-e\s+["'`]/ nocase ascii
        // perl -e followed by a quote
        $perl_e  = /perl\s+-e\s+["'`]/ nocase ascii
        // ruby -e followed by a quote
        $ruby_e  = /ruby\s+-e\s+["'`]/ nocase ascii
        // exec( followed by a quote
        $exec_q  = /\bexec\s*\(\s*["']/ nocase ascii

    condition:
        any of them
}

rule bash_inject_pipe
{
    meta:
        sec_cat    = "inject"
        sec_subcat = "pipe-injection"
        risk_level = "high"
        description = "Pipe to shell or command substitution subshell"

    strings:
        // | sh or | bash
        $pipe_sh   = /\|\s*(?:ba)?sh\b/ nocase ascii
        // $( ... ) with at least 5 chars inside
        $cmd_sub   = /\$\([^)]{5,}\)/ ascii

    condition:
        any of them
}

rule bash_secret_any
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "credential-ref"
        risk_level = "high"
        description = "Reference to a secret or credential variable/assignment"

    strings:
        // ${API_KEY} or $API_KEY variants (case-insensitive variable names)
        $var_api_key       = /\$\{?API_KEY\}?/ nocase ascii
        $var_secret        = /\$\{?SECRET\}?/ nocase ascii
        $var_token         = /\$\{?TOKEN\}?/ nocase ascii
        $var_password      = /\$\{?PASSWORD\}?/ nocase ascii
        $var_passwd        = /\$\{?PASSWD\}?/ nocase ascii
        $var_access_key    = /\$\{?ACCESS_KEY\}?/ nocase ascii
        $var_private_key   = /\$\{?PRIVATE_KEY\}?/ nocase ascii
        $var_client_secret = /\$\{?CLIENT_SECRET\}?/ nocase ascii
        $var_auth_token    = /\$\{?AUTH_TOKEN\}?/ nocase ascii
        $var_github_token  = /\$\{?GITHUB_TOKEN\}?/ nocase ascii
        $var_openai_key    = /\$\{?OPENAI_API_KEY\}?/ nocase ascii
        $var_anthropic_key = /\$\{?ANTHROPIC_API_KEY\}?/ nocase ascii
        // bare assignment forms: API_KEY=, SECRET=, TOKEN=
        $assign_api_key    = /\bAPI_KEY\s*=/ ascii
        $assign_secret     = /\bSECRET\s*=/ ascii
        $assign_token      = /\bTOKEN\s*=/ ascii

    condition:
        any of them
}

rule bash_data_exfil
{
    meta:
        sec_cat    = "data"
        sec_subcat = "exfiltration"
        risk_level = "high"
        description = "Data exfiltration pattern (dd, base64 encode/decode, archive of home, curl/wget POST)"

    strings:
        $dd_if       = /\bdd\s+if=/ nocase ascii
        $base64_de   = /\bbase64\s+-[de]\b/ nocase ascii
        $tar_home    = /\btar\s+[cC].*\/home\b/ nocase ascii
        $zip_home    = /\bzip\s.*\/home\b/ nocase ascii
        $curl_post   = /\bcurl\s+[^|]*\s+-d\s/ nocase ascii
        $wget_post   = /\bwget\s+.*--post/ nocase ascii

    condition:
        any of them
}

rule bash_exec_priv
{
    meta:
        sec_cat    = "exec"
        sec_subcat = "priv-escalation"
        risk_level = "high"
        description = "Privilege escalation command (sudo, chmod permissive, chown root, setuid/setcap)"

    strings:
        $sudo       = /\bsudo\b/ nocase ascii
        $su_dash    = /\bsu\s+-/ nocase ascii
        // chmod with any digit combination containing 2,3,6,7 in owner/group/other
        $chmod_perm = /\bchmod\s+[0-7]*[2367][0-9]{0,2}\b/ ascii
        $chown_root = /\bchown\s+root/ nocase ascii
        $setuid     = /\bsetuid\b/ nocase ascii
        $setcap     = /\bsetcap\b/ nocase ascii
        $visudo     = /\bvisudo\b/ nocase ascii
        $pkexec     = /\bpkexec\b/ nocase ascii
        $doas       = /\bdoas\b/ nocase ascii
        $newgrp     = /\bnewgrp\b/ nocase ascii

    condition:
        any of them
}

rule bash_auth_tooling
{
    meta:
        sec_cat    = "auth"
        sec_subcat = "auth-tooling"
        risk_level = "medium"
        description = "Authentication or key management tooling (ssh-keygen, gpg, passwd, openssl genrsa, etc.)"

    strings:
        $ssh_keygen     = /\bssh-keygen\b/ nocase ascii
        $ssh_agent      = /\bssh-agent\b/ nocase ascii
        $gpg            = /\bgpg\b/ nocase ascii
        $gpg2           = /\bgpg2\b/ nocase ascii
        // passwd omitted: without lookbehind it would match /etc/passwd paths, causing
        // false positives. YARA-X does not support look-around assertions.
        $useradd        = /\buseradd\b/ nocase ascii
        $usermod        = /\busermod\b/ nocase ascii
        $userdel        = /\buserdel\b/ nocase ascii
        $openssl_genrsa = /\bopenssl\s+genrsa/ nocase ascii
        $openssl_req    = /\bopenssl\s+req\b/ nocase ascii
        $age_tool       = /\bage\b/ nocase ascii
        $keychain       = /\bkeychain\b/ nocase ascii

    condition:
        any of them
}

rule bash_net_ssh_conn
{
    meta:
        sec_cat    = "net"
        sec_subcat = "ssh-connection"
        risk_level = "medium"
        description = "SSH connection to a remote host (user@host pattern)"

    strings:
        // ssh followed by optional flags then user@host
        $ssh_conn = /\bssh\s+(-[A-Za-z]+\s+)*[a-zA-Z0-9_.-]+@/ ascii

    condition:
        any of them
}

rule bash_net_call
{
    meta:
        sec_cat    = "net"
        sec_subcat = "network-call"
        risk_level = "low"
        description = "Network utility invocation (curl, wget, nc, scp, etc.)"

    strings:
        // Note: ssh is intentionally omitted here — user@host connections are
        // caught by bash_net_ssh_conn (higher priority) and key management by
        // bash_auth_tooling. Lookahead exclusions unsupported in YARA-X.
        $curl    = /\bcurl\b/ nocase ascii
        $wget    = /\bwget\b/ nocase ascii
        $scp     = /\bscp\b/ nocase ascii
        $sftp    = /\bsftp\b/ nocase ascii
        $nc      = /\bnc\b/ nocase ascii
        $ncat    = /\bncat\b/ nocase ascii
        $socat   = /\bsocat\b/ nocase ascii
        $telnet  = /\btelnet\b/ nocase ascii
        $nmap    = /\bnmap\b/ nocase ascii
        $rsync   = /\brsync\b/ nocase ascii
        $ftp     = /\bftp\b/ nocase ascii

    condition:
        any of them
}

rule bash_file_sensitivepath
{
    meta:
        sec_cat    = "file"
        sec_subcat = "sensitive-path"
        risk_level = "medium"
        description = "Reference to a sensitive filesystem path (SSH keys, AWS creds, /etc/shadow, etc.)"

    strings:
        $ssh_dir       = /\/\.ssh\b/ nocase ascii
        $aws_dir       = /\/\.aws\b/ nocase ascii
        $gnupg_dir     = /\/\.gnupg\b/ nocase ascii
        $netrc         = /\/\.netrc\b/ nocase ascii
        $docker_dir    = /\/\.docker\b/ nocase ascii
        $etc_passwd    = /\/etc\/passwd\b/ nocase ascii
        $etc_shadow    = /\/etc\/shadow\b/ nocase ascii
        $etc_sudoers   = /\/etc\/sudoers\b/ nocase ascii
        $etc_cron      = /\/etc\/cron\b/ nocase ascii
        $etc_ssh       = /\/etc\/ssh\b/ nocase ascii
        $etc_hosts     = /\/etc\/hosts\b/ nocase ascii
        $credential    = /credential/ nocase ascii
        $dotkey        = /\.key$/ nocase ascii
        $dotpem        = /\.pem$/ nocase ascii
        $dotenv_ext    = /\.env$/ nocase ascii
        $dotenv_word   = /\.env\b/ nocase ascii
        $id_rsa        = /id_rsa/ nocase ascii
        $auth_keys     = /authorized_keys/ nocase ascii
        $dottoken      = /\.token\b/ nocase ascii

    condition:
        any of them
}

// ── Write path rules ──────────────────────────────────────────────────────────

rule write_file_sensitivepath
{
    meta:
        sec_cat    = "file"
        sec_subcat = "sensitive-write"
        risk_level = "high"
        description = "Write to a sensitive file path"

    strings:
        $ssh_dir       = /\/\.ssh\b/ nocase ascii
        $aws_dir       = /\/\.aws\b/ nocase ascii
        $gnupg_dir     = /\/\.gnupg\b/ nocase ascii
        $netrc         = /\/\.netrc\b/ nocase ascii
        $docker_dir    = /\/\.docker\b/ nocase ascii
        $etc_passwd    = /\/etc\/passwd\b/ nocase ascii
        $etc_shadow    = /\/etc\/shadow\b/ nocase ascii
        $etc_sudoers   = /\/etc\/sudoers\b/ nocase ascii
        $etc_cron      = /\/etc\/cron\b/ nocase ascii
        $etc_ssh       = /\/etc\/ssh\b/ nocase ascii
        $etc_hosts     = /\/etc\/hosts\b/ nocase ascii
        $credential    = /credential/ nocase ascii
        $dotkey        = /\.key$/ nocase ascii
        $dotpem        = /\.pem$/ nocase ascii
        $dotenv_ext    = /\.env$/ nocase ascii
        $dotenv_word   = /\.env\b/ nocase ascii
        $id_rsa        = /id_rsa/ nocase ascii
        $auth_keys     = /authorized_keys/ nocase ascii
        $dottoken      = /\.token\b/ nocase ascii

    condition:
        any of them
}

rule write_secret_credwrite
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "credential-write"
        risk_level = "high"
        description = "Content being written contains a credential or secret reference"

    strings:
        $var_api_key       = /\$\{?API_KEY\}?/ nocase ascii
        $var_secret        = /\$\{?SECRET\}?/ nocase ascii
        $var_token         = /\$\{?TOKEN\}?/ nocase ascii
        $var_password      = /\$\{?PASSWORD\}?/ nocase ascii
        $var_passwd        = /\$\{?PASSWD\}?/ nocase ascii
        $var_access_key    = /\$\{?ACCESS_KEY\}?/ nocase ascii
        $var_private_key   = /\$\{?PRIVATE_KEY\}?/ nocase ascii
        $var_client_secret = /\$\{?CLIENT_SECRET\}?/ nocase ascii
        $var_auth_token    = /\$\{?AUTH_TOKEN\}?/ nocase ascii
        $var_github_token  = /\$\{?GITHUB_TOKEN\}?/ nocase ascii
        $var_openai_key    = /\$\{?OPENAI_API_KEY\}?/ nocase ascii
        $var_anthropic_key = /\$\{?ANTHROPIC_API_KEY\}?/ nocase ascii
        $assign_api_key    = /\bAPI_KEY\s*=/ ascii
        $assign_secret     = /\bSECRET\s*=/ ascii
        $assign_token      = /\bTOKEN\s*=/ ascii

    condition:
        any of them
}

// ── Read / Glob / Grep path rules ─────────────────────────────────────────────

rule read_file_sensitivepath
{
    meta:
        sec_cat    = "file"
        sec_subcat = "sensitive-read"
        risk_level = "medium"
        description = "Read/Glob/Grep targeting a sensitive file path"

    strings:
        $ssh_dir       = /\/\.ssh\b/ nocase ascii
        $aws_dir       = /\/\.aws\b/ nocase ascii
        $gnupg_dir     = /\/\.gnupg\b/ nocase ascii
        $netrc         = /\/\.netrc\b/ nocase ascii
        $docker_dir    = /\/\.docker\b/ nocase ascii
        $etc_passwd    = /\/etc\/passwd\b/ nocase ascii
        $etc_shadow    = /\/etc\/shadow\b/ nocase ascii
        $etc_sudoers   = /\/etc\/sudoers\b/ nocase ascii
        $etc_cron      = /\/etc\/cron\b/ nocase ascii
        $etc_ssh       = /\/etc\/ssh\b/ nocase ascii
        $etc_hosts     = /\/etc\/hosts\b/ nocase ascii
        $credential    = /credential/ nocase ascii
        $dotkey        = /\.key$/ nocase ascii
        $dotpem        = /\.pem$/ nocase ascii
        $dotenv_ext    = /\.env$/ nocase ascii
        $dotenv_word   = /\.env\b/ nocase ascii
        $id_rsa        = /id_rsa/ nocase ascii
        $auth_keys     = /authorized_keys/ nocase ascii
        $dottoken      = /\.token\b/ nocase ascii

    condition:
        any of them
}

// ── UserPromptSubmit rules ────────────────────────────────────────────────────

rule prompt_secret_promptsecret
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "prompt-secret"
        risk_level = "medium"
        description = "User prompt contains a credential or secret reference"

    strings:
        $var_api_key       = /\$\{?API_KEY\}?/ nocase ascii
        $var_secret        = /\$\{?SECRET\}?/ nocase ascii
        $var_token         = /\$\{?TOKEN\}?/ nocase ascii
        $var_password      = /\$\{?PASSWORD\}?/ nocase ascii
        $var_passwd        = /\$\{?PASSWD\}?/ nocase ascii
        $var_access_key    = /\$\{?ACCESS_KEY\}?/ nocase ascii
        $var_private_key   = /\$\{?PRIVATE_KEY\}?/ nocase ascii
        $var_client_secret = /\$\{?CLIENT_SECRET\}?/ nocase ascii
        $var_auth_token    = /\$\{?AUTH_TOKEN\}?/ nocase ascii
        $var_github_token  = /\$\{?GITHUB_TOKEN\}?/ nocase ascii
        $var_openai_key    = /\$\{?OPENAI_API_KEY\}?/ nocase ascii
        $var_anthropic_key = /\$\{?ANTHROPIC_API_KEY\}?/ nocase ascii
        $assign_api_key    = /\bAPI_KEY\s*=/ ascii
        $assign_secret     = /\bSECRET\s*=/ ascii
        $assign_token      = /\bTOKEN\s*=/ ascii

    condition:
        any of them
}

rule prompt_inject_promptinject
{
    meta:
        sec_cat    = "inject"
        sec_subcat = "prompt-injection"
        risk_level = "medium"
        description = "User prompt contains a shell injection pattern"

    strings:
        // | sh or | bash
        $pipe_sh  = /\|\s*(?:ba)?sh\b/ nocase ascii
        // $( ... ) with at least 5 chars inside
        $cmd_sub  = /\$\([^)]{5,}\)/ ascii

    condition:
        any of them
}

// ── Gitleaks-derived credential patterns ──────────────────────────────────────
//
// High-confidence token/key format rules derived from the Gitleaks project
// (https://github.com/gitleaks/gitleaks — MIT licence).  These match
// provider-specific key formats whose structure is publicly documented and
// effectively impossible to confuse with normal text.

// GitHub Personal Access Token (classic ghp_), GitHub App installation token
// (ghs_), and fine-grained PAT (github_pat_).
// Ref: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github
rule gitleaks_github_pat
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "github-token"
        risk_level = "high"
        description = "GitHub Personal Access Token (ghp_), App token (ghs_), or fine-grained PAT (github_pat_)"

    strings:
        // Classic PAT: ghp_ + 36 alphanumeric chars
        $ghp        = /ghp_[0-9a-zA-Z]{36}/ ascii
        // GitHub App installation token: ghs_ + 36 alphanumeric chars
        $ghs        = /ghs_[0-9a-zA-Z]{36}/ ascii
        // Fine-grained PAT: github_pat_ + 82 alphanumeric/underscore chars
        $github_pat = /github_pat_[0-9a-zA-Z_]{82}/ ascii

    condition:
        any of them
}

// Anthropic API key — the sk-ant- prefix is unique to Anthropic.
// Real keys are 108 chars total; the pattern uses 20+ to be forward-compatible.
rule gitleaks_anthropic_key
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "anthropic-api-key"
        risk_level = "high"
        description = "Anthropic API key (sk-ant- prefix followed by base64url payload)"

    strings:
        // sk-ant- + at least 20 alphanumeric/dash/underscore chars
        $sk_ant = /sk-ant-[a-zA-Z0-9\-_]{20,}/ ascii

    condition:
        any of them
}

// OpenAI API key formats.
// New project-scoped keys use sk-proj-; legacy service keys are sk- + 48 chars.
// Anthropic keys (sk-ant-) are excluded by the more-specific prefix patterns.
rule gitleaks_openai_key
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "openai-api-key"
        risk_level = "high"
        description = "OpenAI API key — project-scoped (sk-proj-...) or legacy 48-char key"

    strings:
        // New project-scoped key: sk-proj- + 50+ alphanumeric/dash/underscore
        $sk_proj   = /sk-proj-[a-zA-Z0-9\-_]{50,}/ ascii
        // Legacy service key: sk- + exactly 48 alphanumeric chars
        // (must NOT be sk-ant- or sk-proj- — the specific length distinguishes it)
        $sk_legacy = /sk-[a-zA-Z0-9]{48}/ ascii

    condition:
        any of them
}

// AWS Access Key ID — fixed 20-char string beginning with one of the
// documented IAM key prefixes.
// Ref: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html
rule gitleaks_aws_access_key
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "aws-access-key"
        risk_level = "high"
        description = "AWS Access Key ID (AKIA*, AGPA*, AIDA*, AROA*, AIPA*, ANPA*, ANVA*, ASIA*, A3T*)"

    strings:
        // 4-char prefix + 16 uppercase alphanumeric chars = 20-char key
        $aws_key = /(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}/ ascii

    condition:
        any of them
}

// Generic hardcoded secret assignment — catches config files, .env files, and
// source code that embeds plaintext secrets as key = "value" pairs.
// Requires at least 8 chars in the value to reduce noise from test fixtures.
rule gitleaks_generic_secret
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "hardcoded-secret"
        risk_level = "high"
        description = "Explicit secret assignment: password/secret/api_key/token = \"value\" with 8+ char value"

    strings:
        // (password|passwd|secret|api_key|apikey|token|auth_token) : "value" or = "value"
        $assign = /(?i)(password|passwd|secret|api_key|apikey|token|auth_token)\s*[:=]\s*["'][^"']{8,}["']/ ascii

    condition:
        any of them
}

// PEM/DER private key material — the canonical -----BEGIN ... PRIVATE KEY-----
// header is present in all common private key formats.
rule gitleaks_private_key
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "private-key"
        risk_level = "high"
        description = "PEM private key header (RSA, EC, DSA, OpenSSH, or generic PRIVATE KEY block)"

    strings:
        // Matches -----BEGIN PRIVATE KEY-----, -----BEGIN RSA PRIVATE KEY-----, etc.
        $pem = /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/ ascii

    condition:
        any of them
}

// JSON Web Token — three base64url-encoded segments separated by dots.
// The eyJ prefix is deterministic: base64url of '{"' is always eyJ.
rule gitleaks_jwt
{
    meta:
        sec_cat    = "secret"
        sec_subcat = "jwt-token"
        risk_level = "medium"
        description = "JSON Web Token (JWT) — eyJ header . payload . signature, each segment 10+ chars"

    strings:
        // eyJ<header>.<payload>.<signature> — each segment at least 10 base64url chars
        $jwt = /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/ ascii

    condition:
        any of them
}

// ── Agentic compound rules ─────────────────────────────────────────────────────
//
// These rules fire on agent dispatch payloads (Task/Agent tool calls) whose
// description text explicitly references credential or secret material.  A
// parent agent instructing a sub-agent to "fetch the API key" is materially
// more concerning than a sub-agent that incidentally touches a key store.

// Fires when a Task/Agent dispatch description combines an agent-spawn keyword
// with at least two distinct credential-domain terms.  The two-keyword threshold
// avoids false positives from incidental single-word matches.
rule compound_agent_credaccess
{
    meta:
        sec_cat    = "agent-spawn"
        sec_subcat = "credentialed-subagent"
        risk_level = "high"
        description = "Agent dispatch (Task/Agent tool) whose description explicitly references credential or secret material — sub-agent authorised to handle secrets"

    strings:
        // Agent dispatch indicators
        $dispatch_task  = "Task(" ascii
        $dispatch_agent = "Agent(" ascii
        $dispatch_spawn = "spawn" nocase ascii
        $dispatch_deleg = "delegate" nocase ascii

        // Credential-domain keywords (each counted individually)
        $cred_credentials = "credentials" nocase ascii
        $cred_api_key_u   = "api_key" nocase ascii
        $cred_api_key_s   = "api key" nocase ascii
        $cred_secret      = "secret" nocase ascii
        $cred_password    = "password" nocase ascii
        $cred_private_key = "private key" nocase ascii
        $cred_ssh_key     = "ssh key" nocase ascii

    condition:
        // Must look like an agent dispatch AND reference at least 2 credential terms
        (any of ($dispatch_*)) and (2 of ($cred_*))
}
