For agencies monitoring verified client websites. Turn scan findings into prioritized actions and agency-branded HTML/PDF reports. Full scans require a plan and current ownership proof; the public check reads publicly available information.
Open Glarion · View the fictional sample report · Deployment signing
Report workflow: a verified website → findings with evidence and corrective guidance → an agency-branded HTML report or printable PDF. The linked sample uses fictional findings and an example client; it is not a customer case study or a measured security outcome.
Website security monitoring and client-ready reporting for digital agencies.
Know what changed before your client does.
Check a website at glarion.app · View a sample report
Glarion helps digital agencies monitor verified client websites, identify meaningful changes and turn technical findings into clear reports under the agency's own name. The ungated public check reads only what a website already publishes; paid full scans add controlled active checks, weekly or monthly monitoring and change-focused notifications.
Built around authorization, not assumptions. Full scans require a plan and current DNS or
.well-knownownership proof, checked again at execution time. Targets are checked for private, loopback and cloud-metadata addresses before scanning; external-scanner enforcement has the limitations described below. Every queued scan retains an authorization trail.
| Proof point | What is enforced |
|---|---|
| Target authorization | Two independent ownership gates, including expiry at execution time |
| SSRF controls | Resolved-address checks, address pinning for internal fetches, and a Nuclei local-network restriction; see network boundaries and known limits |
| Safe scan policy | Detection-only allowlist, rate limits and no fuzzing, brute force or denial-of-service templates |
| Verified quality | Live-database integration tests and mutation checks over the authorization gate |
| Actionable output | Findings are normalized and triaged into self-contained client reports |
The source is public for technical review and transparency, not for reuse. Glarion is proprietary software; see LICENSE.
A service that scans arbitrary domains on request is an attack tool unless it can prove the requester owns the target. So no scan runs against a domain whose ownership has not been proved and whose proof has not lapsed.
Ownership is proved with a DNS TXT record or a .well-known file and
expires after 30 days. The check runs in two independent places:
crates/api/src/routes/scans.rsrefuses to queue a job for an unverified target.crates/orchestrator/src/runner.rschecks again at execution time, because a job can sit in the queue while its verification lapses, and the runner is what actually sends traffic.
Every queued scan writes an authorization record in the same transaction as the job, so a job cannot exist without a trail of who authorized it.
A full scan probes: it requests paths a site never advertised and tries known vulnerability fingerprints against them. That is the act the gate above exists for.
Reading a site's DNS records, its TLS configuration, the response headers
on its front page, and the two files it publishes for automated readers is
not that. It is what a browser or a search-engine crawler collects on an
ordinary visit, and there is nothing to authorise about reading what a site
broadcasts to anyone. POST /api/preview does exactly that much and no
more — one front-page request, two well-known files, GET only, no
redirects followed — and needs neither an account nor a verified domain.
Keeping them apart matters commercially as much as legally. Before it, nobody could see a single result without first editing DNS for a client's domain, which is to say before we had shown them anything at all.
The split is also where the money is. The free check stays free, with no
account and no limit on how many domains you point it at. The full scan
answers to two gates rather than one: proving ownership settles whether we
may run it, and a plan settles whether it is paid for. Both are checked in
create_scan, next to each other, and both fail closed. The free plan gets
the check and stops there — it is a real product on its own, and it is not
the scanner.
Proving ownership of a name is not the same as proving the name is safe to
contact. A hostname that passes every syntactic check can still resolve to
127.0.0.1 or 169.254.169.254, which would point the scanner at our own
infrastructure or at the cloud metadata service. Two layers handle this:
crates/orchestrator/src/domain.rsrejects targets written as an IP literal, aslocalhost, or under an internal suffix.net_guard.rschecks resolved addresses and rejects the target if any address matches its non-public address filters, including private, loopback and cloud-metadata addresses.
The connection boundary differs by caller. Internal HTTPS fetches configure
the client to use a validated address and disable redirects; the TLS probe
connects directly to a validated socket address. See
verification.rs,
preview.rs and
tls.rs.
The external Nuclei wrapper
checks addresses immediately before spawning the scanner, but passes a
hostname, not a pinned address. It always adds
-restrict-local-network-access. That is a separate scanner-side control;
its enforcement depends on the installed Nuclei implementation. These
controls do not establish that every DNS-rebinding path is closed.
Intensity is capped independently of authorization: detection-only tools on an allowlist, six scans per target per day, an enforced request rate, and fuzzing, brute-force and denial-of-service template categories excluded. External tools are spawned with an argument vector, never a shell string.
crates/api— HTTP API (Axum): auth, targets, verification, scans, results. Also builds therunnerbinary.crates/orchestrator— verification, target validation, scan policy, job runner, tool wrappers.crates/report— report rendering. Triage lives inorchestrator, since deciding which findings matter is domain knowledge rather than presentation, and the worker needs it too.web/— the dashboard: TypeScript compiled withtsc, no framework and no bundler, matching the rest of the portfolio. Driven from the keyboard:⌘K/Ctrl-Kor/opens the command palette,j/kwalk the list.migrations/— Postgres schema, applied with sqlx.
bash scripts/dev-api.sh # API + database
cargo run --bin runner # scan worker
cd web && npm install && npm run build # dashboard, once
python -m http.server 5173 --directory web # serve itscripts/dev-api.sh starts the development database, generates a JWT
secret on first run, and opens CORS to the local dashboard. In production
the API needs DATABASE_URL and JWT_SECRET (32+ characters); PORT and
CORS_EXTRA_ORIGINS are optional. Both binaries refuse to start rather
than fall back to a default.
Email uses Resend, the provider already used elsewhere in the portfolio:
set RESEND_API_KEY, MAIL_FROM, and PUBLIC_URL (where confirmation
links should point — taken from configuration rather than from a request
header, since a link built from an attacker-supplied Host is how
confirmation mail becomes phishing). With no key configured, messages are
logged instead of sent and the confirmation link appears in the API output,
so the signup flow can be exercised locally without a mail account.
The dashboard talks to the same origin it is served from, including local
previews on a custom port. If frontend and API are served separately, set the
glarion-api meta tag explicitly to the API origin.
The runner needs nuclei on
PATH. A job whose tool is missing fails with a recorded reason rather
than hanging.
bash scripts/dev-db.sh testCreates and starts a Postgres cluster dedicated to this project, then runs the whole suite with the integration tests active. 270 tests at present.
Plain cargo test --workspace also works, but the integration tests in
crates/api/tests/scan_gate.rs skip silently without a database, so a
green run on a machine with no Postgres does not mean the gate was
exercised. Before pushing, run the full set of checks:
bash scripts/ci-local.shThe local script runs formatting, clippy, the integration suite and an
assertion that the gate tests ran rather than skipped. It also runs
cargo audit when installed, and frontend checks when their dependencies
are installed. GitHub Actions
runs formatting, clippy, frontend checks and the database-backed suite,
including the gate-test assertion; it does not currently run cargo audit.
Check the run for the commit you are evaluating rather than relying on a
previously successful build. One advisory is deliberately ignored by the
local audit — see .cargo/audit.toml.
Since 2026-10-21 production is self-hosted behind a Cloudflare Tunnel and
deployed by hand from master: a clean clone, the release build from the
Dockerfile, a health check, and an automatic switch back to the previous
build if the new one does not come up. Production runs
DATABASE_MIGRATIONS=verify, so a new migration is applied by hand before
the deploy. A CI run does not deploy by itself. A scheduled workflow
(uptime.yml) asks https://glarion.app/health
from outside every 15 minutes and fails, so GitHub sends an email, when it
does not answer 200 after three tries.
scripts/dev-db.sh manages a cluster in ~/.glarion-devdb on port 5433,
created under your normal user account with a generated password. It is
deliberately separate from any system PostgreSQL install: it needs no
superuser credentials and cannot disturb an existing database.
bash scripts/dev-db.sh start # create and start
bash scripts/dev-db.sh stop
bash scripts/dev-db.sh status
bash scripts/dev-db.sh url # connection URL for the test database
bash scripts/dev-db.sh url dev # connection URL for the app's database
bash scripts/dev-db.sh destroy # delete it entirelyThe cluster holds two databases. glarion_test is what the integration
tests use, and they TRUNCATE every table in it; glarion_dev is what the
running app uses. Keeping them apart is why a test run no longer destroys
the accounts and scan history you were working with.
Working: ownership verification, target and resolved-address validation, authentication, scan policy and both gate checks, the Nuclei wrapper, and the job runner.
Registration collects a name and date of birth alongside the address; the date of birth exists to check the account holder is old enough to enter a paid contract, which is the only reason to hold it. An account cannot be used until the address is confirmed through an emailed link, and neither signup nor sign-in reveals whether an address is already registered.
Authentication uses Argon2 with JWTs pinned to HS256 and a token_version
column for revocation. Login performs the same work whether or not the
account exists, so response timing does not reveal which addresses are
registered. Confirmation links are stored only as a hash, expire after 24
hours, and stop working once used. Authentication and verification
endpoints are rate limited.
The gate has been verified against a live database and mutation-checked:
disabling the expiry check makes
scan_is_refused_when_verification_has_expired fail, so the test is known
to be capable of catching a regression rather than merely passing.
Findings are triaged rather than listed: a real scan of a small production site returned 32 results, of which three warranted action and nineteen were inventory. The scanner's own severity is not used as the priority — it rates a missing Content-Security-Policy as informational, which is wrong for a report going to a client. Reports are rendered as a single self-contained HTML file under the agency's name, printable to PDF.
Sites can be put on a weekly or monthly schedule. A schedule is standing authorization, so it can only be set while ownership is proved, and the scheduler refuses — loudly — once that proof lapses rather than quietly scanning on. Notification is sent only when a result differs from the one before it: a weekly "still three issues" teaches people to filter the sender, and then the message that mattered goes unread too.
| Plan | Price | Sites | Full scan | Automatic checks |
|---|---|---|---|---|
| Free | — | 1 | No — the free check only | No |
| Solo | 19 EUR / month, 170 / year | 5 | Yes | Weekly or monthly |
| Studio | 39 EUR / month, 350 / year | 10 | Yes | Weekly or monthly |
| Agency | 99 EUR / month, 750 / year | 40 | Yes | Weekly or monthly |
Prices exclude VAT, which Stripe adds at checkout by country.
Priced per account with a site allowance, not per site. That structure is intended for agencies managing a portfolio of client websites rather than a single application.
The site allowance is derived from the plan rather than stored beside it: two copies of the same number drift apart the moment either is written without the other, and the one that drifts is the one enforcing a paid limit.
Checkout and cancellation are hosted by Stripe, so no card detail reaches these servers.
TRIAL_ENABLED=true turns on the three-day trial; unset or anything else
leaves billing exactly as before (the 14-day Solo trial for a first
subscription, and a failed payment keeping the plan while Stripe retries).
With it on:
- Every paid plan, monthly or yearly, offers two buttons: Start 3-day free
trial (
start_trial: true) and Subscribe now (false, charged at once). The trial needs a payment method; Stripe charges automatically when it ends. - One trial per account, per normalised mailbox (
email_hash) and per card (card_fingerprint), held intrial_claimsand enforced when Stripe creates the subscription: a duplicate is cancelled before any charge, withcancellation_details.comment = trial_already_used. A site already scanned during another account's trial is not scanned free again. - During the trial: 3 sites at most, 2 full scans per site a day, reports without agency branding. The ownership gate is unchanged.
- A failed payment blocks the plan at once (
past_dueis no longer grace); a later successful payment restores it. Schedules pause rather than reset. GET /api/billing/prices(public) publishes the Stripe Prices;GET /api/billingaddsstate,blocked_reason,trial_ends_at,can_start_trial,trial_limitsandfirst_charge(Stripe's preview).- The worker sends the reminder a day before the trial ends.
The webhook endpoint needs invoice.paid, invoice.payment_failed,
invoice.payment_action_required and customer.subscription.trial_will_end
besides the customer.subscription.* events. The behaviour is pinned by
crates/api/tests/trial.rs, which plays Stripe back through a local stand-in.
Copy .env.local.example to .env.local and fill it in; dev-api.sh
loads it. That file is gitignored, and secrets belong in it rather than on
a command line, where they end up in shell history. With none of it set the
billing endpoints refuse rather than granting anything, and the rest of the
product works unchanged.
Live at glarion.app, including paid checkout. A subscription is continuous business activity, which in Italy needs a P.IVA and VAT handling — Stripe Tax computes the rates, but the filing obligation is not something software solves, and is being handled outside this repository rather than a blocker on shipping.
A Privacy Policy and Terms of Service
cover what the product collects and why. Deleting an account
(DELETE /api/account) anonymizes rather than removes the row: the audit
trail described above — who authorized a scan, and when — has to survive
the account that authorized it, which is exactly the retention GDPR
Article 17(3)(e) allows for establishing or defending a legal claim.
Not built yet: the httpx/subfinder tool wrappers. Certificate expiry —
originally planned as a testssl.sh wrapper — is read directly over a raw
TLS handshake instead (crates/orchestrator/src/tools/tls.rs), which
avoids both the shell dependency and duplicating what Nuclei's own
ssl-* templates already report.
-
Production rate limits share PostgreSQL counters. Client attribution depends on a trusted proxy rewriting the client-address header (
TRUST_PROXY_CLIENT_IP=cloudflarereadsCF-Connecting-IP,1readsFly-Client-IP); a chain of proxies needs an operational check to avoid grouping unrelated visitors together. -
Own HTTP requests pin validated addresses. Nuclei resolves independently and is launched with its local-network restriction; worker egress isolation is an additional operational boundary to verify.
-
The IPv6 destination policy deliberately excludes special-purpose and transition ranges, including some legitimate non-website protocol services.
-
Passing tests and dependency audits does not establish complete security.
-
Nuclei resolves the hostname independently after Glarion's address check, so a DNS change can occur between that check and a scanner connection. Glarion passes
-restrict-local-network-access, but its unit test checks the presence of the flag, not end-to-end DNS-rebinding resistance of the installed scanner. Do not treat this as a guarantee that scanning is safe against every SSRF or DNS-rebinding scenario.
- PC Tweaker: Windows tuning for performance, gaming and privacy, with hardware monitoring and restore tools for supported settings.
- PC Tweaker Uninstaller: Windows uninstaller with evidence-based removal scores, rollback snapshots and a local removal ledger.
- Tweaky Driver: Windows driver inventory and update review, showing detected devices, installed versions and available driver offers.
- Redaxa: Sensitive-data checks and redaction for AI prompts, plus local repository secret reviews on Windows.
- Redexa Social: Private YouTube analytics for Windows, kept in a database on your PC.
- Frame Witness: Windows photo and video recovery with source offsets, SHA-256 hashes and dated findings.

