autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
97
skills/free-tools-abuse/scripts/api-provider-probe-safe.py
Normal file
97
skills/free-tools-abuse/scripts/api-provider-probe-safe.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python
|
||||
"""Safe official API connectivity probe.
|
||||
Reads provider credentials from env, redacts output, and never buys/rents/registers.
|
||||
"""
|
||||
import argparse, base64, json, os, secrets, string, time, urllib.error, urllib.request, urllib.parse
|
||||
from pathlib import Path
|
||||
|
||||
UA = 'HermesOfficialAPIProbe/skill'
|
||||
SECRET_HINTS = ('token','secret','password','api_key','apikey','authorization','auth','sid')
|
||||
|
||||
def mask(v):
|
||||
s = '' if v is None else str(v)
|
||||
return '' if not s else ('***' if len(s) <= 8 else s[:4] + '...' + s[-4:])
|
||||
|
||||
def redact(x):
|
||||
if isinstance(x, dict):
|
||||
return {k: (mask(v) if any(h in k.lower().replace('-','_') for h in SECRET_HINTS) else redact(v)) for k, v in x.items()}
|
||||
if isinstance(x, list):
|
||||
return [redact(i) for i in x[:3]] + ([{'_truncated_count': len(x)-3}] if len(x) > 3 else [])
|
||||
return x
|
||||
|
||||
def parse(raw):
|
||||
if not raw: return None
|
||||
try: return json.loads(raw)
|
||||
except Exception: return raw[:1200]
|
||||
|
||||
def req(method, url, headers=None, data=None, timeout=25):
|
||||
h = {'User-Agent': UA, 'Accept': 'application/json, text/plain;q=0.8, */*;q=0.5'}
|
||||
if headers: h.update(headers)
|
||||
body = None
|
||||
if data is not None:
|
||||
body = json.dumps(data).encode(); h.setdefault('Content-Type','application/json')
|
||||
r = urllib.request.Request(url, data=body, headers=h, method=method)
|
||||
t = time.time()
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=timeout) as resp:
|
||||
return resp.status, parse(resp.read().decode('utf-8','replace')), round((time.time()-t)*1000,1)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, parse(e.read().decode('utf-8','replace')), round((time.time()-t)*1000,1)
|
||||
except Exception as e:
|
||||
return 0, {'error': type(e).__name__, 'message': str(e)[:500]}, round((time.time()-t)*1000,1)
|
||||
|
||||
def step(name, status, body, ms, ok=(200,201,202,204)):
|
||||
return {'step': name, 'status': status, 'ok': status in set(ok), 'ms': ms, 'body': redact(body)}
|
||||
|
||||
def first_domain(p):
|
||||
arr = p if isinstance(p, list) else next((p.get(k) for k in ('hydra:member','member','domains','data') if isinstance(p, dict) and isinstance(p.get(k), list)), [])
|
||||
if not arr: return None
|
||||
return arr[0].get('domain') if isinstance(arr[0], dict) else str(arr[0])
|
||||
|
||||
def mailtm(lifecycle=False):
|
||||
base='https://api.mail.tm'; out={'provider':'mail.tm','configured':True,'steps':[]}
|
||||
st,b,ms=req('GET',base+'/domains'); out['steps'].append(step('domains',st,b,ms,{200}))
|
||||
if not lifecycle or st != 200: return out
|
||||
acct=tok=None
|
||||
try:
|
||||
dom=first_domain(b); assert dom, 'no domain'
|
||||
addr='hermes-probe-'+''.join(secrets.choice(string.ascii_lowercase+string.digits) for _ in range(12))+'@'+dom
|
||||
pwd='P-'+secrets.token_urlsafe(24)
|
||||
st,a,ms=req('POST',base+'/accounts',data={'address':addr,'password':pwd}); acct=a.get('id') if isinstance(a,dict) else None
|
||||
out['steps'].append({'step':'create_account','status':st,'ok':st==201,'ms':ms,'address':addr,'account_id':acct})
|
||||
st,t,ms=req('POST',base+'/token',data={'address':addr,'password':pwd}); tok=t.get('token') if isinstance(t,dict) else None
|
||||
out['steps'].append({'step':'get_token','status':st,'ok':st==200 and bool(tok),'ms':ms,'token':mask(tok),'token_len':len(tok or '')})
|
||||
if tok:
|
||||
st,me,ms=req('GET',base+'/me',headers={'Authorization':'Bearer '+tok}); out['steps'].append(step('me',st,me,ms,{200}))
|
||||
finally:
|
||||
if acct and tok:
|
||||
st,b,ms=req('DELETE',base+f'/accounts/{acct}',headers={'Authorization':'Bearer '+tok}); out['steps'].append(step('delete_account',st,b,ms,{200,202,204}))
|
||||
return out
|
||||
|
||||
def keyed(name, url, env, header_name=None, basic=False, no_key_ok=(401,403)):
|
||||
key=os.getenv(env,''); out={'provider':name,'configured':bool(key),'env':env,'steps':[]}
|
||||
headers=None
|
||||
ok=range(200,300)
|
||||
if key:
|
||||
if basic: headers={'Authorization':'Basic '+base64.b64encode((key+':').encode()).decode()}
|
||||
elif header_name: headers={header_name:key}
|
||||
else:
|
||||
ok=no_key_ok
|
||||
st,b,ms=req('GET',url,headers=headers); out['steps'].append(step('probe',st,b,ms,ok))
|
||||
return out
|
||||
|
||||
def main():
|
||||
ap=argparse.ArgumentParser(); ap.add_argument('--mailtm-lifecycle',action='store_true'); ap.add_argument('--out',default='')
|
||||
a=ap.parse_args()
|
||||
probes=[
|
||||
mailtm(a.mailtm_lifecycle),
|
||||
keyed('webshare','https://proxy.webshare.io/api/v2/proxy/list/?mode=direct&page=1&page_size=1','WEBSHARE_TOKEN','Authorization', no_key_ok={401,403}),
|
||||
keyed('mailsac','https://mailsac.com/api/me','MAILSAC_KEY','Mailsac-Key', no_key_ok={200,401,403}),
|
||||
keyed('mailslurp','https://api.mailslurp.com/inboxes?size=1','MAILSLURP_API_KEY','x-api-key', no_key_ok={401,403}),
|
||||
keyed('mailosaur','https://mailosaur.com/api/servers','MAILOSAUR_API_KEY',basic=True, no_key_ok={401,403}),
|
||||
]
|
||||
report={'generated_at':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime()),'scope':'official API connectivity only','probes':probes}
|
||||
text=json.dumps(report,ensure_ascii=False,indent=2)
|
||||
if a.out: Path(a.out).write_text(text+'\n',encoding='utf-8')
|
||||
print(text)
|
||||
if __name__ == '__main__': main()
|
||||
Reference in New Issue
Block a user