A practical website security baseline: seven starting points
Seven checks for a small business website, from password hashing to secret-access logs, with the limits of each check made explicit.

This is a practical starting order for seven checks, not a complete security audit. Initial checks can be quick; establishing that a system is secure takes more work. Include software updates, MFA, backups and restore tests, HTTPS, session management and incident monitoring in the wider plan.
The examples below are retained from the project draft, including problems the team reported finding in its own system.
1. Use a password hashing function
If a database leaks, password storage affects how easily attackers can recover passwords and try them elsewhere.
Bare MD5, SHA-1 or SHA-256 are unsuitable: their speed helps attackers.
bcrypt remains an option for existing systems with appropriate configuration.
OWASP recommends Argon2id for new systems; its memory cost raises the cost of guessing.
Look at the stored format as a first check. Prefixes such as $argon2id$ or $2a$/$2b$ help identify the algorithm, but do not prove that its parameters, salt or implementation are adequate. A 32- or 64-character hexadecimal value deserves investigation, not an automatic diagnosis from appearance alone.
2. Rate-limit public entry points
Login, contact forms, newsletter subscriptions and chat can all be called repeatedly. Pay particular attention to endpoints that trigger paid services, such as an AI provider.
The draft reports adding nginx limits and observing:
Test | Reported result |
|---|---|
Normal page visit with assets | No blocked requests |
150 sequential requests | 17 blocked |
300 parallel requests | 225 blocked |
Testing normal traffic matters as much as blocking abusive traffic. A rule that blocks real customers is likely to be removed, leaving no protection.
Test against the configured threshold on staging or another authorized system. Not seeing a 429 after 100 calls does not prove a limiter is absent: the threshold, time window, burst allowance and identity key all matter. Avoid unplanned load on production or paid downstream services.
3. Use a meaningful status code
429 says the client is sending too many requests; 503 indicates service unavailability. Use the response that describes the situation.
Automatic blocking tools still need matching log rules. They do not universally understand every 429 or ignore every 503 without configuration.
4. Define failure behavior explicitly
When an action requires a security check, a failed check must not silently count as a successful one.
For example, a reCAPTCHA verification request that cannot reach Google should return an error rather than mark the submission valid:
// Google unreachable is NOT a pass.
return fmt.Errorf("recaptcha unreachable: %w", err)That can interrupt a legitimate contact request. Design any fallback channel deliberately according to the risk of the workflow.
Missing keys are a configuration error. An admin interface should refuse to enable required CAPTCHA until keys are present and clearly show its state. If verification is required at runtime but cannot be performed, reject the action or use an explicitly designed alternative. Do not silently skip it while presenting the feature as protected.
5. Review secrets in backups too
Secrets can appear outside source code. The draft describes finding a reCAPTCHA secret in a binary column called value_encrypted after an earlier scan missed it.
It identifies these project-specific locations:
ai_providers.api_key
push_keys.private_key
system_settings.value_encrypted
two_factor_secrets.secretSearching for sk-, api_key, secret or BEGIN PRIVATE KEY is only a first screen. Secrets may be in text, JSON, binary or encrypted fields. Review the schema and configuration, restrict backup access, encrypt backups and set retention limits. If a secret was exposed, revoke or rotate it.
6. Limit public data deliberately
Giving every account access to everything turns one compromised account into a much wider incident.
The draft reports a public team endpoint returning internal names and email addresses that were also login identifiers. That increases opportunities for account enumeration and phishing; an email address alone is not a password.
Use a central public-response projection that exposes only approved fields. Check that every new endpoint uses it; an architectural description is not proof that no bypass exists.
Inspect public HTML and APIs, then ask whether each disclosed field is actually needed by visitors.
7. Log sensitive reads
Changes leave traces more readily than viewing an existing API key. If a key is later misused, it helps to know who requested access to it.
The project draft describes three controls for viewing a stored key:
Appropriate administrator permission.
Reauthentication to reduce risk from an unattended session; consider MFA for sensitive operations.
Rate limiting so the reauthentication endpoint does not become a password-guessing route.
Record the actor, time and key identifier—not the secret value. Prefer masking and rotation over revealing a recoverable key whenever the workflow allows it.
Initial review checklist
Area | First check |
|---|---|
Passwords | Identify the algorithm and review its configuration |
Rate limits | Test configured thresholds in an authorized environment |
Error codes | Check the response for a throttled request |
Verification failure | Confirm failure does not silently pass |
Backups | Review secret locations and access controls |
Public data | Inspect actual public responses |
Secret reads | Confirm access is authorized and logged without secret values |
Prioritize dangerous password storage promptly. If one check fails open, review other error paths too; the same handling pattern may have been reused.
Situations and request counts are retained from the October 2026 Zukatek draft. The original logs were not available for independent verification.
Reference: OWASP Password Storage Cheat Sheet.



