Methodology · v1.0
The method, written down.
Most vendors describe their process in adjectives. This is the actual scoring table, the actual suppression list, and the actual limits the scanner runs under — the same ones the engine reads at runtime. If a finding in your report contradicts this page, that is a bug and we want to hear about it.
Three passes
The pipeline is AI-driven and human-verified. A person reads the whole report before it is delivered, and any finding the arbiter could not confirm is held for that review rather than shipped.
- 01
Blue team Claude Sonnet
Runs every check and raises everything it can justify.
Deliberately over-eager. A finding that is never raised cannot be verified later, so this pass is tuned for recall rather than precision — which is why the count here is not the count you receive.
- 02
Red team Claude Opus
Gets the findings and nothing else. Told to refute them.
A separate context with no source, no scan notes and no memory of pass 1. It demands a specific proof for each Critical and High finding, checks the score against the pinned table, and removes anything matching a known-noise category.
- 03
Arbiter Claude Opus
Sees both arguments and rules.
Confirmed findings go in the report with the argument attached. Disputed ones become POTENTIAL and are held for a person rather than padded in. Removed ones are logged so we can measure our own false-positive rate.
What we throw away
Pass 2 removes findings in these categories by default. This is the list that keeps a report short and honest instead of long and padded.
- Hardcoded values that are not credentials — project IDs, table names, hostnames, ports, public API URLs.
- Development fallback secrets, unless the fallback actually ships in the deployed artifact.
- Findings whose only evidence sits in a test file — unless that evidence is a credential, key or token.
- Null-dereference crashes with no security consequence.
- Log spoofing and the logging of non-personal data.
- Telemetry keys that are designed to be public, such as a Sentry DSN.
- Theoretical race conditions with no concrete exploit path.
- SQL injection claims where the query is genuinely parameterised with bound arguments.
- XSS claims that rest only on text interpolation in React or Angular, with no raw-HTML sink named.
One category we deliberately did not adopt from the public tooling this list is adapted from: “missing hardening with no demonstrated attack path”. Missing headers, absent HSTS and weak transport settings are most of what a passive audit legitimately finds. Discarding them would produce a shorter report that told you less.
What we never throw away
These override every rule above. Public review tooling routinely excludes rate limiting and denial of service by policy, because it is written to review a team’s own pull requests. We audit deployed applications, where these are the findings that matter most.
- An authentication or authorization control that can be defeated by request volume, batching or aliasing — including absent brute-force protection on login, password reset, signup or MFA entry. These read as rate limiting and are confidentiality findings.
- A field the user can write that governs their own quota, credits or entitlement. This is mass assignment, and it is the signature defect of apps built on AI app-builders.
- An AI endpoint or agent loop with no usage, step or spend ceiling. Unbounded third-party cost is a financial denial of service.
- Unbounded pagination, query depth or upload size.
- A concrete credential, key or token — or an interpolated argument reaching a query, exec or template sink — regardless of which file it lives in.
- Any finding that names a raw-HTML or dynamic-code sink such as innerHTML, dangerouslySetInnerHTML, v-html or new Function.
Scoring
CVSS 3.1, with the vector printed next to every finding. Common finding types are pinned to a fixed band so the same issue does not score differently between two engagements. Moving outside a band takes a written justification in the report. This is an extract; the full table is in your report’s appendix.
| Finding | Severity | CVSS |
|---|---|---|
| Supabase service_role key exposed | CRITICAL | 9.0–9.8 |
| Firebase Admin SDK in client bundle | CRITICAL | 9.5–9.8 |
| OpenAI / Anthropic API key in client JS | CRITICAL | 9.0–9.8 |
| Row-level security gap exposing personal data | CRITICAL | 8.5–9.5 |
| Row-level security gap allowing writes | HIGH | 7.0–8.5 |
| Public storage bucket | HIGH | 6.5–8.0 |
| System prompt in client code | HIGH | 6.5–7.5 |
| Missing Content-Security-Policy, dynamic content | MEDIUM | 5.0–6.5 |
| Source map exposed in production | MEDIUM | 5.0–6.0 |
| Wildcard CORS with credentials | MEDIUM | 5.0–6.5 |
| Missing subresource integrity on external scripts | MEDIUM | 4.5–6.0 |
| Sensitive path disclosed in robots.txt | LOW | 3.0–3.5 |
| Missing HSTS | LOW | 2.5–3.5 |
| Supabase anon key in client JS | INFO | 0.0 |
How the scanner behaves
- Request rate
- Two requests per second for a passive engagement, five for an active one, and never more than ten. Three concurrent connections to one host. A 429 halves the rate; a 503 pauses for a minute.
- Identification
- We scan from a single declared user-agent that names Pilum and carries a contact address. We do not impersonate a browser during passive testing, and we do not attempt to evade a WAF — if one blocks us, that goes in the report as a limitation and as a point in your favour.
- Where we will not go
- DNS is resolved once and pinned for the whole engagement, and the resolved address is checked against a blocklist covering private ranges, loopback, link-local, carrier-grade NAT, and every cloud metadata endpoint — including the encoded forms used to slip past naive string matching. Redirects are capped and re-checked. This exists so our own scanner cannot be pointed at something it should not reach.
- Passive means passive
- A passive engagement makes GET, HEAD and OPTIONS requests only. No writes, no authentication attempts, no form submissions, no fuzzing. Anything beyond that needs a signed scope document first, and the pipeline refuses to deliver a paid report without one on file.
What the report must contain
The report is validated against a fixed structure before it can be delivered, and again after it is written. Sections cannot be silently dropped to make a thin engagement look finished.
- Scope, methodology and dates on the cover, with the rate limit and user-agent we scanned from.
- A named section for what was not covered, including our own methodology limits.
- Every finding with severity, CVSS vector, evidence, business impact, an OWASP mapping, and three levels of fix.
- The Pass 2 challenge and the Pass 3 ruling for each finding — including the ones where our own review argued the finding down.
- Positive controls: the things your application already does correctly.
- A remediation priority matrix, and a plain-language verdict.
What this is not
We do not currently run a formal retest cycle, and a certificate is only issued when an engagement closes with zero Critical and zero High findings. If your reviewer requires a signed attestation regardless of outcome, or a named human tester on an accredited firm’s letterhead, say so before you engage — we will tell you on the call whether we can help rather than after the invoice. Most vendor security questionnaires do not require it. Some contracts do.
A security audit is a point-in-time assessment. Absence of findings is not proof of absence of vulnerabilities, and we would rather write that here than imply otherwise in a sales conversation.