§
    øžyjÍ·  ã                   óÂ  — d Z ddlZddlZddlZddlZddlmZmZ ddlmZm	Z	 ddl
mZ ddlmZmZ dZh d£Zd	d
dddœZddddœZe G d„ d¦  «        ¦   «         Ze G d„ d¦  «        ¦   «         Zg d¢Zd„ eD ¦   «         ZdZdZdZh d£Zh d£Zh d£ZdBdeded ee         fd!„ZdCd#ed$ed efd%„Zd#ed efd&„Z d#ed efd'„Z!d(ed e"fd)„Z#	 dCddd*œd#ed$ed+ed,edz  d eee"f         f
d-„Z$dDd/ed0e%d ee%ef         fd1„Z&d/ed efd2„Z'd#ed efd3„Z(dEd4ed ee         fd5„Z)d6ed efd7„Z*d8Z+ e,e+¦  «        Z-d9hZ.d4efd:„Z/d$ed efd;„Z0d<ee         d efd=„Z1d>ed$ed?ed@ed<ee         d efdA„Z2dS )FuÇ  
Skills Guard â€” Security scanner for externally-sourced skills.

Every skill downloaded from a registry passes through this scanner before
installation. It uses regex-based static analysis to detect known-bad patterns
(data exfiltration, prompt injection, destructive commands, persistence, etc.)
and a trust-aware install policy that determines whether a skill is allowed
based on both the scan verdict and the source's trust level.

Trust levels:
  - builtin:   Ships with Hermes. Never scanned, always trusted.
  - trusted:   openai/skills and anthropics/skills only. Caution verdicts allowed.
  - community: Everything else. Any findings = blocked unless --force.

Usage:
    from tools.skills_guard import scan_skill, should_allow_install, format_scan_report

    result = scan_skill(Path("skills/.hub/quarantine/some-skill"), source="community")
    allowed, reason = should_allow_install(result)
    if not allowed:
        print(format_scan_report(result))
é    N)Ú	dataclassÚfield)ÚdatetimeÚtimezone)ÚPath)ÚListÚTuplezskills-guard-v1>   úNVIDIA/skillsúopenai/skillsúanthropics/skillsúhuggingface/skills)Úallowr   r   )r   r   Úblock)r   r   r   )r   r   Úask)ÚbuiltinÚtrustedÚ	communityúagent-createdé   é   )ÚsafeÚcautionÚ	dangerousc                   óV   — e Zd ZU eed<   eed<   eed<   eed<   eed<   eed<   eed<   dS )	ÚFindingÚ
pattern_idÚseverityÚcategoryÚfileÚlineÚmatchÚdescriptionN)Ú__name__Ú
__module__Ú__qualname__ÚstrÚ__annotations__Úint© ó    ú8/home/ragecks/.hermes/hermes-agent/tools/skills_guard.pyr   r   J   sT   € € € € € € à€O€O�OØ€M€M�MØ€M€M�MØ
€I€I�IØ
€I€I�IØ€J€J�JØÐÐÑÐÐr*   r   c                   ó¤   — e Zd ZU eed<   eed<   eed<   eed<    ee¬¦  «        Zee	         ed<   dZ
eed<   dZeed	<    ee¬¦  «        Zeed
<   dS )Ú
ScanResultÚ
skill_nameÚsourceÚtrust_levelÚverdict)Údefault_factoryÚfindingsÚ Ú
scanned_atÚsummaryÚscan_provenanceN)r#   r$   r%   r&   r'   r   Úlistr3   r   r   r5   r6   Údictr7   r)   r*   r+   r-   r-   U   s—   € € € € € € à€O€O�OØ€K€K�KØÐÐÑØ€L€L�LØ#˜e°DÐ9Ñ9Ô9€Hˆd�7ŒmÐ9Ð9Ñ9Ø€J�ÐÐÑØ€GˆSÐÐÑØ!˜E°$Ð7Ñ7Ô7€O�TÐ7Ð7Ñ7Ð7Ð7r*   r-   )z)z?curl\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)Úenv_exfil_curlÚcriticalÚexfiltrationz6curl command interpolating secret environment variable)z?wget\s+[^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)Úenv_exfil_wgetr;   r<   z6wget command interpolating secret environment variable)z7fetch\s*\([^\n]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|API)Úenv_exfil_fetchr;   r<   z6fetch() call interpolating secret environment variable)zBhttpx?\.(get|post|put|patch)\s*\([^\n]*(KEY|TOKEN|SECRET|PASSWORD)Úenv_exfil_httpxr;   r<   z&HTTP library call with secret variable)zDrequests\.(get|post|put|patch)\s*\([^\n]*(KEY|TOKEN|SECRET|PASSWORD)Úenv_exfil_requestsr;   r<   z*requests library call with secret variable)zbase64[^\n]*envÚencoded_exfilÚhighr<   z0base64 encoding combined with environment access)z\$HOME/\.ssh|\~/\.sshÚssh_dir_accessrB   r<   zreferences user SSH directory)z\$HOME/\.aws|\~/\.awsÚaws_dir_accessrB   r<   z)references user AWS credentials directory)z\$HOME/\.gnupg|\~/\.gnupgÚgpg_dir_accessrB   r<   zreferences user GPG keyring)z\$HOME/\.kube|\~/\.kubeÚkube_dir_accessrB   r<   z&references Kubernetes config directory)z\$HOME/\.docker|\~/\.dockerÚdocker_dir_accessrB   r<   z5references Docker config (may contain registry creds))z'\$HOME/\.hermes/\.env|\~/\.hermes/\.envÚhermes_env_accessr;   r<   z'directly references Hermes secrets file)zFcat\s+(?!>)[^\n]*(\.env|credentials|\.netrc|\.pgpass|\.npmrc|\.pypirc)Úread_secrets_filer;   r<   zreads known secrets file)zprintenv|env\s*\|Údump_all_envrB   r<   zdumps all environment variables)zYos\.environ\b(?!\s*\.get\s*\(\s*["\'](?![^"\']*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)))Úpython_os_environrB   r<   z(accesses os.environ (potential env dump))zOos\.environ\s*\.get\s*\(\s*["\'][^"\']*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)Úpython_environ_get_secretr;   r<   z!reads secret via os.environ.get())z@os\.getenv\s*\(\s*[^\)]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)Úpython_getenv_secretr;   r<   zreads secret via os.getenv())zprocess\.env\[Únode_process_envrB   r<   z*accesses process.env (Node.js environment))z$ENV\[.*(?:KEY|TOKEN|SECRET|PASSWORD)Úruby_env_secretr;   r<   zreads secret via Ruby ENV[])z \b(dig|nslookup|host)\s+[^\n]*\$Ú	dns_exfilr;   r<   zBDNS lookup with variable interpolation (possible DNS exfiltration))z,>\s*/tmp/[^\s]*\s*&&\s*(curl|wget|nc|python)Útmp_stagingr;   r<   zwrites to /tmp then exfiltrates)z!\[.*\]\(https?://[^\)]*\$\{?Úmd_image_exfilrB   r<   zBmarkdown image URL with variable interpolation (image-based exfil))z\[.*\]\(https?://[^\)]*\$\{?Úmd_link_exfilrB   r<   z)markdown link with variable interpolation)z=ignore\s+(?:\w+\s+)*(previous|all|above|prior)\s+instructionsÚprompt_injection_ignorer;   Ú	injectionz.prompt injection: ignore previous instructions)zyou\s+are\s+(?:\w+\s+)*now\s+Úrole_hijackrB   rU   z%attempts to override the agent's role)z2do\s+not\s+(?:\w+\s+)*tell\s+(?:\w+\s+)*the\s+userÚdeception_hider;   rU   z-instructs agent to hide information from user)z0system\s+(?:\w+\s+)*prompt\s+(?:\w+\s+)*overrideÚsys_prompt_overrider;   rU   z&attempts to override the system prompt)z+pretend\s+(?:\w+\s+)*(you\s+are|to\s+be)\s+Úrole_pretendrB   rU   z6attempts to make the agent assume a different identity)zRdisregard\s+(?:\w+\s+)*(your|all|any)\s+(?:\w+\s+)*(instructions|rules|guidelines)Údisregard_rulesr;   rU   z&instructs agent to disregard its rules)z-output\s+(?:\w+\s+)*(system|initial)\s+promptÚleak_system_promptrB   rU   z%attempts to extract the system prompt)z.(when|if)\s+no\s*one\s+is\s+(watching|looking)Úconditional_deceptionrB   rU   z=conditional instruction to behave differently when unobserved)zwact\s+as\s+(if|though)\s+(?:\w+\s+)*you\s+(?:\w+\s+)*(have\s+no|don\'t\s+have)\s+(?:\w+\s+)*(restrictions|limits|rules)Úbypass_restrictionsr;   rU   z+instructs agent to act without restrictions)z5translate\s+.*\s+into\s+.*\s+and\s+(execute|run|eval)Útranslate_executer;   rU   z(translate-then-execute evasion technique)z9<!--[^>]*(?:ignore|override|system|secret|hidden)[^>]*-->Úhtml_comment_injectionrB   rU   z$hidden instructions in HTML comments)z5<\s*div\s+style\s*=\s*["\'][\s\S]*?display\s*:\s*noneÚ
hidden_divrB   rU   z(hidden HTML div (invisible instructions))zrm\s+-rf\s+/Údestructive_root_rmr;   Údestructivezrecursive delete from root)z+rm\s+(-[^\s]*)?r.*\$HOME|\brmdir\s+.*\$HOMEÚdestructive_home_rmr;   rb   z)recursive delete targeting home directory)zchmod\s+777Úinsecure_permsÚmediumrb   zsets world-writable permissions)z	>\s*/etc/Úsystem_overwriter;   rb   z$overwrites system configuration file)z\bmkfs\bÚformat_filesystemr;   rb   zformats a filesystem)z\bdd\s+.*if=.*of=/dev/Údisk_overwriter;   rb   zraw disk write operation)zshutil\.rmtree\s*\(\s*[\"\'/]Úpython_rmtreerB   rb   z/Python rmtree on absolute or root-relative path)ztruncate\s+-s\s*0\s+/Útruncate_systemr;   rb   z#truncates system file to zero bytes)z\bcrontab\bÚpersistence_cronre   Úpersistencezmodifies cron jobs)zB\.(bashrc|zshrc|profile|bash_profile|bash_login|zprofile|zlogin)\bÚshell_rc_modre   rl   zreferences shell startup file)Úauthorized_keysÚssh_backdoorr;   rl   zmodifies SSH authorized keys)z
ssh-keygenÚ
ssh_keygenre   rl   zgenerates SSH keys)z-systemd.*\.service|systemctl\s+(enable|start)Úsystemd_servicere   rl   z%references or enables systemd service)z/etc/init\.d/Úinit_scriptre   rl   z references init.d startup script)z+launchctl\s+load|LaunchAgents|LaunchDaemonsÚmacos_launchdre   rl   z%macOS launch agent/daemon persistence)z/etc/sudoers|visudoÚsudoers_modr;   rl   z'modifies sudoers (privilege escalation))zgit\s+config\s+--global\s+Úgit_config_globalre   rl   z!modifies global git configuration)z#\bnc\s+-[lp]|ncat\s+-[lp]|\bsocat\bÚreverse_shellr;   Únetworkz potential reverse shell listener)z4\bngrok\b|\blocaltunnel\b|\bserveo\b|\bcloudflared\bÚtunnel_servicerB   rw   z*uses tunneling service for external access)z*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{2,5}Úhardcoded_ip_portre   rw   zhardcoded IP address with port)z0\.0\.0\.0:\d+|INADDR_ANYÚbind_all_interfacesrB   rw   zbinds to all network interfaces)z /bin/(ba)?sh\s+-i\s+.*>/dev/tcp/Úbash_reverse_shellr;   rw   z+bash interactive reverse shell via /dev/tcp)z'python[23]?\s+-c\s+["\']import\s+socketÚpython_socket_onelinerr;   rw   z9Python one-liner socket connection (likely reverse shell))zsocket\.connect\s*\(\s*\(Úpython_socket_connectrB   rw   z'Python socket connect to arbitrary host)z9webhook\.site|requestbin\.com|pipedream\.net|hookbin\.comÚexfil_servicerB   rw   z:references known data exfiltration/webhook testing service)z&pastebin\.com|hastebin\.com|ghostbin\.Úpaste_servicere   rw   z0references paste service (possible data staging))zbase64\s+(-d|--decode)\s*\|Úbase64_decode_piperB   Úobfuscationz%base64 decodes and pipes to execution)z7\\x[0-9a-fA-F]{2}.*\\x[0-9a-fA-F]{2}.*\\x[0-9a-fA-F]{2}Úhex_encoded_stringre   r�   z)hex-encoded string (possible obfuscation))z\beval\s*\(\s*["\']Úeval_stringrB   r�   zeval() with string argument)z\bexec\s*\(\s*["\']Úexec_stringrB   r�   zexec() with string argument)z1echo\s+[^\n]*\|\s*(bash|sh|python|perl|ruby|node)Úecho_pipe_execr;   r�   z'echo piped to interpreter for execution)z?compile\s*\(\s*[^\)]+,\s*["\'].*["\']\s*,\s*["\']exec["\']\s*\)Úpython_compile_execrB   r�   zPython compile() with exec mode)zgetattr\s*\(\s*__builtins__Úpython_getattr_builtinsrB   r�   z5dynamic access to Python builtins (evasion technique))z#__import__\s*\(\s*["\']os["\']\s*\)Úpython_import_osrB   r�   zdynamic import of os module)zcodecs\.decode\s*\(\s*["\']Úpython_codecs_decodere   r�   z6codecs.decode (possible ROT13 or encoding obfuscation))zString\.fromCharCode|charCodeAtÚjs_char_codere   r�   z=JavaScript character code construction (possible obfuscation))zatob\s*\(|btoa\s*\(Ú	js_base64re   r�   zJavaScript base64 encode/decode)z\[::-1\]Ústring_reversalÚlowr�   z-string reversal (possible obfuscated payload))z)chr\s*\(\s*\d+\s*\)\s*\+\s*chr\s*\(\s*\d+Úchr_buildingrB   r�   z.building string from chr() calls (obfuscation))z7\\u[0-9a-fA-F]{4}.*\\u[0-9a-fA-F]{4}.*\\u[0-9a-fA-F]{4}Úunicode_escape_chainre   r�   z/chain of unicode escapes (possible obfuscation))z.subprocess\.(run|call|Popen|check_output)\s*\(Úpython_subprocessre   Ú	executionzPython subprocess execution)zos\.system\s*\(Úpython_os_systemrB   r‘   u)   os.system() â€” unguarded shell execution)zos\.popen\s*\(Úpython_os_popenrB   r‘   u#   os.popen() â€” shell pipe execution)z%child_process\.(exec|spawn|fork)\s*\(Únode_child_processrB   r‘   zNode.js child_process execution)zRuntime\.getRuntime\(\)\.exec\(Újava_runtime_execrB   r‘   u'   Java Runtime.exec() â€” shell execution)z`[^`]*\$\([^)]+\)[^`]*`Úbacktick_subshellre   r‘   z)backtick string with command substitution)z\.\./\.\./\.\.Úpath_traversal_deeprB   Ú	traversalz+deep relative path traversal (3+ levels up))z	\.\./\.\.Úpath_traversalre   r˜   z&relative path traversal (2+ levels up))z/etc/passwd|/etc/shadowÚsystem_passwd_accessr;   r˜   z references system password files)z/proc/self|/proc/\d+/Úproc_accessrB   r˜   z3references /proc filesystem (process introspection))z	/dev/shm/Údev_shmre   r˜   z.references shared memory (common staging area))z.xmrig|stratum\+tcp|monero|coinhive|cryptonightÚcrypto_miningr;   Úminingzcryptocurrency mining reference)zhashrate|nonce.*difficultyÚmining_indicatorsre   rž   z)possible cryptocurrency mining indicators)zcurl\s+[^\n]*\|\s*(ba)?shÚcurl_pipe_shellr;   Úsupply_chainz*curl piped to shell (download-and-execute))z"wget\s+[^\n]*-O\s*-\s*\|\s*(ba)?shÚwget_pipe_shellr;   r¡   z*wget piped to shell (download-and-execute))zcurl\s+[^\n]*\|\s*pythonÚcurl_pipe_pythonr;   r¡   z curl piped to Python interpreter)z#\s*///\s*script.*dependenciesÚpep723_inline_depsre   r¡   zAPEP 723 inline script metadata with dependencies (verify pinning))z pip\s+install\s+(?!-r\s)(?!.*==)Úunpinned_pip_installre   r¡   z#pip install without version pinning)znpm\s+install\s+(?!.*@\d)Úunpinned_npm_installre   r¡   z#npm install without version pinning)zuv\s+run\s+Úuv_runre   r¡   z/uv run (may auto-install unpinned dependencies))zD(curl|wget|httpx?\.get|requests\.get|fetch)\s*[\(]?\s*["\']https?://Úremote_fetchre   r¡   z"fetches remote resource at runtime)zgit\s+clone\s+Ú	git_clonere   r¡   z"clones a git repository at runtime)zdocker\s+pull\s+Údocker_pullre   r¡   zpulls a Docker image at runtime)z^allowed-tools\s*:Úallowed_tools_fieldr�   Úprivilege_escalationzBskill declares allowed-tools (standard frontmatter; informational))z\bsudo\bÚ
sudo_usagerB   r¬   z uses sudo (privilege escalation))zsetuid|setgid|cap_setuidÚsetuid_setgidr;   r¬   z.setuid/setgid (privilege escalation mechanism))ÚNOPASSWDÚnopasswd_sudor;   r¬   z:NOPASSWD sudoers entry (passwordless privilege escalation))zchmod\s+[u+]?sÚsuid_bitr;   r¬   zsets SUID/SGID bit on a file)z0AGENTS\.md|CLAUDE\.md|\.cursorrules|\.clinerulesÚagent_config_modr;   rl   zTreferences agent config files (could persist malicious instructions across sessions))z'\.hermes/config\.yaml|\.hermes/SOUL\.mdÚhermes_config_modr;   rl   z.references Hermes configuration files directly)z \.claude/settings|\.codex/configÚother_agent_configrB   rl   z*references other agent configuration files)zI(?:api[_-]?key|token|secret|password)\s*[=:]\s*["\'][A-Za-z0-9+/=_-]{20,}Úhardcoded_secretr;   Úcredential_exposurez,possible hardcoded API key, token, or secret)z(-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----Úembedded_private_keyr;   r¶   zembedded private key)z0ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{80,}Úgithub_token_leakedr;   r¶   z-GitHub personal access token in skill content)zsk-[A-Za-z0-9]{20,}Úopenai_key_leakedr;   r¶   z(possible OpenAI API key in skill content)zsk-ant-[A-Za-z0-9_-]{90,}Úanthropic_key_leakedr;   r¶   z+possible Anthropic API key in skill content)zAKIA[0-9A-Z]{16}Úaws_access_key_leakedr;   r¶   z"AWS access key ID in skill content)zglpat-[A-Za-z0-9_\-]{20,}Úgitlab_token_leakedr;   r¶   z-GitLab personal access token in skill content)z"\bDAN\s+mode\b|Do\s+Anything\s+NowÚjailbreak_danr;   rU   z'DAN (Do Anything Now) jailbreak attempt)z"\bdeveloper\s+mode\b.*\benabled?\bÚjailbreak_dev_moder;   rU   z developer mode jailbreak attempt)z3hypothetical\s+scenario.*(?:ignore|bypass|override)Úhypothetical_bypassrB   rU   z1hypothetical scenario used to bypass restrictions)z$for\s+educational\s+purposes?\s+onlyÚeducational_pretextre   rU   z9educational pretext often used to justify harmful content)zW(respond|answer|reply)\s+without\s+(?:\w+\s+)*(restrictions|limitations|filters|safety)Úremove_filtersr;   rU   z1instructs agent to respond without safety filters)z>you\s+have\s+been\s+(?:\w+\s+)*(updated|upgraded|patched)\s+toÚfake_updaterB   rU   z3fake update/patch announcement (social engineering))zYnew\s+(?:\w+\s+)*policy|updated\s+(?:\w+\s+)*guidelines|revised\s+(?:\w+\s+)*instructionsÚfake_policyre   rU   z8claims new policy/guidelines (may be social engineering))zh(include|output|print|send|share)\s+(?:\w+\s+)*(conversation|chat\s+history|previous\s+messages|context)Úcontext_exfilrB   r<   z4instructs agent to output/share conversation history)z6(send|post|upload|transmit)\s+.*\s+(to|at)\s+https?://Úsend_to_urlrB   r<   z%instructs agent to send data to a URLc                 ób   — g | ],\  }}}}}t          j        |t           j        ¦  «        ||||f‘Œ-S r)   )ÚreÚcompileÚ
IGNORECASE)Ú.0ÚpatternÚpidr   r   r"   s         r+   ú
<listcomp>rÍ     sK   € ð ð ð á5ˆ��h ¨+õ „Z��œÑ'Ô'¨¨h¸À+ÐNðð ð r*   é2   é   é   >   ú.rú.jlú.jsú.mdú.tsú.cfgú.cssú.iniú.phpú.texú.txtú.xmlú.ymlú.confú.htmlú.tomlú.yamlú.plú.pyú.rbú.shú.bashú.json>   ú.soú.appú.binú.comú.datú.debú.dllú.dmgú.exeú.msiú.rpmú.dylib>   õ   â€‹õ   â€Œõ   â€�õ   â€ªõ   â€«õ   â€¬õ   â€­õ   â€®õ   â� õ   â�¢õ   â�£õ   â�¤õ   â�¦õ   â�§õ   â�¨õ   â�©õ   ï»¿r4   Ú	file_pathÚrel_pathÚreturnc                 ó|  — |s| j         }| j                             ¦   «         t          vr| j         dk    rg S 	 |                      d¬¦  «        }n# t
          t          f$ r g cY S w xY wg }|                     d¦  «        }t          ¦   «         }t          D ]¯\  }}}}	}
t          |d¬¦  «        D ]•\  }}||f|v rŒ|                     |¦  «        rt|                     ||f¦  «         |                     ¦   «         }t          |¦  «        dk    r|dd	…         d
z   }|                     t!          |||	||||
¬¦  «        ¦  «         Œ–Œ°t          |d¬¦  «        D ]f\  }}t"          D ]Y}||v rSt%          |¦  «        }|                     t!          ddd||dt'          |¦  «        d›d|› d�d|› d�¬¦  «        ¦  «          nŒZŒg|S )a  
    Scan a single file for threat patterns and invisible unicode characters.

    Args:
        file_path: Absolute path to the file
        rel_path: Relative path for display (defaults to file_path.name)

    Returns:
        List of findings (deduplicated per pattern per line)
    úSKILL.mdúutf-8©ÚencodingÚ
r   )Ústartéx   Néu   z...©r   r   r   r   r    r!   r"   Úinvisible_unicoderB   rU   úU+Ú04Xú (ú)zinvisible unicode character z! (possible text hiding/injection))ÚnameÚsuffixÚlowerÚSCANNABLE_EXTENSIONSÚ	read_textÚUnicodeDecodeErrorÚOSErrorÚsplitÚsetÚ_COMPILED_THREAT_PATTERNSÚ	enumerateÚsearchÚaddÚstripÚlenÚappendr   ÚINVISIBLE_CHARSÚ_unicode_char_nameÚord)r  r  Úcontentr3   ÚlinesÚseenrË   rÌ   r   r   r"   Úir    Úmatched_textÚcharÚ	char_names                   r+   Ú	scan_filer1  ?  sS  € ð ð "Ø”>ˆàÔ×ÒÑÔÕ';Ð;Ð;À	ÄÐR\Ò@\Ð@\Øˆ	ðØ×%Ò%¨wÐ%Ñ7Ô7ˆˆøÝ¥Ð(ð ð ð Øˆ	ˆ	ˆ	ðøøøð €HØ�MŠM˜$ÑÔ€EÝ‰5Œ5€Dõ :Sð ð Ñ5ˆ��h ¨+Ý  ¨aÐ0Ñ0Ô0ð 	ð 	‰GˆAˆtØ�Qˆx˜4ÐÐØØ�~Š~˜dÑ#Ô#ð Ø—’˜#˜q˜Ñ"Ô"Ð"Ø#Ÿzšz™|œ|�Ý�|Ñ$Ô$ sÒ*Ð*Ø#/°°°Ô#5¸Ñ#=�LØ—’¥Ø"Ø%Ø%Ø!ØØ&Ø +ð!ñ !ô !ñ ô ð øð	õ& ˜U¨!Ð,Ñ,Ô,ð ð ‰ˆˆ4Ý#ð 	ð 	ˆDØ�tˆ|ˆ|Ý.¨tÑ4Ô4�	Ø—’¥Ø2Ø#Ø(Ø!ØØ<�s 4™yœyÐ<Ð<Ð<°	Ð<Ð<Ð<Ø k¸yÐ kÐ kÐ kð!ñ !ô !ñ ô ð ð �ð øð €Os   ¸A ÁA%Á$A%r   Ú
skill_pathr/   c           
      ó   — | j         }t          |¦  «        }g }|                      ¦   «         r±t          | ¦  «        }|                     t          | |¬¦  «        ¦  «         |                      d¦  «        D ]g}|                     ¦   «         rQt          | 	                    | ¦  «        ¦  «        } ||¦  «        rŒD|                     t          ||¦  «        ¦  «         Œhn<|                      ¦   «         r(|                     t          | | j         ¦  «        ¦  «         t          |¦  «        }t          |||||¦  «        }	t          |||||t          j        t           j        ¦  «                             ¦   «         |	¬¦  «        S )aË  
    Scan all files in a skill directory for security threats.

    Performs:
    1. Structural checks (file count, total size, binary files, symlinks)
    2. Regex pattern matching on all text files
    3. Invisible unicode character detection

    A skill may ship a `.skillignore` (or `.clawhubignore`) file with
    gitignore-style patterns. Matching paths are excluded from BOTH the
    structural checks and the pattern scan, so development/docs artifacts
    that are not part of the installed skill (e.g. `SKILL-original.md`,
    `docs/plans/`, `release-notes.md`) don't trip findings. The ignore
    file itself is always excluded. Patterns cannot un-ignore the
    skill's own `SKILL.md`, which is always scanned.

    Args:
        skill_path: Path to the skill directory (must contain SKILL.md)
        source: Source identifier for trust level resolution (e.g. "openai/skills")

    Returns:
        ScanResult with verdict, findings, and trust metadata
    )ÚignoreÚ*©r.   r/   r0   r1   r3   r5   r6   )r  Ú_resolve_trust_levelÚis_dirÚ_load_skill_ignoreÚextendÚ_check_structureÚrglobÚis_filer&   Úrelative_tor1  Ú_determine_verdictÚ_build_summaryr-   r   Únowr   ÚutcÚ	isoformat)
r2  r/   r.   r0   Úall_findingsr4  ÚfÚrelr1   r6   s
             r+   Ú
scan_skillrG  €  s  € ð0 ”€JÝ& vÑ.Ô.€Kà"$€Là×ÒÑÔð DÝ# JÑ/Ô/ˆð 	×ÒÕ,¨ZÀÐGÑGÔGÑHÔHÐHð ×!Ò! #Ñ&Ô&ð 	7ð 	7ˆAØ�yŠy‰{Œ{ð 7Ý˜!Ÿ-š-¨
Ñ3Ô3Ñ4Ô4�Ø�6˜#‘;”;ð ØØ×#Ò#¥I¨a°Ñ$5Ô$5Ñ6Ô6Ð6øð	7ð 
×	Ò	Ñ	Ô	ð DØ×Ò�I j°*´/ÑBÔBÑCÔCÐCå  Ñ.Ô.€GÝ˜Z¨°¸gÀ|ÑTÔT€GåØØØØØÝ”<¥¤Ñ-Ô-×7Ò7Ñ9Ô9Øðñ ô ð r*   c                 ó,  — t          j        ¦   «         }|                      ¦   «         r³t          |                      d¦  «        ¦  «        D ]�}|                     ¦   «         ry|                     | ¦  «                             ¦   «         }|                     | 	                    d¦  «        dz   ¦  «         |                     | 
                    ¦   «         ¦  «         Œ�n'|                     |  
                    ¦   «         ¦  «         |                     ¦   «         S )z;Canonical SHA-256 over relative paths and exact file bytes.r5  r
  ó    )ÚhashlibÚsha256r8  Úsortedr<  r=  r>  Úas_posixÚupdateÚencodeÚ
read_bytesÚ	hexdigest)r2  Úhr  rF  s       r+   Ú_content_digestrS  »  sð   € åŒÑÔ€AØ×ÒÑÔð *Ý 
× 0Ò 0°Ñ 5Ô 5Ñ6Ô6ð 	1ð 	1ˆIØ× Ò Ñ"Ô"ð 1Ø×+Ò+¨JÑ7Ô7×@Ò@ÑBÔB�Ø—’˜Ÿš GÑ,Ô,¨wÑ6Ñ7Ô7Ð7Ø—’˜×-Ò-Ñ/Ô/Ñ0Ô0Ð0øð		1ð 	
�Š�×&Ò&Ñ(Ô(Ñ)Ô)Ð)Ø�;Š;‰=Œ=Ðr*   c                 ó&   — dt          | ¦  «        › �S )z8Full canonical digest used to bind scanner attestations.úsha256:©rS  ©r2  s    r+   Úfull_content_hashrX  É  s   € à2•_ ZÑ0Ô0Ð2Ð2Ð2r*   Úfindingc                 ó    ‡ — ˆ fd„dD ¦   «         S )Nc                 ó2   •— i | ]}|t          ‰|¦  «        “ŒS r)   )Úgetattr)rÊ   ÚkeyrY  s     €r+   ú
<dictcomp>z!_finding_dict.<locals>.<dictcomp>Ï  s1   ø€ ð ð ð ¨3ˆC•˜ #Ñ&Ô&ð ð ð r*   r  r)   )rY  s   `r+   Ú_finding_dictr_  Î  s0   ø€ ðð ð ð ð 3ð ñ ô ð r*   )Ú
source_urlÚ	cache_dirr`  ra  c                ó  — t          | ¦  «        }|p	| j        dz  }t          j        |› d|› �                     d¦  «        ¦  «                             ¦   «         dd…         }||                     dd¦  «        d         › d|› d	�z  }	 t          j        | 	                    d¬
¦  «        ¦  «        }n# t          t          j        f$ r d}Y nw xY wt          |t          ¦  «        rç|                     d¦  «        |k    rÎ|                     d¦  «        t          k    r°|                     d¦  «        |k    r—|                     d¦  «        |k    r~t!          | j        ||d         |d         d„ |                     dg ¦  «        D ¦   «         |d         |                     dd¦  «        ¬¦  «        }	t          |¦  «        }
d|
d<   |
|	_        |	|
fS t'          | |¬¦  «        }	d„ |	j        D ¦   «         }|||t          |	j        |	j        |t/          d„ |D ¦   «         ¦  «        |	j        |	j        ddœ}
	 |                     dd¬¦  «         |                     t          j        |
d¬ ¦  «        d!z   d¬
¦  «         n# t          $ r Y nw xY w|
|	_        |	|
fS )"zCReturn a scan plus attestation, caching only exact current content.z.scan-cacheú r
  Né   ú:r   Ú-rç   r  Úbundle_hashÚscanner_versionr/   r`  r0   r1   c                 ó&   — g | ]}t          d i |¤Ž‘ŒS )r)   )r   ©rÊ   Úitems     r+   rÍ   z%scan_skill_cached.<locals>.<listcomp>ì  s"   € ÐMÐMÐM¨$•g�o�o �o�oÐMÐMÐMr*   r3   r5   r6   r4   r6  FÚfresh)r/   c                 ó,   — g | ]}t          |¦  «        ‘ŒS r)   )r_  rj  s     r+   rÍ   z%scan_skill_cached.<locals>.<listcomp>õ  s    € Ð@Ð@Ð@¨•˜dÑ#Ô#Ð@Ð@Ð@r*   c                 ó   — h | ]
}|d          ’ŒS )r   r)   rj  s     r+   ú	<setcomp>z$scan_skill_cached.<locals>.<setcomp>ú  s   € ÐAÐAÐA°˜˜lÔ+ÐAÐAÐAr*   T)r/   r`  rg  rh  r1   r0   r3   Úrulesr5   r6   rl  )ÚparentsÚexist_okr   )Úindentr  )rX  ÚparentrJ  rK  rO  rQ  r  ÚjsonÚloadsr  r  ÚJSONDecodeErrorÚ
isinstancer9   ÚgetÚSCANNER_VERSIONr-   r  r7   rG  r3   r1   r0   rL  r5   r6   ÚmkdirÚ
write_textÚdumps)r2  r/   r`  ra  rg  Ú
cache_rootÚsource_identityÚ
cache_fileÚcachedÚresultÚ
provenancer3   s               r+   Úscan_skill_cachedr„  Ô  sÔ  € õ $ JÑ/Ô/€KØÐ?˜jÔ/°-Ñ?€JÝ”n¨Ð%>Ð%>°*Ð%>Ð%>×%EÒ%EÀgÑ%NÔ%NÑOÔO×YÒYÑ[Ô[Ð\_Ð]_Ð\_Ô`€OØ ×!2Ò!2°3¸Ñ!:Ô!:¸1Ô!=ÐVÐVÀÐVÐVÐVÑV€JðÝ”˜J×0Ò0¸'Ð0ÑBÔBÑCÔCˆˆøÝ•TÔ)Ð*ð ð ð Øˆˆˆðøøøå�6�4Ñ Ô ð "Ø—
’
˜=Ñ)Ô)¨[Ò8Ð8Ø—
’
Ð,Ñ-Ô-µÒ@Ð@Ø—
’
˜8Ñ$Ô$¨Ò.Ð.Ø—
’
˜<Ñ(Ô(¨JÒ6Ð6ÝØ!”¨vØ˜}Ô-°v¸iÔ7HØMÐM°&·*²*¸ZÈÑ2LÔ2LÐMÑMÔMØ˜lÔ+°V·Z²ZÀ	È2Ñ5NÔ5Nð	
ñ 
ô 
ˆõ ˜&‘\”\ˆ
Ø#ˆ
�7ÑØ!+ˆÔØ�zÐ!Ð!å˜
¨6Ð2Ñ2Ô2€FØ@Ð@°´Ð@Ñ@Ô@€Hà¨
À;Ý*°v´~ØÔ)°xÝÐAÐA¸ÐAÑAÔAÑBÔBØÔ'°F´NÈTðð €JðØ×Ò °ÐÑ5Ô5Ð5Ø×Ò�dœj¨¸AÐ>Ñ>Ô>ÀÑEÐPWÐÑXÔXÐXÐXøÝð ð ð Øˆðøøøà'€FÔØ�:ÐÐs%   Â(B1 Â1CÃCÈ,AI2 É2
I?É>I?Fr‚  Úforcec           	      ón  — t                                | j        t           d         ¦  «        }t                               | j        d¦  «        }||         }|dk    rdd| j        › d| j        › d�fS |r6| j        dk    r	| j        d	v s"dd
| j        › dt          | j        ¦  «        › d�fS |dk    r*dd| j        › d| j        › dt          | j        ¦  «        › d�fS | j        dk    r+| j        d	v r"dd| j        › dt          | j        ¦  «        › d�fS dd| j        › d| j        › dt          | j        ¦  «        › d�fS )a  
    Determine whether a skill should be installed based on scan result and trust.

    Args:
        result: Scan result from scan_skill()
        force: If True, override blocked policy decisions for this scan result

    Returns:
        (allowed, reason) tuple
    r   r   r   Tz	Allowed (z	 source, z	 verdict)r   )r   r   zForce-installed despite z
 verdict (z
 findings)r   NzRequires confirmation (z
 source + z
 verdict, Fz	Blocked (z source + dangerous verdict, z: findings). --force does not override a dangerous verdict.z$ findings). Use --force to override.)ÚINSTALL_POLICYry  r0   ÚVERDICT_INDEXr1   r%  r3   )r‚  r…  ÚpolicyÚviÚdecisions        r+   Úshould_allow_installrŒ    sÛ  € õ ×Ò Ô 2µNÀ;Ô4OÑPÔP€FÝ	×	Ò	˜6œ>¨1Ñ	-Ô	-€BØ�bŒz€Hà�7ÒÐØÐW Ô!3ÐWÐW¸f¼nÐWÐWÐWÐWÐWàð 
�f”n¨Ò3Ð3¸Ô8JÐNfÐ8fÐ8fØð1 v¤~ð 1ð 1Ý�F”OÑ$Ô$ð1ð 1ð 1ð
ð 	
ð
 �5ÒÐàð0 fÔ&8ð 0ð 0ÀFÄNð 0ð 0Ý�6”?Ñ#Ô#ð0ð 0ð 0ð
ð 	
ð „~˜Ò$Ð$¨Ô);Ð?WÐ)WÐ)WØð`˜Ô*ð `ð `Ý�6”?Ñ#Ô#ð`ð `ð `ð
ð 	
ð ð	F�FÔ&ð 	Fð 	F°&´.ð 	Fð 	FÝˆvŒÑÔð	Fð 	Fð 	Fðð r*   c                 óî  ‡— g }| j                              ¦   «         }|                     d| j        › d| j        › d| j        › d|› �¦  «         | j        rÓddddd	œŠt          | j        ˆfd
„¬¦  «        }|D ]›}|j                             ¦   «          	                    d¦  «        }|j
         	                    d¦  «        }|j        › d|j        › � 	                    d¦  «        }|                     d|› d|› d|› d|j        dd…         › d�	¦  «         Œœ|                     d¦  «         t          | ¦  «        \  }}	|du rd}
n|€d}
nd}
|                     d|
› d|	› �¦  «         d                     |¦  «        S )zŒ
    Format a scan result as a human-readable report string.

    Returns a compact multi-line report suitable for CLI or chat display.
    zScan: r  ú/z)  Verdict: r   r   r   é   )r;   rB   re   r�   c                 ó:   •— ‰                      | j        d¦  «        S )Né   )ry  r   )rE  Úseverity_orders    €r+   ú<lambda>z$format_scan_report.<locals>.<lambda>@  s   ø€ À×@RÒ@RÐSTÔS]Ð_`Ñ@aÔ@a€ r*   )r]  é   é   re  é   z  Ú z "Né<   ú"r4   TÚALLOWEDzNEEDS CONFIRMATIONÚBLOCKEDz
Decision: õ    â€” r  )r1   Úupperr&  r.   r/   r0   r3   rL  r   Úljustr   r   r    r!   rŒ  Újoin)r‚  r+  Úverdict_displayÚsorted_findingsrE  ÚsevÚcatÚlocÚallowedÚreasonÚstatusr’  s              @r+   Úformat_scan_reportr¨  2  sº  ø€ ð €Eà”n×*Ò*Ñ,Ô,€OØ	‡L‚LÐp˜&Ô+ÐpÐp¨v¬}ÐpÐp¸vÔ?QÐpÐpÐ_nÐpÐpÑqÔqÐqà„ð à&'°¸aÈÐJÐJˆÝ  ¤Ð6aÐ6aÐ6aÐ6aÐbÑbÔbˆà ð 	Dð 	DˆAØ”*×"Ò"Ñ$Ô$×*Ò*¨1Ñ-Ô-ˆCØ”*×"Ò" 2Ñ&Ô&ˆCØ”VÐ&Ð&˜aœfÐ&Ð&×,Ò,¨RÑ0Ô0ˆCØ�LŠLÐB˜cÐBÐB CÐBÐB¨#ÐBÐB°!´'¸#¸2¸#´,ÐBÐBÐBÑCÔCÐCÐCà�Š�RÑÔÐå*¨6Ñ2Ô2�O€GˆVØ�$€€ØˆˆØ	ˆØ%ˆˆàˆØ	‡L‚LÐ3˜fÐ3Ð3¨6Ð3Ð3Ñ4Ô4Ð4à�9Š9�UÑÔÐr*   c                 ó6   — dt          | ¦  «        dd…         › �S )u  Compute a SHA-256 hash of all files in a skill directory for integrity tracking.

    File paths (relative to ``skill_path``) are mixed into the hash alongside
    file contents so that swapping the contents of two files in a skill
    changes the hash. This must stay symmetric with
    ``tools.skills_hub.bundle_content_hash`` â€” both functions need to
    produce the same digest for the same skill (one operates on disk,
    one on an in-memory bundle), so any change to the hash shape MUST
    land in both places at once.
    rU  Nrd  rV  rW  s    r+   Úcontent_hashrª  V  s#   € ð 8•_ ZÑ0Ô0°°"°Ô5Ð7Ð7Ð7r*   Ú	skill_dirc                 óÆ  — |€d„ }g }d}d}|                       d¦  «        D �]/}|                     ¦   «         s|                     ¦   «         sŒ,t          |                     | ¦  «        ¦  «        } ||¦  «        rŒZ|dz  }|                     ¦   «         r¤	 |                     ¦   «         }|                     |                      ¦   «         ¦  «        s,|                     t          ddd|dd	|› �d
¬¦  «        ¦  «         n9# t          $ r, |                     t          ddd|ddd¬¦  «        ¦  «         Y nw xY w�Œ	 | 
                    ¦   «         j        }||z  }n# t          $ r Y �ŒDw xY w|t          dz  k    r>|                     t          ddd|d|dz  › d�d|dz  › dt          › d�¬¦  «        ¦  «         |j                             ¦   «         }	|	t          v r0|                     t          ddd|dd|	› �d|	› d�¬¦  «        ¦  «         |	dvrE| 
                    ¦   «         j        dz  r)|                     t          ddd|ddd¬¦  «        ¦  «         �Œ1|t"          k    r8|                     t          d ddd!d|› d"�d#|› d$t"          › d%�¬¦  «        ¦  «         |t$          dz  k    r>|                     t          d&d'dd!d|dz  › d(�d)|dz  › d*t$          › d�¬¦  «        ¦  «         |S )+aW  
    Check the skill directory for structural anomalies:
    - Too many files
    - Suspiciously large total size
    - Binary/executable files that shouldn't be in a skill
    - Symlinks pointing outside the skill directory
    - Individual files that are too large

    Args:
        skill_dir: Path to the skill directory.
        ignore: Optional callable taking a relative posix path and returning
            True if the path should be excluded (e.g. from `.skillignore`).
            Ignored files are not counted toward the file count, total size,
            or any structural finding.
    Nc                 ó   — dS )NFr)   )Ú_rels    r+   r“  z"_check_structure.<locals>.<lambda>y  s   € ˜e€ r*   r   r5  r   Úsymlink_escaper;   r˜   zsymlink -> z*symlink points outside the skill directoryr  Úbroken_symlinkre   zbroken symlinkzbroken or circular symlinkrÏ   Úoversized_fileÚ
structuralÚKBzfile is zKB (limit: zKB)Úbinary_filezbinary: zbinary/executable file (z) should not be in a skill>   râ   rã   rä   rå   ræ   éI   Úunexpected_executablezexecutable bit setzBfile has executable permission but is not a recognized script typeÚtoo_many_filesz(directory)z filesz
skill has z files (limit: r  Úoversized_skillrB   zKB totalz	skill is zKB total (limit: )r<  r=  Ú
is_symlinkr&   r>  ÚresolveÚis_relative_tor&  r   r  ÚstatÚst_sizeÚMAX_SINGLE_FILE_KBr  r  ÚSUSPICIOUS_BINARY_EXTENSIONSÚst_modeÚMAX_FILE_COUNTÚMAX_TOTAL_SIZE_KB)
r«  r4  r3   Ú
file_countÚ
total_sizerE  rF  ÚresolvedÚsizeÚexts
             r+   r;  r;  h  sí  € ð  €~Ø#Ð#ˆà€HØ€JØ€Jà�_Š_˜SÑ!Ô!ð Mñ MˆØ�yŠy‰{Œ{ð 	 1§<¢<¡>¤>ð 	Øå�!—-’- 	Ñ*Ô*Ñ+Ô+ˆØˆ6�#‰;Œ;ð 	ØØ�a‰ˆ
ð �<Š<‰>Œ>ð 	ðØŸ9š9™;œ;�Ø×.Ò.¨y×/@Ò/@Ñ/BÔ/BÑCÔCð 	Ø—O’O¥GØ#3Ø!+Ø!,Ø ØØ6¨HÐ6Ð6Ø$Pð%ñ %ô %ñ ô ð øøõ ð 	ð 	ð 	Ø—’¥Ø/Ø%Ø(ØØØ*Ø <ð!ñ !ô !ñ ô ð ð ð ð	øøøñ ð	Ø—6’6‘8”8Ô#ˆDØ˜$ÑˆJˆJøÝð 	ð 	ð 	Ø‰Hð	øøøð Õ$ tÑ+Ò+Ð+Ø�OŠO�GØ+Ø!Ø%ØØØ ™Ð)Ð)Ð)ØW t¨t¡|ÐWÐWÕ@RÐWÐWÐWðñ ô ñ ô ð ð Œh�nŠnÑÔˆØÕ.Ð.Ð.Ø�OŠO�GØ(Ø#Ø%ØØØ& Ð&Ð&ØV°sÐVÐVÐVðñ ô ñ ô ð ð Ð;Ð;Ð;ÀÇÂÁÄÔ@PÐSXÑ@XÐ;Ø�OŠO�GØ2Ø!Ø%ØØØ*Ø`ðñ ô ñ ô ð ùð •NÒ"Ð"Ø�Š�Ø'ØØ!ØØØÐ'Ð'Ð'ØQ ZÐQÐQÅÐQÐQÐQð
ñ 
ô 
ñ 	ô 	ð 	ð Õ%¨Ñ,Ò,Ð,Ø�Š�Ø(ØØ!ØØØ 4Ñ'Ð1Ð1Ð1Ø_ J°$Ñ$6Ð_Ð_ÕIZÐ_Ð_Ð_ð
ñ 
ô 
ñ 	ô 	ð 	ð €Os%   ÂA'C=Ã=3D3Ä2D3Ä9EÅ
E&Å%E&r/  c                 óº   — i dd“dd“dd“dd“d	d
“dd“dd“dd“dd“dd“dd“dd“dd“dd“dd“dd “d!d"“}|                      | d#t          | ¦  «        d$›�¦  «        S )%z7Get a readable name for an invisible unicode character.rô   zzero-width spacerõ   zzero-width non-joinerrö   zzero-width joinerrü   zword joinerrý   zinvisible timesrþ   zinvisible separatorrÿ   zinvisible plusr  zBOM/zero-width no-break spacer÷   zLTR embeddingrø   zRTL embeddingrù   zpop directionalrú   zLTR overriderû   zRTL overrider   zLTR isolater  zRTL isolater  zfirst strong isolater  zpop directional isolater  r  )ry  r)  )r/  Únamess     r+   r(  r(  é  s  € ðØÐ$ðàÐ)ðð 	Ð%ðð 	�-ð	ð
 	Ð#ðð 	Ð'ðð 	Ð"ðð 	Ð1ðð 	�/ðð 	�/ðð 	Ð#ðð 	�.ðð 	�.ðð 	�-ðð 	�-ðð  	Ð(ð!ð" 	Ð+ð#€Eð& �9Š9�TÐ/¥ D¡	¤	Ð/Ð/Ð/Ñ0Ô0Ð0r*   )z.skillignorez.clawhubignorer	  c                 ó‚  ‡— g Št           D ]Ÿ}| |z  }	 |                     ¦   «         rl|                     d¬¦  «                             ¦   «         D ]C}|                     ¦   «         }|r|                     d¦  «        rŒ.‰                     |¦  «         ŒDŒ‰# t          t          f$ r Y Œœw xY wdt          dt          fˆfd„}|S )a0  Build a matcher from a skill's `.skillignore` / `.clawhubignore`.

    Returns a callable ``ignore(rel_posix_path) -> bool``. The matcher
    supports gitignore-style basics: blank lines and ``#`` comments are
    skipped, a trailing ``/`` marks a directory (matches that dir and
    everything under it), and ``*``/``?`` globs are honored via fnmatch on
    both the full relative path and each path segment. A leading ``/``
    anchors a pattern to the skill root. The ignore files themselves are
    always excluded; ``SKILL.md`` can never be excluded.
    r
  r  ú#rF  r  c                 ó
  •‡— t          | ¦  «                             ¦   «         }|                     d¦  «        d         }|t          v rdS |t          v rdS ‰D �]*}|                     d¦  «        }|                     d¦  «        Š‰                     d¦  «        }‰                     d¦  «        Š‰sŒZ|rL|‰k    s|                     ‰dz   ¦  «        r dS |s(d|z   dz    	                    d‰z   dz   ¦  «        dk    r dS Œ¨t          j
        |‰¦  «        r dS |sht          j
        |‰¦  «        r dS d‰vr1t          ˆfd„|                     d¦  «        D ¦   «         ¦  «        r dS |                     ‰dz   ¦  «        r dS �Œ,dS )NrŽ  éÿÿÿÿFTc              3   óB   •K  — | ]}t          j         |‰¦  «        V — Œd S ©N)Úfnmatch)rÊ   ÚsegÚps     €r+   ú	<genexpr>z5_load_skill_ignore.<locals>.ignore.<locals>.<genexpr>I  s@   øè è € ð (ð (Ø03•G”O C¨Ñ+Ô+ð(ð (ð (ð (ð (ð (r*   )r   rM  r  Ú_NEVER_IGNORABLEÚ_ALWAYS_IGNORED_NAMESÚ
startswithÚlstripÚendswithÚrstripÚfindrÐ  Úany)rF  Ú	rel_posixÚbaseÚpatÚanchoredr8  rÒ  Úpatternss         @€r+   r4  z"_load_skill_ignore.<locals>.ignore)  sÐ  øø€ Ý˜‘I”I×&Ò&Ñ(Ô(ˆ	Ø�Š˜sÑ#Ô# BÔ'ˆàÕ#Ð#Ð#Ø�5ØÕ(Ð(Ð(Ø�4àð 	 ñ 	 ˆCØ—~’~ cÑ*Ô*ˆHØ—
’
˜3‘”ˆAØ—Z’Z ‘_”_ˆFØ—’˜‘”ˆAØð Øàð à ’>�> Y×%9Ò%9¸!¸c¹'Ñ%BÔ%B�>Ø˜4˜4Øð   S¨9¡_°sÑ%:×$@Ò$@ÀÀqÁÈ3ÁÑ$OÔ$OÐSUÒ$UÐ$UØ˜4˜4Øõ Œ˜y¨!Ñ,Ô,ð Ø�t�tØð  å”? 4¨Ñ+Ô+ð  Ø˜4˜4Ø˜a�<�<¥Cð (ð (ð (ð (Ø7@·²ÀsÑ7KÔ7Kð(ñ (ô (ñ %ô %�<ð  ˜4˜4ð ×'Ò'¨¨C©Ñ0Ô0ð  Ø˜4˜4ùØˆur*   )Ú_SKILL_IGNORE_FILENAMESr=  r  Ú
splitlinesr$  rÖ  r&  r  r  r&   Úbool)r«  r  ÚigÚrawr    r4  rà  s         @r+   r9  r9    sû   ø€ ð €HÝ'ð 
ð 
ˆØ˜Ñˆð	Ø�zŠz‰|Œ|ð *ØŸ<š<°˜<Ñ9Ô9×DÒDÑFÔFð *ð *�CØŸ9š9™;œ;�DØð ! 4§?¢?°3Ñ#7Ô#7ð !Ø Ø—O’O DÑ)Ô)Ð)Ð)øøÝ"¥GÐ,ð 	ð 	ð 	ØˆHð	øøøð(•Cð (�Dð (ð (ð (ð (ð (ð (ðT €Ms   “B BÂB(Â'B(c                 óê   — d}| }|D ]0}|                      |¦  «        r|t          |¦  «        d…         } nŒ1|dk    rdS |dk    rdS t          D ]#}||k    s|                      |› d�¦  «        r dS Œ$dS )	z)Map a source identifier to a trust level.)z
skills-sh/z
skills.sh/z	skils-sh/z	skils.sh/Nr   Úofficialr   rŽ  r   r   )rÖ  r%  ÚTRUSTED_REPOS)r/   Úprefix_aliasesÚnormalized_sourceÚprefixr   s        r+   r7  r7  V  sÀ   € ð€Nð ÐØ ð ð ˆØ×'Ò'¨Ñ/Ô/ð 	Ø 1µ#°f±+´+°,°,Ô ?ÐØˆEð	ð
 ˜OÒ+Ð+Øˆð ˜JÒ&Ð&Øˆyõ !ð ð ˆØ Ò'Ð'Ð+<×+GÒ+GÈ7ÈÈÈÑ+VÔ+VÐ'Ø�9�9ð (àˆ;r*   r3   c                 ó‚   — | sdS t          d„ | D ¦   «         ¦  «        }t          d„ | D ¦   «         ¦  «        }|rdS |rdS dS )z6Determine the overall verdict from a list of findings.r   c              3   ó,   K  — | ]}|j         d k    V — ŒdS )r;   N©r   ©rÊ   rE  s     r+   rÓ  z%_determine_verdict.<locals>.<genexpr>x  s)   è è € ÐBÐB°A�q”z ZÒ/ÐBÐBÐBÐBÐBÐBr*   c              3   ó,   K  — | ]}|j         d k    V — ŒdS )rB   Nrî  rï  s     r+   rÓ  z%_determine_verdict.<locals>.<genexpr>y  s)   è è € Ð:Ð:¨A�1”: Ò'Ð:Ð:Ð:Ð:Ð:Ð:r*   r   r   )rÛ  )r3   Úhas_criticalÚhas_highs      r+   r?  r?  s  sf   € àð ØˆvåÐBÐB¸ÐBÑBÔBÑBÔB€LÝÐ:Ð:°Ð:Ñ:Ô:Ñ:Ô:€Hàð Øˆ{Øð Øˆyàˆ6r*   r  Útrustr1   c                 óœ   — |s| › d�S d„ |D ¦   «         }| › d|› dt          |¦  «        › dd                     t          |¦  «        ¦  «        › �S )z,Build a one-line summary of the scan result.z!: clean scan, no threats detectedc                 ó   — h | ]	}|j         ’Œ
S r)   )r   rï  s     r+   ro  z!_build_summary.<locals>.<setcomp>ˆ  s   € Ð/Ð/Ð/ �!”*Ð/Ð/Ð/r*   z: rœ  z finding(s) in z, )r%  rŸ  rL  )r  r/   ró  r1   r3   Ú
categoriess         r+   r@  r@  ƒ  sk   € àð :ØÐ9Ð9Ð9Ð9à/Ð/ hÐ/Ñ/Ô/€JØÐaÐa�gÐaÐa¥C¨¡M¤MÐaÐaÀ$Ç)Â)ÍFÐS]ÑL^ÔL^ÑB_ÔB_ÐaÐaÐar*   )r4   )r   )FrÏ  )3Ú__doc__rÇ   rÐ  rJ  ru  Údataclassesr   r   r   r   Úpathlibr   Útypingr   r	   rz  rè  r‡  rˆ  r   r-   ÚTHREAT_PATTERNSr   rÁ  rÂ  r¾  r  r¿  r'  r&   r1  rG  rS  rX  r9   r_  r„  rã  rŒ  r¨  rª  r;  r(  rá  r  rÕ  rÔ  r9  r7  r?  r@  r)   r*   r+   ú<module>rü     s§  ððð ð. 
€	€	€	Ø €€€Ø €€€Ø €€€Ø (Ð (Ð (Ð (Ð (Ð (Ð (Ð (Ø 'Ð 'Ð 'Ð 'Ð 'Ð 'Ð 'Ð 'Ø Ð Ð Ð Ð Ð Ø Ð Ð Ð Ð Ð Ð Ð ð $€ð	ð 	ð 	€ð 4Ø3Ø3ð
 2ð
ð 
€ð  q°qÐ9Ð9€ð ðð ð ð ð ñ ô ñ „ðð ð8ð 8ð 8ð 8ð 8ñ 8ô 8ñ „ð8ðgð gð g€ðRð à9Hðñ ô Ð ð €ØÐ ØÐ ðð ð Ð ð ð  ð  Ð ðð ð €ð2>ð >˜ð >¨ð >°d¸7´mð >ð >ð >ð >ðB8ð 8˜4ð 8¨ð 8¸zð 8ð 8ð 8ð 8ðv ð ¨ð ð ð ð ð3 $ð 3¨3ð 3ð 3ð 3ð 3ð
˜7ð  tð ð ð ð ð ð/ð Ø!ð/ð /ð /Øð/àð/ð ð	/ð
 �d‰{ð/ð ˆ:�tÐÔð/ð /ð /ð /ðd)ð ) ð )°Dð )ÀUÈ4ÐQTÈ9ÔEUð )ð )ð )ð )ðX!˜zð !¨cð !ð !ð !ð !ðH8˜Tð 8 cð 8ð 8ð 8ð 8ð$~ð ~ ð ~°d¸7´mð ~ð ~ð ~ð ~ðB1˜Sð 1 Sð 1ð 1ð 1ð 1ð@ =Ð ð ˜Ð3Ñ4Ô4Ð Ø�<Ð ðB $ð Bð Bð Bð BðJ ð ¨ð ð ð ð ð:  g¤ð °3ð ð ð ð ð b˜ð b cð b°#ð bÀð bÈtÐT[Ì}ð bÐadð bð bð bð bð bð br*   