hubric 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hubric/__init__.py +6 -0
- hubric/__main__.py +5 -0
- hubric/auth/__init__.py +8 -0
- hubric/auth/classifier.py +187 -0
- hubric/auth/interviewer.py +497 -0
- hubric/auth/resolver.py +160 -0
- hubric/auth/schemes.py +188 -0
- hubric/auth/store.py +292 -0
- hubric/auth/verifier.py +325 -0
- hubric/cli/__init__.py +1 -0
- hubric/cli/commands/ci.py +53 -0
- hubric/cli/commands/forecast.py +241 -0
- hubric/cli/commands/inspect.py +76 -0
- hubric/cli/commands/payload.py +127 -0
- hubric/cli/commands/run.py +706 -0
- hubric/cli/commands/settings.py +44 -0
- hubric/cli/commands/trace.py +195 -0
- hubric/cli/helpers.py +527 -0
- hubric/cli/main.py +116 -0
- hubric/cli/tracer.py +109 -0
- hubric/config/__init__.py +5 -0
- hubric/config/loader.py +108 -0
- hubric/core/__init__.py +1 -0
- hubric/core/assertions.py +152 -0
- hubric/core/env.py +30 -0
- hubric/core/llm.py +545 -0
- hubric/core/schema.py +964 -0
- hubric/core/security.py +451 -0
- hubric/core/synthesizer.py +551 -0
- hubric/engine/__init__.py +1 -0
- hubric/engine/aggregator.py +211 -0
- hubric/engine/auth_pool.py +126 -0
- hubric/engine/dispatcher.py +69 -0
- hubric/engine/executor.py +792 -0
- hubric/engine/journey.py +806 -0
- hubric/engine/rate_limit.py +56 -0
- hubric/engine/registry.py +351 -0
- hubric/engine/runner.py +5 -0
- hubric/engine/scheduler.py +645 -0
- hubric/engine/types.py +55 -0
- hubric/engine/utils.py +214 -0
- hubric/forecast/__init__.py +0 -0
- hubric/forecast/analyzer.py +350 -0
- hubric/forecast/reporter.py +1234 -0
- hubric/report/__init__.py +1 -0
- hubric/report/diagnostics.py +122 -0
- hubric/report/export.py +14 -0
- hubric/report/export_html.py +158 -0
- hubric/report/export_json.py +23 -0
- hubric/report/html_assets.py +154 -0
- hubric/report/html_helpers.py +135 -0
- hubric/report/html_sections.py +429 -0
- hubric/report/junit.py +100 -0
- hubric/report/metrics.py +435 -0
- hubric/report/reporter.py +8 -0
- hubric/report/scoring.py +288 -0
- hubric/report/terminal.py +506 -0
- hubric/settings.py +355 -0
- hubric-0.5.0.dist-info/METADATA +395 -0
- hubric-0.5.0.dist-info/RECORD +64 -0
- hubric-0.5.0.dist-info/WHEEL +5 -0
- hubric-0.5.0.dist-info/entry_points.txt +3 -0
- hubric-0.5.0.dist-info/licenses/LICENSE +21 -0
- hubric-0.5.0.dist-info/top_level.txt +1 -0
hubric/__init__.py
ADDED
hubric/__main__.py
ADDED
hubric/auth/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from .store import load_auth, save_auth, delete_credentials, auth_file_exists
|
|
2
|
+
from .interviewer import run_auth_interview
|
|
3
|
+
from .resolver import resolve_headers
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"load_auth", "save_auth", "delete_credentials", "auth_file_exists",
|
|
7
|
+
"run_auth_interview", "resolve_headers",
|
|
8
|
+
]
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auth classification rules and pattern detection.
|
|
3
|
+
|
|
4
|
+
Categorizes endpoints into:
|
|
5
|
+
- token_source: login, register, signup (creates tokens; tested with payloads)
|
|
6
|
+
- untestable: email verification, OAuth callbacks, password resets, OTP/MFA
|
|
7
|
+
- external_dep: Stripe, Resend, Twilio, etc.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
# Routes that generate tokens from public credentials — should never require a token to be tested
|
|
13
|
+
TOKEN_SOURCE_SIGNALS = (
|
|
14
|
+
"/login", "/register", "/signup", "/sign-up", "/sign_up",
|
|
15
|
+
"/token", "/auth/token", "/refresh", "/auth/refresh",
|
|
16
|
+
"/signin", "/sign-in", "/sign_in", "/api/login", "/api/auth/login",
|
|
17
|
+
"/api/authenticate", "/authenticate", "/oauth/token", "/oauth2/token",
|
|
18
|
+
"/users/login", "/user/login", "/session"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Routes that depend on out-of-band flows — can never be load tested
|
|
22
|
+
UNTESTABLE_PATTERNS = [
|
|
23
|
+
# Email / phone verification & confirmation & activation
|
|
24
|
+
r"verify.*(email|phone|account|user|mobile)",
|
|
25
|
+
r"(email|phone|account|user|mobile).*verify",
|
|
26
|
+
r"confirm.*(email|phone|account|user)",
|
|
27
|
+
r"(email|phone|account|user).*confirm",
|
|
28
|
+
r"activate.*(account|user)",
|
|
29
|
+
r"(account|user).*activate",
|
|
30
|
+
r"resend.*(verif|email|code|otp|token|activation)",
|
|
31
|
+
r"(send|request).*(verif|code|otp|activation)",
|
|
32
|
+
|
|
33
|
+
# Password recovery & reset
|
|
34
|
+
r"reset.*password",
|
|
35
|
+
r"password.*reset",
|
|
36
|
+
r"forgot.*password",
|
|
37
|
+
r"password.*forgot",
|
|
38
|
+
r"recover.*password",
|
|
39
|
+
r"password.*recover",
|
|
40
|
+
r"change.*password.*token",
|
|
41
|
+
|
|
42
|
+
# Two-factor / MFA / Passkey / OTP
|
|
43
|
+
r"\bmfa\b", r"\b2fa\b", r"sms.*code",
|
|
44
|
+
r"\btotp\b", r"\bwebauthn\b", r"\bfido\b",
|
|
45
|
+
r"magic.?link",
|
|
46
|
+
r"send.?otp",
|
|
47
|
+
|
|
48
|
+
# Outbound notifications / invites / dispatches (trigger external email/SMS without testable state)
|
|
49
|
+
r"resend",
|
|
50
|
+
r"\binvite(s|d)?\b",
|
|
51
|
+
r"\binvitations?\b",
|
|
52
|
+
r"request.*(otp|code|token|verif|link)",
|
|
53
|
+
r"dispatch.*(email|sms|notification)",
|
|
54
|
+
|
|
55
|
+
# OAuth / SAML / Webhook callbacks
|
|
56
|
+
r"oauth.*callback",
|
|
57
|
+
r"auth0.*callback",
|
|
58
|
+
r"saml.*acs",
|
|
59
|
+
r"webhook.*callback",
|
|
60
|
+
r"/callbacks?(/|$)",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# Patterns specifically identifying outbound dispatch triggers
|
|
64
|
+
OUTBOUND_DISPATCH_PATTERNS = [
|
|
65
|
+
r"resend",
|
|
66
|
+
r"send.?otp",
|
|
67
|
+
r"magic.?link",
|
|
68
|
+
r"\binvite(s|d)?\b",
|
|
69
|
+
r"\binvitations?\b",
|
|
70
|
+
r"request.*(otp|code|token|verif|link)",
|
|
71
|
+
r"dispatch.*(email|sms|notification)",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
def _is_outbound_dispatch(text: str) -> bool:
|
|
75
|
+
"""Returns True if text indicates an outbound notification or invitation route."""
|
|
76
|
+
text_lower = text.lower()
|
|
77
|
+
return any(re.search(p, text_lower) for p in OUTBOUND_DISPATCH_PATTERNS)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Known external service signals in route, summary, description, body fields
|
|
81
|
+
EXTERNAL_SERVICE_PATTERNS = [
|
|
82
|
+
r"stripe", r"paystack", r"flutterwave", r"paypal",
|
|
83
|
+
r"resend", r"sendgrid", r"mailgun", r"twilio",
|
|
84
|
+
r"webhook", r"callback",
|
|
85
|
+
r"auth0", r"cognito", r"clerk", r"supabase", r"firebase", r"okta", r"kinde",
|
|
86
|
+
r"square", r"braintree", r"razorpay", r"paddle", r"lemonsqueezy",
|
|
87
|
+
r"aws-sns", r"plaid", r"hubspot", r"salesforce"
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
# Parameter and body field names that indicate out-of-band flows (email codes, OTPs, magic links)
|
|
91
|
+
OOB_BODY_SIGNALS = {
|
|
92
|
+
"oob_code", "oobCode", "otp", "otp_code", "otpCode",
|
|
93
|
+
"verification_code", "verificationCode", "verify_code", "verifyCode",
|
|
94
|
+
"activation_token", "activationToken", "activation_code", "activationCode",
|
|
95
|
+
"reset_token", "resetToken", "reset_code", "resetCode",
|
|
96
|
+
"confirm_token", "confirmToken", "confirmation_code", "confirmationCode",
|
|
97
|
+
"magic_link_token", "magicLinkToken",
|
|
98
|
+
"email_token", "emailToken", "email_code", "emailCode",
|
|
99
|
+
"sms_code", "smsCode", "mfa_token", "mfaToken", "mfa_code", "mfaCode",
|
|
100
|
+
"totp", "totp_code", "totpCode", "two_factor_code", "two_factor_token",
|
|
101
|
+
"passcode"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _is_token_source(ep: dict) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Returns True if this endpoint generates tokens rather than consuming them.
|
|
108
|
+
These need valid request payloads, not Authorization headers.
|
|
109
|
+
Rubric tests them as public endpoints with synthesized bodies.
|
|
110
|
+
"""
|
|
111
|
+
route = ep.get("route", "").lower()
|
|
112
|
+
return any(signal in route for signal in TOKEN_SOURCE_SIGNALS)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _is_refresh_endpoint(ep: dict) -> bool:
|
|
116
|
+
"""Returns True if this endpoint is a token refresh endpoint."""
|
|
117
|
+
route = ep.get("route", "").lower()
|
|
118
|
+
summary = ep.get("summary", "").lower()
|
|
119
|
+
description = ep.get("description", "").lower()
|
|
120
|
+
text = f"{route} {summary} {description}"
|
|
121
|
+
return bool(
|
|
122
|
+
re.search(r"\b(auth/)?(token/)?refresh\b", text)
|
|
123
|
+
or "/refresh" in route
|
|
124
|
+
or "refresh token" in text
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _detect_untestable(ep: dict) -> bool:
|
|
129
|
+
"""Returns True if the endpoint requires an out-of-band flow.
|
|
130
|
+
|
|
131
|
+
Detection strategy:
|
|
132
|
+
1. Route/summary/description regex matching against UNTESTABLE_PATTERNS
|
|
133
|
+
2. Parameter inspection — if ANY body, query, or path parameter
|
|
134
|
+
matches OOB_BODY_SIGNALS, the endpoint requires an external code/token
|
|
135
|
+
that Rubric cannot synthesize (e.g. resetToken, otp, email_code).
|
|
136
|
+
3. Parameter inspection for generic 'token' or 'code' on verification/reset routes.
|
|
137
|
+
"""
|
|
138
|
+
text = " ".join([
|
|
139
|
+
ep.get("route", ""),
|
|
140
|
+
ep.get("summary", ""),
|
|
141
|
+
ep.get("description", ""),
|
|
142
|
+
]).lower()
|
|
143
|
+
if any(re.search(p, text) for p in UNTESTABLE_PATTERNS) or _is_outbound_dispatch(text):
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
# Collect parameter names across body, query, and path params
|
|
147
|
+
param_names = set()
|
|
148
|
+
for p in ep.get("query_params", []) + ep.get("path_params", []):
|
|
149
|
+
param_names.add(p.get("name", "").lower())
|
|
150
|
+
|
|
151
|
+
body_schema = ep.get("body_schema") or {}
|
|
152
|
+
properties = body_schema.get("properties") or {}
|
|
153
|
+
param_names.update(k.lower() for k in properties.keys())
|
|
154
|
+
|
|
155
|
+
oob_lower = {k.lower() for k in OOB_BODY_SIGNALS}
|
|
156
|
+
if param_names.intersection(oob_lower):
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
# Generic token/code on verification/reset endpoints
|
|
160
|
+
if ("token" in param_names or "code" in param_names) and any(
|
|
161
|
+
kw in text for kw in ("verify", "confirm", "reset", "activate", "unlock")
|
|
162
|
+
):
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _detect_external(ep: dict) -> str | None:
|
|
169
|
+
"""
|
|
170
|
+
Returns a short label if the endpoint looks like an external service dep.
|
|
171
|
+
Checks route, summary, description, tags, and body field names.
|
|
172
|
+
"""
|
|
173
|
+
targets = [
|
|
174
|
+
ep.get("route", ""),
|
|
175
|
+
ep.get("summary", ""),
|
|
176
|
+
ep.get("description", ""),
|
|
177
|
+
" ".join(ep.get("tags", [])),
|
|
178
|
+
]
|
|
179
|
+
body = ep.get("body_schema") or {}
|
|
180
|
+
targets += list((body.get("properties") or {}).keys())
|
|
181
|
+
text = " ".join(targets).lower()
|
|
182
|
+
|
|
183
|
+
for pattern in EXTERNAL_SERVICE_PATTERNS:
|
|
184
|
+
if re.search(pattern, text):
|
|
185
|
+
match = re.search(pattern, text)
|
|
186
|
+
return match.group(0) if match else "external service"
|
|
187
|
+
return None
|