Engagement summary
| Target | https://staging.crm.example-app.invalid (fictional) |
| Type | Authenticated live testing (multi-principal) |
| Auth | RS256 bearer JWTs (standard user + zero-permission limited user) |
| Harness | PentAGI + GLM-5.2 (local) with stop-and-decide prompt curation |
| Volume | ~300 live API calls; 10 attack vectors |
| Date | 2026-07-08 |
Headline results
| ID | Vector | Severity | Status |
|---|---|---|---|
| I | Portal token-in-URL PII | High | CONFIRMED |
| C | MCP-style rate-limit / permission gap | Medium | CONFIRMED |
| G | Webhook token-only injection | Medium | Primary NOT-CONFIRMED / secondary CONFIRMED |
| A | Cross-tenant IDOR | Medium | Primary NOT-CONFIRMED / secondary CONFIRMED |
| J | Client route/leak enumeration | Low | Partial (route leak CONFIRMED) |
| F | Stored XSS via unsafe HTML path | Low | NOT-CONFIRMED (latent) |
| E | Upload MIME/size spoof | Low | NOT-CONFIRMED (mitigated) |
| D | Impersonation / org-switch | Low | NOT-CONFIRMED (mitigated) |
| B | Session route auth defect | Info | NOT-CONFIRMED (mitigated) |
| H | Test-mode HS256 bypass | Info | NOT-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
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
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
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)
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)
- 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.