ndomo 0.1.0
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/.bun-version +1 -0
- package/.dockerignore +79 -0
- package/.editorconfig +18 -0
- package/.env.example +19 -0
- package/.github/CODEOWNERS +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +62 -0
- package/.github/ISSUE_TEMPLATE/config.yml +2 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +34 -0
- package/.github/dependabot.yml +36 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/release.yml +30 -0
- package/.github/workflows/gitleaks.yml +28 -0
- package/.github/workflows/release-please.yml +27 -0
- package/.github/workflows/smoke.yml +29 -0
- package/.husky/commit-msg +1 -0
- package/CHANGELOG.md +114 -0
- package/Dockerfile +32 -0
- package/README.es.md +174 -0
- package/README.md +187 -0
- package/agents/chronicler.md +98 -0
- package/agents/ci-smith.md +136 -0
- package/agents/craftsman.md +341 -0
- package/agents/deploy-smith.md +138 -0
- package/agents/foreman.md +377 -0
- package/agents/go-smith.md +164 -0
- package/agents/guild.md +188 -0
- package/agents/inspector.md +83 -0
- package/agents/js-smith.md +127 -0
- package/agents/ops-scout.md +173 -0
- package/agents/painter.md +200 -0
- package/agents/python-smith.md +120 -0
- package/agents/ranger.md +307 -0
- package/agents/release-smith.md +165 -0
- package/agents/rust-smith.md +159 -0
- package/agents/sage.md +178 -0
- package/agents/scout.md +144 -0
- package/agents/scribe.md +156 -0
- package/agents/smith.md +201 -0
- package/agents/vue-smith.md +155 -0
- package/agents/warden.md +216 -0
- package/agents/zig-smith.md +156 -0
- package/bin/ndomo-analyses.ts +4 -0
- package/bin/ndomo-status.ts +4 -0
- package/biome.json +57 -0
- package/bun.lock +514 -0
- package/commitlint.config.js +3 -0
- package/config/ndomo.config.json +258 -0
- package/config/ndomo.schema.json +166 -0
- package/docs/agents.md +375 -0
- package/docs/bugs/plan-create-orphan-fk.md +131 -0
- package/docs/bugs/task_create_batch-order-index-collision.md +158 -0
- package/docs/configuration.md +276 -0
- package/docs/database.md +364 -0
- package/docs/features/feature-flexible-builder-v1.md +724 -0
- package/docs/features/feature-flexible-builder-v2.md +882 -0
- package/docs/features/feature-flexible-builder.md +974 -0
- package/docs/http-server.md +244 -0
- package/docs/installation.md +259 -0
- package/docs/integrations.md +129 -0
- package/docs/operations/anti-pattern-sub-agent-verify-2026-06-21.md +32 -0
- package/docs/operations/audit-v1.md +417 -0
- package/docs/operations/audit-v2.md +197 -0
- package/docs/operations/audit-v3.md +306 -0
- package/docs/operations/db-optimize-foundations.md +123 -0
- package/docs/operations/verify-gate-architecture.md +82 -0
- package/docs/workflows.md +448 -0
- package/opencode.json +5 -0
- package/package.json +65 -0
- package/release-please-config.json +11 -0
- package/scripts/dev-bust-cache.sh +164 -0
- package/scripts/install.sh +688 -0
- package/scripts/smoke-e2e.ts +704 -0
- package/scripts/smoke-hot.ts +417 -0
- package/scripts/smoke-http.sh +228 -0
- package/scripts/smoke-v4.ts +256 -0
- package/scripts/smoke-v5.ts +397 -0
- package/scripts/smoke.sh +9 -0
- package/scripts/uninstall.sh +224 -0
- package/skills/api-security-best-practices/SKILL.md +915 -0
- package/skills/bash-scripting/SKILL.md +201 -0
- package/skills/bun/SKILL.md +313 -0
- package/skills/cavecrew/SKILL.md +82 -0
- package/skills/caveman/SKILL.md +74 -0
- package/skills/caveman-review/README.md +33 -0
- package/skills/caveman-review/SKILL.md +55 -0
- package/skills/find-skills/SKILL.md +142 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +55 -0
- package/skills/golang-patterns/SKILL.md +674 -0
- package/skills/golang-security/SKILL.md +185 -0
- package/skills/golang-security/evals/evals.json +595 -0
- package/skills/golang-security/references/architecture.md +268 -0
- package/skills/golang-security/references/checklist.md +80 -0
- package/skills/golang-security/references/cookies.md +200 -0
- package/skills/golang-security/references/cryptography.md +424 -0
- package/skills/golang-security/references/filesystem.md +285 -0
- package/skills/golang-security/references/injection.md +315 -0
- package/skills/golang-security/references/logging.md +163 -0
- package/skills/golang-security/references/memory-safety.md +241 -0
- package/skills/golang-security/references/network.md +253 -0
- package/skills/golang-security/references/secrets.md +189 -0
- package/skills/golang-security/references/third-party.md +159 -0
- package/skills/golang-security/references/threat-modeling.md +189 -0
- package/skills/golang-testing/SKILL.md +720 -0
- package/skills/grill-me/SKILL.md +7 -0
- package/skills/javascript-testing-patterns/SKILL.md +537 -0
- package/skills/javascript-testing-patterns/references/advanced-testing-patterns.md +513 -0
- package/skills/modern-javascript-patterns/SKILL.md +43 -0
- package/skills/modern-javascript-patterns/references/advanced-patterns.md +487 -0
- package/skills/modern-javascript-patterns/references/details.md +457 -0
- package/skills/python-anti-patterns/SKILL.md +349 -0
- package/skills/python-design-patterns/SKILL.md +85 -0
- package/skills/python-design-patterns/references/details.md +353 -0
- package/skills/python-error-handling/SKILL.md +193 -0
- package/skills/python-error-handling/references/details.md +171 -0
- package/skills/python-testing-patterns/SKILL.md +278 -0
- package/skills/python-testing-patterns/references/advanced-patterns.md +411 -0
- package/skills/python-testing-patterns/references/details.md +349 -0
- package/skills/rust-patterns/SKILL.md +500 -0
- package/skills/rust-testing/SKILL.md +501 -0
- package/skills/security-review/SKILL.md +504 -0
- package/skills/security-review/cloud-infrastructure-security.md +361 -0
- package/skills/vue-best-practices/SKILL.md +154 -0
- package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue-best-practices/references/component-data-flow.md +307 -0
- package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue-best-practices/references/composables.md +290 -0
- package/skills/vue-best-practices/references/directives.md +162 -0
- package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue-best-practices/references/reactivity.md +344 -0
- package/skills/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/zig-0.16/SKILL.md +840 -0
- package/skills/zig-0.16/scripts/check-zig-version.sh +21 -0
- package/src/cli/analyses.ts +280 -0
- package/src/cli/index.ts +108 -0
- package/src/cli/serve.ts +192 -0
- package/src/cli/smoke.ts +131 -0
- package/src/cli/status.test.ts +204 -0
- package/src/cli/status.ts +263 -0
- package/src/cli/vacuum.test.ts +82 -0
- package/src/cli/vacuum.ts +96 -0
- package/src/config/schema.test.ts +88 -0
- package/src/config/schema.ts +64 -0
- package/src/db/analyses-migration.test.ts +210 -0
- package/src/db/analyses.test.ts +466 -0
- package/src/db/analyses.ts +375 -0
- package/src/db/auto-checkpoint.ts +131 -0
- package/src/db/client.test.ts +129 -0
- package/src/db/client.ts +55 -0
- package/src/db/fts-escape.ts +20 -0
- package/src/db/incidents.test.ts +201 -0
- package/src/db/incidents.ts +93 -0
- package/src/db/index.ts +86 -0
- package/src/db/migrations-v13.test.ts +141 -0
- package/src/db/migrations-v8.test.ts +301 -0
- package/src/db/migrations.ts +147 -0
- package/src/db/plan-archive.test.ts +180 -0
- package/src/db/plan-archive.ts +274 -0
- package/src/db/plan-create.test.ts +276 -0
- package/src/db/plan-create.ts +78 -0
- package/src/db/plan-files.test.ts +289 -0
- package/src/db/plan-update-status.ts +287 -0
- package/src/db/plans.test.ts +490 -0
- package/src/db/plans.ts +534 -0
- package/src/db/resolve-project-dir.test.ts +143 -0
- package/src/db/resolve-project-dir.ts +75 -0
- package/src/db/rollbacks.test.ts +150 -0
- package/src/db/rollbacks.ts +67 -0
- package/src/db/schema.ts +907 -0
- package/src/db/sessions.test.ts +80 -0
- package/src/db/sessions.ts +135 -0
- package/src/db/shutdown.test.ts +147 -0
- package/src/db/shutdown.ts +45 -0
- package/src/db/tasks.test.ts +921 -0
- package/src/db/tasks.ts +747 -0
- package/src/db/types.ts +619 -0
- package/src/http/__tests__/auth.test.ts +196 -0
- package/src/http/__tests__/routes.test.ts +465 -0
- package/src/http/__tests__/sse.test.ts +317 -0
- package/src/http/auth.ts +72 -0
- package/src/http/middleware/cors.ts +53 -0
- package/src/http/middleware/security-headers.ts +21 -0
- package/src/http/routes/events.ts +112 -0
- package/src/http/routes/health.ts +51 -0
- package/src/http/routes/plans.ts +66 -0
- package/src/http/routes/sessions.ts +50 -0
- package/src/http/routes/tasks.ts +60 -0
- package/src/http/server.ts +95 -0
- package/src/http/sse.ts +116 -0
- package/src/index.ts +37 -0
- package/src/lib.ts +65 -0
- package/src/mem/scoped.ts +65 -0
- package/src/orchestrator/background.test.ts +268 -0
- package/src/orchestrator/background.ts +293 -0
- package/src/orchestrator/memory-hook.ts +182 -0
- package/src/orchestrator/reconciler.ts +123 -0
- package/src/orchestrator/scheduler.test.ts +300 -0
- package/src/orchestrator/scheduler.ts +243 -0
- package/src/plugin.test.ts +2574 -0
- package/src/plugin.ts +1690 -0
- package/src/sdk/client.ts +66 -0
- package/src/worktrees/manager.ts +236 -0
- package/src/worktrees/state.ts +87 -0
- package/tests/integration/ranger-flow.test.ts +257 -0
- package/tools/analysis_archive.ts +28 -0
- package/tools/analysis_create.ts +55 -0
- package/tools/analysis_get.ts +33 -0
- package/tools/analysis_link_plan.ts +44 -0
- package/tools/analysis_list.ts +48 -0
- package/tools/analysis_search.ts +36 -0
- package/tools/analysis_update.ts +44 -0
- package/tools/plan_approve.ts +31 -0
- package/tools/plan_create.ts +58 -0
- package/tools/plan_get.ts +40 -0
- package/tools/plan_list.ts +37 -0
- package/tools/plan_search.ts +34 -0
- package/tools/plan_update_status.ts +71 -0
- package/tools/session_checkpoint.ts +31 -0
- package/tools/session_end.ts +26 -0
- package/tools/session_start.ts +43 -0
- package/tools/task_create_batch.ts +70 -0
- package/tools/task_list.ts +35 -0
- package/tools/task_next_for_agent.ts +30 -0
- package/tools/task_search.ts +34 -0
- package/tools/task_update_status.ts +37 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# Cryptography Security Rules
|
|
2
|
+
|
|
3
|
+
Cryptography vulnerabilities threaten confidentiality and integrity of sensitive data.
|
|
4
|
+
|
|
5
|
+
**Rules:**
|
|
6
|
+
|
|
7
|
+
1. TLS MUST use 1.2+.
|
|
8
|
+
2. NEVER use DES, RC4, MD5, or SHA1 for security purposes.
|
|
9
|
+
3. SSH host keys MUST be verified — NEVER use `InsecureIgnoreHostKey`.
|
|
10
|
+
4. Passwords MUST be hashed with Argon2id (preferred) or bcrypt.
|
|
11
|
+
5. Security-critical randomness MUST use `crypto/rand`.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Algorithm Selection Guide
|
|
16
|
+
|
|
17
|
+
Choose the right algorithm for the job — using the wrong primitive (e.g. SHA256 for passwords) is as dangerous as using a broken one:
|
|
18
|
+
|
|
19
|
+
| Use Case | Recommended | Avoid | Why |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| Symmetric encryption | AES-256-GCM, ChaCha20-Poly1305 | DES, 3DES, AES-ECB, RC4 | ECB reveals patterns; DES/RC4 are broken |
|
|
22
|
+
| Password hashing | Argon2id (preferred), bcrypt, scrypt | MD5, SHA-1, plain SHA-256 | Fast hashes enable brute-force; memory-hard functions resist GPU attacks |
|
|
23
|
+
| Message authentication | HMAC-SHA256, Poly1305 | HMAC-MD5, HMAC-SHA1 | MD5/SHA1 have known collision weaknesses |
|
|
24
|
+
| Digital signatures | Ed25519, ECDSA P-256 | RSA-PKCS1v1.5 | PKCS1v1.5 has padding oracle vulnerabilities |
|
|
25
|
+
| Key exchange | X25519, ECDH P-256 | Static RSA key transport | Forward secrecy requires ephemeral keys |
|
|
26
|
+
| Random generation | `crypto/rand` | `math/rand` | `math/rand` output is predictable |
|
|
27
|
+
| TLS | TLS 1.2+ (prefer 1.3) | TLS 1.0, 1.1, SSL | Known attacks (BEAST, POODLE) on older versions |
|
|
28
|
+
|
|
29
|
+
### Key Size Requirements
|
|
30
|
+
|
|
31
|
+
| Algorithm | Minimum Key Size | Recommended |
|
|
32
|
+
| --------- | ------------------------ | ---------------- |
|
|
33
|
+
| RSA | 2048 bits | 4096 bits |
|
|
34
|
+
| AES | 128 bits | 256 bits |
|
|
35
|
+
| ECDSA | P-256 (128-bit security) | P-256 or Ed25519 |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Key Rotation Pattern
|
|
40
|
+
|
|
41
|
+
Keys should be rotated periodically. Use envelope encryption so rotating the Key Encryption Key (KEK) doesn't require re-encrypting all data:
|
|
42
|
+
|
|
43
|
+
```go
|
|
44
|
+
// Envelope encryption: encrypt data with a DEK, encrypt DEK with KEK
|
|
45
|
+
func EnvelopeEncrypt(kek, plaintext []byte) (encryptedDEK, ciphertext []byte, err error) {
|
|
46
|
+
// 1. Generate random Data Encryption Key
|
|
47
|
+
dek := make([]byte, 32)
|
|
48
|
+
if _, err := rand.Read(dek); err != nil {
|
|
49
|
+
return nil, nil, err
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. Encrypt data with DEK
|
|
53
|
+
ciphertext, err = EncryptAESGCM(dek, plaintext)
|
|
54
|
+
if err != nil {
|
|
55
|
+
return nil, nil, err
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 3. Encrypt DEK with KEK
|
|
59
|
+
encryptedDEK, err = EncryptAESGCM(kek, dek)
|
|
60
|
+
if err != nil {
|
|
61
|
+
return nil, nil, err
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return encryptedDEK, ciphertext, nil
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func EnvelopeDecrypt(kek, encryptedDEK, ciphertext []byte) ([]byte, error) {
|
|
68
|
+
dek, err := DecryptAESGCM(kek, encryptedDEK)
|
|
69
|
+
if err != nil {
|
|
70
|
+
return nil, err
|
|
71
|
+
}
|
|
72
|
+
return DecryptAESGCM(dek, ciphertext)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func EncryptAESGCM(key, plaintext []byte) ([]byte, error) {
|
|
76
|
+
block, err := aes.NewCipher(key)
|
|
77
|
+
if err != nil { return nil, err }
|
|
78
|
+
aead, err := cipher.NewGCM(block)
|
|
79
|
+
if err != nil { return nil, err }
|
|
80
|
+
nonce := make([]byte, aead.NonceSize())
|
|
81
|
+
if _, err := rand.Read(nonce); err != nil { return nil, err }
|
|
82
|
+
return aead.Seal(nonce, nonce, plaintext, nil), nil
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func DecryptAESGCM(key, ciphertext []byte) ([]byte, error) {
|
|
86
|
+
block, err := aes.NewCipher(key)
|
|
87
|
+
if err != nil { return nil, err }
|
|
88
|
+
aead, err := cipher.NewGCM(block)
|
|
89
|
+
if err != nil { return nil, err }
|
|
90
|
+
nonceSize := aead.NonceSize()
|
|
91
|
+
if len(ciphertext) < nonceSize {
|
|
92
|
+
return nil, errors.New("ciphertext too short")
|
|
93
|
+
}
|
|
94
|
+
return aead.Open(nil, ciphertext[:nonceSize], ciphertext[nonceSize:], nil)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
When the KEK is rotated, only re-encrypt the DEKs (small), not the data (potentially large).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Common Cryptographic Mistakes
|
|
103
|
+
|
|
104
|
+
### Mistake 1: AES-ECB reveals patterns — High
|
|
105
|
+
|
|
106
|
+
ECB encrypts each block independently — identical plaintext blocks produce identical ciphertext blocks, revealing data structure:
|
|
107
|
+
|
|
108
|
+
```go
|
|
109
|
+
// Bad — ECB mode reveals patterns in structured data
|
|
110
|
+
block, _ := aes.NewCipher(key)
|
|
111
|
+
// Using block.Encrypt directly = ECB mode
|
|
112
|
+
|
|
113
|
+
// Good — GCM provides authenticated encryption
|
|
114
|
+
aead, err := cipher.NewGCM(block) // randomized, authenticated
|
|
115
|
+
if err != nil {
|
|
116
|
+
return nil, err
|
|
117
|
+
}
|
|
118
|
+
nonce := make([]byte, aead.NonceSize())
|
|
119
|
+
if _, err := rand.Read(nonce); err != nil {
|
|
120
|
+
return nil, err
|
|
121
|
+
}
|
|
122
|
+
ciphertext := aead.Seal(nonce, nonce, plaintext, nil)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Mistake 2: Reusing nonces — Critical
|
|
126
|
+
|
|
127
|
+
A nonce reuse with AES-GCM completely breaks confidentiality and authentication:
|
|
128
|
+
|
|
129
|
+
```go
|
|
130
|
+
// Bad — static or reused nonce
|
|
131
|
+
nonce := []byte("fixed_nonce!") // catastrophic with GCM
|
|
132
|
+
|
|
133
|
+
// Good — random nonce per encryption
|
|
134
|
+
nonce := make([]byte, 12) // 96-bit for GCM
|
|
135
|
+
if _, err := rand.Read(nonce); err != nil {
|
|
136
|
+
return nil, err
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Mistake 3: Non-constant-time comparison for secrets — Medium
|
|
141
|
+
|
|
142
|
+
Comparing secrets with `==` short-circuits on the first differing byte, leaking timing information. See [Network/Web Security — Observable Timing](./network.md) for constant-time comparison patterns using `crypto/subtle`.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Insecure TLS Configuration — High
|
|
147
|
+
|
|
148
|
+
Using insecure TLS configurations can expose your application to man-in-the-middle attacks.
|
|
149
|
+
|
|
150
|
+
**Bad:**
|
|
151
|
+
|
|
152
|
+
```go
|
|
153
|
+
transport := &http.Transport{
|
|
154
|
+
TLSClientConfig: &tls.Config{
|
|
155
|
+
InsecureSkipVerify: true, // DON'T: verify certificates
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Good:**
|
|
161
|
+
|
|
162
|
+
```go
|
|
163
|
+
import "crypto/tls"
|
|
164
|
+
|
|
165
|
+
func secureConfig() *tls.Config {
|
|
166
|
+
return &tls.Config{
|
|
167
|
+
MinVersion: tls.VersionTLS12,
|
|
168
|
+
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## DES Encryption — High
|
|
176
|
+
|
|
177
|
+
DES is cryptographically broken.
|
|
178
|
+
|
|
179
|
+
**Bad:**
|
|
180
|
+
|
|
181
|
+
```go
|
|
182
|
+
import "crypto/des"
|
|
183
|
+
block, _ := des.NewCipher(key) // DON'T: broken
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Good:**
|
|
187
|
+
|
|
188
|
+
```go
|
|
189
|
+
import "crypto/aes"
|
|
190
|
+
block, _ := aes.NewCipher(key) // OK: AES
|
|
191
|
+
cipher.NewGCM(block) // OK: GCM for auth
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Insecure SSH Host Key Verification — High
|
|
197
|
+
|
|
198
|
+
**Bad:**
|
|
199
|
+
|
|
200
|
+
```go
|
|
201
|
+
import "golang.org/x/crypto/ssh"
|
|
202
|
+
&ssh.ClientConfig{
|
|
203
|
+
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // DON'T
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Good:**
|
|
208
|
+
|
|
209
|
+
```go
|
|
210
|
+
import "golang.org/x/crypto/ssh"
|
|
211
|
+
&ssh.ClientConfig{
|
|
212
|
+
HostKeyCallback: ssh.FixedHostKey(publicKey),
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## MD5 Hash — High
|
|
219
|
+
|
|
220
|
+
MD5 is collision-prone and weak for security.
|
|
221
|
+
|
|
222
|
+
**Bad:**
|
|
223
|
+
|
|
224
|
+
```go
|
|
225
|
+
import "crypto/md5"
|
|
226
|
+
hash := md5.Sum([]byte(data)) // DON'T: weak
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Good:**
|
|
230
|
+
|
|
231
|
+
```go
|
|
232
|
+
// For password hashing:
|
|
233
|
+
import "golang.org/x/crypto/argon2"
|
|
234
|
+
hash := argon2.IDKey([]byte(pw), salt, 3, 64*1024, 4, 32)
|
|
235
|
+
|
|
236
|
+
// Or bcrypt (simpler API, no salt management):
|
|
237
|
+
import "golang.org/x/crypto/bcrypt"
|
|
238
|
+
hash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
|
|
239
|
+
if err != nil {
|
|
240
|
+
return nil, err
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// For general-purpose hashing (not passwords):
|
|
244
|
+
import "crypto/sha256"
|
|
245
|
+
digest := sha256.Sum256(data)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## RC4 Cipher — High
|
|
251
|
+
|
|
252
|
+
RC4 is cryptographically broken.
|
|
253
|
+
|
|
254
|
+
**Bad:**
|
|
255
|
+
|
|
256
|
+
```go
|
|
257
|
+
import "crypto/rc4"
|
|
258
|
+
cipher, _ := rc4.NewCipher(key) // DON'T: broken
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Good:**
|
|
262
|
+
|
|
263
|
+
```go
|
|
264
|
+
import "crypto/cipher"
|
|
265
|
+
import "crypto/aes"
|
|
266
|
+
aead, _ := cipher.NewGCM(block) // OK: AES-GCM
|
|
267
|
+
|
|
268
|
+
// Or ChaCha20:
|
|
269
|
+
import "golang.org/x/crypto/chacha20poly1305"
|
|
270
|
+
aead, _ := chacha20poly1305.New(key)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## SHA1 Hash — Medium
|
|
276
|
+
|
|
277
|
+
SHA1 provides insufficient collision resistance.
|
|
278
|
+
|
|
279
|
+
**Bad:**
|
|
280
|
+
|
|
281
|
+
```go
|
|
282
|
+
import "crypto/sha1"
|
|
283
|
+
hash := sha1.Sum(data) // DON'T: weak
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Good:**
|
|
287
|
+
|
|
288
|
+
```go
|
|
289
|
+
import "crypto/sha256"
|
|
290
|
+
hash := sha256.Sum256(data)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Weak Cryptographic Algorithms — Medium
|
|
296
|
+
|
|
297
|
+
**Bad:**
|
|
298
|
+
|
|
299
|
+
```go
|
|
300
|
+
import "crypto/hmac"
|
|
301
|
+
import "crypto/md5"
|
|
302
|
+
mac := hmac.New(md5.New, key) // DON'T: HMAC-MD5
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Good:**
|
|
306
|
+
|
|
307
|
+
```go
|
|
308
|
+
import "crypto/sha256"
|
|
309
|
+
mac := hmac.New(sha256.New, key)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Insufficient Key Strength — Medium
|
|
315
|
+
|
|
316
|
+
RSA keys smaller than 2048 bits are insufficient.
|
|
317
|
+
|
|
318
|
+
**Bad:**
|
|
319
|
+
|
|
320
|
+
```go
|
|
321
|
+
import "crypto/rsa"
|
|
322
|
+
key, _ := rsa.GenerateKey(rand.Reader, 1024) // DON'T: too weak
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Good:**
|
|
326
|
+
|
|
327
|
+
```go
|
|
328
|
+
key, _ := rsa.GenerateKey(rand.Reader, 4096) // OK: 2048+ bits
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Weak Random Number Generators — High
|
|
334
|
+
|
|
335
|
+
`math/rand` is predictable, never use for security.
|
|
336
|
+
|
|
337
|
+
**Bad:**
|
|
338
|
+
|
|
339
|
+
```go
|
|
340
|
+
import "math/rand"
|
|
341
|
+
bytes := make([]byte, 16)
|
|
342
|
+
rand.Read(bytes) // DON'T: predictable
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Good:**
|
|
346
|
+
|
|
347
|
+
```go
|
|
348
|
+
import "crypto/rand"
|
|
349
|
+
_, err := rand.Read(bytes) // OK: cryptographically secure
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Weak TLS Versions — High
|
|
355
|
+
|
|
356
|
+
TLS 1.0 and 1.1 have known vulnerabilities.
|
|
357
|
+
|
|
358
|
+
**Bad:**
|
|
359
|
+
|
|
360
|
+
```go
|
|
361
|
+
import "crypto/tls"
|
|
362
|
+
&tls.Config{MinVersion: tls.VersionTLS10} // DON'T
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Good:**
|
|
366
|
+
|
|
367
|
+
```go
|
|
368
|
+
&tls.Config{MinVersion: tls.VersionTLS12} // OK
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Password Hashing — High
|
|
374
|
+
|
|
375
|
+
Don't use MD5, SHA1, or single-iteration hashes for passwords.
|
|
376
|
+
|
|
377
|
+
**Bad:**
|
|
378
|
+
|
|
379
|
+
```go
|
|
380
|
+
import "crypto/sha256"
|
|
381
|
+
hash := sha256.Sum256([]byte(password)) // DON'T: too fast
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Good:**
|
|
385
|
+
|
|
386
|
+
```go
|
|
387
|
+
// Argon2id (preferred) — memory-hard, resists GPU attacks:
|
|
388
|
+
import "golang.org/x/crypto/argon2"
|
|
389
|
+
key := argon2.IDKey([]byte(password), salt, 3, 64*1024, 4, 32)
|
|
390
|
+
|
|
391
|
+
// Or bcrypt (simpler API, widely supported):
|
|
392
|
+
import "golang.org/x/crypto/bcrypt"
|
|
393
|
+
hash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
|
|
394
|
+
if err != nil {
|
|
395
|
+
return nil, err
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Or PBKDF2 with 600,000+ iterations (Go 1.24+ stdlib):
|
|
399
|
+
import "crypto/pbkdf2"
|
|
400
|
+
key, err := pbkdf2.Key(sha512.New, password, salt, 600_000, 32)
|
|
401
|
+
if err != nil {
|
|
402
|
+
return err
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Or scrypt:
|
|
406
|
+
import "golang.org/x/crypto/scrypt"
|
|
407
|
+
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
|
|
408
|
+
if err != nil {
|
|
409
|
+
return err
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
For Go 1.24+, prefer stdlib `crypto/hkdf`, `crypto/pbkdf2`, and `crypto/sha3`. Use `golang.org/x/crypto/...` fallbacks only for modules targeting older Go versions or for algorithms still outside the standard library.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## CWE References
|
|
418
|
+
|
|
419
|
+
- **CWE-327**: Use of a Broken or Risky Cryptographic Algorithm
|
|
420
|
+
- **CWE-331**: Insufficient Entropy
|
|
421
|
+
- **CWE-326**: Inadequate Encryption Strength
|
|
422
|
+
- **CWE-295**: Improper Certificate Validation
|
|
423
|
+
- **CWE-330**: Use of Insufficiently Random Values
|
|
424
|
+
- **CWE-916**: Use of Password Hash With Insufficient Computational Effort
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Filesystem Security Rules
|
|
2
|
+
|
|
3
|
+
Filesystem vulnerabilities can lead to unauthorized file access, data leakage, and denial-of-service attacks.
|
|
4
|
+
|
|
5
|
+
**Rules:**
|
|
6
|
+
|
|
7
|
+
1. User-controlled file paths MUST be confined to an allowed root.
|
|
8
|
+
2. `os.Root` SHOULD be used for scoped file access (Go 1.24+).
|
|
9
|
+
3. Zip extraction MUST check for ZipSlip path traversal.
|
|
10
|
+
4. Temporary files MUST use `os.CreateTemp` — NEVER predictable names.
|
|
11
|
+
5. File permissions MUST be restrictive (0600 for secrets, 0750 for directories).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Directory Traversal — High
|
|
16
|
+
|
|
17
|
+
Paths like `../../etc/passwd` access files outside intended directory.
|
|
18
|
+
|
|
19
|
+
**Bad:**
|
|
20
|
+
|
|
21
|
+
```go
|
|
22
|
+
filepath := filepath.Join("/var/www", filename) // DON'T
|
|
23
|
+
http.ServeFile(w, r, filepath)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Good (Go 1.24+) — use `os.Root` for safe, scoped directory access:**
|
|
27
|
+
|
|
28
|
+
```go
|
|
29
|
+
root, err := os.OpenRoot("/var/www")
|
|
30
|
+
if err != nil { return err }
|
|
31
|
+
defer root.Close()
|
|
32
|
+
f, err := root.Open(filename) // cannot escape root directory
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`os.Root` prevents ordinary path traversal at the OS level. All operations (`Open`, `Create`, `Stat`, `OpenFile`, etc.) are confined to the root directory, and symlinks that resolve outside the root are rejected. It is not a full sandbox: it does not by itself block bind mounts, special device files, or all `/proc`-style filesystem behavior. For archive extraction and uploads, still reject special files and choose a root without attacker-controlled mounts.
|
|
36
|
+
|
|
37
|
+
**Good (pre-Go 1.24 fallback):**
|
|
38
|
+
|
|
39
|
+
```go
|
|
40
|
+
func safeJoin(baseDir, userPath string) (string, error) {
|
|
41
|
+
if userPath == "" || filepath.IsAbs(userPath) || !filepath.IsLocal(userPath) {
|
|
42
|
+
return "", errors.New("invalid relative path")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
full := filepath.Join(baseDir, userPath)
|
|
46
|
+
|
|
47
|
+
rel, err := filepath.Rel(baseDir, full)
|
|
48
|
+
if err != nil {
|
|
49
|
+
return "", fmt.Errorf("checking path: %w", err)
|
|
50
|
+
}
|
|
51
|
+
if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
|
52
|
+
return "", errors.New("path escapes base directory")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return full, nil
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This lexical fallback is not a full symlink-resistant substitute for `os.Root`.
|
|
60
|
+
|
|
61
|
+
**Bad:**
|
|
62
|
+
|
|
63
|
+
```go
|
|
64
|
+
fullPath := filepath.Join(baseDir, filename)
|
|
65
|
+
if !strings.HasPrefix(filepath.Clean(fullPath), filepath.Clean(baseDir)) {
|
|
66
|
+
return errors.New("access denied")
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Zip Archive Path Traversal — High
|
|
73
|
+
|
|
74
|
+
Malicious zip files can escape extraction directory.
|
|
75
|
+
|
|
76
|
+
**Bad:**
|
|
77
|
+
|
|
78
|
+
```go
|
|
79
|
+
for _, file := range reader.File {
|
|
80
|
+
path := filepath.Join(dest, file.Name) // DON'T: No validation
|
|
81
|
+
file.Create(path)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Good (Go 1.24+) — use `os.Root` to scope extraction:**
|
|
86
|
+
|
|
87
|
+
```go
|
|
88
|
+
root, err := os.OpenRoot(dest)
|
|
89
|
+
if err != nil { return err }
|
|
90
|
+
defer root.Close()
|
|
91
|
+
for _, file := range reader.File {
|
|
92
|
+
f, err := root.OpenFile(file.Name, os.O_CREATE|os.O_WRONLY, 0644)
|
|
93
|
+
if err != nil { return err } // rejects paths escaping root
|
|
94
|
+
// ... copy contents ...
|
|
95
|
+
f.Close()
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Good (pre-Go 1.24 fallback):**
|
|
100
|
+
|
|
101
|
+
```go
|
|
102
|
+
for _, file := range reader.File {
|
|
103
|
+
if !filepath.IsLocal(file.Name) {
|
|
104
|
+
return fmt.Errorf("unsafe archive path: %q", file.Name)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
targetPath, err := safeJoin(dest, file.Name)
|
|
108
|
+
if err != nil {
|
|
109
|
+
return err
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// create parent directories, then write targetPath
|
|
113
|
+
_ = targetPath
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Decompression Bomb — Medium
|
|
120
|
+
|
|
121
|
+
Tiny compressed files can expand to GBs.
|
|
122
|
+
|
|
123
|
+
**Bad:**
|
|
124
|
+
|
|
125
|
+
```go
|
|
126
|
+
gr, _ := gzip.NewReader(f)
|
|
127
|
+
out, _ := os.Create(dst)
|
|
128
|
+
io.Copy(out, gr) // DON'T: No size limits
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Good:**
|
|
132
|
+
|
|
133
|
+
```go
|
|
134
|
+
const maxDecompressedSize = 100 * 1024 * 1024 // 100MB limit
|
|
135
|
+
|
|
136
|
+
var errDecompressedSizeLimitExceeded = errors.New("decompressed size limit exceeded")
|
|
137
|
+
|
|
138
|
+
type limitedReader struct {
|
|
139
|
+
r io.Reader
|
|
140
|
+
read int64
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func (l *limitedReader) Read(p []byte) (int, error) {
|
|
144
|
+
if l.read >= maxDecompressedSize {
|
|
145
|
+
// Return a sentinel error — io.EOF would be treated as success by io.Copy
|
|
146
|
+
return 0, errDecompressedSizeLimitExceeded
|
|
147
|
+
}
|
|
148
|
+
n, err := l.r.Read(p)
|
|
149
|
+
l.read += int64(n)
|
|
150
|
+
return n, err
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
lr := &limitedReader{r: gr}
|
|
154
|
+
if _, err := io.Copy(out, lr); err != nil {
|
|
155
|
+
return fmt.Errorf("decompressing: %w", err)
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Insecure Temporary File Creation — Medium
|
|
162
|
+
|
|
163
|
+
Creating temp files without proper permissions.
|
|
164
|
+
|
|
165
|
+
**Bad:**
|
|
166
|
+
|
|
167
|
+
```go
|
|
168
|
+
f, _ := os.Create("/tmp/myapp.temp") // DON'T: Predictable name
|
|
169
|
+
f.WriteString(data)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Good:**
|
|
173
|
+
|
|
174
|
+
```go
|
|
175
|
+
f, err := os.CreateTemp("", "myapp.*")
|
|
176
|
+
defer os.Remove(f.Name())
|
|
177
|
+
f.Chmod(0600) // Restrictive permissions
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Insecure File Permissions — Medium
|
|
183
|
+
|
|
184
|
+
Opening files with excessive permissions.
|
|
185
|
+
|
|
186
|
+
**Bad:**
|
|
187
|
+
|
|
188
|
+
```go
|
|
189
|
+
f, _ := os.OpenFile("config.json", os.O_CREATE, 0644) // DON'T: World-readable
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Good:**
|
|
193
|
+
|
|
194
|
+
```go
|
|
195
|
+
f, _ := os.OpenFile("config.json", os.O_CREATE, 0600) // OK: Owner only
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Insecure mkdir — Low
|
|
201
|
+
|
|
202
|
+
Creating directories with overly permissive permissions.
|
|
203
|
+
|
|
204
|
+
**Bad:**
|
|
205
|
+
|
|
206
|
+
```go
|
|
207
|
+
os.MkdirAll("/var/myapp/cache", 0777) // DON'T: World-writable
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Good:**
|
|
211
|
+
|
|
212
|
+
```go
|
|
213
|
+
os.MkdirAll("/var/myapp/cache", 0750) // OK: Group-writable
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Insecure File Write Permissions — Medium
|
|
219
|
+
|
|
220
|
+
Opening files for writing with inappropriate permissions.
|
|
221
|
+
|
|
222
|
+
**Bad:**
|
|
223
|
+
|
|
224
|
+
```go
|
|
225
|
+
os.OpenFile("app.log", os.O_CREATE, 0666) // DON'T: World-writable
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Good:**
|
|
229
|
+
|
|
230
|
+
```go
|
|
231
|
+
os.OpenFile("app.log", os.O_CREATE|os.O_APPEND, 0640) // OK
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Tainted File Read — High
|
|
237
|
+
|
|
238
|
+
Reading files based on unvalidated input.
|
|
239
|
+
|
|
240
|
+
**Bad:**
|
|
241
|
+
|
|
242
|
+
```go
|
|
243
|
+
func readFile(filename string) ([]byte, error) {
|
|
244
|
+
return os.ReadFile(filename) // DON'T: No validation
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Good (Go 1.24+):**
|
|
249
|
+
|
|
250
|
+
```go
|
|
251
|
+
const allowedDir = "/var/www/public/"
|
|
252
|
+
|
|
253
|
+
func readFile(filename string) ([]byte, error) {
|
|
254
|
+
root, err := os.OpenRoot(allowedDir)
|
|
255
|
+
if err != nil { return nil, err }
|
|
256
|
+
defer root.Close()
|
|
257
|
+
f, err := root.Open(filename) // cannot escape root directory
|
|
258
|
+
if err != nil { return nil, err }
|
|
259
|
+
defer f.Close()
|
|
260
|
+
return io.ReadAll(f)
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Good (pre-Go 1.24 fallback):**
|
|
265
|
+
|
|
266
|
+
```go
|
|
267
|
+
const allowedDir = "/var/www/public/"
|
|
268
|
+
|
|
269
|
+
func readFile(filename string) ([]byte, error) {
|
|
270
|
+
fullPath, err := safeJoin(allowedDir, filename)
|
|
271
|
+
if err != nil {
|
|
272
|
+
return nil, err
|
|
273
|
+
}
|
|
274
|
+
return os.ReadFile(fullPath)
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## CWE References
|
|
281
|
+
|
|
282
|
+
- **CWE-22**: Path Traversal (Directory Traversal)
|
|
283
|
+
- **CWE-409**: Zip Bomb Decompression
|
|
284
|
+
- **CWE-379**: Insecure Temp File Creation
|
|
285
|
+
- **CWE-732**: Incorrect File Permissions
|