#!/usr/bin/env python3
"""vdss full-clone history secret scan — 81 newly-cloned repos (10 already done in L4).
Read-only offline scan. Engagement one-off."""
import json, re, subprocess, sys
from pathlib import Path
D = Path('/root/ir-assessment/redteam/gitlab_vdss_com_vn')
DEST = D/'repos'
ALREADY = {  # already scanned in L4_clone_secrets.json
 'merchantx_ma-service.git','merchantx_ma-webapp.git','poms_be_core.git','poms_be_gateway.git',
 'poms_be_hrm-service.git','poms_be_notification.git','poms_be_project-service.git',
 'poms_be_storage.git','poms_be_task-service.git','poms_fe_poms.git'}
INTERESTING = re.compile(r'glpat-|\d{8,10}:AA[\w-]{33}|BEGIN [A-Z ]*PRIVATE KEY|AKIA[0-9A-Z]{16}|'
    r'(password|passwd|secret|api_key|apikey|token|access_key|secret_key|clientSecret|client_secret)\s*[:=]\s*["\']?[^\s"\']{6,}', re.I)
JDBC = re.compile(r'jdbc:(mysql|postgresql|mariadb|oracle|sqlserver)://[^\s"\'$]+', re.I)
URLCRED = re.compile(r'://[^/\s:@]+:[^@\s]{3,}@')
NOISE = re.compile(r'node_modules|vendor/|\.lock|package-lock|yarn\.lock|\.min\.js|change-?password|forget-?password|reset-?password|'
                   r'\.dart$|\.ts$|\.scss|component\.ts|README|process\.env|env\(\'|tobemodified|\.svg', re.I)
PATS = ['password','passwd','secret','api_key','apikey','private_key','BEGIN','AKIA','access_key','secret_key','clientSecret','jdbc:']

def scan(repo):
    revs = subprocess.run(['git','-C',str(repo),'rev-list','--all'],capture_output=True,text=True).stdout.split()
    if not revs: return {}
    hits={}
    for pat in PATS:
        r = subprocess.run(['git','-C',str(repo),'grep','-F','-I','-n','--full-name','-e',pat]+revs,
                           capture_output=True,text=True,errors='ignore')
        for line in r.stdout.splitlines():
            m = re.match(r'^[0-9a-f]{40}:(.*?):(\d+):(.*)$', line)
            if not m: continue
            path,content = m.group(1), m.group(3)
            c = content.strip()
            if not (INTERESTING.search(c) or JDBC.search(c) or URLCRED.search(c)): continue
            if NOISE.search(path) or (NOISE.search(c) and not INTERESTING.search(c)): continue
            key=(path,c[:160])
            hits.setdefault(path,{})
            hits[path][key]=c[:240]
    return {p:sorted(set(v)) for p,v in hits.items()}

report={}
CK = D/'L4_fullclone_ck'; CK.mkdir(exist_ok=True)
repos=[r for r in sorted(DEST.glob('*.git')) if r.name not in ALREADY]
print(f'scanning {len(repos)} new repos (skipping {len(ALREADY)} done)', file=sys.stderr)
for i,rp in enumerate(repos,1):
    ckf = CK/(rp.name+'.json')
    if ckf.exists():
        res = json.load(open(ckf))
    else:
        res=scan(rp)
        ckf.write_text(json.dumps(res,ensure_ascii=False))
    if res:
        report[rp.name]=res
        print(f'  [{i}/{len(repos)}] {rp.name}: {sum(len(v) for v in res.values())} hits in {len(res)} files', flush=True)
# merge ALL checkpoints (resume-safe)
report={}
for rp in repos:
    ckf = CK/(rp.name+'.json')
    if ckf.exists():
        res=json.load(open(ckf))
        if res: report[rp.name]=res
(D/'L4_fullclone_secrets.json').write_text(json.dumps(report,ensure_ascii=False,indent=1))
tot=sum(len(v) for v in report.values())
print(f'[+] DONE {len(report)} repos with findings, {tot} hits -> L4_fullclone_secrets.json')
