@sun-asterisk/sunlint 1.3.40 → 1.3.42
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/core/rule-selection-service.js +11 -0
- package/package.json +1 -1
- package/skill-assets/sunlint-code-quality/rules/dart/D001-recommended-lints.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D002-dispose-resources.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D003-prefer-widget-classes.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D004-avoid-shrinkwrap.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D005-widget-nesting.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D006-large-callbacks.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D007-lifecycle-order.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D008-long-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D009-function-parameters.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D010-cyclomatic-complexity.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D011-named-parameters.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D012-named-booleans.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D013-single-public-class.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D014-safe-collection-access.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D015-copywith-consistency.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D016-project-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D017-review-dependencies.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D018-no-commented-code.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D019-single-child-wrappers.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D020-if-else-limit.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D021-negated-booleans.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D022-setstate-usage.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D023-unnecessary-overrides.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D024-avoid-unnecessary-statefulwidget.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D025-nested-ternaries.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/go/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C013-no-dead-code.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C014-dependency-injection.md +85 -0
- package/skill-assets/sunlint-code-quality/rules/go/C017-no-constructor-logic.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/go/C018-generic-errors.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/C020-no-unused-imports.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C022-no-unused-variables.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/C023-no-duplicate-names.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/C024-centralize-constants.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/C029-catch-log-root-cause.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/C030-custom-error-classes.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/go/C033-separate-data-access.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go/C035-error-context-logging.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C041-no-hardcoded-secrets.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C042-boolean-naming.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/C052-controller-parsing.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/go/C060-superclass-logic.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/go/C067-no-hardcoded-config.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S003-open-redirect.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/go/S004-no-log-credentials.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/go/S005-server-authorization.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S006-default-credentials.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S007-output-encoding.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S009-approved-crypto.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/S010-csprng.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S011-encrypted-client-hello.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/S012-secrets-management.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S013-tls-connections.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/go/S016-no-sensitive-query-string.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S017-parameterized-queries.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/go/S019-email-input-sanitization.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S020-eval-code-execution.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S022-context-escaping.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S023-dynamic-js-encoding.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S025-server-validation.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/S026-tls-encryption.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S027-mtls-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S028-upload-limits.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S029-csrf-protection.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S030-directory-browsing.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S031-secure-cookie-flag.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S032-httponly-cookie.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S033-samesite-cookie.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S034-host-prefix-cookie.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S035-app-hostnames.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S036-internal-file-paths.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/S037-anti-cache-headers.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S039-tls-certificate-validation.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/S041-logout-invalidation.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S042-long-lived-sessions.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S044-critical-changes-reauth.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S045-brute-force-protection.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S047-oauth-csrf-protection.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S048-oauth-redirect-validation.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S049-auth-code-expiry.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S050-token-entropy.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S051-password-length.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S052-otp-entropy.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S053-generic-error-messages.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S054-no-default-admin.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S055-content-type-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S056-log-injection.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S057-synchronized-time.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S058-ssrf-protection.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB001-use-snake-case.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB002-use-camel-case.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB003-use-screaming-snake-case.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB004-predicate-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB005-dangerous-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB006-indentation.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB007-line-length.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB008-rescue-exception.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB009-save-bang.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB010-avoid-n-plus-one.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB011-use-find-each.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB012-sql-injection.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB013-prefer-has-many-through.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB014-dependent-associations.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB015-modern-validations.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB016-thin-controllers.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB017-avoid-fat-models.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB018-service-objects.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB019-avoid-metaprogramming.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB020-use-pluck.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB021-use-size.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB022-order-by-timestamps.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB023-where-missing.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB024-method-length.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB025-parameter-limits.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB026-avoid-deep-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB027-guard-clauses.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB028-class-length.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB029-meaningful-names.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB030-dry-principle.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB031-mvc-architecture.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB032-use-concerns.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB033-moderate-callbacks.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB034-use-decorators.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB035-comprehensive-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB036-frozen-string-literal.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB037-it-parameter.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB038-modern-enum-syntax.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB039-solid-adapters.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB040-rails-authentication.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB041-async-query-loading.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB042-hotwire-turbo.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB043-use-propshaft.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB044-structured-logging.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB045-prism-parser.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW001-block-based-kvo.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW002-class-delegate-protocol.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW003-compiler-protocol-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW004-contains-over-filter-count.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW005-convenience-type.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW006-discarded-notification-center-observer.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW007-discouraged-direct-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW008-discouraged-optional-boolean.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW009-empty-count.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW010-empty-string.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW011-explicit-init.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW012-fatal-error-message.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW013-for-where.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW014-force-cast.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW015-force-try.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW016-force-unwrapping.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW017-function-parameter-count.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW018-large-tuple.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW019-legacy-constructor.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW020-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW021-no-extension-access-modifier.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW022-overridden-super-call.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW023-override-in-extension.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW024-private-over-fileprivate.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW025-private-unit-test.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW026-prohibited-super-call.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW027-sorted-first-last.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW028-syntactic-sugar.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW029-unused-closure-parameter.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW030-unused-enumerated.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW031-unused-optional-binding.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW032-valid-ibinspectable.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW033-vertical-parameter-alignment.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW034-void-return.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW035-weak-delegate.md +28 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: OTPs Must Have 20-bit Entropy Minimum
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents OTP brute-forcing
|
|
5
|
+
tags: otp, entropy, authentication, 2fa, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## OTPs Must Have 20-bit Entropy Minimum
|
|
9
|
+
|
|
10
|
+
Low-entropy OTPs (like 4 digits) can be brute-forced easily. 20 bits of entropy requires at least 6 decimal digits.
|
|
11
|
+
|
|
12
|
+
**Incorrect (low entropy OTPs):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// 4 digits = ~13 bits entropy
|
|
16
|
+
otp := fmt.Sprintf("%04d", rand.Intn(10000))
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (high entropy OTPs via crypto/rand):**
|
|
20
|
+
|
|
21
|
+
```go
|
|
22
|
+
import (
|
|
23
|
+
"crypto/rand"
|
|
24
|
+
"math/big"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// 6-digit numeric OTP (≈20 bits entropy)
|
|
28
|
+
func GenerateOTP() string {
|
|
29
|
+
max := big.NewInt(1000000)
|
|
30
|
+
n, _ := rand.Int(rand.Reader, max)
|
|
31
|
+
return fmt.Sprintf("%06d", n)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 8-digit for higher security (≈26 bits)
|
|
35
|
+
func GenerateStrongOTP() string {
|
|
36
|
+
max := big.NewInt(100000000)
|
|
37
|
+
n, _ := rand.Int(rand.Reader, max)
|
|
38
|
+
return fmt.Sprintf("%08d", n)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**OTP requirements:**
|
|
43
|
+
- Must use `crypto/rand` (CSPRNG).
|
|
44
|
+
- Minimum 6 digits (20 bits).
|
|
45
|
+
- Short expiration (5-10 minutes).
|
|
46
|
+
- Rate limit verification attempts.
|
|
47
|
+
|
|
48
|
+
**Tools:** Manual Review, Unit Test
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Return Generic Error Messages
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents information disclosure
|
|
5
|
+
tags: error-messages, information-disclosure, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Return Generic Error Messages
|
|
9
|
+
|
|
10
|
+
Detailed error messages can help attackers understand your system's internals. Return generic messages to end-users.
|
|
11
|
+
|
|
12
|
+
**Incorrect (detailed errors to user):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
err := db.QueryRow("...").Scan(&id)
|
|
17
|
+
if err != nil {
|
|
18
|
+
// Exposes database details!
|
|
19
|
+
http.Error(w, err.Error(), 500)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// User enumeration
|
|
25
|
+
if userNotFound {
|
|
26
|
+
http.Error(w, "User john@example.com dose not exist", 404)
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct (generic errors with internal logging):**
|
|
31
|
+
|
|
32
|
+
```go
|
|
33
|
+
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
34
|
+
err := db.QueryRow("...").Scan(&id)
|
|
35
|
+
if err != nil {
|
|
36
|
+
// Log internally
|
|
37
|
+
slog.Error("query failed", "error", err, "request_id", r.Header.Get("X-Request-Id"))
|
|
38
|
+
|
|
39
|
+
// Return generic message
|
|
40
|
+
http.Error(w, "An internal error occurred", 500)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Same message for "user not found" and "wrong password"
|
|
46
|
+
if !userExists || !passwordMatches {
|
|
47
|
+
http.Error(w, "Invalid credentials", 401)
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Tools:** Global Error Middleware, `slog`
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Default Admin/Root Accounts
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents easy initial access by attackers
|
|
5
|
+
tags: admin, default-accounts, credentials, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid Default Admin/Root Accounts
|
|
9
|
+
|
|
10
|
+
Default accounts with known credentials (e.g., admin/admin) are the first thing attackers check.
|
|
11
|
+
|
|
12
|
+
**Incorrect (default admin in seed):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Seed script
|
|
16
|
+
db.Exec("INSERT INTO users (email, password, role) VALUES ('admin@example.com', 'admin123', 'admin')")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (secure initial setup):**
|
|
20
|
+
|
|
21
|
+
```go
|
|
22
|
+
// 1. Setup wizard on first run
|
|
23
|
+
func SetupHandler(w http.ResponseWriter, r *http.Request) {
|
|
24
|
+
if adminExists() {
|
|
25
|
+
http.Error(w, "Setup already done", 403)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
// Form to create first admin...
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Or use environment variables for bootstrap
|
|
32
|
+
func BootstrapAdmin() {
|
|
33
|
+
email := os.Getenv("INITIAL_ADMIN_EMAIL")
|
|
34
|
+
pass := os.Getenv("INITIAL_ADMIN_PASSWORD")
|
|
35
|
+
|
|
36
|
+
if pass == "" || len(pass) < 16 {
|
|
37
|
+
log.Fatal("Secure INITIAL_ADMIN_PASSWORD required")
|
|
38
|
+
}
|
|
39
|
+
createAdmin(email, pass)
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Tools:** Security Audit, Configuration Review
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Validate Content-Type In REST Services
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents content-type confusion attacks
|
|
5
|
+
tags: rest, content-type, validation, api, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Validate Content-Type In REST Services
|
|
9
|
+
|
|
10
|
+
Accepting unexpected content types can lead to parsing vulnerabilities or bypass security controls.
|
|
11
|
+
|
|
12
|
+
**Incorrect (accepting any content):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
// No check on Content-Type header
|
|
17
|
+
var data map[string]interface{}
|
|
18
|
+
json.NewDecoder(r.Body).Decode(&data)
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (strict content-type validation):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
func validateContentType(allowed ...string) func(http.Handler) http.Handler {
|
|
26
|
+
return func(next http.Handler) http.Handler {
|
|
27
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
28
|
+
ct := r.Header.Get("Content-Type")
|
|
29
|
+
|
|
30
|
+
isValid := false
|
|
31
|
+
for _, a := range allowed {
|
|
32
|
+
if strings.Contains(strings.ToLower(ct), strings.ToLower(a)) {
|
|
33
|
+
isValid = true
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if !isValid {
|
|
39
|
+
http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
next.ServeHTTP(w, r)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Router usage
|
|
49
|
+
mux.Handle("/api/data", validateContentType("application/json")(http.HandlerFunc(Handler)))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Tools:** API Gateway, Middleware, OWASP ZAP
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Protect Against Log Injection
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents log forging and exploitation
|
|
5
|
+
tags: logging, injection, sanitization, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Protect Against Log Injection
|
|
9
|
+
|
|
10
|
+
Log injection allows attackers to forge log entries, hide their tracks, or inject malicious control characters.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unsanitized logging):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
user := r.FormValue("user")
|
|
17
|
+
slog.Info("User logged in: " + user)
|
|
18
|
+
// Attacker: "admin\n[ERROR] Payment failed for user: victim"
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (sanitized structured logging):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
// 1. Use structured logging (automatically handles quotes/escaping in key-value pairs)
|
|
26
|
+
slog.Info("User logged in", "username", sanitizeForLog(user))
|
|
27
|
+
|
|
28
|
+
// 2. Sanitize input
|
|
29
|
+
func sanitizeForLog(input string) string {
|
|
30
|
+
// Replace CRLF/tabs with space
|
|
31
|
+
replacer := strings.NewReplacer("\r", " ", "\n", " ", "\t", " ")
|
|
32
|
+
return replacer.Replace(input)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 3. Recommended: Use JSON logger in production
|
|
36
|
+
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
|
37
|
+
logger.Info("User logged in", "username", user)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Tools:** `log/slog`, `zap`, `gosec`
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Synchronized Time (UTC) In Logs
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: enables accurate incident correlation
|
|
5
|
+
tags: logging, time, utc, synchronization, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Synchronized Time (UTC) In Logs
|
|
9
|
+
|
|
10
|
+
Inconsistent timestamps across servers/services make incident investigation difficult. Use UTC and ISO 8601 format.
|
|
11
|
+
|
|
12
|
+
**Incorrect (local time/custom formats):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Local time - depends on server TZ
|
|
16
|
+
log.Printf("Event time: %v", time.Now())
|
|
17
|
+
|
|
18
|
+
// Different formats
|
|
19
|
+
fmt.Println(time.Now().Unix())
|
|
20
|
+
fmt.Println(time.Now().Format("2006-01-02"))
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (UTC, ISO 8601):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
// Use .UTC() and .Format(time.RFC3339)
|
|
27
|
+
slog.Info("User action",
|
|
28
|
+
"timestamp", time.Now().UTC().Format(time.RFC3339Nano),
|
|
29
|
+
"action", "login",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// Output: {"timestamp":"2024-01-15T10:30:00.123456Z", "level":"INFO", "message":"User action", "action":"login"}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Requirements:**
|
|
36
|
+
- Use UTC timezone externally.
|
|
37
|
+
- Use ISO 8601 (RFC 3339) with nanosecond/millisecond precision.
|
|
38
|
+
- Ensure servers are NTP-synchronized.
|
|
39
|
+
|
|
40
|
+
**Tools:** `time` (Standard Library), `slog`, NTP
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Protect Against SSRF Attacks
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents internal network access from user input
|
|
5
|
+
tags: ssrf, url, network, internal, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Protect Against SSRF Attacks
|
|
9
|
+
|
|
10
|
+
SSRF allows attackers to make requests from your server to internal services, local files, or cloud metadata endpoints.
|
|
11
|
+
|
|
12
|
+
**Incorrect (accepting user URLs without validation):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
url := r.URL.Query().Get("url")
|
|
17
|
+
resp, _ := http.Get(url) // Attacker controls URL!
|
|
18
|
+
io.Copy(w, resp.Body)
|
|
19
|
+
}
|
|
20
|
+
// Attacker: ?url=http://169.254.169.254/latest/meta-data/
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (URL validation and IP blocking):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
import (
|
|
27
|
+
"net"
|
|
28
|
+
"net/url"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
var allowedHosts = []string{"api.example.com", "cdn.example.com"}
|
|
32
|
+
|
|
33
|
+
func SafeFetch(userURL string) (*http.Response, error) {
|
|
34
|
+
parsed, err := url.Parse(userURL)
|
|
35
|
+
if err != nil {
|
|
36
|
+
return nil, err
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 1. Protocol whitelist
|
|
40
|
+
if parsed.Scheme != "http" && parsed.Scheme != "https" {
|
|
41
|
+
return nil, errors.New("protocol not allowed")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2. Host whitelist
|
|
45
|
+
isAllowed := false
|
|
46
|
+
for _, h := range allowedHosts {
|
|
47
|
+
if parsed.Hostname() == h {
|
|
48
|
+
isAllowed = true
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Resolve IP and block internal ranges
|
|
54
|
+
ips, _ := net.LookupIP(parsed.Hostname())
|
|
55
|
+
for _, ip := range ips {
|
|
56
|
+
if ip.IsLoopback() || ip.IsPrivate() || ip.IsUnspecified() {
|
|
57
|
+
return nil, errors.New("internal IP blocked")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
client := &http.Client{
|
|
62
|
+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
63
|
+
return http.ErrUseLastResponse // Disable redirects
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
return client.Get(userURL)
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Tools:** `net/url`, `net.LookupIP`, `gosec` (G107)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use snake_case for symbols, methods, and variables
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Follow Ruby community naming conventions for consistency and readability.
|
|
5
|
+
tags: ruby, naming, snake_case, convention
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use snake_case for symbols, methods, and variables
|
|
9
|
+
|
|
10
|
+
Follow Ruby community naming conventions for consistency and readability. Use `snake_case` (all lowercase with underscores) for symbols, methods, and variables. Avoid camelCase or other naming styles for these elements.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def calculateTotal(price, taxRate)
|
|
16
|
+
totalAmount = price * taxRate
|
|
17
|
+
:orderStatus
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct:**
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
def calculate_total(price, tax_rate)
|
|
25
|
+
total_amount = price * tax_rate
|
|
26
|
+
:order_status
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** RuboCop (`Naming/VariableName`, `Naming/MethodName`)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use CamelCase for classes and modules
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Follow Ruby community naming conventions for classes and modules.
|
|
5
|
+
tags: ruby, naming, CamelCase, architecture
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use CamelCase for classes and modules
|
|
9
|
+
|
|
10
|
+
Use `CamelCase` (also known as PascalCase) for class and module names. Keep acronyms uppercase (e.g., `HTTPClient`, `XMLParser`).
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class User_profile
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module Api_Helper
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class HttpClient # Acronym 'HTTP' should be all caps
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct:**
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
class UserProfile
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module ApiHelper
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class HTTPClient
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Tools:** RuboCop (`Naming/ClassName`)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use SCREAMING_SNAKE_CASE for constants
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Clearly distinguish constants from other identifiers.
|
|
5
|
+
tags: ruby, naming, constants, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use SCREAMING_SNAKE_CASE for constants
|
|
9
|
+
|
|
10
|
+
Clearly distinguish constants from other identifiers. Use `SCREAMING_SNAKE_CASE` for constants that do not refer to classes or modules.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
DefaultTimeout = 30
|
|
16
|
+
Max_Retry_Count = 5
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct:**
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
DEFAULT_TIMEOUT = 30
|
|
23
|
+
MAX_RETRY_COUNT = 5
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Tools:** RuboCop (`Naming/ConstantName`)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Predicate methods should end with ?
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Make boolean-returning methods immediately recognizable.
|
|
5
|
+
tags: ruby, naming, predicate, boolean
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Predicate methods should end with ?
|
|
9
|
+
|
|
10
|
+
Make boolean-returning methods immediately recognizable. Methods that return boolean values should end with a question mark (`?`). Avoid prefixing with auxiliary verbs like `is_`, `does_`, or `can_`.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def is_valid
|
|
16
|
+
# logic
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if user.is_authenticated
|
|
20
|
+
# logic
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct:**
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
def valid?
|
|
28
|
+
# logic
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if user.authenticated?
|
|
32
|
+
# logic
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Tools:** RuboCop (`Naming/PredicateName`)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dangerous methods should end with !
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Clearly indicate methods that modify the receiver or can raise exceptions.
|
|
5
|
+
tags: ruby, naming, bang, side-effect
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Dangerous methods should end with !
|
|
9
|
+
|
|
10
|
+
Clearly indicate methods that modify the receiver or can raise exceptions. Methods that modify the object in place or can raise exceptions should end with a bang (`!`).
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Method raises an exception if validation fails
|
|
16
|
+
def save
|
|
17
|
+
# complex logic
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# In-place sort
|
|
21
|
+
values.sort
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct:**
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
# Recommends save! for raising exceptions
|
|
28
|
+
def save!
|
|
29
|
+
# complex logic
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Indicates in-place sort
|
|
33
|
+
values.sort!
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Tools:** RuboCop (`Style/BangPredicate`)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use 2 spaces for indentation
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Follow Ruby community standard for code formatting.
|
|
5
|
+
tags: ruby, formatting, indentation, style
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use 2 spaces for indentation
|
|
9
|
+
|
|
10
|
+
Follow Ruby community standard for code formatting. Use 2 spaces for indentation, never tabs. Ensure consistent indentation throughout the codebase.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def hello
|
|
16
|
+
puts "hello" # Tab used
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def world
|
|
20
|
+
puts "world" # 4 spaces used
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct:**
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
def hello
|
|
28
|
+
puts "hello" # 2 spaces
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Tools:** RuboCop (`Layout/IndentationWidth`)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Keep lines under 120 characters
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Improve code readability and prevent horizontal scrolling.
|
|
5
|
+
tags: ruby, formatting, readability, side-scrolling
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Keep lines under 120 characters
|
|
9
|
+
|
|
10
|
+
Improve code readability and prevent horizontal scrolling. Limit line length to 120 characters maximum. Break long lines into multiple lines when necessary.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
puts "This is an extremely long string that definitely exceeds the recommended limit of one hundred and twenty characters that we have set for our coding standards in this project."
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct:**
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
puts "This is an extremely long string that definitely exceeds " \
|
|
22
|
+
"the recommended limit of one hundred and twenty characters..."
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Tools:** RuboCop (`Layout/LineLength`)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid rescuing the Exception class
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Prevent hiding critical system errors.
|
|
5
|
+
tags: ruby, errors, exception, safety
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid rescuing the Exception class
|
|
9
|
+
|
|
10
|
+
Prevent hiding critical system errors. Never rescue the generic `Exception` class as it catches system-level errors (like `SignalException`, `NoMemoryError`, `SystemExit`). Rescue specific exception classes or `StandardError` instead.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Dangerous):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
begin
|
|
16
|
+
do_something
|
|
17
|
+
rescue Exception => e
|
|
18
|
+
logger.error(e.message)
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (Safe):**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
begin
|
|
26
|
+
do_something
|
|
27
|
+
rescue StandardError => e # Or just 'rescue => e' which defaults to StandardError
|
|
28
|
+
logger.error(e.message)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Or specific
|
|
32
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
33
|
+
# handle
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Tools:** RuboCop (`Lint/RescueException`)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use save! or handle return values
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Ensure database operations are properly validated and errors are not silently ignored.
|
|
5
|
+
tags: rails, database, validations, active-record
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use save! or handle return values
|
|
9
|
+
|
|
10
|
+
Ensure database operations are properly validated and errors are not silently ignored. Use bang methods (`save!`, `create!`, `update!`, `destroy!`) to raise exceptions on failure. If using non-bang methods, always check the return value.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def create
|
|
16
|
+
@user = User.new(user_params)
|
|
17
|
+
@user.save # Failure will be silent
|
|
18
|
+
redirect_to @user
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct:**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
def create
|
|
26
|
+
@user = User.new(user_params)
|
|
27
|
+
if @user.save
|
|
28
|
+
redirect_to @user
|
|
29
|
+
else
|
|
30
|
+
render :new
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Or using bang
|
|
35
|
+
def create
|
|
36
|
+
@user = User.create!(user_params)
|
|
37
|
+
redirect_to @user
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tools:** RuboCop (`Rails/SaveBang`)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid N+1 queries with eager loading
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Prevent performance issues caused by N+1 query problems.
|
|
5
|
+
tags: rails, performance, database, n+1, active-record
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid N+1 queries with eager loading
|
|
9
|
+
|
|
10
|
+
Prevent performance issues caused by N+1 query problems. Use `includes`, `preload`, or `eager_load` to load associations upfront. Avoid iterating over collections and accessing associations without eager loading.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Triggers N+1 queries (one query for users, and one query per user for posts)
|
|
16
|
+
users = User.limit(10)
|
|
17
|
+
users.each do |user|
|
|
18
|
+
puts user.posts.count
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct:**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Eager loads posts in 2 queries total
|
|
26
|
+
users = User.includes(:posts).limit(10)
|
|
27
|
+
users.each do |user|
|
|
28
|
+
puts user.posts.count
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Tools:** Bullet gem, RuboCop Rails (`Rails/FindEach`)
|