Protect by default
Choose secure defaults
Make a new account, deployment, or feature safe before the user changes any optional security setting.
Defaults become reality for most users. A security control hidden behind an optional setting protects fewer people than you think.
When someone creates an account, a bucket, or a project, they accept whatever the system chose for them. Almost nobody reads the settings page first. So the defaults are a security decision you make on behalf of every user who never touches them.
What a secure default looks like
Four rules cover most cases:
- Keep new resources private until someone explicitly shares them.
- Deny operations you do not recognize instead of allowing them.
- Grant restrictive permissions and let users widen them deliberately.
- Require an explicit decision before anything becomes publicly reachable.
Cloud storage is the classic cautionary tale. For years, public buckets leaked data not because attackers were clever, but because “public” was one unchecked box away and nothing forced a decision.
A quiet default becomes a repeated leak
Here is the failure in miniature. A project-creation endpoint treats visibility as optional:
const project = await db.projects.create({
name: body.name,
visibility: body.visibility ?? 'public', // the bug is this fallback
})
Every client that omits the field creates a public project. Most users never revisit the setting, so a quiet default becomes a repeated data leak — one new exposure per created project, until someone notices.
The fix is one word:
visibility: body.visibility ?? 'private',
If compatibility genuinely needs a weaker mode, make the tradeoff visible and temporary: log it, surface it in the UI, and put an expiry date on the exception.
Test the default from outside
Do not trust the code review. Create a project without touching any optional security setting and record its resulting visibility and permissions. Then open a signed-out browser and try to reach it:
curl -i https://app.example.com/projects/8231
# HTTP/1.1 404 Not Found <- what you want to see
Require a denial before calling the default safe. A 200 with project data means your default is doing the attacker’s work for them.
A restrictive default may add one explicit publishing step for people who want public projects. That small cost is easier to explain than silently exposing every new project.
Lesson completed