
    `gj&                       U d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	m
Z
mZmZ ddlmZ  ej                   e      Zddd	d
Zded<   dZd$dZd$dZg Zded<   ej2                  D ]  Zed   ed   ej7                  d      ej7                  d       eej7                  dd            ddZded<   ej7                  d      Zer	  ej>                  e      ed<   ejG                  e        d%dZ$d&dZ%d'dZ&d&d Z'	 	 d(	 	 	 	 	 	 	 d)d!Z(	 	 	 d*	 	 	 	 	 	 	 	 	 d+d"Z)d,d#Z*y# ej@                  $ r!Z!ejE                  ded   ee!       Y dZ![!dZ![!ww xY w)-u  security-guidance plugin — fast pattern-matched security warnings on file writes.

Wires one behaviour:

* ``transform_tool_result`` hook — scans the *content being written* by
  ``write_file`` / ``patch`` / ``skill_manage`` (write/patch modes) for known
  dangerous code patterns (eval(, pickle.load, yaml.load, os.system,
  subprocess(shell=True), dangerouslySetInnerHTML, verify=False, ECB,
  XXE-prone XML parsers, GitHub Actions ``${{ github.event.* }}`` injection,
  torch.load without ``weights_only=True``, ...). When any pattern matches,
  the plugin appends a ``⚠️ Security warning`` block to the JSON tool-result
  string. The file is still written; the model sees the warning in the next
  turn's tool message and can self-correct.

Why not block? Patterns have a non-trivial false-positive rate (``eval(`` in
a tokenizer, ``yaml.load`` already wrapped in ``yaml.SafeLoader``, ECB inside
a test fixture). Blocking would force every false positive into an approval
prompt or an interrupted workflow. Warning is the right severity for layer
1 — the agent reads the warning and either fixes the code or briefly
documents why the construct is safe.

For block-mode (refuse the write entirely), set
``SECURITY_GUIDANCE_BLOCK=1``. This trades convenience for strictness and
is intended for shared dev environments where unsafe-by-default patterns
are policy violations.

Pattern data lives in ``patterns.py``, forked verbatim from Anthropic's
``claude-plugins-official`` under Apache-2.0. See ``LICENSE`` and ``NOTICE``
in this directory.
    )annotationsN)AnyDictListOptionalTuple   )patterns)path)content)r   )
new_stringpatch)	file_path)file_contentr   )
write_filer   skill_managez&Dict[str, Tuple[str, Tuple[str, ...]]]_TARGET_TOOLSi   c                 b    t         j                  j                  dd      j                         dv S )NSECURITY_GUIDANCE_BLOCK >   1onyestrueosenvirongetlower     U/root/.hermes/venv/lib/python3.12/site-packages/plugins/security-guidance/__init__.py_block_mode_enabledr#   B   s'    ::>>3R8>>@D^^^r!   c                 b    t         j                  j                  dd      j                         dv S )NSECURITY_GUIDANCE_DISABLEr   >   r   r   r   r   r   r    r!   r"   _plugin_disabledr&   F   s'    ::>>5r:@@BF```r!   zList[Dict[str, Any]]	_COMPILEDruleNamereminderpath_filter
path_check
substringsr    )r(   r)   r*   r+   r,   regexzDict[str, Any]_entryr-   u<   security-guidance: skipping rule %s — invalid regex %r: %sc                   |r#t        |j                  dd            t        kD  rg S g }t        D ]  }|j	                  d      }|'	  || xs d      r|j                  |d   |d   f       =|j	                  d      }|	  || xs d      s^	 d	}|d
   D ]
  }||v sd} n |s|d   |d   j                  |      rd}|s|j                  |d   |d   f        |S # t        $ r Y w xY w# t        $ r Y w xY w)u  Return [(ruleName, reminder), ...] for every pattern that matches.

    ``path`` is used by per-rule path filters (path_filter / path_check).
    Each rule fires at most once per call — multiple matches of the same
    rule collapse into a single warning entry.
    zutf-8ignore)errorsr+   r   r(   r)   r*   Fr,   Tr-   )lenencode_MAX_SCAN_BYTESr'   r   append	Exceptionsearch)r   r   hitsentryr+   r*   matchedsubs           r"   _scan_contentr<   h   sM    c'...BCoU	"$D @ YY|,
!djb)KKz!2E*4E FG  ii.""4:2. / & 	Cg~	 5>5W~$$W-KKz*E*,=>??@@ K/    s$   %C C/ 	C,+C,/	C;:C;c                2   t         j                  |       }|t        |t              sg S |\  }}|j                  |      xs d}t        |t              sd}g }|D ]:  }|j                  |      }t        |t              s%|s(|j                  ||f       < |S )zIReturn [(path, content), ...] for a tool call.  Empty if nothing to scan.r   )r   r   
isinstancedictstrr5   )		tool_nameargsspecpath_keycontent_keysr   outckvals	            r"   _extract_path_and_contentrI      s    Y'D|:dD1	!Hl88H#DdC !#C $hhrlc3CJJc{#$ Jr!   c           	         dj                  d | D              }dddt        |        dt        |       dk7  rdnd d	| d
dg}| D ]'  \  }}|j                  |       |j                  d       ) |j                  d       dj                  |      S )zBRender findings into a Markdown block appended to the tool result.z, c              3  &   K   | ]	  \  }}|  y w)Nr    ).0name_s      r"   	<genexpr>z(_format_warning_block.<locals>.<genexpr>   s     3wtQd3s   r   z---u   ⚠️ Security guidance — z patternr	   sz
 matched ()zPattern matches can be false positives. If the construct is safe in this context, briefly document why in a code comment and continue. Otherwise, fix the code before moving on.
)joinr2   r5   )findingsnameslinesrN   r)   s        r"   _format_warning_blockrW      s    II3(33E

'Hhc(mWXFXs^`>aaklqkrrst
	E   8XR 
LL	)
 99Ur!   c                    t               rg S g }t        | |      D ]   \  }}|j                  t        ||             " |S )zcCommon scan path used by both pre_tool_call (block mode) and
    transform_tool_result (warn mode).)r&   rI   extendr<   )rA   rB   rT   r   r   s        r"   
_scan_argsrZ      sF     	&(H29dC 6gdG456Or!   c                ^    t               syt        | |      }|syddt        |      z   dz   dS )u   In block mode, refuse the write if any pattern matches.

    Default mode is non-blocking — we return None here and let
    ``transform_tool_result`` append a warning to the result instead.
    Nblockz&security-guidance refused this write: z7

To override, unset SECURITY_GUIDANCE_BLOCK and retry.)actionmessage)r#   rZ   rW   )rA   rB   rN   rT   s       r"   _on_pre_tool_callr_      sE      )T*H4#H-.IJ r!   c                   t               ryt        | |      }|syt        |t              sy	 t	        j
                  |      }t        |t              rd|v rt        |      dk  ry|dz   t        |      z   S # t        t        f$ r Y "w xY w)zWarn-mode hook: append a security-warning block to the tool result.

    Returning a string replaces the result that the model sees in the next
    turn. Returning None leaves the result unchanged.
    Nerror   z

)r#   rZ   r>   r@   jsonloadsr?   r2   
ValueError	TypeErrorrW   )rA   rB   resultrN   rT   parseds         r"   _on_transform_tool_resultri      s     )T*Hfc"F#fd#6(9c&kQ>N F?28<<< 	" s   7A6 6BBc                \    | j                  dt               | j                  dt               y )Npre_tool_calltransform_tool_result)register_hookr_   ri   )ctxs    r"   registerro     s%    o'89-/HIr!   )returnbool)r   r@   r   r@   rp   List[Tuple[str, str]])rA   r@   rB   r   rp   rr   )rT   rr   rp   r@   )r   N)rA   r@   rB   r   rN   r   rp   zOptional[Dict[str, str]])r   NN)
rA   r@   rB   r   rg   r   rN   r   rp   zOptional[str])rp   None)+__doc__
__future__r   rc   loggingr   retypingr   r   r   r   r   r   r
   	_patterns	getLogger__name__loggerr   __annotations__r4   r#   r&   r'   SECURITY_PATTERNS_ruler   tupler.   _re_srccompilera   _errwarningr5   r<   rI   rW   rZ   r_   ri   ro   r    r!   r"   <module>r      s  > #   	 	 3 3 #			8	$ ). B95  _a #%	 $(( E*%*%yy/ii-EIIlB78FN  ii G	(bjj1F7O V',*Z"2 
 
 	4 ==
= = 
	=
 =<JE xx 	NNNj!7D 	s   D##E2EE