@rafter-security/cli 0.6.6 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -10
- package/dist/commands/agent/audit-skill.js +22 -20
- package/dist/commands/agent/audit.js +27 -0
- package/dist/commands/agent/components.js +800 -0
- package/dist/commands/agent/config.js +2 -1
- package/dist/commands/agent/disable.js +47 -0
- package/dist/commands/agent/enable.js +50 -0
- package/dist/commands/agent/exec.js +2 -0
- package/dist/commands/agent/index.js +6 -0
- package/dist/commands/agent/init.js +162 -163
- package/dist/commands/agent/install-hook.js +15 -14
- package/dist/commands/agent/list.js +72 -0
- package/dist/commands/agent/scan.js +4 -3
- package/dist/commands/agent/verify.js +1 -1
- package/dist/commands/backend/run.js +12 -3
- package/dist/commands/backend/scan-status.js +3 -2
- package/dist/commands/brief.js +22 -2
- package/dist/commands/ci/init.js +25 -21
- package/dist/commands/completion.js +4 -3
- package/dist/commands/docs/index.js +18 -0
- package/dist/commands/docs/list.js +37 -0
- package/dist/commands/docs/show.js +64 -0
- package/dist/commands/mcp/server.js +84 -0
- package/dist/commands/report.js +42 -41
- package/dist/commands/scan/index.js +7 -5
- package/dist/commands/skill/index.js +14 -0
- package/dist/commands/skill/install.js +89 -0
- package/dist/commands/skill/list.js +79 -0
- package/dist/commands/skill/registry.js +273 -0
- package/dist/commands/skill/remote.js +333 -0
- package/dist/commands/skill/review.js +975 -0
- package/dist/commands/skill/uninstall.js +65 -0
- package/dist/core/audit-logger.js +262 -21
- package/dist/core/config-manager.js +3 -0
- package/dist/core/docs-loader.js +148 -0
- package/dist/core/policy-loader.js +72 -1
- package/dist/core/risk-rules.js +16 -3
- package/dist/index.js +19 -9
- package/dist/scanners/gitleaks.js +6 -2
- package/package.json +1 -1
- package/resources/skills/rafter/SKILL.md +77 -97
- package/resources/skills/rafter/docs/backend.md +106 -0
- package/resources/skills/rafter/docs/cli-reference.md +199 -0
- package/resources/skills/rafter/docs/finding-triage.md +79 -0
- package/resources/skills/rafter/docs/guardrails.md +91 -0
- package/resources/skills/rafter/docs/shift-left.md +64 -0
- package/resources/skills/rafter-agent-security/SKILL.md +1 -1
- package/resources/skills/rafter-code-review/SKILL.md +91 -0
- package/resources/skills/rafter-code-review/docs/api.md +90 -0
- package/resources/skills/rafter-code-review/docs/asvs.md +120 -0
- package/resources/skills/rafter-code-review/docs/cwe-top25.md +78 -0
- package/resources/skills/rafter-code-review/docs/investigation-playbook.md +101 -0
- package/resources/skills/rafter-code-review/docs/llm.md +87 -0
- package/resources/skills/rafter-code-review/docs/web-app.md +84 -0
- package/resources/skills/rafter-secure-design/SKILL.md +103 -0
- package/resources/skills/rafter-secure-design/docs/api-design.md +97 -0
- package/resources/skills/rafter-secure-design/docs/auth.md +67 -0
- package/resources/skills/rafter-secure-design/docs/data-storage.md +90 -0
- package/resources/skills/rafter-secure-design/docs/dependencies.md +101 -0
- package/resources/skills/rafter-secure-design/docs/deployment.md +104 -0
- package/resources/skills/rafter-secure-design/docs/ingestion.md +98 -0
- package/resources/skills/rafter-secure-design/docs/standards-pointers.md +102 -0
- package/resources/skills/rafter-secure-design/docs/threat-modeling.md +128 -0
- package/resources/skills/rafter-skill-review/SKILL.md +106 -0
- package/resources/skills/rafter-skill-review/docs/authorship-provenance.md +82 -0
- package/resources/skills/rafter-skill-review/docs/changelog-review.md +99 -0
- package/resources/skills/rafter-skill-review/docs/data-practices.md +88 -0
- package/resources/skills/rafter-skill-review/docs/malware-indicators.md +79 -0
- package/resources/skills/rafter-skill-review/docs/prompt-injection.md +85 -0
- package/resources/skills/rafter-skill-review/docs/telemetry.md +78 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# API Design — Design Questions
|
|
2
|
+
|
|
3
|
+
The shape of your API decides which vulnerabilities are *possible*. Good shape makes BOLA, BFLA, and mass assignment hard to write. Bad shape makes them hard to avoid.
|
|
4
|
+
|
|
5
|
+
## Resource modeling — is this endpoint BOLA-shaped?
|
|
6
|
+
|
|
7
|
+
- For each endpoint, what is the resource being named, and *how is it named*? `GET /orders/:id` names an order by global id — the caller can enumerate and try any id. Contrast with `GET /me/orders/:id` — scoped to the caller.
|
|
8
|
+
- The scoping prefix (`/me`, `/org/:org_id`) doesn't enforce authZ by itself, but it makes the enforcement gap visible. "I forgot to check" is harder when the URL structure announces the scope.
|
|
9
|
+
- Are identifiers **opaque** (random, unguessable) or **sequential**? Sequential ids aren't a security control, but combined with missing authZ they turn a 5-minute bug into a data breach. Opaque ids (UUIDv4, ULIDs with enough entropy) buy you a little defense-in-depth.
|
|
10
|
+
- GraphQL: the resource boundary is per-field, not per-endpoint. You need authZ on every resolver that returns a resource, including nested resolvers. Think: "can a query walk from a public node to a private one via an edge?"
|
|
11
|
+
|
|
12
|
+
## AuthZ enforcement point
|
|
13
|
+
|
|
14
|
+
- Where does each endpoint check authorization?
|
|
15
|
+
- Before the handler (middleware / decorator): good for coarse checks (authenticated? role?).
|
|
16
|
+
- Inside the domain layer, against the specific resource: required for resource-level checks (can user X read order Y?).
|
|
17
|
+
- Both: middleware filters obvious unauthenticated traffic; domain checks the specific access.
|
|
18
|
+
- Missing authZ checks are the #1 API bug class. Is there a test that *proves* every endpoint either returns 401 without auth or has an authZ test that denies a different user?
|
|
19
|
+
- BFLA (broken function-level authz): admin actions on regular-user endpoints. Is there a single codepath that's reachable by multiple roles where only the check is different? That's the BFLA shape.
|
|
20
|
+
|
|
21
|
+
## Request shape — mass assignment
|
|
22
|
+
|
|
23
|
+
- Does the handler bind the full request body into a model, then save? `User.create(request.body)` is mass assignment — a client can set `is_admin: true` if the field exists on the model.
|
|
24
|
+
- Explicit allowlist per endpoint, even if it's verbose. Frameworks that "automatically filter" are a landmine — the filter is correct until a field is added.
|
|
25
|
+
- For updates: what fields are read-only? Created-at, created-by, tenant-id, owner-id — none of these should be settable by the client.
|
|
26
|
+
|
|
27
|
+
## Idempotency & safety
|
|
28
|
+
|
|
29
|
+
- Write endpoints: does the spec say idempotent or not? `PUT /things/:id` should be idempotent; `POST /things` usually isn't. Clients will retry — non-idempotent writes without an idempotency key will double-charge, double-send, double-create.
|
|
30
|
+
- If you accept an `Idempotency-Key` header (Stripe-style): how long is the key scoped? Per-user, per-hour, per-day? Too short = legitimate retries fail; too long = stale dedup.
|
|
31
|
+
- HTTP verb discipline: does the server accept verb-override headers (`X-HTTP-Method-Override`)? If yes, the "GET is safe" assumption breaks — any GET can become a POST.
|
|
32
|
+
|
|
33
|
+
## Rate limiting & abuse
|
|
34
|
+
|
|
35
|
+
- What are the **three** rate-limit keys? Per-IP (cheapest), per-API-key / per-user (account-level abuse), per-endpoint (expensive endpoints get lower limits).
|
|
36
|
+
- Authentication endpoints (login, password reset, MFA): count per-account *and* per-IP. Per-IP alone misses credential stuffing with rotating IPs; per-account alone misses enumeration.
|
|
37
|
+
- Webhook senders: self-rate-limit (queue, backoff). A storm of retries is a self-DoS.
|
|
38
|
+
- Abuse cost: for expensive operations (file upload, image processing, LLM calls), what prevents one user from burning all the budget? Quotas > rate limits for cost control.
|
|
39
|
+
|
|
40
|
+
## Error taxonomy — what leaks
|
|
41
|
+
|
|
42
|
+
- Do errors distinguish "record not found" from "record exists but you can't see it"? They should **not** — both return 404. Revealing existence is an oracle.
|
|
43
|
+
- Login errors: "invalid email" vs. "invalid password" = enumeration oracle. Both return "invalid credentials".
|
|
44
|
+
- Stack traces, SQL errors, file paths in error responses — all debugging aids that become disclosure bugs in production. What's the production error shape? Do you have tests that assert it doesn't leak?
|
|
45
|
+
- Error codes: are they stable and documented? "ERR_1042" isn't user-hostile; "database connection timeout on host db-prod-01.internal" is.
|
|
46
|
+
|
|
47
|
+
## Pagination & bulk ops
|
|
48
|
+
|
|
49
|
+
- Is there an upper bound on `limit`? Unbounded = trivial DoS and data exfil. What's the cap (1000 is common), and does the client know it was capped (via `has_next`)?
|
|
50
|
+
- Cursor vs. offset: cursor-based is better for deep pagination and for immutable-once-read semantics. Offset lets attackers enumerate by incrementing.
|
|
51
|
+
- Bulk ops (`POST /things/bulk`): per-element authZ, not just outer authZ. The handler might accept 500 resource ids and forget to check each one.
|
|
52
|
+
|
|
53
|
+
## Webhooks (outbound)
|
|
54
|
+
|
|
55
|
+
- Is the webhook destination user-supplied? If yes: this is SSRF-shaped. Allowlist the target (domain allowlist *plus* IP allowlist that excludes RFC1918, link-local, cloud metadata `169.254.169.254`).
|
|
56
|
+
- Signed payloads: HMAC with a per-receiver secret, signature in a header, timestamp in the payload. Receivers should verify signature *and* reject stale timestamps (replay protection).
|
|
57
|
+
- Retry policy: exponential backoff with a cap; max retries; dead-letter queue. Unbounded retries = self-DoS on receiver outages.
|
|
58
|
+
- Does the delivery include PII? If yes, the receiver URL is now part of your data flow for compliance purposes. You need a deletion story for their side too.
|
|
59
|
+
|
|
60
|
+
## Webhooks (inbound)
|
|
61
|
+
|
|
62
|
+
- Verification: HMAC check, timestamp tolerance (< 5 min), replay cache (seen this signature recently?).
|
|
63
|
+
- The payload is untrusted. Parse into a typed schema, reject unknown fields — don't echo into the DB.
|
|
64
|
+
|
|
65
|
+
## Versioning & deprecation
|
|
66
|
+
|
|
67
|
+
- How do you version? URL path (`/v1/`), header (`Accept: application/vnd.example.v1+json`), or query param? Pick one and stick with it.
|
|
68
|
+
- How do you deprecate an endpoint? Sunset date, `Deprecation` header, metrics on which clients still call it. Deprecation without metrics = deprecation forever.
|
|
69
|
+
- Old versions are old attack surface. Every live version is a maintenance cost.
|
|
70
|
+
|
|
71
|
+
## API keys & client credentials
|
|
72
|
+
|
|
73
|
+
- Scope per key (what endpoints, what data); expiration; revocation.
|
|
74
|
+
- Does the key identify a **principal** (user / service) or just a **contract**? Per-principal is easier to audit; "contract keys" that many services share lose attribution.
|
|
75
|
+
- Key display: show once at creation, store hashed. Rotation flow: overlap window (old + new valid) to avoid downtime.
|
|
76
|
+
- Per-key audit log: every authenticated call names the key.
|
|
77
|
+
|
|
78
|
+
## Refuse-list
|
|
79
|
+
|
|
80
|
+
- Endpoints that accept the full request body into an ORM model without an allowlist.
|
|
81
|
+
- 404 vs. 403 that leaks existence. (It's fine to 403 on a *permission* mismatch when the user knows the resource exists; not on resource-existence probes.)
|
|
82
|
+
- Unbounded `limit` parameters.
|
|
83
|
+
- User-supplied URLs fetched without an allowlist + IP denylist.
|
|
84
|
+
- Login / password-reset endpoints without rate limits on both IP *and* account.
|
|
85
|
+
- Error responses that include DB errors, file paths, or stack traces in production.
|
|
86
|
+
- Webhook verification that's only "is there a signature header" without validating it.
|
|
87
|
+
- API versioning schemes where v1 is never sunsetted (perpetual liability).
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Exit criteria
|
|
92
|
+
|
|
93
|
+
- Every endpoint has a one-line authZ rule ("caller's user_id must equal the resource's owner_id, or the caller's role must be admin").
|
|
94
|
+
- Mass-assignment story is explicit — allowlist, not auto-bind.
|
|
95
|
+
- Rate limit keys are defined and justified per endpoint class.
|
|
96
|
+
- Error taxonomy is in the spec, not up to the implementer.
|
|
97
|
+
- Webhook designs (if any) specify signing, replay protection, and (outbound) SSRF defense.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Authentication & Authorization — Design Questions
|
|
2
|
+
|
|
3
|
+
Answer each block *before* you write code. If the answer is "we'll figure it out later", you have a design gap, not a plan. Cite the proposed primitive (library, spec, service) in your answer.
|
|
4
|
+
|
|
5
|
+
## Identity — who is the user?
|
|
6
|
+
|
|
7
|
+
- Is this **end users** (humans), **services** (internal/external), or **agents** (LLMs, automations)? The authN primitive differs for each; do not use one pipeline for all three.
|
|
8
|
+
- For humans: are you federating (SSO / OIDC / SAML) or running your own password + MFA? Running your own is a maintenance burden — can you justify not federating?
|
|
9
|
+
- For services: mTLS? Signed JWTs with a trusted issuer? Workload identity (SPIFFE, cloud IAM)? What *refuses* a service call — is absence of credential a 401 or a silent allow?
|
|
10
|
+
- For agents: is the agent acting **as the user** (delegated) or **as itself** (service principal)? Delegated needs scoped tokens with user consent; as-itself needs audit trails that name the agent + the invoking user.
|
|
11
|
+
|
|
12
|
+
## AuthN — choose the primitive and say why
|
|
13
|
+
|
|
14
|
+
- Session cookies + server-side session store, or self-contained tokens (JWT / PASETO)?
|
|
15
|
+
- Sessions: easier to revoke, harder to scale across regions without sticky state.
|
|
16
|
+
- JWT: scales, harder to revoke — do you have a plan for revocation (short TTL + refresh, or a revocation list)?
|
|
17
|
+
- If JWT: which algorithm are you signing with? **Refuse `alg: none`** and **refuse HS256 with any key the verifier can confuse with a public key.** Prefer `EdDSA` or `RS256`/`ES256` with a clearly separated key store.
|
|
18
|
+
- If OAuth / OIDC: which flow? Authorization Code + PKCE for any client that isn't a trusted backend. **Never implicit flow in 2026.** If you have a reason, write it down.
|
|
19
|
+
- Password policy: bcrypt / scrypt / argon2id — which one and what cost? Do you plan to rehash on login when cost parameters bump? What's the plan for credential stuffing (rate limit, captcha, breach-list checks)?
|
|
20
|
+
- MFA: present at login only, or also at sensitive actions (password change, MFA enrollment, payment method change, export-all-data)? MFA enrollment itself needs anti-bypass (don't let "add a new device" bypass existing MFA).
|
|
21
|
+
|
|
22
|
+
## AuthZ — model the access, don't reinvent it
|
|
23
|
+
|
|
24
|
+
- Is the model **RBAC** (roles → permissions), **ABAC** (attributes → policy), or **ReBAC** (relationships, Zanzibar-style)?
|
|
25
|
+
- RBAC: cheap, coarse. Fails on "users can only see their own records" — that's a resource-ownership check, not a role.
|
|
26
|
+
- ABAC: flexible, hard to audit. If the policy is "user.org == resource.org AND (user.role == admin OR resource.owner == user)", write it as a policy engine input (Rego, Cedar), not scattered `if` statements.
|
|
27
|
+
- ReBAC: best for hierarchical sharing (docs, folders, workspaces). Expensive to bolt on later — decide now if you'll need it.
|
|
28
|
+
- Where is authZ enforced? **At every entry point to the domain layer**, not per-route. Router-layer middleware checks authN, the domain layer checks authZ against the resource. If both live in the controller, the next developer will forget one.
|
|
29
|
+
- IDOR / BOLA: for every resource access, is the ID scoped to the caller? `GET /orders/:id` that returns any order in the database is a bug. Are you checking `order.tenant_id == user.tenant_id` *and* `order.user_id == user.id` (or a delegated-access rule)?
|
|
30
|
+
|
|
31
|
+
## Sessions / tokens — lifetime and revocation
|
|
32
|
+
|
|
33
|
+
- Session lifetime: idle timeout and absolute timeout? "Remember me" — what invalidates it on password change, MFA reset, account deletion?
|
|
34
|
+
- Refresh tokens: single-use (rotating) or replayable? Rotating + detection-on-reuse is the modern default. If you can't detect reuse, you lose the audit signal.
|
|
35
|
+
- Revocation list: where does it live? Is it read on every authZ check (expensive) or pushed to token TTL only? If the latter, your TTL *is* your worst-case revocation delay — be honest about that.
|
|
36
|
+
- Logout: does it actually invalidate server-side, or just clear the cookie? A logout that only clears the client is a lie.
|
|
37
|
+
|
|
38
|
+
## Multi-tenant isolation
|
|
39
|
+
|
|
40
|
+
- Tenancy is an authZ concern, not a DB trick. Is the tenant id on every query? What enforces that — raw SQL, ORM hook, policy engine? (ORM hook is easy to bypass with raw queries; policy engine with query-rewriting is strongest.)
|
|
41
|
+
- Is the tenant id from the **session**, never from the **request**? `?tenant_id=X` in the URL is a footgun.
|
|
42
|
+
- Cross-tenant sharing (delegation, impersonation for support, data export): designed explicitly, or accidental because of a missing check?
|
|
43
|
+
|
|
44
|
+
## Service-to-service
|
|
45
|
+
|
|
46
|
+
- Zero-trust posture: does every internal call still carry and verify identity, or is the internal network treated as trusted? (Treat-as-trusted has failed in every post-mortem for a decade.)
|
|
47
|
+
- How is service identity bootstrapped? Static long-lived secrets in env vars are the weakest option — workload identity (AWS IAM, GCP WIF, Kubernetes SA tokens, SPIFFE) is strongest.
|
|
48
|
+
- Does the callee log *which* service called it? Without that, you can't incident-respond.
|
|
49
|
+
|
|
50
|
+
## Refuse-list — if any of these are in the proposal, stop and redesign
|
|
51
|
+
|
|
52
|
+
- Homegrown password hashing (`sha256(pw + salt)` is not hashing).
|
|
53
|
+
- Homegrown JWT signing/verification (pick a maintained library, prefer PASETO for new designs).
|
|
54
|
+
- `alg: none` acceptance, or JWT libraries that don't pin algorithm.
|
|
55
|
+
- "The internal network is trusted, so we skip auth between services."
|
|
56
|
+
- Tenant id derived from the request path or query string rather than the session.
|
|
57
|
+
- MFA that can be bypassed by enrolling a new device without re-verifying.
|
|
58
|
+
- Password reset tokens that are long-lived, non-rotating, or tied to email only without rate-limit + recent-activity checks.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Exit criteria
|
|
63
|
+
|
|
64
|
+
- Each subsection above has a one-line answer, naming a specific primitive or library.
|
|
65
|
+
- The refuse-list has been checked against the proposal; any hits are explicitly waived with a written "we accept this because...".
|
|
66
|
+
- AuthZ model chosen and a first sketch of the policy (RBAC table / ABAC rules / ReBAC relations) exists.
|
|
67
|
+
- You're ready to hand this section to the implementing engineer without ambiguity.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Data Storage — Design Questions
|
|
2
|
+
|
|
3
|
+
Where data lives determines blast radius. Every decision here is about making compromise cheap: key rotation, minimal retention, isolation by default.
|
|
4
|
+
|
|
5
|
+
## Classify — what *is* this data?
|
|
6
|
+
|
|
7
|
+
Before anything else, tag each field the design stores:
|
|
8
|
+
|
|
9
|
+
- **Identifier**: email, username, account id. Useful to enumerate, often PII on its own.
|
|
10
|
+
- **Credential**: password, API key, OAuth token, session id. Compromise = account takeover.
|
|
11
|
+
- **PII / PHI / PCI**: personal / health / payment. Regulatory scope — GDPR, HIPAA, PCI-DSS apply. Which?
|
|
12
|
+
- **Secret**: business-internal (encryption keys, signing keys, webhook secrets).
|
|
13
|
+
- **Content**: user-generated text, files, comments. Defamation / CSAM risk is real — do you have a moderation path?
|
|
14
|
+
- **Derived**: embeddings, summaries, ML features. Often treated as "not the original data" but can leak it — an embedding can sometimes reconstruct input.
|
|
15
|
+
- **Audit / log**: who did what when. Usually keep-forever, but often contains identifiers — classify the fields, not just the collection.
|
|
16
|
+
|
|
17
|
+
If a field doesn't fit a bucket, ask why it's being stored at all. **Data you don't have can't leak.**
|
|
18
|
+
|
|
19
|
+
## Encryption at rest
|
|
20
|
+
|
|
21
|
+
- Is the store's default disk-encryption enough (AWS RDS / GCP Cloud SQL / DynamoDB with CMK)? For most data, yes — don't add a second layer without a reason.
|
|
22
|
+
- Application-level encryption is worth it when: (a) the DB operator is a different trust boundary than the app, (b) you need field-level access control tied to the app's authn, (c) compliance demands customer-managed keys. Don't encrypt application-side just to "feel safer" — you'll break queries, search, and analytics.
|
|
23
|
+
- Envelope encryption (KMS-wrapped DEKs) is the pattern for app-side encryption. Who holds the KEK? Can you rotate it without re-encrypting every row?
|
|
24
|
+
- Deterministic vs. randomized encryption: deterministic lets you query/join, but leaks equality. Decide per field.
|
|
25
|
+
|
|
26
|
+
## Keys — the *actual* security boundary
|
|
27
|
+
|
|
28
|
+
- Where do the keys live? KMS / HSM / Vault / env var? **Env var is weakest** — it ends up in logs, dumps, and `ps auxe` output.
|
|
29
|
+
- Who can *use* the key (decrypt) vs. who can *manage* the key (rotate, destroy)? These must be separate IAM principals.
|
|
30
|
+
- Rotation schedule: signing keys < 1 year, data keys rotated via envelope re-wrap (cheap), password hashing upgrade on login (transparent).
|
|
31
|
+
- Key separation by tenant: single tenant per key is strongest (revoke = delete tenant key) but expensive. Per-tenant DEK with a shared KEK is a good middle ground.
|
|
32
|
+
- Break-glass: how do you get out when KMS is down? Do you have a tested runbook, or will you find out during the incident?
|
|
33
|
+
|
|
34
|
+
## Secrets (the application's own)
|
|
35
|
+
|
|
36
|
+
- Application secrets (DB passwords, API keys, signing keys, webhook secrets) go in a secret manager (Vault, AWS Secrets Manager, GCP Secret Manager, Kubernetes Secrets with encryption-at-rest). **Not in env vars committed to repo; not in env vars set by deploy scripts that log them.**
|
|
37
|
+
- Rotation: can you rotate without an outage? If the answer is "restart every service", that's OK for weekly but not daily. For rotation under pressure (leak detected), test the runbook *before* the leak.
|
|
38
|
+
- Least privilege: each service gets its own secret with the smallest scope. The web frontend does not need the DB's admin password.
|
|
39
|
+
|
|
40
|
+
## Encryption in transit
|
|
41
|
+
|
|
42
|
+
- TLS everywhere, including internal. "Internal network is trusted" is not a posture, it's a wish.
|
|
43
|
+
- What TLS version floor? TLS 1.2 is the practical minimum in 2026; 1.3 is the default for new designs.
|
|
44
|
+
- Certificate management: automated (ACME, cert-manager, cloud-managed) or manual? Manual renewal is a recurring outage.
|
|
45
|
+
- mTLS for service-to-service? Worth it when services are owned by different teams or spans security zones.
|
|
46
|
+
|
|
47
|
+
## Retention & deletion
|
|
48
|
+
|
|
49
|
+
- How long does each class live? Default to **shortest defensible** — you're not obligated to keep it forever. Data you delete can't subpoena, leak, or breach.
|
|
50
|
+
- GDPR / CCPA deletion: when a user requests deletion, *what* is deleted? Logs, backups, analytics exports, ML training sets, embeddings? If the answer is "we'll figure it out", you'll fail an audit.
|
|
51
|
+
- Soft-delete vs. hard-delete: soft-delete is good for recovery windows, bad for compliance. After the window closes, hard-delete and prove it (cryptographic erasure = destroy the key).
|
|
52
|
+
- Backup scope: backups inherit the data's sensitivity. Are backups encrypted with a *different* key than live data (so a live-data compromise doesn't grant backups)?
|
|
53
|
+
|
|
54
|
+
## Tenancy isolation
|
|
55
|
+
|
|
56
|
+
- Row-level: single DB, tenant_id column. Cheapest, weakest — one missed `where tenant_id` = cross-tenant leak.
|
|
57
|
+
- Schema-per-tenant: same DB, separate schemas. Mid-cost, mid-strength — ORM must respect search_path.
|
|
58
|
+
- DB-per-tenant: separate DB per tenant. Most expensive, strongest — compromise of one DB doesn't leak others.
|
|
59
|
+
- Decide by the cost of a cross-tenant leak, not by current scale. Upgrading later means a data migration.
|
|
60
|
+
|
|
61
|
+
## Logs — the forgotten data store
|
|
62
|
+
|
|
63
|
+
- Do logs contain the data classified above? Request bodies wholesale, error messages with stack traces containing secrets, URL query strings with tokens, user inputs echoed back — all common.
|
|
64
|
+
- Who reads logs? A dev on their laptop? A SaaS log provider? Each hop is a trust boundary. Scrub secrets before the hop.
|
|
65
|
+
- Log retention is often longer than the application's data retention — a GDPR deletion that misses logs is incomplete.
|
|
66
|
+
|
|
67
|
+
## Caches, queues, search indices
|
|
68
|
+
|
|
69
|
+
- Redis / Memcached / Elasticsearch / SQS / Kafka — each is a secondary data store. Classify what's in it.
|
|
70
|
+
- Is the cache encrypted at rest? Accessible over TLS? Authenticated? **Unauthenticated Redis on a public IP is still the #1 cloud leak source in 2026.**
|
|
71
|
+
- Search indices often copy data verbatim — a deleted record in the DB can linger in Elasticsearch unless you wire the deletion into both.
|
|
72
|
+
|
|
73
|
+
## Refuse-list
|
|
74
|
+
|
|
75
|
+
- Custom crypto primitives ("we xor with a rotating key"). Pick `libsodium` / `AEAD` via a maintained library.
|
|
76
|
+
- Storing passwords reversibly. Ever.
|
|
77
|
+
- Log statements that print request bodies, auth headers, or token-bearing URLs.
|
|
78
|
+
- Backups in the same blast radius as live data (same account, same region, same key).
|
|
79
|
+
- Tenant isolation enforced only at the ORM layer (raw queries bypass it).
|
|
80
|
+
- Embeddings / ML features stored without the classification that the source data had.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Exit criteria
|
|
85
|
+
|
|
86
|
+
- Every stored field has a classification and a retention policy.
|
|
87
|
+
- Encryption story names specific keys, a specific KMS, and a rotation cadence.
|
|
88
|
+
- Secret distribution path is explicit — not "env vars set by Terraform".
|
|
89
|
+
- Deletion path is defined for each data class, including logs and backups.
|
|
90
|
+
- Tenant isolation level is chosen with a written justification.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Dependencies & Supply Chain — Design Questions
|
|
2
|
+
|
|
3
|
+
Every dependency is a trust transfer: their bugs become yours, their maintainers become your dependency on goodwill. The question at design time is "is this worth the transfer?"
|
|
4
|
+
|
|
5
|
+
## Pick vs. write — which one
|
|
6
|
+
|
|
7
|
+
- Cryptography, authN / authZ primitives, parsers for complex formats, protocol implementations: **pick, don't write.** The library has years of eyes and fuzz time.
|
|
8
|
+
- Glue code, config loaders, small utility functions: **write, don't pick.** A 5-line helper beats a transitively-huge dependency.
|
|
9
|
+
- The middle (rate limiters, retry logic, caches): depends on how mature your language's standard library is. Go stdlib + a small helper often beats pulling in a 300-line middleware framework.
|
|
10
|
+
|
|
11
|
+
## Maintenance signal — before you adopt
|
|
12
|
+
|
|
13
|
+
Read the repo before adopting. Answers to these in one sitting:
|
|
14
|
+
|
|
15
|
+
- When was the last commit, release, CVE response? Dormant ≠ dead, but "last release 2019" for a security-adjacent lib is a risk.
|
|
16
|
+
- How many maintainers? Solo-maintainer packages are a bus-factor and takeover risk (npm `event-stream`, PyPI `ctx`).
|
|
17
|
+
- Does the project publish a security policy (SECURITY.md, GHSA history)? Projects that have handled CVEs well handle them well.
|
|
18
|
+
- Download count and reverse-dependency count: high-popularity packages get eyes on them; low-popularity is higher chance of silent badness.
|
|
19
|
+
- Typosquat / slopsquat check: is this the real package name? LLM-generated install instructions now routinely hallucinate package names that bad actors then register. Verify from the project's own README / GitHub.
|
|
20
|
+
|
|
21
|
+
## Install-time execution
|
|
22
|
+
|
|
23
|
+
- `postinstall` / `preinstall` / `prepare` hooks in npm, arbitrary `setup.py` code in Python, Gradle init scripts, Cargo build scripts — all run with your developer's or CI's permissions.
|
|
24
|
+
- Does your package manager have a way to disable these? npm `--ignore-scripts`, `pnpm install --ignore-scripts` + allowlist via `packageExtensions`. Pip has `--no-binary` but less granular.
|
|
25
|
+
- CI should install with the strictest flags. Developers can run with scripts enabled *after* review.
|
|
26
|
+
|
|
27
|
+
## Pinning & lockfiles
|
|
28
|
+
|
|
29
|
+
- Lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `poetry.lock`, `Cargo.lock`, `go.sum`) committed. No exceptions for "libraries" — downstream lockfiles are the user's responsibility, but your CI needs reproducibility.
|
|
30
|
+
- Range pinning in the manifest (`^1.2.3`) is fine for libraries; applications benefit from exact pins + a lockfile for reproducibility.
|
|
31
|
+
- Lockfile verification in CI (`npm ci`, `pnpm install --frozen-lockfile`, `yarn install --immutable`, `poetry install --no-update`). Without verification, a drifted lockfile ships unknown code.
|
|
32
|
+
|
|
33
|
+
## Vendoring vs. registry
|
|
34
|
+
|
|
35
|
+
- Registry (npm, PyPI, Go proxy, crates.io): convenient, but the registry is a trust root. Compromise of a maintainer account has shipped malware repeatedly.
|
|
36
|
+
- Registry mirror / proxy (Artifactory, Cloudsmith, Google Artifact Registry): lets you cache + scan + pin. Best-of-both for teams with infra.
|
|
37
|
+
- Vendoring: committing dependency code into your repo. Highest control, highest cost. Justified for (a) critical dependencies you need to patch locally, (b) airgapped builds, (c) compliance requirements.
|
|
38
|
+
|
|
39
|
+
## SCA — hook it in, don't treat it as a quarterly task
|
|
40
|
+
|
|
41
|
+
- SCA on every PR and on main: Dependabot, Renovate, Snyk, Trivy, Grype, `rafter run` (which aggregates SCA).
|
|
42
|
+
- Auto-PRs for dependency updates: accept them with tests gating. Batching 3 months of updates is worse than a weekly drip.
|
|
43
|
+
- Critical CVEs (known-exploited, CVSS ≥ 9): page on detection, not "log and review later".
|
|
44
|
+
- Noise management: not every CVE applies to how you use the library. Triage policy is part of the design — who decides what's accepted, and how is the decision logged?
|
|
45
|
+
|
|
46
|
+
## Supply chain attacks to design against
|
|
47
|
+
|
|
48
|
+
- **Typosquat / slopsquat**: package name misspellings, especially for names an LLM might generate. Pin from upstream README only.
|
|
49
|
+
- **Dependency confusion**: your private package name registered publicly. Publish a placeholder of your internal package names, or use scoped packages with registry routing.
|
|
50
|
+
- **Maintainer takeover**: compromised maintainer account publishes malware. Defenses: pin by digest (where supported), monitor for unexpected releases.
|
|
51
|
+
- **Protestware / hacktivism**: maintainer deliberately ships malware or destructive code (e.g., `node-ipc`). Pinning catches it; SCA post-mortem confirms.
|
|
52
|
+
- **Compromised CI**: build-time tamper that injects malware into your artifact. Defenses: reproducible builds, signed provenance (SLSA), isolated build environment.
|
|
53
|
+
|
|
54
|
+
## Transitive depth
|
|
55
|
+
|
|
56
|
+
- How deep is the dep tree? `npm ls` / `cargo tree` / `pipdeptree`. Dozens of transitive deps per direct dep = huge attack surface.
|
|
57
|
+
- Does each direct dep pull in its own HTTP client, its own JSON parser, its own date library? Consolidate at the application level where possible.
|
|
58
|
+
- Transitive version conflicts: which wins? In npm / pnpm, hoisting rules. In Python, last-wins. Explicit `overrides` / `resolutions` let you force a patched version.
|
|
59
|
+
|
|
60
|
+
## Container images as dependencies
|
|
61
|
+
|
|
62
|
+
- Base images are dependencies — same maintenance questions apply. Distroless (Google-maintained) and Chainguard (security-first) are first-party; random Docker Hub images are not.
|
|
63
|
+
- Pin by digest. `image:tag` is mutable.
|
|
64
|
+
- Multi-stage builds: builder image can be heavy; final image should be minimal. Don't ship your build toolchain to prod.
|
|
65
|
+
- Image scanning in CI: `trivy image`, `grype`, cloud-native scanners. Block deploys on critical findings for production.
|
|
66
|
+
|
|
67
|
+
## SaaS dependencies
|
|
68
|
+
|
|
69
|
+
- Adopting a SaaS is also a dep: your data, their availability and security posture.
|
|
70
|
+
- Do they publish a SOC 2 / ISO 27001 / security whitepaper? Not gospel, but absence is a signal.
|
|
71
|
+
- Where does the data live (region, sub-processors)? For PII, this is a compliance question.
|
|
72
|
+
- Offboarding: if they vanish or you churn, how do you migrate? Vendor lock-in is a security issue too (can't rotate away from a breach).
|
|
73
|
+
|
|
74
|
+
## LLM / AI libraries — the new supply chain
|
|
75
|
+
|
|
76
|
+
- Model weights are dependencies. Which model, which version, hosted where?
|
|
77
|
+
- Inference SDKs (openai, anthropic, litellm) are dependencies with the standard risks *plus* credential-surface (API keys per provider).
|
|
78
|
+
- Vector DB clients (pinecone, qdrant, chroma) are dependencies that also hold your embeddings — classify accordingly.
|
|
79
|
+
- `prompt-injection-guard` style libraries are pattern-based and will never catch novel attacks — adopt but don't trust absolutely.
|
|
80
|
+
|
|
81
|
+
## Refuse-list
|
|
82
|
+
|
|
83
|
+
- Pulling a dependency from a raw git URL or GitHub tarball without pinning commit SHA.
|
|
84
|
+
- Adopting a package because an LLM suggested the name, without verifying it exists upstream (slopsquat bait).
|
|
85
|
+
- `:latest` tags on base images or dependency versions.
|
|
86
|
+
- CI that installs with `postinstall` enabled on every run without script review.
|
|
87
|
+
- Solo-maintained packages in your critical path (auth, crypto, payments) without a forking / vendoring plan.
|
|
88
|
+
- Adopting a SaaS for a compliance-scoped workload without reviewing their posture.
|
|
89
|
+
- Skipping the lockfile because "we're a library".
|
|
90
|
+
- SCA as a quarterly scan rather than a PR-level gate.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Exit criteria
|
|
95
|
+
|
|
96
|
+
- Every new direct dependency has a one-line justification (pick vs. write, maintenance signal reviewed).
|
|
97
|
+
- Install-time execution policy is specified for CI.
|
|
98
|
+
- Lockfile + verification in CI is confirmed.
|
|
99
|
+
- SCA tool is wired to PRs, with a triage policy for findings.
|
|
100
|
+
- Base images are pinned by digest with a rebuild cadence.
|
|
101
|
+
- If the design uses a SaaS or LLM provider, the data-flow and credential-scope are drawn.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Deployment — Design Questions
|
|
2
|
+
|
|
3
|
+
Deployment is where "the app is secure" meets reality. Network boundaries, runtime posture, secret distribution, build provenance — each decided here survives every refactor of the code.
|
|
4
|
+
|
|
5
|
+
## Network topology — zones, not flat
|
|
6
|
+
|
|
7
|
+
- Sketch zones: public edge (LB / CDN / WAF), app tier, data tier, admin tier, third-party egress. Each is a distinct security zone.
|
|
8
|
+
- What traffic is allowed **between** zones, and what's denied by default? Default-deny is the only sane starting point. If the default is allow and you block selectively, you're one misconfiguration from exposure.
|
|
9
|
+
- Public edge: what terminates TLS? WAF in front or not? WAF is good for cheap-filter; not a substitute for app-side validation.
|
|
10
|
+
- Admin access (SSH, kube-exec, DB console): over the public internet? Over a VPN / zero-trust proxy (Tailscale, Cloudflare Access, Teleport)? The public-internet-with-a-bastion is a 2005 pattern.
|
|
11
|
+
|
|
12
|
+
## Egress — the forgotten boundary
|
|
13
|
+
|
|
14
|
+
- Can your app reach arbitrary internet destinations? Default should be "allowlist of known egress targets" (external APIs you integrate with, OS package mirrors, telemetry).
|
|
15
|
+
- Egress control is the best SSRF defense *and* the best data-exfiltration defense. If a compromised app can only reach `api.stripe.com`, the blast radius is Stripe calls.
|
|
16
|
+
- Metadata services (169.254.169.254): block at the network layer, not just the app. IMDSv2 on AWS (required hop limit = 1 + session token) blocks the rebinding variant.
|
|
17
|
+
|
|
18
|
+
## Identity & IAM
|
|
19
|
+
|
|
20
|
+
- Every compute workload has a workload identity (AWS IAM role, GCP service account, Kubernetes ServiceAccount + bound tokens, SPIFFE ID). **Not shared credentials, not long-lived keys.**
|
|
21
|
+
- Least privilege per workload. "The web service has DB read + DB write + admin on this one table" is better than "the web service has AdminAccess".
|
|
22
|
+
- Break-glass access: there's an auditable path for a human to gain emergency privileges. Not a shared `root` password.
|
|
23
|
+
- IAM changes go through code review (Terraform PR, Pulumi PR). Click-ops IAM is how wide-open permissions persist.
|
|
24
|
+
|
|
25
|
+
## Secret distribution
|
|
26
|
+
|
|
27
|
+
- Where does each service get its secrets? Secret manager (Vault, AWS SM, GCP SM, Kubernetes Secrets with sealed / external-secrets), *not* Terraform-plan output, *not* env vars set by a deploy script that logs them.
|
|
28
|
+
- Secrets rotate. Short-lived DB credentials (Vault dynamic secrets, IAM database auth) > long-lived passwords. If your design says "quarterly rotation of a static password", name who does it and how.
|
|
29
|
+
- Secrets are scoped per service. The web tier doesn't have the admin DB credential.
|
|
30
|
+
- Encryption-at-rest for the secret manager itself: by default on all cloud-managed; verify for self-hosted.
|
|
31
|
+
- Secrets in CI: scoped per job, never printed to logs, masked in output. PR workflows triggered from forks don't see secrets.
|
|
32
|
+
|
|
33
|
+
## Container / runtime posture
|
|
34
|
+
|
|
35
|
+
- Run as non-root. If `USER 0` or `runAsUser: 0`, flag it.
|
|
36
|
+
- Read-only root filesystem where possible. Writable mounts are explicit (`/tmp`, named volumes).
|
|
37
|
+
- Capabilities: drop all, add back only what's needed. `CAP_NET_BIND_SERVICE` is the usual one.
|
|
38
|
+
- Seccomp / AppArmor / SELinux profile: a real profile, not "Unconfined".
|
|
39
|
+
- Resource limits: CPU and memory limits per container. No limit = one compromised pod can starve the node.
|
|
40
|
+
|
|
41
|
+
## Base images
|
|
42
|
+
|
|
43
|
+
- Distroless / Alpine / minimal / scratch > Ubuntu full. Fewer packages = fewer CVEs, smaller attack surface.
|
|
44
|
+
- Pin by digest (`image@sha256:...`), not tag. `:latest` and even `:v1.2.3` can be overwritten; digests are immutable.
|
|
45
|
+
- SCA on base images in CI. Re-pull / rebuild cadence (weekly) to pick up upstream patches.
|
|
46
|
+
- Who maintains the base image? First-party (your team) > team-adjacent > "some Docker Hub account". Unmaintained bases rot.
|
|
47
|
+
|
|
48
|
+
## Build provenance & supply chain
|
|
49
|
+
|
|
50
|
+
- Is the build reproducible? Given the same inputs, does a rebuild produce the same artifact? Not always achievable, but worth asking.
|
|
51
|
+
- SLSA level: aim for SLSA 3 (hosted builder, signed provenance) for anything shipping to production. SLSA 1 (provenance exists) is the minimum.
|
|
52
|
+
- Artifact signing: Sigstore / Cosign / Notary. Signatures verified at deploy, not just at build.
|
|
53
|
+
- Dependency pinning: lockfile committed, lockfile verified in CI.
|
|
54
|
+
- `postinstall` / `prepare` scripts from dependencies: ban or audit. These execute arbitrary code on install — it's the npm supply-chain attack class.
|
|
55
|
+
- SBOM generation at build time. Store it with the artifact.
|
|
56
|
+
|
|
57
|
+
## CI/CD posture
|
|
58
|
+
|
|
59
|
+
- Who can deploy to prod? Production deploys gated on approval, signed tags, or protected branch merges.
|
|
60
|
+
- CI runners: ephemeral (fresh VM / container per job), not long-running hosts with persistent state.
|
|
61
|
+
- Workflow permissions: least-privilege GITHUB_TOKEN / equivalent. Write-all is the click-to-compromise default.
|
|
62
|
+
- Self-hosted runners + public repo = RCE. Either make the repo private, use GitHub-hosted runners for public workflows, or lock runners to specific workflows.
|
|
63
|
+
- Branch protection: required reviews, required status checks, no force-push to main. Linear history if you need audit simplicity.
|
|
64
|
+
|
|
65
|
+
## Production-vs-staging parity
|
|
66
|
+
|
|
67
|
+
- Same architecture in staging as prod, with masked / synthetic data. Staging that uses prod data = a second prod blast radius with half the controls.
|
|
68
|
+
- Config differences are explicit and minimal. "We disable auth in staging" is how auth gets disabled in prod one day by accident.
|
|
69
|
+
- Feature flags that default-off in prod and default-on in staging: tested in both states.
|
|
70
|
+
|
|
71
|
+
## Multi-region / DR
|
|
72
|
+
|
|
73
|
+
- If the design spans regions: is the active/passive or active/active model clear? What's replicated, what's per-region?
|
|
74
|
+
- Encryption keys per region, or a global key? (Global is simpler but expands blast radius.)
|
|
75
|
+
- Failover runbook exists and was tested in the last 12 months. Not-yet-tested = doesn't work.
|
|
76
|
+
|
|
77
|
+
## Logging & monitoring posture
|
|
78
|
+
|
|
79
|
+
- Structured logs, shipped to a separate system (not the same DB the app writes to). A compromise of app storage shouldn't delete the audit trail.
|
|
80
|
+
- Authentication to the log system: workload identity, not shared token.
|
|
81
|
+
- What paging signals exist? Login-anomaly rates, authZ denials, 5xx surges, unusual egress — without these, the breach is found by the customer.
|
|
82
|
+
- Retention: logs often outlive production data. Classify log contents and apply retention accordingly.
|
|
83
|
+
|
|
84
|
+
## Refuse-list
|
|
85
|
+
|
|
86
|
+
- Long-lived static cloud credentials baked into container images or env vars.
|
|
87
|
+
- Privileged containers (`privileged: true`, `runAsUser: 0` without justification).
|
|
88
|
+
- `:latest` tags or unpinned base images in production manifests.
|
|
89
|
+
- CI workflows with write-all GITHUB_TOKEN scope by default.
|
|
90
|
+
- "We'll add network policy later" — network default-allow is not a plan.
|
|
91
|
+
- Secrets set via Terraform variable with plan output visible in logs.
|
|
92
|
+
- Shared SSH keys, shared `root` password, shared admin console.
|
|
93
|
+
- Metadata service reachable from a public-facing container (IMDSv1, or IMDSv2 with unlimited hop count).
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Exit criteria
|
|
98
|
+
|
|
99
|
+
- Zone diagram exists; cross-zone traffic is allowlisted, not denylisted.
|
|
100
|
+
- Each workload has a named identity and a scoped IAM role.
|
|
101
|
+
- Secret distribution names the secret manager and the rotation model.
|
|
102
|
+
- Container runtime posture is specified: user, filesystem, capabilities, resource limits.
|
|
103
|
+
- Build pipeline specifies provenance (SLSA), signing, and dependency pinning.
|
|
104
|
+
- Log shipping + retention is set, independent of application storage.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Ingestion — Design Questions
|
|
2
|
+
|
|
3
|
+
Every byte crossing your trust boundary is a question: "who says this is safe, and how?" Most of the OWASP Top 10 lives at ingestion — parsers, decoders, fetchers, uploaders.
|
|
4
|
+
|
|
5
|
+
## Trust boundaries — name them
|
|
6
|
+
|
|
7
|
+
- Draw the boundary: external (internet, partner API, user upload) → your edge → your internal services → your storage.
|
|
8
|
+
- Each boundary crossing is a *validation point*. Validation means: shape check (schema), size check (bytes / fields), semantic check (does this make sense here?).
|
|
9
|
+
- Validation at the edge is necessary but not sufficient — internal services that re-read the data need to re-validate if the trust delta matters (e.g., a cached input re-used later as a filename).
|
|
10
|
+
- Parsers *are* the boundary for complex formats. A "validated JSON blob" that contains an eval-able code path is still a hole.
|
|
11
|
+
|
|
12
|
+
## Input schemas — declare, don't hand-parse
|
|
13
|
+
|
|
14
|
+
- Have a typed schema for every external input: JSON Schema, Zod, Pydantic, protobuf, OpenAPI-generated types. Reject unknown fields (`additionalProperties: false`).
|
|
15
|
+
- Accepting unknown fields is how mass-assignment bugs enter — the attacker ships `is_admin: true` and the schema silently accepts it.
|
|
16
|
+
- Length / size / range bounds on every field. Strings have max lengths, numbers have ranges, arrays have max sizes, nesting has max depth. Unbounded = DoS shape.
|
|
17
|
+
- Regex validation: anchor with `^` and `$`. Fear catastrophic backtracking — test with a regex-safety linter or prefer RE2-backed engines.
|
|
18
|
+
|
|
19
|
+
## Size limits — everywhere, early
|
|
20
|
+
|
|
21
|
+
- Request body size cap at the edge (reverse proxy / API gateway). Don't rely on the framework to cap — it parses first, rejects second.
|
|
22
|
+
- Per-field limits inside the body.
|
|
23
|
+
- Upload size limits, file-count limits, total-request-size limits.
|
|
24
|
+
- Decoder limits: JSON depth, XML entity count, zip expansion ratio (zip bomb). The default parser often has no cap — configure it explicitly.
|
|
25
|
+
|
|
26
|
+
## Parser selection — safe default, not fast default
|
|
27
|
+
|
|
28
|
+
- JSON: language-standard parser with strict mode. Reject duplicate keys (behavior varies across parsers — pick one that matches what your schema validator sees).
|
|
29
|
+
- YAML: `yaml.safe_load` in Python, `js-yaml` with `safeLoad` / schema, `serde_yaml` with explicit types. **Never `yaml.load` without `SafeLoader`.**
|
|
30
|
+
- XML: disable external entity resolution (XXE). `defusedxml` in Python, libraries with XXE off by default. If your design needs XML, flag this explicitly and pick the right library.
|
|
31
|
+
- CSV: beware formula injection (`=CMD(...)` in a field opened by Excel). Prefix fields starting with `= + - @ \t \r` when exporting.
|
|
32
|
+
- Protobuf / Thrift / MessagePack: safe-by-construction for schema violations, but size limits still needed.
|
|
33
|
+
- Regex-heavy parsers: ReDoS risk. Prefer PEG / EBNF grammars for untrusted input where possible.
|
|
34
|
+
- HTML / Markdown: never innerHTML raw; always sanitize (DOMPurify, bleach). Markdown renderers have inline-HTML modes — disable them for untrusted content.
|
|
35
|
+
|
|
36
|
+
## Deserialization — the silent RCE
|
|
37
|
+
|
|
38
|
+
- Any of `pickle.loads`, `yaml.load` (default), Java `ObjectInputStream`, PHP `unserialize`, .NET `BinaryFormatter`, `Marshal.load` — on untrusted bytes — is RCE-shaped.
|
|
39
|
+
- If you *need* cross-language serialization: JSON, Protobuf, MessagePack, Avro. If you *need* native: sign the payload (HMAC) so only your own emitters are accepted, and still validate after deserialization.
|
|
40
|
+
- Node `JSON.parse` + object assignment: prototype pollution via `__proto__` / `constructor` / `prototype` keys. Use `Object.create(null)` for dictionaries or a library that filters.
|
|
41
|
+
|
|
42
|
+
## File uploads
|
|
43
|
+
|
|
44
|
+
- What file types are accepted? Allowlist by **content sniff + declared MIME + extension**, not any one of them alone.
|
|
45
|
+
- Storage: write under a random name (UUID) — never preserve the client-supplied filename in the path. Preserving it enables path traversal and overwrite attacks.
|
|
46
|
+
- Scanner: for user-to-user content, run an AV / malware scan. For images, re-encode to strip EXIF + polyglot tricks.
|
|
47
|
+
- Serving: serve from a different origin / subdomain than your app (so a rendered SVG or HTML can't steal same-origin cookies). Set `Content-Disposition: attachment` for anything that isn't trusted media.
|
|
48
|
+
- Size: per-file and per-user/per-day quotas. Unbounded upload = cheap DoS + storage bomb.
|
|
49
|
+
|
|
50
|
+
## Server-side fetchers — SSRF-shaped
|
|
51
|
+
|
|
52
|
+
If any part of the design does "take a URL from user, fetch it":
|
|
53
|
+
|
|
54
|
+
- Is there a concrete business reason? Image proxy, webhook configurer, PDF-from-URL, OAuth metadata fetch — each is a known SSRF vector.
|
|
55
|
+
- Allowlist the destination **after** DNS resolution. `https://attacker.com` that DNS-resolves to `127.0.0.1` is the rebinding attack — resolve first, then decide.
|
|
56
|
+
- Deny: RFC1918 (10/8, 172.16/12, 192.168/16), link-local (169.254/16), loopback (127/8, ::1), cloud metadata (169.254.169.254, metadata.google.internal, fd00:ec2::254), IPv6 equivalents, and any internal CIDR you own.
|
|
57
|
+
- Redirects are fresh SSRF checks per hop. Disable redirects or re-validate each one.
|
|
58
|
+
- Timeouts + max-response-size: unbounded fetches = DoS.
|
|
59
|
+
- Response parsing: the fetched content is *still untrusted*. Don't eval it, don't template it, don't copy it to storage unsanitized.
|
|
60
|
+
|
|
61
|
+
## Content rendering — templates, markdown, rich text
|
|
62
|
+
|
|
63
|
+
- Which template engine? Autoescape on by default for HTML (`{{ user }}` escapes). The unsafe marker is `|safe` (Jinja), `{!! !!}` (Blade), `dangerouslySetInnerHTML` (React), `v-html` (Vue). Every use of the unsafe marker is a review point.
|
|
64
|
+
- Markdown: does the renderer allow inline HTML? For untrusted authors, disable it or sanitize post-render with a DOMPurify-equivalent.
|
|
65
|
+
- Rich text (TinyMCE, Quill, Slate): sanitize the HTML output *server-side* before storing. Client-side sanitization is advisory, not authoritative.
|
|
66
|
+
- SVG: SVGs can embed scripts. Re-render to PNG server-side, or sanitize with a tool that strips `<script>`, event handlers, and external references.
|
|
67
|
+
|
|
68
|
+
## Search inputs
|
|
69
|
+
|
|
70
|
+
- Full-text search: user input goes into a query parser (Lucene syntax, etc.). Is there an injection risk (`field:*` to bypass scoping)? Sanitize or use parameterized search API.
|
|
71
|
+
- Sort / filter parameters: if user-controlled, allowlist the column names. `ORDER BY {user_input}` is SQL injection even if the rest of the query is parameterized.
|
|
72
|
+
|
|
73
|
+
## Imports (batch data)
|
|
74
|
+
|
|
75
|
+
- CSV / XLS / JSON imports are trust-boundary crossings at scale. Same rules — schema, size, field limits — applied per row.
|
|
76
|
+
- Streaming vs. load-all: streaming is kinder to memory and enables early rejection. Load-all with a 1GB file = OOM.
|
|
77
|
+
- Partial-failure semantics: if row 500 is bad, does the import roll back rows 1-499? Either answer can be right, but it must be *decided*, not accidental.
|
|
78
|
+
|
|
79
|
+
## Refuse-list
|
|
80
|
+
|
|
81
|
+
- `yaml.load` / `pickle.loads` / `Marshal.load` on any externally-sourced bytes.
|
|
82
|
+
- XML parsers with external entity resolution enabled.
|
|
83
|
+
- Uploads stored under client-supplied filenames.
|
|
84
|
+
- Server-side URL fetchers without an allowlist + post-DNS IP denylist.
|
|
85
|
+
- Schemas that accept unknown fields (`additionalProperties: true` by default).
|
|
86
|
+
- Unbounded sizes: no request body cap, no per-field length, no decoder depth limit.
|
|
87
|
+
- Markdown / HTML rendering of untrusted content without server-side sanitization.
|
|
88
|
+
- Regex patterns without anchors or on backtracking engines with untrusted input.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Exit criteria
|
|
93
|
+
|
|
94
|
+
- Every external input has a named schema and a size/shape limit.
|
|
95
|
+
- Parser choices are listed with the safe variant selected.
|
|
96
|
+
- If any fetcher is in the design, its allowlist + IP denylist + redirect policy is specified.
|
|
97
|
+
- File upload flow names the content-sniff library, the storage-naming scheme, and the serving origin.
|
|
98
|
+
- The design identifies every "untrusted bytes → executable context" path and closes it.
|