
    j0                    (   d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlmZ ddlZdZdddZej"                  d        Zej&                  j)                  dg d	      d
        Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Zd Z d Z!d Z"d Z#y)u1  task-2579 v3.1 audit-trail Bash capture follow-up hardening tests.

회장 결정 2026-05-14 — task-2579 anu-direct implementation:
- LOKI-REC-1: Python destructive ops capture (os.remove/unlink/rmdir/shutil.rmtree/open-write/write_text)
- LOKI-REC-4: JWT / Authorization Token (non-Bearer) redaction
- audit_log_dir_overridden 가시성 필드 (false attribution guard 확장)
- captured_by = post-tool-use.sh.v3.1 (forward marker)
- jq-fallback 필드 통일 (MAAT-REC-4)

본 test는 production hook을 stdin feed로 호출 + AUDIT_TRAIL_LOG_DIR override.
    )annotationsN)Pathz(/home/jay/.claude/hooks/post-tool-use.shc                h   d||d| id}i t         j                  dt        |      i}t        j                  dt
        gt        j                  |      dd|d      }|j                  }d	}||k(  }	|	st        j                  d
|	fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }
t        j                  d|j                          dz   d|
iz  }t#        t        j$                  |            d x}x}	}|dz  }|j'                         sg S |j)                         j+                         D cg c](  }|j-                         st        j.                  |      * c}S c c}w )NBashcommand)	tool_namecwd
session_id
tool_inputAUDIT_TRAIL_LOG_DIRbashT
   )inputcapture_outputtextenvtimeoutr   ==)z2%(py2)s
{%(py2)s = %(py0)s.returncode
} == %(py5)sresult)py0py2py5zhook failed: z
>assert %(py7)spy7zaudit-trail.jsonl)osenvironstr
subprocessrun	HOOK_PATHjsondumps
returncode
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_saferepr_format_assertmsgstderrAssertionError_format_explanationexists	read_text
splitlinesstriploads)r   log_dirr
   r	   payloadr   r   @py_assert1@py_assert4@py_assert3@py_format6@py_format8log_filelines                 F/home/jay/workspace/tests/regression/test_audit_trail_followup_2579.py	_run_hookr=      s?     '*	G >RZZ
=.G
=C^^	jj!F BB!BBBBBBBBB6BBB6BBBBBBBBB]6==/#BBBBBBBB,,H??	)1););)=)H)H)J[djjlDJJt[[[s   ?F/F/c                    | dz  S )Nlogs )tmp_paths    r<   tmp_audit_dirrB   1   s    f    zcommand, expected_pattern))-python3 -c "import os; os.remove('/tmp/abc')"python-destructive)z,python -c "import os; os.unlink('/tmp/xyz')"rE   )z-python3 -c "import os; os.rmdir('/tmp/dir1')"rE   )z6python3 -c "import shutil; shutil.rmtree('/tmp/dir2')"rE   )z>python3 -c "from pathlib import Path; Path('/tmp/p').unlink()"rE   )/python3 -c "open('/tmp/f.txt', 'w').write('x')"python-open-write)z1python3 -c "open('/tmp/f.bin', 'wb').write(b'x')"rG   )zIpython3 -c "from pathlib import Path; Path('/tmp/p').write_text('hello')"zpython-write-textc                   t        ||       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}x}}|d	   }	|	d
   }
d}|
|k(  }|slt        j                  d|fd|
|f      t        j                  |
      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x}
x}}|	d   }
d}|
|u }|st        j                  d|fd|
|f      t        j                  |
      t        j                  |      dz  }t        j                  d|       dz   d|iz  }t        t        j                  |            d x}
x}}|	d   }||v }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        j                  d| d|	d    d      dz   d|iz  }t        t        j                  |            d x}}y ) N   r   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slenentriesr   py1py3py6assert %(py8)spy8r   schema_version   z%(py1)s == %(py4)srN   py4assert %(py6)srP   is_destructiveTisz%(py1)s is %(py4)sz"is_destructive expected True for: 
>assert %(py6)sdestructive_pattern_matchedin)z%(py0)s in %(py3)sexpected_patternr   rO   z	pattern 'z
' not in ''
>assert %(py5)sr   r=   rK   r$   r%   r&   r'   r(   r)   r,   r-   r*   )rB   r   ra   rL   @py_assert2@py_assert5r6   @py_format7@py_format9entry@py_assert0r7   @py_format5r5   @py_format4r8   s                   r<   (test_python_destructive_pattern_capturedrn   ;   s    /Gw<1<1<133ww<1AJE!"'a'"a''''"a'''"'''a'''''''!"ZdZ"d*ZZZ"dZZZ"ZZZdZZZ.PQXPY,ZZZZZZZZ$%BC CC  C               D    $%Z6S0T/UUVW    rC   c                @   t        d|       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            d
x}x}}y
)z7python read-only ops should NOT be flagged destructive.z1python3 -c "import os; print(os.listdir('/tmp'))"rI   r   rJ   rK   rL   rM   rQ   rR   Nr   rY   FrZ   r\   rV   rX   rP   
r=   rK   r$   r%   r&   r'   r(   r)   r,   r-   
rB   rL   rf   rg   r6   rh   ri   rk   r7   rl   s
             r<   'test_python_non_destructive_not_flaggedrr   S   s    M}]Gw<1<1<133ww<11:&'050'50000'5000'00050000000rC   c                @   t        d|       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            d
x}x}}y
)z0python open with 'r' mode should NOT be flagged.z+python3 -c "open('/tmp/f.txt', 'r').read()"rI   r   rJ   rK   rL   rM   rQ   rR   Nr   rY   FrZ   r\   rV   rX   rP   rp   rq   s
             r<   !test_python_open_read_not_flaggedrt   Z   s    GWGw<1<1<133ww<11:&'050'50000'5000'00050000000rC   c                   d}d| d}t        ||       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }d
d|iz  }t        t        j                  |            dx}x}}|d   }	|	d   }
d}|
|u }|slt        j                  d|fd|
|f      t        j                  |
      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}
x}}d}
|	d   }|
|v }|st        j                  d|fd|
|f      t        j                  |
      t        j                  |      dz  }t        j                  d|	d          dz   d|iz  }t        t        j                  |            dx}
x}}y)z4JWT format eyJ...header.payload.signature redaction.zdeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ.abcdefghijklmnopzcurl -H "X-Auth: z" https://api.example.comrI   r   rJ   rK   rL   rM   rQ   rR   Nr   redaction_appliedTrZ   r\   rV   rX   rP   z<REDACTED:jwt>r   r_   z%(py1)s in %(py4)szjwt placeholder missing: r]   re   )rB   	jwt_tokencmdrL   rf   rg   r6   rh   ri   rj   rk   r7   rl   s                r<   test_jwt_redactionrz   f   sy   vIi[(A
BC]+Gw<1<1<133ww<1AJE$%--%----%---%----------_uY/_//___/______/___3LUS\M]L^1________rC   c                ~   d}t        ||       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   }|d   }	d}
|	|
u }|slt        j                  d|fd|	|
f      t        j                  |	      t        j                  |
      dz  }dd|iz  }t        t        j                  |            d
x}	x}}
d}	|d   }
|	|
v }|st        j                  d|fd|	|
f      t        j                  |	      t        j                  |
      dz  }t        j                  d|d          dz   d|iz  }t        t        j                  |            d
x}	x}}
y
)zJAuthorization header with non-Bearer scheme (e.g. Token, Basic) redaction.zUcurl -H "Authorization: Token abc123def456ghi789jkl012mno345" https://api.example.comrI   r   rJ   rK   rL   rM   rQ   rR   Nr   rv   TrZ   r\   rV   rX   rP   <REDACTED:authz_token>r   r_   rw   z!authz_token placeholder missing: r]   re   rB   ry   rL   rf   rg   r6   rh   ri   rj   rk   r7   rl   s               r<   'test_authorization_non_bearer_redactionr~   q   sk   
aC]+Gw<1<1<133ww<1AJE$%--%----%---%----------#ouY'7o#'77ooo#'7ooo#ooo'7ooo;\]bcl]m\n9oooooooorC   c                   d}t        ||       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   }|d   }	d}
|	|
u }|slt        j                  d|fd|	|
f      t        j                  |	      t        j                  |
      dz  }dd|iz  }t        t        j                  |            d
x}	x}}
g }d}|d   }||v }|}	|sd}|d   }||v }|}	|	st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd	|iz  }|j                  |       |s_t        j                  dfdf      t        j                  |      t        j                  |      dz  }dd|iz  }|j                  |       t        j                  |d      i z  }dd|iz  }t        t        j                  |            d
x}	x}x}x}x}x}x}}y
) u<   Bearer 토큰은 기존 token redaction 유지 (regression).zPcurl -H "Authorization: Bearer abc123def456ghi789jkl012" https://api.example.comrI   r   rJ   rK   rL   rM   rQ   rR   Nr   rv   TrZ   r\   rV   rX   rP   z<REDACTED:token>r   r|   r_   )z%(py3)s in %(py6)s)rO   rP   z%(py8)s)z%(py11)s in %(py14)s)py11py14z%(py16)spy16zassert %(py19)spy19)r=   rK   r$   r%   r&   r'   r(   r)   r,   r-   append_format_boolop)rB   ry   rL   rf   rg   r6   rh   ri   rj   rk   r7   rl   r5   @py_assert10@py_assert13@py_assert12@py_format15@py_format17@py_format18@py_format20s                       r<   #test_bearer_still_redacted_as_tokenr   {   s   
\C]+Gw<1<1<133ww<1AJE$%--%----%---%----------ee%	"2e"22e8PeTYZcTde8PTd8deee"2eeeeee"2eeeeeee8PTdeee8PeeeTdeeeeeeeeeeeeeerC   c                   t        d|       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   }d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }	dd|	iz  }
t        t        j                  |
            d
x}}|d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d
x}x}}|d   }t        |       }||k(  }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}y
)u?   AUDIT_TRAIL_LOG_DIR override 시 audit_log_dir_overridden=true.zecho override-testrI   r   rJ   rK   rL   rM   rQ   rR   Nr   audit_log_dir_overriddenr_   z%(py1)s in %(py3)srj   rN   rO   assert %(py5)sr   TrZ   r\   rV   rX   rP   audit_log_dir)z0%(py1)s == %(py6)s
{%(py6)s = %(py3)s(%(py4)s)
}r   rB   )rN   rO   rW   rP   )r=   rK   r$   r%   r&   r'   r(   r)   r,   r-   r   )rB   rL   rf   rg   r6   rh   ri   rj   rk   rm   r8   r7   rl   s                r<   6test_audit_log_dir_overridden_field_true_when_test_envr      s   ,m<Gw<1<1<133ww<1AJE%.%....%...%................+,44,4444,444,4444444444!7S%77!%77777!%7777!777777S777S777777777777%77777777rC   c                p   t        d|       }|d   }d}||v }|st        j                  d|fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd	|iz  }t        t        j                  |            d
x}}y
)u<   audit_log_dir 필드 자체가 entry에 박제되어야 함.zecho dir-fieldr   r   r_   r   rj   r   r   r   N)	r=   r$   r%   r)   r&   r'   r(   r,   r-   )rB   rL   rj   rk   rf   rm   r8   s          r<   ,test_audit_log_dir_field_present_in_requiredr      sq    (-8GAJE#?e####?e###?######e###e#######rC   c                B   t        d|       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   d   }d}||k(  }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            d
x}x}}y
)u8   task-2579 적용 시 captured_by = post-tool-use.sh.v3.1zecho v3.1-markerrI   r   rJ   rK   rL   rM   rQ   rR   Nr   captured_bypost-tool-use.sh.v3.1rU   rV   rX   rP   rp   rq   s
             r<   test_captured_by_v3_1_markerr      s    *M:Gw<1<1<133ww<11:m$?(??$(?????$(????$???(????????rC   c                ^   t        d|       }|d   }h d}||j                         z
  }| }|s~t        j                  d|       dz   ddt	        j
                         v st        j                  |      rt        j                  |      ndiz  }t        t        j                  |            d}|d	   }d
}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}|d   }d}||k(  }	|	slt        j                  d|	fd||f      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}	}y)u_   v3.1 신규 필드 (audit_log_dir_overridden / audit_log_dir) + 기존 v3 필드 모두 박제.zecho schema-v3.1-testr   >   tsbotr	   filetoolmemberts_kstr   sessiontask_idr   command_hashr   claimed_actorrY   rS   rv   affected_path_candidatesr   owner_attribution_statusactor_verification_evidencer^   zmissing v3.1 fields: z
>assert not %(py0)sr   missingNrS   rT   r   rU   rV   rX   rP   r   r   )r=   keysr$   r*   r&   r'   r(   r)   r,   r-   r%   )rB   rL   rj   v3_1_requiredr   r5   @py_format2rk   r7   rf   rl   rh   s               r<   (test_schema_v3_1_required_fields_presentr      s%   /?GAJEM0 ejjl*G;9;99/y9999999w999w999999!"'a'"a''''"a'''"'''a''''''':#::#:::::#:::::::#::::::::rC   c                F   d}t        ||       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   }|d   }	d}
|	|
u }|slt        j                  d|fd|	|
f      t        j                  |	      t        j                  |
      dz  }dd|iz  }t        t        j                  |            d
x}	x}}
d}	|d   }
|	|
v }|slt        j                  d|fd|	|
f      t        j                  |	      t        j                  |
      dz  }dd|iz  }t        t        j                  |            d
x}	x}}
y
)u`   task-2516+1 forensic 동기 시나리오: python으로 file truncate 시도가 capture되는지.z@python3 -c "open('utils/replacement_pr_runner.py', 'w').close()"rI   r   rJ   rK   rL   rM   rQ   rR   Nr   rY   TrZ   r\   rV   rX   rP   rG   r^   r_   rw   rp   r}   s               r<   5test_task_2516plus1_python_truncate_scenario_capturedr      sQ   
NC]+Gw<1<1<133ww<1AJE!"*d*"d****"d***"***d*******F%(E"FF"FFFFF"FFFFFFF"FFFFFFFFrC   c                   ddl }d}g }t        d      D ]H  }|j                         }t        || d|        |j	                  |j                         |z
  dz         J |j                          |t        |      dz     }d	}||k  }|st        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }	t        j                  d|dd|       dz   d|	iz  }
t        t        j                  |
            dx}}y)u   D-AUDIT-9: python -c 미포함 명령은 sub-pattern grep 진입 안 함 → fast path.
    median hook latency 200ms 미만 강제 (회장 hard limit).
    r   Nzecho hello worldr   zperf-)r
   i        )<)z%(py0)s < %(py3)s	median_msrb   z)D-AUDIT-9 violation: median hook latency z.1fz/ms exceeds 200ms hard limit. All measurements: rd   r   )timerangeperf_counterr=   r   sortrK   r$   r%   r&   r'   r(   r)   r*   r,   r-   )rB   r   ry   latencies_ms_startr   rf   r5   rm   r8   s              r<   *test_d_audit_9_no_python_command_fast_pathr      s6    
CL2Y B!!##}5=T..058D@AB
 S.!34I 9s?  9s                  4Ic? C)N	,    rC   c                D   t        d|       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            d
x}x}}d}|d   d   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            d
x}x}}y
)uK   hotfix 후에도 python destructive ops capture 정상 작동 (regression).rD   rI   r   rJ   rK   rL   rM   rQ   rR   Nr   rY   TrZ   r\   rV   rX   rP   rE   r^   r_   rw   rp   rq   s
             r<   =test_d_audit_9_python_destructive_still_captured_after_hotfixr      sL   I=YGw<1<1<133ww<11:&'/4/'4////'4///'///4///////L71:.K#LL#LLLLL#LLLLLLL#LLLLLLLLrC   c                D   t        d|       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            d
x}x}}d}|d   d   }||v }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            d
x}x}}y
)u,   hotfix 후 open-write 패턴 정상 capture.rF   rI   r   rJ   rK   rL   rM   rQ   rR   Nr   rY   TrZ   r\   rV   rX   rP   rG   r^   r_   rw   rp   rq   s
             r<   <test_d_audit_9_python_open_write_still_captured_after_hotfixr     sL   K][Gw<1<1<133ww<11:&'/4/'4////'4///'///4///////K'!*-J"KK"KKKKK"KKKKKKK"KKKKKKKKrC   c                @   t        d|       }t        |      }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd	|iz  }t        t        j                  |            d
x}x}}|d   d   }d}||u }|slt        j                  d|fd||f      t        j                  |      t        j                  |      dz  }	dd|	iz  }t        t        j                  |            d
x}x}}y
)uj   prefilter 효과 검증: python -c 없는 명령에 .write_text(가 있어도 destructive 아님 (정상).z'echo 'doc says .write_text() is useful'rI   r   rJ   rK   rL   rM   rQ   rR   Nr   rY   FrZ   r\   rV   rX   rP   rp   rq   s
             r<   Atest_d_audit_9_write_text_subpattern_not_triggered_without_pythonr   	  s    A=QGw<1<1<133ww<11:&'050'50000'5000'00050000000rC   )z	test-sessz/home/jay/workspace)
r   r   r3   r   r
   r   r	   r   returnz
list[dict])$__doc__
__future__r   builtinsr&   _pytest.assertion.rewrite	assertionrewriter$   r!   r   r   pathlibr   pytestr    r=   fixturerB   markparametrizern   rr   rt   rz   r~   r   r   r   r   r   r   r   r   r   r   r@   rC   r<   <module>r      s   
 #    	   6	\0   	11`pf 8$@;NG *ML1rC   