TILLSHIELD · TILLDRILL

Test what you own, from the outside.

Beta

TillDrill tests your sites and APIs the way an attacker or a traffic spike would, before either arrives. It tests only what you prove you own, and it checks that proof again before every run. A scanner that probed any host would be reconnaissance for attackers, and a load tester that hit any host would be a denial-of-service service, so ownership comes first. Manage targets and read results under Shield → TillDrill.

What’s here
Targets, the proof of owning them, passive checks, safe active checks, latency from 13 regions, load drills, app build scans, defense drills, exposure scans of the addresses your cloud accounts hold, and the opt-in intrusive probes are all here. The intrusive probes send inputs built to be mishandled, so they stay off until you turn them on, inside a window you set.
01Targets

What you can add

A target is one of two kinds:

  • A domain: a bare hostname such as app.example.com. Its site is tested over https. Pasting https://app.example.com/ works too, and keeps only the hostname.
  • A URL: a base URL with its scheme, port and path, such as https://api.example.com:8443/v1, for an API. It can’t carry a query, a fragment or credentials.

A target has to be reachable from the internet by name. Private and reserved names such as localhost, .local, .internal and .test are refused, and so are bare IP addresses and Till’s own domains. A workspace holds up to 50 targets. Only owners and admins can add, check, run or remove them.

02Ownership

Prove you own it

Each target gets its own value to publish. Any one of these three proves it:

A DNS record

Add a TXT record at _tillshield. plus the hostname, holding the value TillShield shows:

dns
_tillshield.app.example.com.  300  IN  TXT  "tillshield-verify=<value from TillShield>"

A file on the server

Serve the same value as a line of plain text at /.well-known/tillshield-verify on the target’s origin, including its port for a URL target. TillShield reads it with no redirects, from a public address only, and reads at most 4 KB.

text
# served at https://app.example.com/.well-known/tillshield-verify, status 200, no redirect
tillshield-verify=<value from TillShield>

A domain you already verified

A domain this workspace verified for single sign-on covers itself and every subdomain. A TillAuth custom domain covers itself. There’s nothing to publish. Instead, the domain’s own record is checked again, and a TillAuth domain also counts while it still points at TillAuth. A target one of these covers is verified the moment you add it, and that check reads only your own DNS records, never the target.

What each proof allows

A DNS record shows you control the name, but a name can point at anyone’s server. Only the file shows you control the server that answers for it. So the proofs allow different drills:

DrillDNS record or verified domainFile on the server
Passive checksYesYes
Latency checksYesYes
Active checksOver https only, where the certificate ties the server to the nameYes
Intrusive probesNoYes, and only with your authorisation and a window
Load drillsNoYes

A defense drill exercises a rule rather than a target, so it needs no proof.

03Re-checks

Checked again before every run

A proof is only as good as the moment it was seen, so every drill looks for it again before it sends a single request. It tries the method that worked last first. A drill doesn’t start when:

  • no proof holds; the target then shows as lapsed if it was proven before;
  • the only proof that holds doesn’t allow that drill, such as a DNS record for a load drill;
  • a DNS lookup fails on Till’s side, so ownership can’t be checked. The target’s state stays as it was.

Check proof on a target, or tilldev shield drill targets check, looks for the proof straight away. Each check reaches out to the target, so a workspace can ask for 30 an hour, and one target once every 10 seconds. When no proof holds, the target shows what each check saw.

Adding, proving, lapsing and removing a target, changing its schedules or latency settings, recording or withdrawing authorisation for intrusive probes, starting a run by hand, stopping one, and the end of every load drill, with who did it, are all in the workspace audit log. Removing a target retires it: nothing runs against it again, and past runs keep its name. Adding it back gives it a new value to publish.

04Passive checks

What any visitor can see

A passive run reads what your site already shows every visitor and judges it against 32 checks. Each check is tagged with the OWASP Top 10 category it falls under, and with ASVS requirements where one fits. A run makes only TLS handshakes, GET requests and DNS lookups, and nothing on the target is changed. Start a run with Run now on a target, from the CLI or the API, or set it to repeat.

A run goes in this order, and stops at the first step that fails:

  1. It checks the proof again, as above.
  2. It resolves the hostname. If any address it resolves to is private or reserved, the run is refused and nothing is sent.
  3. Every connection to the target goes to those checked addresses only, so a DNS change mid-run can’t point it somewhere else. Each connection gives up after 10 seconds, and the whole run after 90 seconds.
  4. It reads four sections, each on its own, so one that can’t be reached doesn’t hide the others.

HTTP requests carry the user agent TillShield-Drill/1 (+https://tilldev.dev/docs/tillshield/drill), so you can find them in your logs. TillShield keeps the names and attributes of cookies, never their values, and keeps URLs without their query strings.

TLS and certificate

One handshake as a browser would make it, then each protocol version from TLS 1.0 to 1.3 tried on its own. The certificate is checked for trust, for the hostname, for its expiry and for its key size.

CheckLevelOWASP
Certificate has expired
drill-cert-expired
Higha04, asvs.12.2.2
Certificate expires within 14 days
drill-cert-expiring
Mediuma04, asvs.12.2.2
Certificate doesn’t cover this hostname
drill-cert-name-mismatch
Higha04, asvs.12.2.2
Certificate isn’t trusted
drill-cert-untrusted
Higha04, asvs.12.2.2
Certificate key is too short
drill-cert-weak-key
Higha04
Served over plain http
drill-plain-http
Higha04, asvs.12.2.1
Doesn’t offer TLS 1.3
drill-tls-no-1-3
Lowa04, asvs.12.1.1
Accepts TLS 1.0 or 1.1
drill-tls-old-protocol
Mediuma04, asvs.12.1.1

Response

A GET of the target, following up to 3 redirects on the same origin. A redirect to another origin is recorded, not followed. Only the status, the security and server headers, and the names and attributes of cookies are kept. The body is never read.

CheckLevelOWASP
Cookie without a __Host- or __Secure- prefix
drill-cookie-no-prefix
Informationala02, asvs.3.3.1
Cookie has no SameSite attribute
drill-cookie-no-samesite
Lowa01, asvs.3.3.2
Cookie sent without Secure
drill-cookie-not-secure
Mediuma02, asvs.3.3.1
Cookie readable by scripts
drill-cookie-script-readable
Lowa02, asvs.3.3.4
No Cross-Origin-Opener-Policy on a page
drill-coop-missing
Informationala02, asvs.3.4.8
Content-Security-Policy allows eval
drill-csp-eval
Lowa05, asvs.3.4.3
Content-Security-Policy allows inline scripts
drill-csp-inline-scripts
Mediuma05, asvs.3.4.3
No Content-Security-Policy on a page
drill-csp-missing
Mediuma02, asvs.3.4.3
Content-Security-Policy leaves plugins or base URLs open
drill-csp-plugins-base
Lowa02, asvs.3.4.3
Any site can frame the page
drill-framing
Mediuma02, asvs.3.4.6
No Strict-Transport-Security header
drill-hsts-missing
Mediuma02, asvs.3.4.1
HSTS lasts less than a year
drill-hsts-short
Lowa02, asvs.3.4.1
HSTS doesn’t cover subdomains
drill-hsts-subdomains
Informationala02, asvs.3.4.1
No X-Content-Type-Options: nosniff
drill-nosniff-missing
Lowa02, asvs.3.4.4
No Referrer-Policy header
drill-referrer-policy
Informationala02, asvs.3.4.5
Response names software versions
drill-version-disclosed
Lowa02, asvs.13.4.6

Plain http

What port 80 answers for the same hostname, and whether it sends visitors to https on that hostname. A URL target with its own port, or one already on http, skips this section.

CheckLevelOWASP
Plain http doesn’t redirect to https
drill-http-no-upgrade
Mediuma04, asvs.12.2.1

DNS and mail

MX, SPF, DMARC and CAA records, looking up the name tree for DMARC and CAA, and whether answers are signed with DNSSEC. These are public records any resolver can read.

CheckLevelOWASP
No CAA record
drill-caa-missing
Informationala02
Sends or takes mail but has no DMARC policy
drill-dmarc-missing
Mediuma02
DMARC policy only monitors
drill-dmarc-none
Lowa02
DNS answers aren’t signed
drill-dnssec-off
Informationala02
Takes mail but publishes no SPF
drill-spf-missing
Mediuma02
More than one SPF record
drill-spf-multiple
Mediuma02
SPF doesn’t reject other senders
drill-spf-permissive
Mediuma02
05Active checks

Ask the target directly, and read only

Active checks ask your own target questions a passive run can’t: does this path answer, does this folder list its files, what does this API share with another origin, does this link parameter send visitors somewhere else, does this name still point at a provider that claims it. They are judged against 15 checks, tagged the same way as the passive ones.

Everything a run sends is a read. There is no PUT, DELETE, PATCH or CONNECT, no password guessed, no injected payload, nothing written and nothing deleted. Two values a run sends are deliberately unreachable: the origin it uses to ask about cross-origin sharing, and the host it uses to test redirect parameters, both under a name reserved so it can never resolve. A target that honours the redirect sends nobody anywhere real.

A run goes through the same gate as every other drill, and then some of its own:

  1. The proof is checked again. Active checks need the file on the server, or a DNS record over https, where the certificate ties the server to the name.
  2. The hostname is resolved once; a private or reserved address refuses the run, and every request is pinned to the addresses it resolved to.
  3. The target is asked for a path that cannot exist, to learn what it answers for anything missing. That answer is the baseline every later finding is measured against.
  4. Each part runs on its own, so one that can’t be reached never hides the others, and a part that couldn’t run is reported as not run, never as a pass.

One run sends at most 160 requests, 4 at a time and 60 ms apart, reads at most 4 KB of each answer, and stops after 180 seconds. Requests carry the user agent TillShield-Drill/1 (+https://tilldev.dev/docs/tillshield/drill), so you can find them in your logs, and Stop ends a run within seconds and keeps what it saw. Active checks are off for a new target: turn them on under a target, with tilldev shield drill targets active <target-id> daily, or run one now with tilldev shield drill active <target-id>. When the name resolves nowhere, nothing is sent and only the DNS part runs — which is exactly when a name pointing at nothing matters most.

Paths that answer when they shouldn’t

A short fixed list of paths that leak by accident: version control directories, environment files, database dumps, build manifests and open admin panels. Each is one plain GET. A path counts only when the bytes read match what that file looks like and differ from the page the target serves for anything missing, so a site that answers every path is never reported. Only the hash of those bytes is kept, never the body.

CheckLevelOWASP
A status or debug endpoint answers without authentication
drill-exposed-admin
Higha05
A backup or database dump is served to the public
drill-exposed-backup
Higha05
An environment or settings file is served to the public
drill-exposed-env
Criticala05
A dependency manifest is served to the public
drill-exposed-manifest
Lowa06
Version-control metadata is served to the public
drill-exposed-vcs
Higha05

Directory listing

Whether a folder answers with the names of the files inside it, for the site root and a handful of common asset folders.

CheckLevelOWASP
A directory is served as a list of its files
drill-directory-listing
Lowa05

Methods

One OPTIONS request reads the methods the target states it allows, and one TRACE request shows whether it echoes a request back. Methods that could change something are read from the target’s own answer and reported; they are never sent.

CheckLevelOWASP
The target advertises methods that change content
drill-methods-write-advertised
Mediuma05
The target echoes requests back with TRACE
drill-trace-enabled
Mediuma05

Cross-origin sharing

One GET carrying an Origin header set to a reserved name that cannot exist, to read what the target would share with a page on another origin, and whether it would share credentials with it.

CheckLevelOWASP
Any site may read responses from this target
drill-cors-any-origin
Mediuma05
Signed-in responses are readable by any site
drill-cors-credentials-open
Criticala01
The target repeats back whatever origin asks
drill-cors-reflects-origin
Mediuma05

Redirect parameters

Common link parameters, each tried once with a reserved host as its value, to see whether the target sends a visitor off-site. The host cannot resolve anywhere, so an honoured redirect reaches nobody, and the redirect is read, never followed.

CheckLevelOWASP
A parameter sends visitors to any address
drill-open-redirect
Mediuma01

Names pointing at nothing

DNS only, no requests: whether the target’s name, or one of its records, points at a provider that no longer claims it, or at a name that resolves nowhere. Either is a name someone else could claim and serve as yours.

CheckLevelOWASP
A DNS record points at something that is no longer there
drill-dangling-record
Mediuma05
A DNS record points at a service anyone could claim
drill-subdomain-takeover
Criticala05

Advisories for the version it names

When the target names a software version in its own response, public advisories for that version are looked up. These are fingerprints, not proof: the result says so, and a patched build that still names an old version is a false alarm to confirm by hand.

CheckLevelOWASP
The version the target gives away has published advisories
drill-version-advisories
Mediuma06
What a run never does
7 parts, every one a read. Nothing is written, nothing is deleted, no credential is tried, and no request carries a payload meant to change behaviour. The paths asked for are a fixed list that ships with TillShield, not a crawl of your site, and the bodies read are hashed and thrown away.
06Intrusive probes

The one part that sends inputs built to be mishandled

Every other drill reads. Intrusive probes send values made to be parsed wrong — a stray quote, a value a template engine would evaluate, an over-long field, a sign-in that must fail — to find where your own target mishandles them. Because they reach the target in a way the other drills never do, they are off until you turn them on, and three gates stand between them and a run.

Opt in, three ways at once

  1. The strongest proof. Only the file served on the target allows intrusive probes. A DNS record or a domain you verified elsewhere never does, because they prove the name, not the server.
  2. Your authorisation, in writing. You record that you are authorised to test the target. The authorisation stands for 90 days and then lapses until you record it again; withdrawing it stops the probes and turns off their schedule at once.
  3. A maintenance window. They run only inside a window you set — a weekday, a start time and a length from 30 minutes to 8 hours, read in the timezone you choose, up to 7 windows a target. A run started outside every window is refused.

With all three in place, run one by hand, or set a schedule to run inside the next window. Only one intrusive run happens at a time across the whole workspace. Every run goes through the same gate as the others first — the proof is checked again, the name resolves once, a private or reserved address refuses the run, and every request is pinned to the addresses it resolved — and then confirms the window is still open.

Where your input lands

Each parameter is sent a harmless one-time marker, and the answer is read to see whether that marker comes back unescaped in the HTML, a header or a redirect. It finds the places untrusted input reaches the page; the marker is inert and carries no script.

CheckLevelOWASP
A value breaks out of an attribute it is written into
drill-reflected-attribute
Mediuma03
A value is written into the page without being encoded
drill-reflected-html
Mediuma03

Inputs built to trip a parser

A parameter gets a value with a stray quote, and the answer is read for a database error the target leaks; and a value a template engine would evaluate, read back only to see whether the engine ran it. Nothing is a working exploit: the values are the smallest that reveal mishandling, and a leaked error or an evaluated marker is reported, never used.

CheckLevelOWASP
A quote reaches the database unescaped
drill-sql-error
Higha03
A parameter is evaluated as a server-side template
drill-template-evaluated
Criticala03

Malformed and oversized values

A parameter is sent an over-long or malformed value to see whether the target answers with a server error or a stack trace instead of handling it. It measures resilience with one request at a time against the baseline, never a flood.

CheckLevelOWASP
A framework debug page is shown to visitors
drill-debug-page
Higha05
A stack trace is shown to visitors
drill-stack-trace
Mediuma05

Sign-ins that are meant to fail

When the target has a sign-in form, a few deliberately wrong sign-ins are tried against a reserved username at @drill.invalid, which can never be a real account. It reads whether the session cookie is marked secure, HTTP-only and same-site, whether a cross-site request token is present, and whether repeated failures are throttled. No real account is ever touched and no password is guessed.

CheckLevelOWASP
A sign-in form carries no anti-forgery field
drill-login-no-csrf
Mediuma01
A sign-in form never slows down under repeated failures
drill-login-no-throttle
Mediuma07
A sign-in form submits over plain HTTP
drill-login-over-http
Higha02
A session cookie is set without Secure or HttpOnly
drill-session-cookie-insecure
Mediuma05

One run sends at most 240 requests, 1 at a time and 200 ms apart, reads at most 64 KB of each answer, and stops after 5 minutes. It reads at most 6 pages to find the 20 parameters it tries, and makes at most 8 failing sign-ins. Requests carry the user agent TillShield-Drill/1 (intrusive; +https://tilldev.dev/docs/tillshield/drill), so you can find them in your logs. A workspace starts 6 intrusive runs a day and one per target every 1 hour. A run stops itself if the target starts failing. Stop ends one within seconds and keeps what it saw.

What a run never does
It writes no data and deletes none, guesses no real password, and signs in as no real person: the failing sign-ins only ever use a reserved username that cannot exist. The values it sends are the smallest that reveal mishandling, never a working exploit, and a leaked error or evaluated marker is reported, never used. It touches at most 6 pages and 20 parameters, one request at a time, so it is never a load test.
07Latency

How fast it answers, from where your users are

A latency run sends 5 ordinary GET requests to the target from each region, 500 ms apart, and times each one: connecting, the TLS handshake, the first byte and the whole answer. It goes through the same steps as a passive run first: the proof is checked again, the hostname is resolved once, a private or reserved address refuses the run, and every request goes to the checked address. A redirect is timed as it is and not followed. Each request gives up after 10 seconds and reads at most 1 MB of the answer, which is never kept.

Every target has a budget: the 95th-percentile total time a request should stay under, 1000 ms unless you set one from 50 ms to 30 seconds. A run lists each region with its 50th and 95th percentile, first byte, connect and TLS times and the answers it got, and judges the result against 6 checks: over budget, a slow first byte (over 800 ms), regions far slower than the rest, failed requests, and the target down from some or every region.

Measure it now with Measure now under Speed and load on a target, or set it to repeat hourly or daily; latency is off for a new target. Pick the regions it runs from, or leave it on every region that can measure when the run starts. A latency run is allowed by any proof, and a whole run ends within 4 minutes. Requests carry the user agent TillShield-Latency/1 (+https://tilldev.dev/docs/tillshield/drill) and the header x-tilldrill-run: <run id>.

08Load

How it holds up, with a hard ceiling

A load drill sends a planned rate of requests to one path on the target and records what came back, second by second. It needs the file on the server: a DNS record or a verified domain never allows one, because a name can point at someone else’s server. Plans past a cap are refused, never trimmed, and the caps are the same in the dashboard, the CLI and the API.

PlanAllowed
Peak rate1 to 500 requests a second, split between the regions
Requests in flightUp to 200
Length30 to 600 seconds
Requests in allUp to 150,000
MethodGET or HEAD; a load drill never sends a body
PathOne path on the target’s own host and port
Stop on errorsWhen 1% to 50% of requests fail; 20% unless set
Stop on latencyWhen the 95th percentile passes 100 ms to 30 seconds; 5 seconds unless set
ShapeHow the rate moves
rampClimbs from 1 request a second to the peak over the first half, then holds it. The default.
stepsClimbs to the peak in five equal steps, so you can see the rate where it starts to slow.
constantHolds the peak from the first second.
spikeSends a tenth of the peak, with the full peak in the middle third.

It stops on its own

Every region judges itself every 10 seconds, once it has sent at least 20 requests in that window. When the error rate or the 95th percentile passes the plan’s threshold, that region stops and the others are stopped with it. Errors are requests with no answer, 5xx answers and 429s. You can stop a drill at any time with Stop, the CLI or the API, and it keeps what it measured until then. A probe host reports in every 5 seconds while it runs, and stops by itself if it can’t reach TillShield for 20 seconds. A drill that runs past its length plus 3 minutes is stopped.

What a drill shows

The requests sent against the plan, the peak rate reached, the error and rate-limited shares, the 95th percentile at the start and at the peak, and a timeline in 5-second steps of what was answered, what failed and how fast. It is judged against 6 checks, such as responses slowing down sharply under load or the planned rate never being reached.

Requests carry the user agent TillShield-Load/1 (+https://tilldev.dev/docs/tillshield/drill) and the header x-tilldrill-run: <run id>, so you can find them in your logs and leave them out of your analytics. A workspace can start 10 load drills a day, one target once every 15 minutes, and run 1 at a time. Load drills never run on a schedule.

Tell your provider
A load drill is real traffic. Let the addresses it comes from through your firewall or WAF first, or the drill measures the filter, and check your host’s terms on load testing.
09Regions

Where the traffic comes from

Latency and load run from TillShield’s probe hosts in 13 regions: Africa (south), Africa (west), Africa (east), Africa (north), Europe (west), Europe (east), Middle East, Asia (south), Asia (east), Oceania, North America (east), North America (west), South America. Each probe host checks every task it is given again before it sends anything: the address must be public, the plan must be inside the caps, and a task it can’t read is refused. A load drill runs only from probe hosts.

When no probe host is online in a region, latency in 9 of them can still be measured from TillShield’s edge, over the same pinned connection to the checked address. The edge can’t reach addresses on its own provider’s network, so for a target there that region comes back not run, with the reason. Each region shows where it really ran from.

Regions in the dashboard, tilldev shield drill regions and GET /shield/drill/regions list which regions can measure now and the addresses their traffic leaves from.

Latency checks

CheckLevel
The target is down from every region
perf-availability-down
Critical
The target is unreachable from some regions
perf-availability-region
High
Some requests failed
perf-errors-latency
Low
Responses are slower than the budget
perf-latency-budget
Medium
The server takes long to send the first byte
perf-latency-first-byte
Low
Some regions are far slower than others
perf-latency-region-slow
Low

Load checks

CheckLevel
The load drill stopped because requests failed
perf-capacity-abort-errors
High
The load drill stopped because responses got too slow
perf-capacity-abort-latency
Medium
Responses slow down sharply under load
perf-capacity-degraded
Low
The planned rate was never reached
perf-capacity-missed-target
Medium
More than 1% of requests failed under load
perf-errors-load
Medium
A rate limit answered part of the load
perf-errors-rate-limited
Informational
10Results

Reading a run

Each check comes out one of four ways:

ResultMeans
FailWhat it looks for is there. The result shows what was seen, such as the header value or the cookies at fault, and the check says how to fix it.
PassWhat it looks for isn’t there.
Doesn’t applyThe check has nothing to judge on this target, such as cookie checks on a response with no cookies, or TLS checks on an http target.
Not runIts section or part couldn’t be collected on this run, and the reason is shown. Not run is never counted as a pass.

A run that couldn’t collect any section fails, and still keeps each check’s reason. A run that was refused, because the proof had gone, the target was removed or an address was private, sends no checks and says why. A run that stops without finishing is marked failed after about 4 minutes, and you can start it again.

11Schedules and alerts

Run it on its own, hear when it gets worse

Each target repeats its passive checks daily, weekly or not at all, and a new target is set to daily. A proven target that has never run starts straight away, and after that runs one period after its last run. A lapsed target stays on its schedule: each scheduled run checks the proof again and is refused until the proof is back. Active checks have their own schedule, daily, weekly or off, and a new target has them off, so a target proven for passive checks alone never starts asking the server on its own. Latency has its own schedule too, hourly, daily or off, and both work the same way. Intrusive probes can repeat daily or weekly as well, but only once you’ve turned them on, and each run waits for the next maintenance window.

Scheduled runs alert on what changed. When a scheduled passive, active, intrusive or latency run finds checks failing at medium or above that didn’t fail on the last successful run of the same kind, it sends one alert listing them, and run.profile says which kind it was. The first run of a target counts every failure as new. A run you started yourself never alerts about failing checks, because you’re already looking at it. Any run, scheduled or started by hand, that finds the proof of a verified target gone sends one alert as the target lapses. A proof check you start yourself shows its result instead.

Alerts go where your other security alerts go: the email, Slack and webhook set under Shield → Settings → Playbooks & security alerts. See security alerts for who gets email and how to verify a webhook’s signature.

Webhook typeSent when
tillshield.drill.checks_failingA scheduled run finds checks newly failing at medium or above.
tillshield.drill.target_lapsedA run finds that a verified target’s proof no longer holds. reason says what each check saw.
json
{
  "type": "tillshield.drill.checks_failing",
  "delivery_id": "6f1c…",
  "target": { "id": "9b2e…", "value": "app.example.com" },
  "run": { "id": "d41a…", "trigger": "schedule", "profile": "surface" },
  "failures": [
    { "id": "drill-cert-expiring", "title": "Certificate expires within 14 days", "level": "medium" }
  ],
  "reason": null,
  "checked_at": "2026-10-01T06:00:12.000Z",
  "link": "https://tilldev.dev/acme/shield/drill?target=9b2e…"
}
12Limits

How much you can run

WhatLimit
Runs started by hand, per workspace20 an hour
Runs of one target started by handOne of each kind every 5 minutes
Runs waiting or running, per workspace5 at once
Runs waiting or running, per targetOne of each kind at a time
One passive run90 seconds
Active runs, per workspace50 a day, 2 at a time
Active runs of one targetOne every 15 minutes
One active run180 seconds and 160 requests
Intrusive runs, per workspace6 a day, 1 at a time, inside a window only
Intrusive runs of one targetOne every 1 hour
One intrusive run5 minutes and 240 requests
Maintenance windows, per target7, each 30 minutes to 8 hours
One latency run4 minutes
Load drills, per workspace10 a day, 1 at a time
Load drills of one targetOne every 15 minutes
Defense drills, per workspace50 a day, 3 at a time
Defense drills of one ruleOne every 2 minutes, one at a time

Scheduled runs don’t count toward the hourly limit.

13App builds

Read your own APK, IPA and desktop builds

TillDrill also reads the builds you ship and judges them against 41 checks, each tagged with the OWASP MASVS control it falls under. It flags debuggable and test builds, plain-text traffic, components any app on the device can open, secret keys shipped inside the app, executables built without the usual hardening, and weak crypto, each with how to fix it. A build is your own file, so no proof of ownership is needed. Scan one under Shield → TillDrill → App builds, from the CLI, or from the API.

UploadRead as
An APKAndroid: manifest, network config, components, signing, DEX strings and native libraries.
An IPAiOS: Info.plist, App Transport Security, entitlements, provisioning profile and every executable.
A zipped .appmacOS: the same as an IPA, plus the hardened runtime and library validation.
A zip of a Windows or Linux buildEvery .exe, .dll, .so and Linux executable inside, and the text files beside them.
One executableAn ELF, PE or Mach-O file on its own.

An Android App Bundle, a DMG, an MSI and Linux packages aren’t read as they are; the scan fails and says how to get a file it reads, such as a universal APK from bundletool build-apks --mode=universal. A build can be up to 1024 MB, sent in parts of 8 MB. A release asset TillForge stored can be scanned without uploading it again.

What is kept

Each build is read once and deleted, and only the report is kept: the app’s id and version, its settings, how each executable was built, and how each check came out. A key found in the code is kept masked, with its first few characters and its length, never in full. A scan reads at most 768 MB of uncompressed content and 4000 files, and a section it stopped short in says so. A scan that runs past 10 minutes fails. Removing a scan removes its report; the audit log keeps who started it and when.

Android manifest and signing

The compiled manifest, the network security config it points to, the components each app can reach, and the signature schemes the APK carries.

CheckLevelMASVS
App data is included in backups without rules
app-android-backup
Lowstorage.2
Plain http allowed for some domains
app-android-cleartext-domains
Lownetwork.1
Android app allows plain http
app-android-cleartext
Mediumnetwork.1
Android build is debuggable
app-android-debuggable
Highresilience.4
Installs on Android versions before 7.0
app-android-old-min
Lowcode.1
Targets an Android version older than 14
app-android-old-target
Mediumcode.1
Activity reachable by every app
app-android-open-activity
Lowplatform.1
Content provider open to every app
app-android-open-provider
Highplatform.1
Service or receiver open to every app
app-android-open-service
Mediumplatform.1
Android build is marked test-only
app-android-test-only
Mediumresilience.4
APK is not signed
app-android-unsigned
Highresilience.2
Release build trusts user-installed certificates
app-android-user-certs
Mediumnetwork.1
Signed with the v1 scheme only
app-android-v1-signing
Mediumresilience.2

Apple settings and entitlements

Info.plist, App Transport Security, the entitlements signed into the main executable, and the embedded provisioning profile.

CheckLevelMASVS
App Transport Security is turned off
app-apple-ats-arbitrary
Mediumnetwork.1
Plain http allowed for some domains
app-apple-ats-insecure-domains
Lownetwork.1
TLS 1.0 or 1.1 allowed for some domains
app-apple-ats-old-tls
Lownetwork.1
Web content may load over plain http
app-apple-ats-web
Lownetwork.1
Documents folder shared through Finder
app-apple-file-sharing
Lowstorage.2
Build lets debuggers attach
app-apple-get-task-allow
Highresilience.4
Apple build is not code signed
app-apple-unsigned
Highresilience.2
Custom URL schemes registered
app-apple-url-schemes
Informationalplatform.1
Mac app honours DYLD environment variables
app-macos-dyld-env
Mediumresilience.2
Mac app loads libraries signed by anyone
app-macos-library-validation-off
Mediumresilience.2
Mac app built without the hardened runtime
app-macos-no-hardened-runtime
Mediumresilience.2
Mac app may run unsigned code from memory
app-macos-unsigned-memory
Lowcode.4

Executables

How each executable and library was built: PIE, NX, RELRO, stack canaries, ASLR, CFG, run paths and signatures, for ELF, PE and Mach-O, up to 64 per build.

CheckLevelMASVS
Executable stack or data
app-native-exec-stack
Highcode.4
64-bit Windows binary with low-entropy ASLR
app-native-low-entropy
Lowcode.4
Windows binary without ASLR
app-native-no-aslr
Mediumcode.4
Native code without stack canaries
app-native-no-canary
Lowcode.4
Windows binary without Control Flow Guard
app-native-no-cfg
Informationalcode.4
Executable is not position independent
app-native-no-pie
Mediumcode.4
Library or executable without RELRO
app-native-no-relro
Lowcode.4
Libraries searched for outside the app
app-native-rpath
Lowcode.4
Executable without a code signature
app-native-unsigned
Mediumresilience.2

Keys, secrets and crypto

Strings in the code (DEX and native) and in bundled text files, searched for secret keys, keys meant for clients, weak ciphers and hashes, and unsafe APIs.

CheckLevelMASVS
Client key embedded in the app
app-client-key
Lowcrypto.2
Hostname verification turned off
app-hostname-verifier
Highnetwork.1
SHA1PRNG random generator in the code
app-insecure-random
Lowcrypto.1
Keychain items readable while the device is locked
app-keychain-always
Mediumstorage.1
Secret embedded in the app
app-secret-embedded
Highcrypto.2
Weak cipher or mode in the code
app-weak-cipher
Mediumcrypto.1
MD5 or SHA-1 in the code
app-weak-hash
Informationalcrypto.1

New since the last build

A scan compares itself with the last successful scan of the same app on the same platform, matched by package name or bundle id, and marks the failing checks that passed there as new. Results read the same way as a passive run: fail, pass, doesn’t apply, or not run when a section couldn’t be read.

In CI

apps upload --wait exits 1 when the build couldn’t be read or a check at the --fail-on level or above fails. With --new-only, only failures the app’s last build didn’t have count, so a release fails on what it made worse.

sh
# after the release build, before it ships
tilldev shield drill apps upload app/build/outputs/apk/release/app-release.apk --wait --new-only --fail-on high

A workspace can start 30 scans an hour and have 3 uploading, waiting or being read at once.

14Defense drills

Prove your rules still fire

A defense drill tests your detection, not your servers. It takes one TillTell rule, replays the rule’s own smallest test that fires into this workspace’s security stream, and times how long TillTell takes to record the finding and how long the response takes. Nothing leaves Till and no target is needed. Start one under Shield → TillDrill → Defense drills, from the CLI, or from the API.

  • The drill sends at most 200 events, 20 ms apart. Each is labelled as a drill and signed by Till. An event that claims to be a drill without that signature is handled as real traffic, so the label can’t be used to hide an attack.
  • Drill events are correlated apart from real traffic and from every other drill. They can’t set off a real detection or mask one. The findings they make are drill findings in drill incidents: they never alert and stay out of the backlog.
  • Every playbook step a drill finding starts is simulated. Nothing is blocked, revoked or isolated.
  • Only rules over the event stream can be drilled. A draft rule records nothing and a paused one isn’t running, so neither can be drilled. A rule that groups by project writes its events to a project you pick, or the workspace’s oldest.
OutcomeMeans
DetectedTillTell recorded a finding of the rule within 5 minutes. Time to detect counts from the moment the events were written.
Playbook ranThe playbook the rule suggests ran to the end, steps simulated, within 10 minutes. Time to respond counts to its last step.
Incident onlyThe finding reached an incident and no playbook ran, with why.
No responseThe finding was recorded and nothing responded, with why: the rule isn’t live, or suggests no playbook.
Response timed outA playbook was proposed and hadn’t finished 10 minutes later, often because a step waits for approval.
MissedNothing was recorded in time. The rule didn’t fire, or the stream is behind. The drill fails with the reason.

Proof on the coverage map

A detected drill marks the rule’s ATT&CK techniques as proven on TillTell’s coverage map for 90 days. The proof belongs to the rule version it drilled: change the rule and the proof goes until it is drilled again. A rule moved back to draft proves nothing.

In CI, defense <rule-id> --wait exits 1 when the drill is missed or refused, so a change that breaks detection fails before it ships. Waiting stops after 20 minutes, and the drill carries on.

15Exposure scans

The ports your cloud addresses answer on

Every other drill tests a site. An exposure scan tests a machine: one public address, and the ports it answers on. The address is never typed in. Addresses come from the cloud accounts you connect under Posture, and the account that lists an address is the proof that it is yours. Read them under Shield → TillDrill → Exposure.

Where an address comes from

  • A sweep reads each connected account’s own inventory for the public addresses it holds. Reading an inventory touches nobody’s network.
  • An address arrives with scanning off. You turn it on for the addresses you want watched, daily or weekly. An address the account stops holding is retired.
  • A scan reads the account again before it makes a single connection, and is refused unless the account confirmed the address in the last 7 days. No other proof allows an exposure scan, and this proof allows nothing else.
  • A workspace holds up to 250 addresses.

What a scan does

It opens a TCP connection to each of 105 well-known ports, 8 at a time and 40 ms apart, reads up to 512 bytes of whatever the service announces on connection, completes the handshake where the port speaks TLS so the certificate can be read, and closes. One scan makes at most 140 connections and stops after 4 minutes.

Which ports answer

Each port on the list gets one connection. A port counts as open only when the handshake completed; everything else is recorded as refused or timed out. A port the catalogue says belongs on an internal network is called out whatever is behind it.

CheckLevelOWASP
A cache or key-value store answers the public internet
drill-exposure-cache-open
Criticala05
A container or cluster control port answers the public internet
drill-exposure-container-control
Criticala05
A database port answers the public internet
drill-exposure-database-open
Criticala05
A debug or tooling port answers the public internet
drill-exposure-debug-port
Higha05
A service meant for an internal network answers the public internet
drill-exposure-internal-service
Mediuma05
A proxy port answers the public internet
drill-exposure-open-proxy
Higha05
Remote desktop answers the public internet
drill-exposure-remote-desktop
Higha07
A cleartext remote shell answers the public internet
drill-exposure-remote-shell
Criticala02
SSH answers the public internet
drill-exposure-ssh-open
Lowa07
This address answers on many ports
drill-exposure-wide-surface
Mediuma05
Windows remote management answers the public internet
drill-exposure-windows-management
Higha05

What is answering

Whatever the service announces when the connection opens is read and matched against known banners to name the software and, where it says so, the version. Nothing is asked for and no command is sent. A protocol with no encryption of its own is reported as carrying whatever it carries in the clear.

CheckLevelOWASP
A service that carries credentials in the clear is reachable
drill-exposure-cleartext-service
Higha02
A service announces its exact version
drill-exposure-version-disclosed
Informationala05

The certificate a TLS port serves

Where a port speaks TLS the handshake completes and the certificate is read: the names on it, who issued it, when it expires, how strong its key is, the version negotiated, and whether it covers the address that was scanned.

CheckLevelOWASP
A port serves an expired certificate
drill-exposure-tls-expired
Higha02
A port serves a certificate nothing vouches for
drill-exposure-tls-self-signed
Mediuma02
A port negotiates a retired TLS version
drill-exposure-tls-weak-protocol
Higha02

Your own rules, next to what answered

The rules in the connected account that open a port to the whole internet are read from the account, not from the network, and lined up against what actually answered: a port that answers with no rule that opens it, and a rule that opens a port with nothing behind it. Where a provider cannot tie its rules to one address, the scan says so instead of guessing.

CheckLevelOWASP
A port answers that no rule in the account opens
drill-exposure-undeclared-port
Higha05
A firewall rule opens a port that nothing answers
drill-exposure-unused-rule
Lowa05

What changed since the last scan

The ports that answered are compared with the last scan of the same address, so a port that has opened since then is a finding of its own, and one that has closed is recorded too.

CheckLevelOWASP
A port is answering that was closed on the last run
drill-exposure-newly-open
Mediuma05

Scanning is off for a new address. A scheduled scan that finds a new failure at medium or above sends a security alert; a scan you start by hand never does. A workspace can have 3 scans waiting or running, start 60 a day, and scan one address once every 15 minutes. Stop ends a scan within seconds and keeps what it saw.

What a scan never does
It sends no payload, tries no credential and changes nothing: it reads what a service volunteers when the connection opens, and closes. It touches only the ports on the list, and only the one address the account proves — never a range, never the host next door. Anyone watching the address will see the connections, so tell whoever watches it before you turn scanning on.
16CLI and API

From the terminal

sh
tilldev shield drill targets add app.example.com
tilldev shield drill targets add https://api.example.com/v1
tilldev shield drill targets show <target-id>     # the record and the file that prove it
tilldev shield drill targets check <target-id>    # exits 1 while no proof holds
tilldev shield drill targets                      # verified, lapsed or unverified
tilldev shield drill targets rm <target-id>

tilldev shield drill targets schedule <target-id> daily   # or weekly, or off
tilldev shield drill run <target-id> --wait               # exits 1 on a failing check at medium or above
tilldev shield drill run <target-id> --wait --fail-on high
tilldev shield drill runs --target <target-id>
tilldev shield drill runs show <run-id> --all             # every check, not only what needs attention
tilldev shield drill checks

tilldev shield drill active <target-id> --wait             # asks the target directly, reading only
tilldev shield drill targets active <target-id> weekly     # or daily, or off
tilldev shield drill stop <run-id>                        # an active run stops within seconds

tilldev shield drill targets intrusive <target-id> on      # record your authorisation; it expires after 90 days
tilldev shield drill targets intrusive <target-id> window add --day Sun --start 02:00 --minutes 120 --tz Africa/Kigali
tilldev shield drill targets intrusive <target-id> schedule weekly   # runs inside a window; or daily, or off
tilldev shield drill intrusive <target-id> --wait          # runs now, only inside an open window with authorisation on

tilldev shield drill regions                              # where latency and load run from, and their addresses
tilldev shield drill latency <target-id> --wait           # exits 1 on a failing check at medium or above
tilldev shield drill latency <target-id> --regions africa-west,europe-west --budget 600
tilldev shield drill targets latency <target-id> --schedule hourly --budget 800 --regions all
tilldev shield drill load <target-id> --peak 100 --duration 120 --path /health --wait
tilldev shield drill load <target-id> --peak 300 --duration 300 --shape steps --abort-errors 5 --abort-p95 2000 --yes
tilldev shield drill stop <run-id>                        # a load drill keeps what it measured
tilldev shield drill runs --profile load

tilldev shield drill apps upload app-release.apk --wait   # exits 1 on a failing check at medium or above
tilldev shield drill apps upload App.ipa --wait --new-only  # only checks the app's last build passed
tilldev shield drill apps releases                        # release assets TillForge stored
tilldev shield drill apps from-release <asset-id> --wait
tilldev shield drill apps                                 # newest first; --app <app-id> for one app
tilldev shield drill apps show <scan-id> --all
tilldev shield drill apps checks

tilldev shield drill defense                             # rules that can be drilled, and when each was last proven
tilldev shield drill defense tell-cred-stuffing --wait   # exits 1 when TillTell records nothing
tilldev shield drill defense show <run-id>               # time to detect and to respond

tilldev shield drill exposure sweep                      # read the connected cloud accounts for addresses
tilldev shield drill exposure                            # each address, and the ports that answered
tilldev shield drill exposure scan <address-id> --wait   # exits 1 on a failing check at medium or above
tilldev shield drill exposure schedule <address-id> weekly  # or daily, or off

A bare hostname is added as a domain, and a value starting with http:// or https:// as a URL. run --wait follows the run, prints what needs attention by section, and exits 1 when the run is refused or fails, or when a check at the --fail-on level or above fails. The level is medium unless you set it, and none never fails. active --wait, latency --wait and load --wait do the same; load asks before it starts unless you pass --yes, and Ctrl-C while it waits stops the drill. Waiting stops after 15 minutes, and the run carries on. The same operations are in the API reference under TillDrill.