Sample report — fictional project, findings written for illustration.
acme-agency/storefront
codewebcompletedhttps://staging.example.com
This is the shape of what lands in your account after a scan: a score and a grade, every finding with the file and line it comes from, why it matters and how to fix it, then an AI reasoning pass that connects the findings to each other.
Each finding deducts points from 100: 25 for Critical, 10 for High, 4 for Medium, 1 for Low, 0 for Info. 90 and above is an A, 75 to 89 a B, 60 to 74 a C, 40 to 59 a D, below 40 an F.
gitleaksosv-scannerweb-scannerheader-check
Hard-coded payment API secret
A live-looking payment provider secret is committed in a configuration file and present in the repository history.
config/payments.ts:12
A02:2021 — Cryptographic Failuresexport const paymentsConfig = {
merchantId: "acme-storefront",
apiSecret: "sample-secret-do-not-use-0000", // illustrative value
};
Why it matters. Anyone who can read the repository, a fork, or a leaked build artifact can act as the shop against the payment provider: issue refunds, read orders, create charges.
Suggested fix. Rotate the secret now, remove it from the history, and load it from the environment (process.env.PAYMENTS_API_SECRET) or a secrets manager. Add a pre-commit secret scan.
gitleaksInsecure direct object reference on order lookup
GET /api/orders/:id returns any order to any signed-in customer; ownership is never compared with the session.
src/routes/orders.ts:48
A01:2021 — Broken Access Controlrouter.get("/api/orders/:id", requireLogin, async (req, res) => {
const order = await db.orders.findById(req.params.id);
res.json(order); // no check that order.customerId === req.user.id
});
Why it matters. Order ids are sequential, so one customer can walk the whole range and read every other customer's name, address and purchases.
Suggested fix. Scope the query to the caller: findOne({ id, customerId: req.user.id }) and return 404 when nothing matches. Prefer non-guessable ids.
web-scannerVulnerable dependency: example-templating 2.3.1
CVE-2099-00001 (illustrative id): server-side template injection in versions below 2.4.0, reachable from the order-confirmation e-mail renderer.
package-lock.json:2187
A06:2021 — Vulnerable and Outdated ComponentsCVE-2099-00001 (illustrative)"node_modules/example-templating": {
"version": "2.3.1", // fixed in 2.4.0
"resolved": "https://registry.example.com/example-templating/-/example-templating-2.3.1.tgz"
}
Why it matters. The renderer receives the customer's display name. A crafted name executes template expressions on the server when the e-mail is built.
Suggested fix. Upgrade to example-templating 2.4.0 or later, rebuild the lockfile, and confirm the resolved version after install.
osv-scannerOpen redirect after login
The `next` parameter is followed without checking that it is a relative path on this site.
src/routes/login.ts:73
A01:2021 — Broken Access Controlres.redirect(req.query.next ?? "/account");
// https://staging.example.com/login?next=https://evil.example.net
Why it matters. A link on the real login page can send customers to a look-alike site right after they typed their password, which makes phishing far more convincing.
Suggested fix. Accept only paths that start with a single `/`, or map `next` to an allow-list of known destinations.
web-scannerMissing security headers
https://staging.example.com answers without Content-Security-Policy, X-Content-Type-Options or a Strict-Transport-Security max-age.
https://staging.example.com/
A05:2021 — Security MisconfigurationHTTP/2 200
content-type: text/html; charset=utf-8
server: example-edge
# no content-security-policy
# no x-content-type-options
# no strict-transport-security
Why it matters. Without a CSP a single injected script runs with full page privileges; without HSTS a first visit over plain HTTP can be intercepted.
Suggested fix. Send `Content-Security-Policy` with a nonce-based script-src, `X-Content-Type-Options: nosniff`, and `Strict-Transport-Security: max-age=31536000; includeSubDomains`.
header-checkSession cookie without SameSite
The session cookie sets Secure and HttpOnly but no SameSite attribute.
src/middleware/session.ts:31
A05:2021 — Security Misconfigurationcookie: { httpOnly: true, secure: true } // sameSite not set
Why it matters. Browsers default differ; an explicit value keeps cross-site requests from carrying the session where they should not.
Suggested fix. Set `sameSite: "lax"` (or "strict" if no cross-site entry point needs the session).
web-scannerServer version banner disclosed
The `X-Powered-By` header names the framework and its major version.
https://staging.example.com/
x-powered-by: Express
Why it matters. Not exploitable on its own; it saves an attacker a step when picking which known issue to try.
Suggested fix. Disable the header (app.disable("x-powered-by")).
header-check Add-onAI reasoning panel
After the deterministic scanners finish, several AI models reason over their findings together and a judge consolidates the result. The pass is read-only: it works from the findings above and sends no new request to the site.
Exploit chain
Leaked secret + order IDOR: from repository access to every customer's orders
- The payment secret in config/payments.ts:12 (F-1) lets a reader of the repository call the provider as the shop.
- The provider returns customer references and order ids, which feed the sequential lookup in src/routes/orders.ts:48 (F-2).
- Together they turn read access to one repository into read access to the whole order book, with no account on the site at all.
Fixing either link breaks the chain; fixing F-1 first also closes the path from outside the site.
Business-logic observation
Discount code is checked when the cart is displayed, not when the order is placed
The code that applies the discount runs in the cart view, and the checkout handler appears to trust the total it receives. If that holds, a customer could keep a discount after the code has expired. No scanner can see this from a response alone, so it is flagged for your consented verification rather than reported as a finding.
How the panel reports. Verdicts are PASS, CAUTION or FAIL. A PASS is the strongest claim a report can make, so it is withheld, and the score capped, when coverage was thin, when no finding was backed by an actual execution, or when a finding cites a location that does not resolve in the code supplied. How to read a report
Smart-contract verification excerpt
For contracts, a finding is not left as an opinion: it is replayed on a local fork of the target chain. This excerpt is from a fictional contract, AcmeVault.sol.
Verdict: FAILbacked by an execution on a local fork
Reentrancy in withdraw() lets a caller drain the vault
contracts/AcmeVault.sol:64
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "insufficient");
(bool ok, ) = msg.sender.call{value: amount}(""); // external call first
require(ok, "send failed");
balances[msg.sender] -= amount; // state updated after
}
$ forge test --fork-url $LOCAL_FORK --match-test test_reentrancyDrain -vv
[PASS] test_reentrancyDrain()
vault balance before : 100.0 ETH
attacker deposit : 1.0 ETH
vault balance after : 0.0 ETH (illustrative output)
Suggested fix. Update the balance before the external call (checks-effects-interactions) or guard the function with a reentrancy lock.
Create an account, run a scan of your own project, and spend your free run on a reasoning pass.