rechtzeitig

All checks

Every rule has a fixed id, so you can refer to it in tickets. Open a card to see how it is checked, how to fix it and where the code lives.

Results

The severity says how much a finding matters, from info to critical. Website checks run when you give a URL. Repository checks run when the workflow checks out your repository first. Lockfiles read: package-lock.json, pnpm-lock.yaml, yarn.lock, composer.lock, pubspec.lock, Cargo.lock, uv.lock, poetry.lock, requirements*.txt, go.mod.

Sources are primary sources only: laws, courts, authorities, standards bodies, and vendor documentation for technical facts. Blogs and law firm articles are never cited. Each card shows when its sources were last reviewed; a rule not reviewed for a year fails the project's tests.

What runs when

Checks that are off show as skip, with the reason and the setting that turns them on. A check marked Sends data asks an outside service; the badge links to what is sent.

CheckRuns when
All website checksOnly with a url.
All repository checksOnly when actions/checkout runs before rechtzeitig (folder: input repository).
A group of checksmodules picks groups: legal, privacy, security, sustainability, accessibility, repo. Empty runs all.
Cancellation button for subscriptions
LEG-DE-BTN-001
Opt-in: only with subscriptions: true.
Withdrawal button for online contracts
LEG-DE-BTN-002
Opt-in: only with sales: true.
Privacy policy names transfers outside the EU
LEG-EU-DSE-005
Opt-in: only when the secret IPINFO_TOKEN is set (input ipinfo-token, token from ipinfo.io). Without it the check is skipped and nothing is sent.
Sends data to ipinfo.io (opt-in)
No dependencies with known vulnerabilities
SEC-DEPS-001
On by default; sends data to api.osv.dev. Turn it off with vulnerabilities: false.
Sends data to api.osv.dev

The rules

Legal 9 rules

LEG-DE-BTN-001high Cancellation button for subscriptions § 312k BGB (German Civil Code) How it is checked
How it is checked
Finds a button labeled "Verträge hier kündigen" on the start page (cancel in lang/*.yml, else cancel_similar on the visible text, which warns) and "jetzt kündigen" (cancel_confirm) on its target page; info when it cannot be followed.
Runs when
Opt-in: only with subscriptions: true.
How to fix
Offer an always available link "Verträge hier kündigen", without login, that leads to a page with a "jetzt kündigen" button.
Reviewed
LEG-EU-DSE-003medium Forms link to the privacy policy Art. 13(1) GDPR How it is checked
How it is checked
For every form with email, name, phone, address or birth date fields, looks for a privacy link or wording (privacy in lang/*.yml) inside the form or its container. Ignores login forms.
How to fix
Explain next to the form what the data is used for and link the privacy policy.
Reviewed
LEG-DE-IMP-002high Imprint contains the required details § 5(1) DDG How it is checked
How it is checked
Searches the imprint text for email, phone or contact form (IBANs ignored), German postcode (register numbers ignored), VAT ID and register number.
How to fix
Give name, postal address, email address and a second fast way of contact, plus register entry and VAT ID if you have them.
Reviewed
LEG-DE-IMP-001high Imprint is linked and reachable § 5 DDG (German Digital Services Act) How it is checked
How it is checked
Finds the imprint link on the start page (imprint), loads it and checks HTTP status and that it has text.
How to fix
Link the imprint from every page, for example in the footer, labeled "Impressum".
Reviewed
LEG-DE-IMP-003medium Imprint and privacy policy are linked on every page § 5 DDG, Art. 12 GDPR How it is checked
How it is checked
Looks for imprint and privacy links on every visited page of the site.
How to fix
Put both links in a shared layout (footer) that every page uses, including apps and sub-sections.
Reviewed
LEG-EU-ODR-001medium No link to the closed EU dispute platform Regulation (EU) 2024/3228 How it is checked
How it is checked
Looks for links to or mentions of the closed EU dispute platform (odr in data/outdated-links.yml) on every visited page.
How to fix
Remove the link and the notice. The platform closed on 20 July 2025. The statement under § 36 VSBG on whether you take part in consumer dispute resolution stays required, unless you have 10 employees or fewer.
Reviewed
LEG-EU-DSE-002medium Privacy policy covers the required topics Art. 13 GDPR How it is checked
How it is checked
Searches the privacy policy for 11 topics (controller, storage_period, right_* ... in lang/*.yml).
How to fix
Add the missing topics: controller, legal basis, storage period, data subject rights, right to complain.
Reviewed
LEG-EU-DSE-001high Privacy policy is linked and reachable Art. 12, 13 GDPR How it is checked
How it is checked
Finds the privacy link on the start page (privacy), loads it and checks HTTP status and that it has text.
How to fix
Link the privacy policy from every page, for example in the footer, labeled "Datenschutz".
Reviewed
LEG-DE-BTN-002high Withdrawal button for online contracts § 356a BGB (German Civil Code, since 19 June 2026) How it is checked
How it is checked
Finds a button labeled "Vertrag widerrufen" on the start page (withdraw in lang/*.yml, else withdraw_similar on the visible text, which warns) and "Widerruf bestätigen" (withdraw_confirm) on its target page; info when it cannot be followed.
Runs when
Opt-in: only with sales: true.
How to fix
Offer an always available, prominent "Vertrag widerrufen" function leading to a form with a "Widerruf bestätigen" button; confirm receipt electronically.
Reviewed

Privacy and consent 6 rules

LEG-EU-CNS-004high Optional cookie categories are not preselected § 25 TDDDG, ECJ C-673/17 (Planet49) How it is checked
How it is checked
Finds ticked, enabled checkboxes whose label is an optional category (consent_optional), also in shadow roots.
How to fix
Leave statistics, marketing and similar categories unticked by default.
Reviewed
LEG-EU-CNS-003high Consent banner can be rejected on the first layer § 25 TDDDG, Art. 7(3) GDPR How it is checked
How it is checked
In dialogs, cookie containers and shadow roots of the start page, looks for an accept button (consent_accept) without a reject button (consent_reject).
How to fix
Offer an equal "Reject all" button right next to "Accept all".
Reviewed
LEG-EU-CNS-002high No cookies before consent § 25 TDDDG (German Telecommunications Digital Services Data Protection Act), BGH I ZR 7/16 (Cookie-Einwilligung II) How it is checked
How it is checked
Lists cookies and localStorage set while loading the site without clicks; matches names against data/third-parties.yml.
How to fix
Set only strictly necessary cookies right away, all others only after consent.
Reviewed
LEG-EU-DSE-005medium Privacy policy names transfers outside the EU Sends data to ipinfo.io (opt-in) Art. 13(1)(f), Art. 44 ff. GDPR, ECJ C-311/18 (Schrems II) How it is checked
How it is checked
Gets the country of each third-party service (country in data/third-parties.yml, else IPinfo) and, for countries outside the EEA (data/countries.yml), looks for third_country wording in the privacy policy; info when no country is known.
Runs when
Opt-in: only when the secret IPINFO_TOKEN is set (input ipinfo-token, token from ipinfo.io). Without it the check is skipped and nothing is sent.
How to fix
Name the country and the safeguard for each service outside the EU/EEA (for example Data Privacy Framework, standard contractual clauses).
Reviewed
LEG-EU-CNS-001high No third-party servers before consent Art. 6(1) GDPR, § 25 TDDDG, LG München I 3 O 17493/20 (Google Fonts) How it is checked
How it is checked
Records every request to another site while loading pages without clicks, groups hosts by vendor from data/third-parties.yml (else by domain); consent: required vendors fail, others warn.
How to fix
Host fonts, scripts and libraries yourself; load embeds (videos, maps) only after a click or consent.
Reviewed
LEG-EU-DSE-004medium Privacy policy names the services the site uses Art. 13(1)(e) GDPR How it is checked
How it is checked
For every contacted third-party service, looks for its name (names in data/third-parties.yml, else its domain) as a whole word in the privacy policy.
How to fix
List every embedded service with purpose and legal basis in the privacy policy.
Reviewed

Security 9 rules

SEC-HDR-002medium Cookies use secure flags Art. 32 GDPR How it is checked
How it is checked
Checks Secure on every cookie set without login (HTTPS sites, and always with SameSite=None) and HttpOnly on cookies whose name looks like a session (data/cookies.yml); cookies of other sites only give info.
How to fix
Set cookies with Secure; session cookies also with HttpOnly and SameSite=Lax.
Reviewed
SEC-DNS-001medium Domain is protected against email spoofing Art. 32 GDPR How it is checked
How it is checked
Reads the SPF TXT record of the registrable domain and the DMARC record at _dmarc.; flags missing ones, SPF that allows every server (+all, ?all) and DMARC p=none.
How to fix
Publish SPF and DMARC records; a domain without mail needs only "v=spf1 -all" and "v=DMARC1; p=reject".
Reviewed
SEC-EXP-002medium Error pages reveal no internals Art. 32 GDPR How it is checked
How it is checked
Requests the error paths from data/probes.yml, searches their visible text for the traces in data/leaks.yml and the headers of the start page and error pages for version headers.
How to fix
Turn off error output in production (for example display_errors=0, DEBUG=false) and remove version headers.
Reviewed
SEC-EXP-001critical No private files are public Art. 32 GDPR How it is checked
How it is checked
Requests the file paths from data/probes.yml; a 200 whose body matches expect fails.
How to fix
Remove the files from the web server or block access to dot files and backups; change any credentials they contain.
Reviewed
SEC-HDR-001low Security headers are set Art. 32 GDPR How it is checked
How it is checked
Reads the response headers of the start page (HSTS, CSP, X-Content-Type-Options, Referrer-Policy, frame protection) and accepts CSP and Referrer-Policy from <meta> tags, which browsers apply too.
How to fix
Set the missing headers in the web server or hosting. CSP and Referrer-Policy also work as <meta> tags; frame-ancestors and nosniff only work as headers.
Reviewed
SEC-DNS-002low www and non-www addresses both work Art. 32 GDPR How it is checked
How it is checked
Requests the domain with www. added or removed and follows redirects.
How to fix
Get a valid certificate for both addresses and redirect one to the other.
Reviewed
SEC-TLS-001high Site uses HTTPS Art. 32 GDPR How it is checked
How it is checked
Checks that the start page is HTTPS and that plain http:// redirects to HTTPS.
How to fix
Enforce HTTPS and redirect http:// to https:// with a 301.
Reviewed
SEC-TLS-003medium No unencrypted resources on HTTPS pages Art. 32 GDPR How it is checked
How it is checked
Lists http:// requests made by pages of an HTTPS site.
How to fix
Load every resource over https://.
Reviewed
SEC-TLS-002high TLS certificate is valid and current Art. 32 GDPR How it is checked
How it is checked
Opens a TLS connection to the host and reads protocol, trust and expiry date of the certificate.
How to fix
Renew the certificate automatically (for example Let's Encrypt) and allow only TLS 1.2 and 1.3.
Reviewed

Sustainability 2 rules

SUS-JS-002low Start page shows content without JavaScript Technical, no single law How it is checked
How it is checked
Compares the visible text of the start page with JavaScript off and on and warns when less than 20 % of it is shown without JavaScript.
How to fix
Deliver content as HTML from the server or at build time.
Sources
w3.org, w3.org
Reviewed
SUS-JS-001medium Imprint and privacy policy work without JavaScript § 5 DDG (easily recognizable, directly accessible), Art. 12(1) GDPR (easily accessible form) How it is checked
How it is checked
Checks that imprint and privacy links (imprint, privacy in lang/*.yml) exist with JavaScript off and that the linked page that loaded shows at least 100 characters of text without it.
How to fix
Deliver the links and texts of the legal pages as static HTML.
Reviewed

Accessibility 1 rule

A11Y-LANG-001low Page declares its language BFSG with § 3 BFSGV; EN 301 549 V3.2.1 clause 9.3.1.1 (WCAG 2.1 success criterion 3.1.1) How it is checked
How it is checked
Reads <html lang> of the start page as the server sent it (JavaScript off) and compares it with the chosen language.
How to fix
Set the lang attribute on <html>, for example <html lang="de">.
Reviewed

Repository 8 rules

SEC-DEPS-005low GitHub Actions are pinned to a commit CRA Art. 13(5), due diligence for third-party components (from 11 Dec 2027) How it is checked
How it is checked
Reads uses: lines in .github/workflows/*.yml and lists actions not pinned to a 40-character commit SHA.
How to fix
Use owner/action@<commit-sha> instead of a tag; Dependabot keeps the SHAs current.
Reviewed
SEC-DBAI-001critical Database rules do not open data to everyone Art. 32 GDPR How it is checked
How it is checked
Splits supabase/**/*.sql into statements and compares create table in the public schema with enable row level security; looks for if true and test mode (request.time < timestamp.date(...)) in Firebase rules.
How to fix
Enable row level security on every table and replace "if true" and test-mode rules with checks on the signed-in user.
Reviewed
SEC-DEPS-003medium Dependency updates are automated CRA Annex I Part II(2) (from 11 Dec 2027), Art. 32 GDPR How it is checked
How it is checked
Looks for .github/dependabot.yml (and which ecosystems it covers), Renovate config, or vulnerability scanners in workflows, as listed in data/update-tools.yml.
How to fix
Set up Dependabot or Renovate for every ecosystem in use.
Reviewed
SEC-SECRETS-002high .env files stay out of git Art. 32 GDPR How it is checked
How it is checked
Lists committed .env* files, also with several suffixes like .env.production.local (templates and fixtures excluded), and checks that .gitignore ignores .env.
How to fix
Add .env to .gitignore; revoke committed values and remove them from the history.
Reviewed
LEG-DE-LIC-001medium Dependency licenses fit the project §§ 31, 69c UrhG (German Copyright Act) How it is checked
How it is checked
Compares licenses from npm and Composer lockfiles with the project license (package.json, composer.json or LICENSE) using the groups in data/licenses.yml; an SPDX OR with a non-copyleft choice is not flagged.
How to fix
Review the license of each flagged dependency; replace it, buy a license or license the project to match.
Reviewed
SEC-DEPS-002low Dependencies are locked and listed in an SBOM CRA Annex I Part II(1) (from 11 Dec 2027) How it is checked
How it is checked
Reads all lockfiles (src/repo/lockfiles.ts) for the SBOM the report writes to rechtzeitig-sbom.cdx.json, and lists manifests without a lockfile in their folder or a parent folder.
How to fix
Commit a lockfile for every manifest; keep the SBOM with each release. The CRA covers products with digital elements made available in the EU, usually not pure web services.
Reviewed
SEC-SECRETS-001critical No secrets in the repository Art. 32 GDPR How it is checked
How it is checked
Matches tracked files line by line against data/secrets.yml, decoding JWT payloads where a pattern names claims (Supabase service_role keys); shows only the first 4 characters and the length of each find.
How to fix
Revoke found keys right away, then remove them from code and git history and store them as secrets or environment variables.
Reviewed
SEC-DEPS-001high No dependencies with known vulnerabilities Sends data to api.osv.dev Art. 32 GDPR, CRA Annex I Part I(2)(a) and Part II(2) (from 11 Dec 2027) How it is checked
How it is checked
Sends the package URLs of all locked components to the OSV API and loads details for up to 100 issues; high and critical issues in production dependencies fail.
Runs when
On by default; sends data to api.osv.dev. Turn it off with vulnerabilities: false.
How to fix
Update the affected packages to at least the fixed version shown.
Reviewed

Rule ids

PrefixArea
LEG-DEGerman law (DDG, BGB, UrhG)
LEG-EUEU law (GDPR, ePrivacy as § 25 TDDDG)
SECSecurity
A11YAccessibility (BFSG)
SUSSustainability

Words and languages

Checks look for words like "Impressum", "Datenschutz" or "Alle ablehnen". The words live in one file per language, currently German and English. Pick the language of your site when you start the run; the page language check tells you if the choice looks wrong.