Engagement summary

Targethttps://staging.crm.example-app.invalid (fictional)
TypeAuthenticated live testing (multi-principal)
AuthRS256 bearer JWTs (standard user + zero-permission limited user)
HarnessPentAGI + GLM-5.2 (local) with stop-and-decide prompt curation
Volume~300 live API calls; 10 attack vectors
Date2026-07-08

Headline results

IDVectorSeverityStatus
IPortal token-in-URL PIIHighCONFIRMED
CMCP-style rate-limit / permission gapMediumCONFIRMED
GWebhook token-only injectionMediumPrimary NOT-CONFIRMED / secondary CONFIRMED
ACross-tenant IDORMediumPrimary NOT-CONFIRMED / secondary CONFIRMED
JClient route/leak enumerationLowPartial (route leak CONFIRMED)
FStored XSS via unsafe HTML pathLowNOT-CONFIRMED (latent)
EUpload MIME/size spoofLowNOT-CONFIRMED (mitigated)
DImpersonation / org-switchLowNOT-CONFIRMED (mitigated)
BSession route auth defectInfoNOT-CONFIRMED (mitigated)
HTest-mode HS256 bypassInfoNOT-CONFIRMED (mitigated)

Of ten vectors: 2 confirmed on primary criterion, 1 partial, 7 not confirmed on primary (several still produced secondary defects). Positive controls mattered as much as the hits — the model was allowed to call NOT-CONFIRMED and move on.

I — Portal share token returns full PII without auth

High Status: CONFIRMED Primary criterion

GET /api/public/proposals/:shareToken returned full customer and sales-rep PII, financial fields, legal agreement text, and signature image material with no authentication and no rate limiting. Share tokens were also listable by any authenticated principal with document-read permission, forming a two-step harvest-then-exfiltrate chain.

Evidence shape (redacted):

GET /api/public/proposals/<64-char-token>
→ 200
{ contact{email, phone, address}, salesRep{email, phone},
  signerMeta, signatureData:"<redacted>", ... }

Why this is the sufficiency example. One working token established CONFIRMED. The first (uncurated) run burned cycles guessing dozens more tokens after the pattern was already obvious. The curated rule is: one PoC = stop enumerating.

Remediation sketch: require auth or short-lived signed URLs; rate-limit and monitor; never return full PII packages to an unauthenticated surface; treat list endpoints that reveal share tokens as sensitive.

C — MCP-style endpoint: no rate limit, no permission gate

Medium Status: CONFIRMED

POST /api/mcp (name illustrative) accepted rapid fire requests with no 429s (20/20 → 200) and accepted a zero-permission limited principal. That combination turns a convenient integration surface into an abuse and privilege-blind automation channel.

Remediation sketch: enforce the same permission model as the rest of the API; add rate limits and abuse detection; prefer scoped tokens for machine callers.

G — Lead webhook: token-only path, optional HMAC

Medium Primary: NOT-CONFIRMED · Secondary: CONFIRMED

End-to-end injection was not demonstrated (no valid webhook token recovered). Secondary confirmation: the route is intentionally unauthenticated (404 rather than 401 without a bearer), and per-row HMAC signing secrets are optional — rows with no secret accept token-only POSTs with no cryptographic verification.

A — Cross-tenant IDOR (primary blocked)

Medium Primary: NOT-CONFIRMED · Secondary: CONFIRMED

No foreign-org row could be captured. Organization context was server-derived from the JWT / DB, and client-supplied org fields were ignored. Secondary issues still confirmed: silent acceptance of org-tampering query params, unauthenticated PII via the same share-token surface as Finding I, and a broken error path returning 500 for missing IDs.

Positive / mitigated results (selected)

Positive These are intentional wins
  • B — Session route auth defect: suspected route not deployed; global /api/* auth middleware compensated.
  • D — Impersonation / org-switch: tampering ignored; admin switch endpoints correctly 403'd for non-admins.
  • E — Upload MIME/size spoof: server-side allowlist + size bounds + auth at presign time.
  • F — Stored XSS: unsafe HTML path exists in code but was not reachable with test privileges; notes rendered through a sanitizer; CSP blocked inline handlers. Logged as latent, not confirmed exploit.
  • H — Test-mode HS256 bypass: strong positive — RS256/JWKS enforced; candidate HS256 secrets, alg:none, and payload variants all 401.

On the first (uncurated) run, several of these would have been infinite grinds. On the closed run, the agent wrote NOT-CONFIRMED, called hack_result, and advanced.

Cross-cutting notes

Strengths observed: uniform unauthenticated 401 on /api/*; server-derived org context; RS256 algorithm pinning.

Weaknesses observed: missing rate limits on sensitive public/integration surfaces; long-lived share tokens as unauthenticated PII oracles; silent acceptance of authorization-relevant client fields (currently harmless, latent escalation risk).

Method note (autonomous harness)

Subtasks were atomic (one vector each). The evidence-sufficiency rule was: one working PoC = CONFIRMED; primary path blocked = NOT-CONFIRMED; stop enumerating once the call can be made. That rule is what turned a fifteen-hour non-closing run into a finished report. See the parent article and FORK-NOTES.md.


← Back to the article · crawlsys/pentagi