@xenos1996/usat 1.0.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/LICENSE +21 -0
- package/README.md +335 -0
- package/USAT.md +596 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +438 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +72 -0
- package/dist/config.js.map +1 -0
- package/dist/detect/index.d.ts +58 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +329 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/engine/audit.d.ts +21 -0
- package/dist/engine/audit.d.ts.map +1 -0
- package/dist/engine/audit.js +295 -0
- package/dist/engine/audit.js.map +1 -0
- package/dist/engine/diff.d.ts +24 -0
- package/dist/engine/diff.d.ts.map +1 -0
- package/dist/engine/diff.js +136 -0
- package/dist/engine/diff.js.map +1 -0
- package/dist/engine/evaluate.d.ts +19 -0
- package/dist/engine/evaluate.d.ts.map +1 -0
- package/dist/engine/evaluate.js +377 -0
- package/dist/engine/evaluate.js.map +1 -0
- package/dist/engine/gate.d.ts +19 -0
- package/dist/engine/gate.d.ts.map +1 -0
- package/dist/engine/gate.js +47 -0
- package/dist/engine/gate.js.map +1 -0
- package/dist/engine/loader.d.ts +22 -0
- package/dist/engine/loader.d.ts.map +1 -0
- package/dist/engine/loader.js +360 -0
- package/dist/engine/loader.js.map +1 -0
- package/dist/engine/maturity.d.ts +20 -0
- package/dist/engine/maturity.d.ts.map +1 -0
- package/dist/engine/maturity.js +157 -0
- package/dist/engine/maturity.js.map +1 -0
- package/dist/engine/score.d.ts +19 -0
- package/dist/engine/score.d.ts.map +1 -0
- package/dist/engine/score.js +141 -0
- package/dist/engine/score.js.map +1 -0
- package/dist/engine/sections.d.ts +14 -0
- package/dist/engine/sections.d.ts.map +1 -0
- package/dist/engine/sections.js +50 -0
- package/dist/engine/sections.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/report/markdown.d.ts +16 -0
- package/dist/report/markdown.d.ts.map +1 -0
- package/dist/report/markdown.js +488 -0
- package/dist/report/markdown.js.map +1 -0
- package/dist/types.d.ts +282 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/util/glob.d.ts +11 -0
- package/dist/util/glob.d.ts.map +1 -0
- package/dist/util/glob.js +86 -0
- package/dist/util/glob.js.map +1 -0
- package/dist/util/project.d.ts +72 -0
- package/dist/util/project.d.ts.map +1 -0
- package/dist/util/project.js +586 -0
- package/dist/util/project.js.map +1 -0
- package/dist/util/yaml.d.ts +28 -0
- package/dist/util/yaml.d.ts.map +1 -0
- package/dist/util/yaml.js +48 -0
- package/dist/util/yaml.js.map +1 -0
- package/docs/ARCHITECTURE.md +119 -0
- package/docs/README.md +27 -0
- package/docs/adr/0001-record-architecture-decisions.md +35 -0
- package/docs/adr/0002-typescript-engine-over-python.md +49 -0
- package/docs/adr/0003-rules-are-data-not-code.md +50 -0
- package/docs/adr/0004-markdown-only-output.md +41 -0
- package/docs/adr/0005-maturity-dampens-severity.md +56 -0
- package/docs/adr/0006-severity-status-two-axes.md +48 -0
- package/docs/adr/0007-suppressions-are-visible-and-expiring.md +46 -0
- package/docs/adr/0008-report-trailer-and-diff.md +48 -0
- package/docs/adr/0009-fail-closed-on-malformed-input.md +56 -0
- package/docs/adr/0010-detector-design.md +54 -0
- package/docs/adr/0011-coexist-with-deep-scanners.md +52 -0
- package/docs/adr/README.md +32 -0
- package/docs/agent-integration.md +129 -0
- package/docs/ci-integration.md +163 -0
- package/docs/concepts.md +196 -0
- package/docs/configuration.md +105 -0
- package/docs/detectors.md +186 -0
- package/docs/getting-started.md +99 -0
- package/docs/maturity-profiles.md +156 -0
- package/docs/reference/api.md +193 -0
- package/docs/reference/cli.md +412 -0
- package/docs/rule-packs.md +278 -0
- package/docs/standards-mapping.md +171 -0
- package/package.json +96 -0
- package/rules/core/architecture.yaml +190 -0
- package/rules/core/cicd.yaml +212 -0
- package/rules/core/code-quality.yaml +261 -0
- package/rules/core/dependencies.yaml +132 -0
- package/rules/core/documentation.yaml +119 -0
- package/rules/core/future-readiness.yaml +91 -0
- package/rules/core/release.yaml +131 -0
- package/rules/core/repo.yaml +303 -0
- package/rules/core/security.yaml +494 -0
- package/rules/core/supply-chain.yaml +357 -0
- package/rules/core/testing.yaml +260 -0
- package/rules/detectors.yaml +2255 -0
- package/rules/index.yaml +41 -0
- package/rules/profiles/maturity.yaml +135 -0
- package/rules/stacks/ai-era.yaml +193 -0
- package/rules/stacks/api-backend.yaml +121 -0
- package/rules/stacks/cli.yaml +105 -0
- package/rules/stacks/compliance.yaml +157 -0
- package/rules/stacks/containers.yaml +124 -0
- package/rules/stacks/data.yaml +165 -0
- package/rules/stacks/go.yaml +134 -0
- package/rules/stacks/iac.yaml +128 -0
- package/rules/stacks/jvm.yaml +150 -0
- package/rules/stacks/ml-ai.yaml +140 -0
- package/rules/stacks/mobile.yaml +121 -0
- package/rules/stacks/node-typescript.yaml +166 -0
- package/rules/stacks/python.yaml +175 -0
- package/rules/stacks/rust.yaml +129 -0
- package/rules/stacks/solidity.yaml +139 -0
- package/rules/stacks/web-frontend.yaml +159 -0
- package/templates/AGENTS.audit.md +135 -0
- package/templates/AUDIT_REPORT.md +155 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
id: stacks/compliance
|
|
2
|
+
title: Accessibility, i18n & Compliance
|
|
3
|
+
section: S13
|
|
4
|
+
section_title: Accessibility, i18n & Compliance
|
|
5
|
+
description: >
|
|
6
|
+
Accessibility, internationalisation, and regulatory obligations. Most of
|
|
7
|
+
these are judgement calls — USAT records what it can and asks for evidence
|
|
8
|
+
on the rest.
|
|
9
|
+
version: '1.0'
|
|
10
|
+
skip_when: { fact: 'maturity:prototype' }
|
|
11
|
+
|
|
12
|
+
rules:
|
|
13
|
+
- id: COMP-001
|
|
14
|
+
title: Interactive elements are keyboard accessible
|
|
15
|
+
section: S13
|
|
16
|
+
section_title: Accessibility, i18n & Compliance
|
|
17
|
+
severity: MEDIUM
|
|
18
|
+
class: compliance
|
|
19
|
+
applies_when:
|
|
20
|
+
any:
|
|
21
|
+
- { fact: 'platform:web' }
|
|
22
|
+
- { fact: 'platform:mobile' }
|
|
23
|
+
check: { kind: manual }
|
|
24
|
+
evidence: 'Tab through the main flow: can you reach and operate everything? Is focus always visible?'
|
|
25
|
+
why: 'Keyboard access is table stakes for screen readers, motor impairments, and power users alike.'
|
|
26
|
+
remediation: 'Use native controls; never remove focus outlines without a replacement; test with keyboard only.'
|
|
27
|
+
references: ['WCAG-2.1.1', 'WCAG-2.4.7', 'EN-301-549']
|
|
28
|
+
|
|
29
|
+
- id: COMP-002
|
|
30
|
+
title: Form inputs have associated labels
|
|
31
|
+
section: S13
|
|
32
|
+
section_title: Accessibility, i18n & Compliance
|
|
33
|
+
severity: MEDIUM
|
|
34
|
+
class: compliance
|
|
35
|
+
applies_when: { fact: 'platform:web' }
|
|
36
|
+
check: { kind: manual }
|
|
37
|
+
evidence: 'Every input has a <label for>, aria-label, or aria-labelledby — and placeholder alone does not count.'
|
|
38
|
+
why: 'Unlabelled inputs are unusable with a screen reader and are the most common WCAG failure.'
|
|
39
|
+
remediation: 'Associate label elements; add eslint-plugin-jsx-a11y to catch regressions.'
|
|
40
|
+
references: ['WCAG-3.3.2', 'WCAG-1.3.1']
|
|
41
|
+
|
|
42
|
+
- id: COMP-003
|
|
43
|
+
title: Colour contrast meets WCAG AA
|
|
44
|
+
section: S13
|
|
45
|
+
section_title: Accessibility, i18n & Compliance
|
|
46
|
+
severity: MEDIUM
|
|
47
|
+
class: compliance
|
|
48
|
+
applies_when: { fact: 'platform:web' }
|
|
49
|
+
check: { kind: manual }
|
|
50
|
+
evidence: 'Contrast ratio for body text (≥4.5:1) and large text (≥3:1), including placeholder and disabled states.'
|
|
51
|
+
why: 'Low contrast is the most common accessibility defect and the easiest to fix.'
|
|
52
|
+
remediation: 'Run axe/Lighthouse; fix the reported contrast failures; check dark mode too.'
|
|
53
|
+
references: ['WCAG-1.4.3']
|
|
54
|
+
|
|
55
|
+
- id: COMP-004
|
|
56
|
+
title: Content is not hardcoded to one locale
|
|
57
|
+
section: S13
|
|
58
|
+
section_title: Accessibility, i18n & Compliance
|
|
59
|
+
severity: MEDIUM
|
|
60
|
+
class: compliance
|
|
61
|
+
applies_when:
|
|
62
|
+
any:
|
|
63
|
+
- { fact: 'platform:web' }
|
|
64
|
+
- { fact: 'platform:mobile' }
|
|
65
|
+
check:
|
|
66
|
+
kind: grep_present
|
|
67
|
+
pattern: '(i18n|i18next|react-intl|vue-i18n|lingui|next-intl|Localizable|NSLocalizedString|intl\.|translations?/)'
|
|
68
|
+
include: ['**/*.{ts,tsx,js,jsx,vue,svelte,swift,kt,java,json,yaml,yml}']
|
|
69
|
+
exclude: ['**/node_modules/**', '**/Pods/**']
|
|
70
|
+
flags: i
|
|
71
|
+
why: 'Retrofitting i18n after launch means touching every string in the codebase.'
|
|
72
|
+
remediation: 'Extract user-facing strings now, even if you ship one language.'
|
|
73
|
+
references: ['WCAG-3.1.1', 'i18n-Best-Practices']
|
|
74
|
+
|
|
75
|
+
- id: COMP-005
|
|
76
|
+
title: Dates, numbers, and currencies are locale-aware
|
|
77
|
+
section: S13
|
|
78
|
+
section_title: Accessibility, i18n & Compliance
|
|
79
|
+
severity: LOW
|
|
80
|
+
class: compliance
|
|
81
|
+
applies_when: { fact: 'practice:i18n' }
|
|
82
|
+
check: { kind: manual }
|
|
83
|
+
evidence: 'Formatting via Intl/ICU rather than string concatenation or a hardcoded MM/DD/YYYY.'
|
|
84
|
+
why: '13/01/2026 is a valid date in most of the world and a bug report in the US.'
|
|
85
|
+
remediation: 'Use Intl.DateTimeFormat / Intl.NumberFormat everywhere; store timestamps in UTC.'
|
|
86
|
+
references: ['WCAG-3.1.2', 'ICU']
|
|
87
|
+
|
|
88
|
+
- id: COMP-006
|
|
89
|
+
title: A privacy policy and terms exist and match reality
|
|
90
|
+
section: S13
|
|
91
|
+
section_title: Accessibility, i18n & Compliance
|
|
92
|
+
severity: MEDIUM
|
|
93
|
+
class: compliance
|
|
94
|
+
applies_when:
|
|
95
|
+
any:
|
|
96
|
+
- { fact: 'has:pii' }
|
|
97
|
+
- { fact: 'maturity:production' }
|
|
98
|
+
check: { kind: manual }
|
|
99
|
+
evidence: 'Links to the live policy, and a check that it names the data you actually collect and the processors you actually use.'
|
|
100
|
+
why: 'A policy that does not match the code is worse than no policy: it is a documented misrepresentation.'
|
|
101
|
+
remediation: 'Generate the data inventory from your code, then write the policy to match it.'
|
|
102
|
+
references: ['GDPR-Art.13', 'CCPA']
|
|
103
|
+
|
|
104
|
+
- id: COMP-007
|
|
105
|
+
title: Data subject rights are implemented
|
|
106
|
+
section: S13
|
|
107
|
+
section_title: Accessibility, i18n & Compliance
|
|
108
|
+
severity: HIGH
|
|
109
|
+
class: compliance
|
|
110
|
+
applies_when: { fact: 'has:pii' }
|
|
111
|
+
check: { kind: manual }
|
|
112
|
+
evidence: 'Working flows for access (export), erasure, and correction — and the response time SLA.'
|
|
113
|
+
why: 'GDPR/CCPA give you 30 days. Without a built flow, each request is an ad-hoc database operation.'
|
|
114
|
+
remediation: 'Build export and delete endpoints behind authentication; log the request and the completion.'
|
|
115
|
+
references: ['GDPR-Art.15', 'GDPR-Art.17', 'CCPA']
|
|
116
|
+
|
|
117
|
+
- id: COMP-008
|
|
118
|
+
title: Card data is kept out of scope
|
|
119
|
+
section: S13
|
|
120
|
+
section_title: Accessibility, i18n & Compliance
|
|
121
|
+
severity: HIGH
|
|
122
|
+
class: compliance
|
|
123
|
+
applies_when: { fact: 'has:payments' }
|
|
124
|
+
check: { kind: manual }
|
|
125
|
+
evidence: 'Whether PAN/CVV ever touches your servers, or whether you use a hosted field / tokenisation (SAQ A/A-EP).'
|
|
126
|
+
why: 'Storing card data drags your entire infrastructure into PCI-DSS scope. Tokenisation removes it.'
|
|
127
|
+
remediation: 'Use provider-hosted fields or a tokenisation proxy; never log or store full card numbers.'
|
|
128
|
+
references: ['PCI-DSS-4.0', 'ASVS-8.3.1']
|
|
129
|
+
|
|
130
|
+
- id: COMP-009
|
|
131
|
+
title: Cookie / consent notice is compliant
|
|
132
|
+
section: S13
|
|
133
|
+
section_title: Accessibility, i18n & Compliance
|
|
134
|
+
severity: MEDIUM
|
|
135
|
+
class: compliance
|
|
136
|
+
applies_when: { fact: 'platform:web' }
|
|
137
|
+
check: { kind: manual }
|
|
138
|
+
evidence: 'Consent is opt-in for non-essential cookies, is as easy to refuse as to accept, and is recorded.'
|
|
139
|
+
why: '"By using this site you agree" has not been valid consent in the EU since 2018.'
|
|
140
|
+
remediation: 'Add a consent manager that defaults to rejected and stores the choice per user.'
|
|
141
|
+
references: ['ePrivacy', 'GDPR-Art.7']
|
|
142
|
+
|
|
143
|
+
- id: COMP-010
|
|
144
|
+
title: Source files carry an SPDX license identifier
|
|
145
|
+
section: S13
|
|
146
|
+
section_title: Accessibility, i18n & Compliance
|
|
147
|
+
severity: LOW
|
|
148
|
+
class: compliance
|
|
149
|
+
applies_when: { fact: 'project:library' }
|
|
150
|
+
check:
|
|
151
|
+
kind: grep_present
|
|
152
|
+
pattern: '(SPDX-License-Identifier|@license)'
|
|
153
|
+
include: ['**/*.{ts,js,py,go,rs,java,kt,c,h,css}']
|
|
154
|
+
exclude: ['**/node_modules/**']
|
|
155
|
+
why: 'Machine-readable licensing is what lets consumers automate compliance — and it costs one line per file.'
|
|
156
|
+
remediation: 'Add `// SPDX-License-Identifier: MIT` headers; enforce with a lint rule (REUSE).'
|
|
157
|
+
references: ['SPDX', 'REUSE-Spec']
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
id: stacks/containers
|
|
2
|
+
title: Containers & Runtime
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when Docker, compose, or Kubernetes manifests are detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when: { fact: 'has:containers', op: 'absent' }
|
|
8
|
+
|
|
9
|
+
rules:
|
|
10
|
+
- id: CTNR-001
|
|
11
|
+
title: Container does not run as root
|
|
12
|
+
section: S15
|
|
13
|
+
section_title: Platform-Specific
|
|
14
|
+
severity: HIGH
|
|
15
|
+
class: security
|
|
16
|
+
check:
|
|
17
|
+
kind: grep_present
|
|
18
|
+
pattern: '(USER\s+(?!root\b)\w+|--chown=|runAsNonRoot\s*:\s*true|securityContext)'
|
|
19
|
+
include: ['Dockerfile*', '**/Dockerfile*', '**/*.{yaml,yml}', 'docker-compose*.yml']
|
|
20
|
+
exclude: ['**/node_modules/**']
|
|
21
|
+
why: 'A root process inside the container is one kernel exploit away from root on the host.'
|
|
22
|
+
remediation: 'Add `USER node` (or a dedicated non-root uid) and `securityContext.runAsNonRoot: true` in Kubernetes.'
|
|
23
|
+
references: ['CWE-250', 'CIS-Docker-4.1', 'ASVS-14.1.3']
|
|
24
|
+
|
|
25
|
+
- id: CTNR-002
|
|
26
|
+
title: Multi-stage builds are used
|
|
27
|
+
section: S15
|
|
28
|
+
section_title: Platform-Specific
|
|
29
|
+
severity: MEDIUM
|
|
30
|
+
class: performance
|
|
31
|
+
check:
|
|
32
|
+
kind: grep_present
|
|
33
|
+
pattern: 'FROM\s+\S+\s+AS\s+\w+'
|
|
34
|
+
include: ['Dockerfile*', '**/Dockerfile*']
|
|
35
|
+
flags: i
|
|
36
|
+
why: 'Without multi-stage, your production image ships the compiler, the test suite, and your .git directory.'
|
|
37
|
+
remediation: 'Build in one stage, copy only the artifact into a slim runtime stage.'
|
|
38
|
+
references: ['Docker-Multi-Stage']
|
|
39
|
+
|
|
40
|
+
- id: CTNR-003
|
|
41
|
+
title: Base image is minimal and pinned
|
|
42
|
+
section: S15
|
|
43
|
+
section_title: Platform-Specific
|
|
44
|
+
severity: MEDIUM
|
|
45
|
+
class: supply-chain
|
|
46
|
+
check:
|
|
47
|
+
kind: grep_absent
|
|
48
|
+
pattern: 'FROM\s+\S+:latest\s*$|FROM\s+\w+\s*$'
|
|
49
|
+
include: ['Dockerfile*', '**/Dockerfile*']
|
|
50
|
+
flags: i
|
|
51
|
+
why: ':latest means the image changes under you with no commit, and full distro images carry hundreds of CVEs.'
|
|
52
|
+
remediation: 'Pin a digest or an exact tag; prefer alpine, distroless, or chainguard images.'
|
|
53
|
+
references: ['OpenSSF-Scorecard:Pinned-Dependencies', 'CIS-Docker-4.2']
|
|
54
|
+
|
|
55
|
+
- id: CTNR-004
|
|
56
|
+
title: A .dockerignore exists
|
|
57
|
+
section: S15
|
|
58
|
+
section_title: Platform-Specific
|
|
59
|
+
severity: MEDIUM
|
|
60
|
+
class: supply-chain
|
|
61
|
+
check:
|
|
62
|
+
kind: any_file
|
|
63
|
+
patterns: ['.dockerignore']
|
|
64
|
+
why: 'Without it, the build context copies node_modules, .git, and — frequently — .env into the build.'
|
|
65
|
+
remediation: 'Add .dockerignore excluding .git, node_modules, .env, secrets, and local build output.'
|
|
66
|
+
references: ['CIS-Docker']
|
|
67
|
+
|
|
68
|
+
- id: CTNR-005
|
|
69
|
+
title: No secrets baked into the image
|
|
70
|
+
section: S15
|
|
71
|
+
section_title: Platform-Specific
|
|
72
|
+
severity: CRITICAL
|
|
73
|
+
class: security
|
|
74
|
+
check:
|
|
75
|
+
kind: grep_absent
|
|
76
|
+
pattern: '(ARG\s+(SECRET|TOKEN|PASSWORD|KEY)|ENV\s+(SECRET|TOKEN|PASSWORD|API_KEY|AWS_SECRET)\s*=|COPY\s+\.env)'
|
|
77
|
+
include: ['Dockerfile*', '**/Dockerfile*', 'docker-compose*.yml', 'compose.yaml']
|
|
78
|
+
flags: i
|
|
79
|
+
why: 'ENV and ARG values are visible in `docker history` to anyone who pulls the image.'
|
|
80
|
+
remediation: 'Inject secrets at runtime (env from the orchestrator, mounted files, or a secret manager).'
|
|
81
|
+
references: ['CWE-798', 'CIS-Docker-4.10']
|
|
82
|
+
|
|
83
|
+
- id: CTNR-006
|
|
84
|
+
title: HEALTHCHECK is defined
|
|
85
|
+
section: S15
|
|
86
|
+
section_title: Platform-Specific
|
|
87
|
+
severity: MEDIUM
|
|
88
|
+
class: operations
|
|
89
|
+
check:
|
|
90
|
+
kind: grep_present
|
|
91
|
+
pattern: '(HEALTHCHECK|livenessProbe|readinessProbe)'
|
|
92
|
+
include: ['Dockerfile*', '**/Dockerfile*', 'docker-compose*.yml', '**/*.{yaml,yml}']
|
|
93
|
+
why: 'The orchestrator cannot restart a container it does not know is sick.'
|
|
94
|
+
remediation: 'Add HEALTHCHECK (Docker) or liveness/readiness probes (Kubernetes) hitting your /health endpoint.'
|
|
95
|
+
references: ['K8s-Probes', 'CIS-Docker-4.6']
|
|
96
|
+
|
|
97
|
+
- id: CTNR-007
|
|
98
|
+
title: Kubernetes workloads declare resource limits
|
|
99
|
+
section: S15
|
|
100
|
+
section_title: Platform-Specific
|
|
101
|
+
severity: MEDIUM
|
|
102
|
+
class: performance
|
|
103
|
+
applies_when: { fact: 'has:kubernetes' }
|
|
104
|
+
check:
|
|
105
|
+
kind: grep_present
|
|
106
|
+
pattern: 'resources\s*:\s*(\s*$|[\s\S]{0,80}(limits|requests)\s*:)'
|
|
107
|
+
include: ['**/*.{yaml,yml}']
|
|
108
|
+
exclude: ['**/node_modules/**']
|
|
109
|
+
why: 'One pod with no limits can starve every other pod on the node.'
|
|
110
|
+
remediation: 'Set requests and limits on CPU and memory for every container; add a LimitRange as a backstop.'
|
|
111
|
+
references: ['K8s-Resource-Management', 'CIS-K8s']
|
|
112
|
+
|
|
113
|
+
- id: CTNR-008
|
|
114
|
+
title: Images are scanned and signed
|
|
115
|
+
section: S15
|
|
116
|
+
section_title: Platform-Specific
|
|
117
|
+
severity: MEDIUM
|
|
118
|
+
class: supply-chain
|
|
119
|
+
applies_when: { fact: 'maturity:production' }
|
|
120
|
+
check: { kind: manual }
|
|
121
|
+
evidence: 'Trivy/Grype in the pipeline and cosign signatures verified at admission (or a documented plan to add both).'
|
|
122
|
+
why: 'Unsigned images mean anything that can push to the registry can run in your cluster.'
|
|
123
|
+
remediation: 'Scan on push, sign with cosign, and enforce verification with a policy controller.'
|
|
124
|
+
references: ['SLSA-Build-L2', 'Sigstore-Cosign']
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
id: stacks/data
|
|
2
|
+
title: Data & Database
|
|
3
|
+
section: S6
|
|
4
|
+
section_title: Data & Database
|
|
5
|
+
description: 'Activated when a database, ORM, or migrations directory is detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when: { fact: 'has:database', op: 'absent' }
|
|
8
|
+
|
|
9
|
+
rules:
|
|
10
|
+
- id: DATA-001
|
|
11
|
+
title: A migration system is in place
|
|
12
|
+
section: S6
|
|
13
|
+
section_title: Data & Database
|
|
14
|
+
severity: HIGH
|
|
15
|
+
class: correctness
|
|
16
|
+
check:
|
|
17
|
+
kind: any_file
|
|
18
|
+
patterns:
|
|
19
|
+
[
|
|
20
|
+
'migrations/**',
|
|
21
|
+
'prisma/migrations/**',
|
|
22
|
+
'alembic/**',
|
|
23
|
+
'db/migrate/**',
|
|
24
|
+
'**/migrations/*.sql',
|
|
25
|
+
'knexfile.*',
|
|
26
|
+
'flyway/**',
|
|
27
|
+
'liquibase/**',
|
|
28
|
+
'atlas.hcl',
|
|
29
|
+
'drizzle/**',
|
|
30
|
+
]
|
|
31
|
+
why: 'Schema changes applied by hand cannot be reviewed, repeated, or rolled back.'
|
|
32
|
+
remediation: 'Adopt the migration tool for your ORM; never alter production schema manually.'
|
|
33
|
+
references: ['NIST-SSDF-PO.5']
|
|
34
|
+
|
|
35
|
+
- id: DATA-002
|
|
36
|
+
title: Foreign keys / relations are defined
|
|
37
|
+
section: S6
|
|
38
|
+
section_title: Data & Database
|
|
39
|
+
severity: MEDIUM
|
|
40
|
+
class: correctness
|
|
41
|
+
check:
|
|
42
|
+
kind: grep_present
|
|
43
|
+
pattern: '(references\s*\(|FOREIGN KEY|foreignKey|@ManyToOne|@Relation|belongs_to|has_many|ForeignKey\(|onDelete)'
|
|
44
|
+
include: ['**/*.{sql,prisma,py,ts,js,go,rs,java,kt,rb,php}']
|
|
45
|
+
exclude: ['**/node_modules/**', '**/tests/**']
|
|
46
|
+
flags: i
|
|
47
|
+
why: 'Relations enforced only in application code are relations that drift out of sync the first time someone writes a script.'
|
|
48
|
+
remediation: 'Declare constraints at the DB level; add a periodic orphan-row check if you cannot.'
|
|
49
|
+
references: ['ASVS-5.3.4']
|
|
50
|
+
|
|
51
|
+
- id: DATA-003
|
|
52
|
+
title: Indexes exist on queried columns
|
|
53
|
+
section: S6
|
|
54
|
+
section_title: Data & Database
|
|
55
|
+
severity: MEDIUM
|
|
56
|
+
class: performance
|
|
57
|
+
check: { kind: manual }
|
|
58
|
+
evidence: 'The five heaviest queries and their EXPLAIN plans. Any sequential scan on a large table needs a justification.'
|
|
59
|
+
why: 'Most "the database is slow" incidents are one missing index on one column.'
|
|
60
|
+
remediation: 'Index every foreign key and every column in a WHERE/ORDER BY on a large table; verify with EXPLAIN.'
|
|
61
|
+
references: ['Use-The-Index-Luke']
|
|
62
|
+
|
|
63
|
+
- id: DATA-004
|
|
64
|
+
title: No N+1 query patterns
|
|
65
|
+
section: S6
|
|
66
|
+
section_title: Data & Database
|
|
67
|
+
severity: HIGH
|
|
68
|
+
class: performance
|
|
69
|
+
check: { kind: manual }
|
|
70
|
+
evidence: 'Query logs for the main list endpoints: is there one query per row, or one query with a join/include?'
|
|
71
|
+
why: 'An endpoint that works with 10 rows falls over at 10,000 — usually in production, rarely in staging.'
|
|
72
|
+
remediation: 'Eager-load relations (include/join/prefetch_related); add query counting assertions to tests.'
|
|
73
|
+
references: ['ORM-N+1']
|
|
74
|
+
|
|
75
|
+
- id: DATA-005
|
|
76
|
+
title: List queries are paginated
|
|
77
|
+
section: S6
|
|
78
|
+
section_title: Data & Database
|
|
79
|
+
severity: HIGH
|
|
80
|
+
class: performance
|
|
81
|
+
check:
|
|
82
|
+
kind: grep_present
|
|
83
|
+
pattern: '(\.limit\s*\(|take\s*:\s*|LIMIT\s+\d|first\s*:\s*|pageSize|per_page|cursor\s*:\s*|page_size|Top\s+\d+)'
|
|
84
|
+
include: ['**/*.{ts,tsx,js,py,go,rs,java,kt,rb,php,graphql,sql}']
|
|
85
|
+
exclude: ['**/node_modules/**', '**/migrations/**', '**/*.test.*']
|
|
86
|
+
flags: i
|
|
87
|
+
why: 'An unbounded SELECT is a denial-of-service you wrote yourself, usually accidentally.'
|
|
88
|
+
remediation: 'Enforce a default and maximum page size on every list endpoint; prefer cursor pagination.'
|
|
89
|
+
references: ['CWE-770', 'ASVS-4.3.2']
|
|
90
|
+
|
|
91
|
+
- id: DATA-006
|
|
92
|
+
title: Connection pooling is configured
|
|
93
|
+
section: S6
|
|
94
|
+
section_title: Data & Database
|
|
95
|
+
severity: MEDIUM
|
|
96
|
+
class: performance
|
|
97
|
+
check:
|
|
98
|
+
kind: grep_present
|
|
99
|
+
pattern: '(pool|pool_size|max_?connections|connection_limit|MaximumPoolSize|poolSize|HikariCP|db_pool)'
|
|
100
|
+
include: ['**/*.{ts,js,py,go,rs,java,kt,rb,php,yml,yaml,toml,env,example,prisma}']
|
|
101
|
+
exclude: ['**/node_modules/**']
|
|
102
|
+
flags: i
|
|
103
|
+
why: 'Without a pool, every request opens a connection and the database runs out at the worst possible moment.'
|
|
104
|
+
remediation: 'Configure a pool sized to (cores × 2) + spindle count as a starting point; add a pgbouncer if needed.'
|
|
105
|
+
references: ['Postgres-Pooling']
|
|
106
|
+
|
|
107
|
+
- id: DATA-007
|
|
108
|
+
title: Multi-step writes are transactional
|
|
109
|
+
section: S6
|
|
110
|
+
section_title: Data & Database
|
|
111
|
+
severity: HIGH
|
|
112
|
+
class: correctness
|
|
113
|
+
check:
|
|
114
|
+
kind: grep_present
|
|
115
|
+
pattern: '(\$transaction|beginTransaction|BEGIN\s*;|withTransaction|atomic\s*\(|session\.with_transaction|db\.transaction)'
|
|
116
|
+
include: ['**/*.{ts,js,py,go,rs,java,kt,rb,php}']
|
|
117
|
+
exclude: ['**/node_modules/**', '**/migrations/**', '**/*.test.*']
|
|
118
|
+
flags: i
|
|
119
|
+
why: 'A half-applied write is worse than a failed one: the data looks fine until someone reconciles it.'
|
|
120
|
+
remediation: 'Wrap related writes in a transaction; make the operation idempotent so retries are safe.'
|
|
121
|
+
references: ['ASVS-5.3.4']
|
|
122
|
+
|
|
123
|
+
- id: DATA-008
|
|
124
|
+
title: No SELECT * in hot paths
|
|
125
|
+
section: S6
|
|
126
|
+
section_title: Data & Database
|
|
127
|
+
severity: LOW
|
|
128
|
+
class: performance
|
|
129
|
+
check:
|
|
130
|
+
kind: grep_wrong
|
|
131
|
+
pattern: 'SELECT\s+\*\s+FROM'
|
|
132
|
+
include: ['**/*.{sql,ts,js,py,go,rs,java,kt,rb,php}']
|
|
133
|
+
exclude: ['**/node_modules/**', '**/migrations/**', '**/tests/**']
|
|
134
|
+
why: 'SELECT * breaks when the schema changes, and pulls columns nobody needs across the network.'
|
|
135
|
+
remediation: 'Select the columns you use.'
|
|
136
|
+
references: ['Use-The-Index-Luke']
|
|
137
|
+
|
|
138
|
+
- id: DATA-009
|
|
139
|
+
title: Delete strategy is defined
|
|
140
|
+
section: S6
|
|
141
|
+
section_title: Data & Database
|
|
142
|
+
severity: LOW
|
|
143
|
+
class: compliance
|
|
144
|
+
check: { kind: manual }
|
|
145
|
+
evidence: 'Whether records are hard-deleted or soft-deleted, and how that interacts with retention and erasure requests.'
|
|
146
|
+
why: '"Delete the account" needs a defined answer before a regulator or a customer asks for it.'
|
|
147
|
+
remediation: 'Pick one per entity type; document it; make sure erasure requests honour the choice everywhere.'
|
|
148
|
+
references: ['GDPR-Art.17']
|
|
149
|
+
|
|
150
|
+
- id: DATA-010
|
|
151
|
+
title: Row-level access is enforced in the data layer
|
|
152
|
+
section: S6
|
|
153
|
+
section_title: Data & Database
|
|
154
|
+
severity: HIGH
|
|
155
|
+
class: security
|
|
156
|
+
applies_when:
|
|
157
|
+
any:
|
|
158
|
+
- { fact: 'db:postgres' }
|
|
159
|
+
- { fact: 'orm:prisma' }
|
|
160
|
+
- { fact: 'orm:sqlalchemy' }
|
|
161
|
+
check: { kind: manual }
|
|
162
|
+
evidence: 'Whether tenant/user scoping is applied by the query layer itself (RLS, a scoped repository) or by remembering to add a WHERE clause.'
|
|
163
|
+
why: 'Scoping that depends on remembering is scoping that will be forgotten exactly once, catastrophically.'
|
|
164
|
+
remediation: 'Use Postgres RLS or a repository pattern that requires the tenant scope; test cross-tenant reads explicitly.'
|
|
165
|
+
references: ['ASVS-4.2.1', 'OWASP-A01:2021']
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
id: stacks/go
|
|
2
|
+
title: Go
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when Go is detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when: { fact: 'lang:go', op: 'absent' }
|
|
8
|
+
|
|
9
|
+
rules:
|
|
10
|
+
- id: GO-001
|
|
11
|
+
title: go.mod declares the Go version
|
|
12
|
+
section: S15
|
|
13
|
+
section_title: Platform-Specific
|
|
14
|
+
severity: MEDIUM
|
|
15
|
+
class: operations
|
|
16
|
+
check:
|
|
17
|
+
kind: grep_present
|
|
18
|
+
pattern: '^go\s+\d+\.\d+'
|
|
19
|
+
include: ['go.mod']
|
|
20
|
+
why: 'Without it the toolchain guesses, and the guess changes when the runner image updates.'
|
|
21
|
+
remediation: 'Add `go 1.22` to go.mod and pin the same version in CI.'
|
|
22
|
+
references: ['Go-Modules']
|
|
23
|
+
|
|
24
|
+
- id: GO-002
|
|
25
|
+
title: Errors are checked, not discarded
|
|
26
|
+
section: S15
|
|
27
|
+
section_title: Platform-Specific
|
|
28
|
+
severity: HIGH
|
|
29
|
+
class: correctness
|
|
30
|
+
check: { kind: manual }
|
|
31
|
+
evidence: 'errcheck or `go vet` output; grep for `_ =` assignments and bare `_` error receivers.'
|
|
32
|
+
why: 'A discarded error is a failure that reports success. This is the canonical Go bug.'
|
|
33
|
+
remediation: 'Run errcheck in CI; handle every returned error or explicitly document why it is ignored.'
|
|
34
|
+
references: ['Go-Proverbs', 'CWE-390']
|
|
35
|
+
|
|
36
|
+
- id: GO-003
|
|
37
|
+
title: Contexts are propagated
|
|
38
|
+
section: S15
|
|
39
|
+
section_title: Platform-Specific
|
|
40
|
+
severity: MEDIUM
|
|
41
|
+
class: correctness
|
|
42
|
+
check:
|
|
43
|
+
kind: grep_present
|
|
44
|
+
pattern: '(ctx\s+context\.Context|context\.WithTimeout|context\.WithCancel|context\.Background)'
|
|
45
|
+
include: ['**/*.go']
|
|
46
|
+
exclude: ['**/*_test.go']
|
|
47
|
+
why: 'Without context there is no cancellation, so a client disconnect leaves goroutines and DB connections hanging.'
|
|
48
|
+
remediation: 'Thread context.Context through every blocking call; derive timeouts at the boundary.'
|
|
49
|
+
references: ['Go-Context']
|
|
50
|
+
|
|
51
|
+
- id: GO-004
|
|
52
|
+
title: No panics in request paths
|
|
53
|
+
section: S15
|
|
54
|
+
section_title: Platform-Specific
|
|
55
|
+
severity: MEDIUM
|
|
56
|
+
class: correctness
|
|
57
|
+
check:
|
|
58
|
+
kind: grep_absent
|
|
59
|
+
pattern: '\bpanic\s*\('
|
|
60
|
+
include: ['**/*.go']
|
|
61
|
+
exclude: ['**/*_test.go', '**/cmd/**', 'main.go']
|
|
62
|
+
why: 'A panic in a handler takes down the whole process, not just the request.'
|
|
63
|
+
remediation: 'Return errors. Recover in middleware that logs and returns 500.'
|
|
64
|
+
references: ['Go-Error-Handling']
|
|
65
|
+
|
|
66
|
+
- id: GO-005
|
|
67
|
+
title: go vet / staticcheck / golangci-lint runs in CI
|
|
68
|
+
section: S15
|
|
69
|
+
section_title: Platform-Specific
|
|
70
|
+
severity: MEDIUM
|
|
71
|
+
class: maintainability
|
|
72
|
+
applies_when: { fact: 'has:ci' }
|
|
73
|
+
check:
|
|
74
|
+
kind: grep_present
|
|
75
|
+
pattern: '(go vet|golangci-lint|staticcheck|gofmt -l)'
|
|
76
|
+
include:
|
|
77
|
+
[
|
|
78
|
+
'.github/workflows/*.yml',
|
|
79
|
+
'.github/workflows/*.yaml',
|
|
80
|
+
'.gitlab-ci.yml',
|
|
81
|
+
'Makefile',
|
|
82
|
+
'justfile',
|
|
83
|
+
'Taskfile.yml',
|
|
84
|
+
'.pre-commit-config.yaml',
|
|
85
|
+
]
|
|
86
|
+
flags: i
|
|
87
|
+
why: 'go vet finds the bug class that compiles cleanly and fails at runtime.'
|
|
88
|
+
remediation: 'Add `go vet ./...` and golangci-lint to CI; start with the default linters.'
|
|
89
|
+
references: ['Go-Vet']
|
|
90
|
+
|
|
91
|
+
- id: GO-006
|
|
92
|
+
title: Goroutine lifecycle is bounded
|
|
93
|
+
section: S15
|
|
94
|
+
section_title: Platform-Specific
|
|
95
|
+
severity: MEDIUM
|
|
96
|
+
class: correctness
|
|
97
|
+
check: { kind: manual }
|
|
98
|
+
evidence: 'For every `go func`: what stops it? Is there a WaitGroup, a context, or an unbuffered channel that can block forever?'
|
|
99
|
+
why: 'Goroutine leaks are invisible in dev and fatal after three weeks in production.'
|
|
100
|
+
remediation: 'Tie every goroutine to a context; add a leak detector (goleak) to the test suite.'
|
|
101
|
+
references: ['Go-Concurrency']
|
|
102
|
+
|
|
103
|
+
- id: GO-007
|
|
104
|
+
title: SQL is parameterised
|
|
105
|
+
section: S15
|
|
106
|
+
section_title: Platform-Specific
|
|
107
|
+
severity: CRITICAL
|
|
108
|
+
class: security
|
|
109
|
+
applies_when: { fact: 'has:database' }
|
|
110
|
+
check:
|
|
111
|
+
kind: grep_absent
|
|
112
|
+
pattern: '(fmt\.Sprintf\(\s*[''"](SELECT|INSERT|UPDATE|DELETE)|db\.Query\(\s*[''"][^''"]*[''"]\s*\+|Exec\(\s*.*\+)'
|
|
113
|
+
include: ['**/*.go']
|
|
114
|
+
exclude: ['**/*_test.go']
|
|
115
|
+
flags: i
|
|
116
|
+
why: 'Sprintf-built SQL is textbook injection.'
|
|
117
|
+
remediation: 'Use placeholders: db.QueryContext(ctx, "SELECT ... WHERE id = $1", id).'
|
|
118
|
+
references: ['CWE-89', 'ASVS-5.3.4']
|
|
119
|
+
|
|
120
|
+
- id: GO-008
|
|
121
|
+
title: HTTP servers set timeouts
|
|
122
|
+
section: S15
|
|
123
|
+
section_title: Platform-Specific
|
|
124
|
+
severity: MEDIUM
|
|
125
|
+
class: performance
|
|
126
|
+
applies_when: { fact: 'platform:server' }
|
|
127
|
+
check:
|
|
128
|
+
kind: grep_present
|
|
129
|
+
pattern: '(ReadHeaderTimeout|ReadTimeout|WriteTimeout|IdleTimeout)'
|
|
130
|
+
include: ['**/*.go']
|
|
131
|
+
exclude: ['**/*_test.go']
|
|
132
|
+
why: 'The default net/http server has no timeouts, which is Slowloris as a service.'
|
|
133
|
+
remediation: 'Set ReadHeaderTimeout, ReadTimeout, WriteTimeout, and IdleTimeout on http.Server.'
|
|
134
|
+
references: ['CWE-400', 'Go-http-Server']
|