@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,166 @@
|
|
|
1
|
+
id: stacks/node-typescript
|
|
2
|
+
title: Node.js / TypeScript
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when JavaScript or TypeScript is detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when:
|
|
8
|
+
all:
|
|
9
|
+
- { fact: 'lang:typescript', op: 'absent' }
|
|
10
|
+
- { fact: 'lang:javascript', op: 'absent' }
|
|
11
|
+
- { fact: 'pm:node', op: 'absent' }
|
|
12
|
+
|
|
13
|
+
rules:
|
|
14
|
+
- id: NODE-001
|
|
15
|
+
title: Node runtime version is pinned
|
|
16
|
+
section: S15
|
|
17
|
+
section_title: Platform-Specific
|
|
18
|
+
severity: MEDIUM
|
|
19
|
+
class: operations
|
|
20
|
+
check:
|
|
21
|
+
kind: grep_present
|
|
22
|
+
pattern: '(node-version-file|node-version:|"engines"|"volta"|"packageManager"|\.nvmrc)'
|
|
23
|
+
include:
|
|
24
|
+
[
|
|
25
|
+
'package.json',
|
|
26
|
+
'.nvmrc',
|
|
27
|
+
'.node-version',
|
|
28
|
+
'.github/workflows/*.yml',
|
|
29
|
+
'.github/workflows/*.yaml',
|
|
30
|
+
'.gitlab-ci.yml',
|
|
31
|
+
]
|
|
32
|
+
flags: i
|
|
33
|
+
why: '"Works on my Node" is a real bug class — native modules, syntax support, and crypto defaults all shift between majors.'
|
|
34
|
+
remediation: 'Add .nvmrc plus "engines": { "node": ">=20" } and node-version-file: .nvmrc in CI.'
|
|
35
|
+
references: ['Node-Release-Schedule']
|
|
36
|
+
|
|
37
|
+
- id: NODE-002
|
|
38
|
+
title: TypeScript strict mode is on
|
|
39
|
+
section: S15
|
|
40
|
+
section_title: Platform-Specific
|
|
41
|
+
severity: MEDIUM
|
|
42
|
+
class: correctness
|
|
43
|
+
applies_when: { fact: 'lang:typescript' }
|
|
44
|
+
check:
|
|
45
|
+
kind: grep_present
|
|
46
|
+
pattern: '"strict"\s*:\s*true|"extends"\s*:\s*(.*strict|@tsconfig/strict)'
|
|
47
|
+
include: ['**/tsconfig*.json']
|
|
48
|
+
flags: i
|
|
49
|
+
why: 'TypeScript without strict is TypeScript with the safety features switched off.'
|
|
50
|
+
remediation: 'Set "strict": true. If the migration is large, enable one flag at a time (strictNullChecks first).'
|
|
51
|
+
references: ['TS-Handbook']
|
|
52
|
+
|
|
53
|
+
- id: NODE-003
|
|
54
|
+
title: Unhandled promise rejections are handled
|
|
55
|
+
section: S15
|
|
56
|
+
section_title: Platform-Specific
|
|
57
|
+
severity: MEDIUM
|
|
58
|
+
class: correctness
|
|
59
|
+
applies_when: { fact: 'platform:server' }
|
|
60
|
+
check:
|
|
61
|
+
kind: grep_present
|
|
62
|
+
pattern: '(unhandledRejection|uncaughtException|process\.on\(\s*[''"]unhandled)'
|
|
63
|
+
include: ['**/*.{ts,js,mjs,cjs}']
|
|
64
|
+
exclude: ['**/*.test.*', '**/*.spec.*', '**/node_modules/**']
|
|
65
|
+
flags: i
|
|
66
|
+
why: 'An unhandled rejection takes the process down in Node 15+, silently dropping in-flight requests.'
|
|
67
|
+
remediation: 'Register process handlers for unhandledRejection/uncaughtException that log and exit non-zero so the orchestrator restarts cleanly.'
|
|
68
|
+
references: ['CWE-248', 'Node-Docs']
|
|
69
|
+
|
|
70
|
+
- id: NODE-004
|
|
71
|
+
title: No private registry credentials committed
|
|
72
|
+
section: S15
|
|
73
|
+
section_title: Platform-Specific
|
|
74
|
+
severity: CRITICAL
|
|
75
|
+
class: security
|
|
76
|
+
check:
|
|
77
|
+
kind: grep_absent
|
|
78
|
+
pattern: '(//[^/]*npm[^/]*/:[_-]?authToken|_auth\s*=|NPM_TOKEN\s*=\s*[A-Za-z0-9_-]{16,})'
|
|
79
|
+
include:
|
|
80
|
+
[
|
|
81
|
+
'.npmrc',
|
|
82
|
+
'**/.npmrc',
|
|
83
|
+
'.yarnrc*',
|
|
84
|
+
'.github/workflows/*.yml',
|
|
85
|
+
'.github/workflows/*.yaml',
|
|
86
|
+
'Dockerfile*',
|
|
87
|
+
]
|
|
88
|
+
flags: i
|
|
89
|
+
why: 'A committed registry token is publish access to your package (and everything it can reach).'
|
|
90
|
+
remediation: 'Use ${{ secrets.NPM_TOKEN }} in CI and `npm config set` at runtime. Rotate anything already exposed.'
|
|
91
|
+
|
|
92
|
+
- id: NODE-005
|
|
93
|
+
title: No deprecated Node APIs in use
|
|
94
|
+
section: S15
|
|
95
|
+
section_title: Platform-Specific
|
|
96
|
+
severity: MEDIUM
|
|
97
|
+
class: maintainability
|
|
98
|
+
check:
|
|
99
|
+
kind: grep_deprecated
|
|
100
|
+
pattern: '(new Buffer\s*\(|url\.parse\s*\(|crypto\.createCipher\s*\(|domain\.create|require\(\s*[''"]sys[''"]|new (?:punycode|querystring))'
|
|
101
|
+
include: ['**/*.{ts,tsx,js,jsx,mjs,cjs}']
|
|
102
|
+
exclude: ['**/*.test.*', '**/node_modules/**']
|
|
103
|
+
why: 'Deprecated APIs are usually deprecated because they are unsafe (createCipher) or broken (url.parse).'
|
|
104
|
+
remediation: 'Buffer.from/alloc, new URL(), crypto.createCipheriv, punycode/. Replace as you touch the file.'
|
|
105
|
+
references: ['Node-Deprecations']
|
|
106
|
+
|
|
107
|
+
- id: NODE-006
|
|
108
|
+
title: Environment variables are validated at startup
|
|
109
|
+
section: S15
|
|
110
|
+
section_title: Platform-Specific
|
|
111
|
+
severity: MEDIUM
|
|
112
|
+
class: correctness
|
|
113
|
+
applies_when: { fact: 'platform:server' }
|
|
114
|
+
check:
|
|
115
|
+
kind: grep_present
|
|
116
|
+
pattern: '(envalid|t3-oss/env|@t3-oss/env|zod.*\.parse\(\s*process\.env|\.parse\(\s*env\b|cleanEnv|convict|dotenv-safe|env\.safeParse)'
|
|
117
|
+
include: ['**/*.{ts,js,mjs,cjs}']
|
|
118
|
+
exclude: ['**/*.test.*', '**/node_modules/**']
|
|
119
|
+
flags: i
|
|
120
|
+
why: 'A missing env var discovered at request time is a 500 in production; at startup it is a failed deploy.'
|
|
121
|
+
remediation: 'Parse process.env against a schema at boot and exit with a clear message on failure.'
|
|
122
|
+
references: ['12-Factor:Config']
|
|
123
|
+
|
|
124
|
+
- id: NODE-007
|
|
125
|
+
title: Libraries declare sideEffects / exports for tree-shaking
|
|
126
|
+
section: S15
|
|
127
|
+
section_title: Platform-Specific
|
|
128
|
+
severity: LOW
|
|
129
|
+
class: performance
|
|
130
|
+
applies_when: { fact: 'project:library' }
|
|
131
|
+
check:
|
|
132
|
+
kind: grep_present
|
|
133
|
+
pattern: '"sideEffects"\s*:|"exports"\s*:'
|
|
134
|
+
include: ['package.json']
|
|
135
|
+
why: 'Without it, bundlers ship your whole library to consumers who use one function.'
|
|
136
|
+
remediation: 'Add "sideEffects": false and an "exports" map in package.json.'
|
|
137
|
+
references: ['Webpack-Tree-Shaking']
|
|
138
|
+
|
|
139
|
+
- id: NODE-008
|
|
140
|
+
title: Module system is consistent
|
|
141
|
+
section: S15
|
|
142
|
+
section_title: Platform-Specific
|
|
143
|
+
severity: LOW
|
|
144
|
+
class: maintainability
|
|
145
|
+
applies_when: { fact: 'lang:typescript' }
|
|
146
|
+
check: { kind: manual }
|
|
147
|
+
evidence: 'Either ESM throughout ("type": "module" + .js extensions in imports) or CJS throughout — not both.'
|
|
148
|
+
why: 'Mixed ESM/CJS is the source of the most confusing Node errors in existence.'
|
|
149
|
+
remediation: 'Pick one; add "type" to package.json; configure tsconfig module/moduleResolution to match.'
|
|
150
|
+
references: ['Node-ESM']
|
|
151
|
+
|
|
152
|
+
- id: NODE-009
|
|
153
|
+
title: A fast, isolated runtime is used for tests
|
|
154
|
+
section: S15
|
|
155
|
+
section_title: Platform-Specific
|
|
156
|
+
severity: LOW
|
|
157
|
+
class: operations
|
|
158
|
+
applies_when: { fact: 'has:tests' }
|
|
159
|
+
check:
|
|
160
|
+
kind: grep_present
|
|
161
|
+
pattern: '(vitest|jest|node --test|node:test|ava|tap)'
|
|
162
|
+
include: ['package.json']
|
|
163
|
+
flags: i
|
|
164
|
+
why: 'Slow tests do not get run. Unrun tests are decoration.'
|
|
165
|
+
remediation: 'Vitest or node:test; keep the unit suite under ~30 seconds so it runs on every save.'
|
|
166
|
+
references: ['DORA']
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
id: stacks/python
|
|
2
|
+
title: Python
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when Python is detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when: { fact: 'lang:python', op: 'absent' }
|
|
8
|
+
|
|
9
|
+
rules:
|
|
10
|
+
- id: PY-001
|
|
11
|
+
title: Dependencies are declared in a modern manifest
|
|
12
|
+
section: S15
|
|
13
|
+
section_title: Platform-Specific
|
|
14
|
+
severity: MEDIUM
|
|
15
|
+
class: supply-chain
|
|
16
|
+
check:
|
|
17
|
+
kind: any_file
|
|
18
|
+
patterns:
|
|
19
|
+
[
|
|
20
|
+
'pyproject.toml',
|
|
21
|
+
'requirements.txt',
|
|
22
|
+
'requirements/*.txt',
|
|
23
|
+
'Pipfile',
|
|
24
|
+
'environment.yml',
|
|
25
|
+
'setup.py',
|
|
26
|
+
]
|
|
27
|
+
why: 'Python projects with no manifest cannot be installed reproducibly by anyone, including CI.'
|
|
28
|
+
remediation: 'Use pyproject.toml with Poetry/uv, or pin requirements.txt with pip-compile and hashes.'
|
|
29
|
+
references: ['PEP-621', 'PyPA']
|
|
30
|
+
|
|
31
|
+
- id: PY-002
|
|
32
|
+
title: Python version is pinned
|
|
33
|
+
section: S15
|
|
34
|
+
section_title: Platform-Specific
|
|
35
|
+
severity: MEDIUM
|
|
36
|
+
class: operations
|
|
37
|
+
check:
|
|
38
|
+
kind: grep_present
|
|
39
|
+
pattern: '(python-version|python_version|requires-python|PYTHON_VERSION|\.python-version)'
|
|
40
|
+
include:
|
|
41
|
+
[
|
|
42
|
+
'pyproject.toml',
|
|
43
|
+
'setup.py',
|
|
44
|
+
'setup.cfg',
|
|
45
|
+
'.python-version',
|
|
46
|
+
'.github/workflows/*.yml',
|
|
47
|
+
'.github/workflows/*.yaml',
|
|
48
|
+
'.gitlab-ci.yml',
|
|
49
|
+
'Dockerfile*',
|
|
50
|
+
'runtime.txt',
|
|
51
|
+
]
|
|
52
|
+
flags: i
|
|
53
|
+
why: 'Python 2→3 style breakage is rare, but 3.9→3.12 removes stdlib modules you may rely on.'
|
|
54
|
+
remediation: 'Set requires-python in pyproject.toml and pin the same version in CI and the container.'
|
|
55
|
+
references: ['endoflife.date/python']
|
|
56
|
+
|
|
57
|
+
- id: PY-003
|
|
58
|
+
title: Dependencies are pinned to exact versions
|
|
59
|
+
section: S15
|
|
60
|
+
section_title: Platform-Specific
|
|
61
|
+
severity: MEDIUM
|
|
62
|
+
class: supply-chain
|
|
63
|
+
check:
|
|
64
|
+
kind: grep_present
|
|
65
|
+
pattern: '^[a-zA-Z0-9._-]+\s*==\s*[0-9]'
|
|
66
|
+
include: ['requirements*.txt', 'requirements/*.txt']
|
|
67
|
+
why: 'Unpinned requirements mean the build changes without a commit.'
|
|
68
|
+
remediation: 'pip-compile with hashes, or Poetry/uv lockfile. Pin exact versions for applications.'
|
|
69
|
+
references: ['OpenSSF-Scorecard:Pinned-Dependencies']
|
|
70
|
+
|
|
71
|
+
- id: PY-004
|
|
72
|
+
title: No bare except clauses
|
|
73
|
+
section: S15
|
|
74
|
+
section_title: Platform-Specific
|
|
75
|
+
severity: MEDIUM
|
|
76
|
+
class: correctness
|
|
77
|
+
check:
|
|
78
|
+
kind: grep_wrong
|
|
79
|
+
pattern: 'except\s*:\s*$|except\s+BaseException\s*:'
|
|
80
|
+
include: ['**/*.py']
|
|
81
|
+
exclude: ['**/tests/**', '**/test_*.py', '**/*_test.py']
|
|
82
|
+
why: 'A bare except swallows KeyboardInterrupt and SystemExit, and every real error with them.'
|
|
83
|
+
remediation: 'Catch the specific exceptions you can handle; let the rest propagate.'
|
|
84
|
+
references: ['PEP-8', 'CWE-396']
|
|
85
|
+
|
|
86
|
+
- id: PY-005
|
|
87
|
+
title: No mutable default arguments
|
|
88
|
+
section: S15
|
|
89
|
+
section_title: Platform-Specific
|
|
90
|
+
severity: MEDIUM
|
|
91
|
+
class: correctness
|
|
92
|
+
check:
|
|
93
|
+
kind: grep_wrong
|
|
94
|
+
pattern: 'def\s+\w+\s*\([^)]*=\s*(\[\]|\{\}|set\(\)|list\(\)|dict\(\))'
|
|
95
|
+
include: ['**/*.py']
|
|
96
|
+
exclude: ['**/tests/**']
|
|
97
|
+
why: 'The default is evaluated once at definition time, so every call shares the same list. This bug survives code review remarkably often.'
|
|
98
|
+
remediation: 'Default to None and assign inside the function: `def f(x=None): x = x or []`.'
|
|
99
|
+
references: ['Python-Gotchas']
|
|
100
|
+
|
|
101
|
+
- id: PY-006
|
|
102
|
+
title: No unsafe deserialisation
|
|
103
|
+
section: S15
|
|
104
|
+
section_title: Platform-Specific
|
|
105
|
+
severity: CRITICAL
|
|
106
|
+
class: security
|
|
107
|
+
check:
|
|
108
|
+
kind: grep_absent
|
|
109
|
+
pattern: '(pickle\.(loads?|load)|marshal\.load|yaml\.load\s*\((?!.*Safe)|torch\.load\s*\((?!.*weights_only)|eval\s*\(\s*(input|request))'
|
|
110
|
+
include: ['**/*.py']
|
|
111
|
+
exclude: ['**/tests/**']
|
|
112
|
+
why: 'pickle.load on untrusted input is remote code execution by design. yaml.load without SafeLoader is the same class of bug.'
|
|
113
|
+
remediation: 'Use json, yaml.safe_load, or torch.load(weights_only=True). Never deserialise untrusted pickle.'
|
|
114
|
+
references: ['CWE-502', 'ASVS-5.5.1']
|
|
115
|
+
|
|
116
|
+
- id: PY-007
|
|
117
|
+
title: Subprocess calls do not use shell=True
|
|
118
|
+
section: S15
|
|
119
|
+
section_title: Platform-Specific
|
|
120
|
+
severity: HIGH
|
|
121
|
+
class: security
|
|
122
|
+
check:
|
|
123
|
+
kind: grep_absent
|
|
124
|
+
pattern: 'shell\s*=\s*True'
|
|
125
|
+
include: ['**/*.py']
|
|
126
|
+
exclude: ['**/tests/**', '**/scripts/**']
|
|
127
|
+
why: 'shell=True with any interpolated input is command injection.'
|
|
128
|
+
remediation: 'Pass an argument list: subprocess.run(["ls", "-l"], check=True).'
|
|
129
|
+
references: ['CWE-78']
|
|
130
|
+
|
|
131
|
+
- id: PY-008
|
|
132
|
+
title: Debug mode is not enabled outside local development
|
|
133
|
+
section: S15
|
|
134
|
+
section_title: Platform-Specific
|
|
135
|
+
severity: CRITICAL
|
|
136
|
+
class: security
|
|
137
|
+
check:
|
|
138
|
+
kind: grep_absent
|
|
139
|
+
pattern: '(DEBUG\s*=\s*True|debug\s*=\s*True|app\.run\(\s*[^)]*debug\s*=\s*True|FLASK_DEBUG\s*=\s*1)'
|
|
140
|
+
include: ['**/*.py', '**/*.env*', 'docker-compose*.yml', 'Dockerfile*']
|
|
141
|
+
exclude: ['**/tests/**', '**/settings/*test*', '**/test_settings*']
|
|
142
|
+
flags: i
|
|
143
|
+
why: 'Django/Flask debug mode exposes a REPL-backed traceback page — full RCE to anyone who can reach it.'
|
|
144
|
+
remediation: 'Drive DEBUG from the environment and default it to False. Assert it is False in the production settings module.'
|
|
145
|
+
references: ['CWE-489', 'ASVS-14.3.3']
|
|
146
|
+
|
|
147
|
+
- id: PY-009
|
|
148
|
+
title: Type hints are used on public functions
|
|
149
|
+
section: S15
|
|
150
|
+
section_title: Platform-Specific
|
|
151
|
+
severity: LOW
|
|
152
|
+
class: maintainability
|
|
153
|
+
check:
|
|
154
|
+
kind: grep_present
|
|
155
|
+
pattern: 'def\s+\w+\s*\([^)]*:\s*\w+[^)]*\)\s*(->|:)'
|
|
156
|
+
include: ['**/*.py']
|
|
157
|
+
exclude: ['**/tests/**', '**/migrations/**']
|
|
158
|
+
why: 'Hints are documentation the type checker enforces, and they make an editor useful.'
|
|
159
|
+
remediation: 'Annotate public functions first; enable mypy/pyright on the modules that matter.'
|
|
160
|
+
references: ['PEP-484']
|
|
161
|
+
|
|
162
|
+
- id: PY-010
|
|
163
|
+
title: A linter/formatter is configured for Python
|
|
164
|
+
section: S15
|
|
165
|
+
section_title: Platform-Specific
|
|
166
|
+
severity: MEDIUM
|
|
167
|
+
class: maintainability
|
|
168
|
+
check:
|
|
169
|
+
kind: grep_present
|
|
170
|
+
pattern: '(\[tool\.ruff\]|\[tool\.black\]|\[tool\.pylint\]|\[tool\.flake8\]|ruff|flake8|pylint)'
|
|
171
|
+
include: ['pyproject.toml', 'setup.cfg', '.flake8', '.ruff.toml', '.pre-commit-config.yaml']
|
|
172
|
+
flags: i
|
|
173
|
+
why: 'Ruff runs in milliseconds and catches the errors reviewers consistently miss.'
|
|
174
|
+
remediation: 'Add ruff with a sensible rule set and wire it into pre-commit and CI.'
|
|
175
|
+
references: ['PyPA']
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
id: stacks/rust
|
|
2
|
+
title: Rust
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when Rust is detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when: { fact: 'lang:rust', op: 'absent' }
|
|
8
|
+
|
|
9
|
+
rules:
|
|
10
|
+
- id: RS-001
|
|
11
|
+
title: Cargo.toml declares edition and metadata
|
|
12
|
+
section: S15
|
|
13
|
+
section_title: Platform-Specific
|
|
14
|
+
severity: MEDIUM
|
|
15
|
+
class: maintainability
|
|
16
|
+
check:
|
|
17
|
+
kind: grep_present
|
|
18
|
+
pattern: 'edition\s*=\s*"20(18|21|24)"'
|
|
19
|
+
include: ['Cargo.toml']
|
|
20
|
+
why: 'Editions change semantics (especially 2018 module paths and 2024 closure capture). Without one you get 2015.'
|
|
21
|
+
remediation: 'Set `edition = "2021"` (or newer) and make sure CI builds with it.'
|
|
22
|
+
references: ['Rust-Editions']
|
|
23
|
+
|
|
24
|
+
- id: RS-002
|
|
25
|
+
title: No unwrap/expect in non-test code
|
|
26
|
+
section: S15
|
|
27
|
+
section_title: Platform-Specific
|
|
28
|
+
severity: MEDIUM
|
|
29
|
+
class: correctness
|
|
30
|
+
check:
|
|
31
|
+
kind: grep_wrong
|
|
32
|
+
pattern: '\.(unwrap|expect)\s*\(\s*\)|\.unwrap\(\)'
|
|
33
|
+
include: ['**/*.rs']
|
|
34
|
+
exclude: ['**/tests/**', '**/benches/**', '**/examples/**']
|
|
35
|
+
why: 'unwrap() is a panic wearing a convenience costume. Fine in tests, a crash in production.'
|
|
36
|
+
remediation: 'Propagate with ? and a typed error, or handle the None case explicitly.'
|
|
37
|
+
references: ['Rust-Error-Handling']
|
|
38
|
+
|
|
39
|
+
- id: RS-003
|
|
40
|
+
title: unsafe blocks are justified and minimal
|
|
41
|
+
section: S15
|
|
42
|
+
section_title: Platform-Specific
|
|
43
|
+
severity: HIGH
|
|
44
|
+
class: security
|
|
45
|
+
check:
|
|
46
|
+
kind: grep_wrong
|
|
47
|
+
pattern: '(unsafe\s*\{|#!\[allow\(unsafe_code\)\]|#\[allow\(unsafe_code\)\])'
|
|
48
|
+
include: ['**/*.rs']
|
|
49
|
+
exclude: ['**/tests/**']
|
|
50
|
+
why: 'unsafe is where Rust stops protecting you. Every block needs a comment stating the invariant it upholds.'
|
|
51
|
+
remediation: 'Wrap in a safe abstraction with a documented invariant; add a // SAFETY: comment per block.'
|
|
52
|
+
references: ['Rust-Nomicon', 'CWE-119']
|
|
53
|
+
|
|
54
|
+
- id: RS-004
|
|
55
|
+
title: Clippy runs in CI
|
|
56
|
+
section: S15
|
|
57
|
+
section_title: Platform-Specific
|
|
58
|
+
severity: MEDIUM
|
|
59
|
+
class: maintainability
|
|
60
|
+
applies_when: { fact: 'has:ci' }
|
|
61
|
+
check:
|
|
62
|
+
kind: grep_present
|
|
63
|
+
pattern: '(cargo clippy|clippy)'
|
|
64
|
+
include:
|
|
65
|
+
[
|
|
66
|
+
'.github/workflows/*.yml',
|
|
67
|
+
'.github/workflows/*.yaml',
|
|
68
|
+
'.gitlab-ci.yml',
|
|
69
|
+
'Makefile',
|
|
70
|
+
'justfile',
|
|
71
|
+
'Taskfile.yml',
|
|
72
|
+
]
|
|
73
|
+
flags: i
|
|
74
|
+
why: 'Clippy catches the idiomatic mistakes a compiler cannot: needless clones, wrong locks, incorrect casts.'
|
|
75
|
+
remediation: 'Add `cargo clippy -- -D warnings` to CI.'
|
|
76
|
+
references: ['Clippy']
|
|
77
|
+
|
|
78
|
+
- id: RS-005
|
|
79
|
+
title: No todo!/unimplemented! in shipped paths
|
|
80
|
+
section: S15
|
|
81
|
+
section_title: Platform-Specific
|
|
82
|
+
severity: MEDIUM
|
|
83
|
+
class: correctness
|
|
84
|
+
check:
|
|
85
|
+
kind: grep_wrong
|
|
86
|
+
pattern: '(todo!\s*\(|unimplemented!\s*\(|unreachable!\s*\()'
|
|
87
|
+
include: ['src/**/*.rs']
|
|
88
|
+
exclude: ['**/tests/**']
|
|
89
|
+
why: 'unreachable! that turns out to be reachable is an abort at runtime.'
|
|
90
|
+
remediation: 'Return a Result for the unfinished paths, or finish them before release.'
|
|
91
|
+
references: ['Rust-API-Guidelines']
|
|
92
|
+
|
|
93
|
+
- id: RS-006
|
|
94
|
+
title: Dependency audit runs in CI (cargo-audit / deny)
|
|
95
|
+
section: S15
|
|
96
|
+
section_title: Platform-Specific
|
|
97
|
+
severity: MEDIUM
|
|
98
|
+
class: supply-chain
|
|
99
|
+
applies_when: { fact: 'has:ci' }
|
|
100
|
+
check:
|
|
101
|
+
kind: grep_present
|
|
102
|
+
pattern: '(cargo-audit|cargo audit|cargo-deny|cargo deny|rustsec)'
|
|
103
|
+
include:
|
|
104
|
+
[
|
|
105
|
+
'.github/workflows/*.yml',
|
|
106
|
+
'.github/workflows/*.yaml',
|
|
107
|
+
'.gitlab-ci.yml',
|
|
108
|
+
'Makefile',
|
|
109
|
+
'justfile',
|
|
110
|
+
]
|
|
111
|
+
flags: i
|
|
112
|
+
why: 'The RustSec advisory DB is the Rust equivalent of npm audit, and it is one CI step away.'
|
|
113
|
+
remediation: 'Add cargo-audit (advisories) and cargo-deny (licenses/bans) to CI.'
|
|
114
|
+
references: ['RustSec', 'SLSA']
|
|
115
|
+
|
|
116
|
+
- id: RS-007
|
|
117
|
+
title: Release builds are optimised and stripped
|
|
118
|
+
section: S15
|
|
119
|
+
section_title: Platform-Specific
|
|
120
|
+
severity: LOW
|
|
121
|
+
class: performance
|
|
122
|
+
applies_when: { fact: 'project:cli' }
|
|
123
|
+
check:
|
|
124
|
+
kind: grep_present
|
|
125
|
+
pattern: '(strip\s*=\s*(true|"symbols"|"debuginfo")|opt-level\s*=\s*("?[23s]"?|"z")|lto\s*=\s*)'
|
|
126
|
+
include: ['Cargo.toml']
|
|
127
|
+
why: 'The default release profile leaves debug symbols in the binary — 5–10× larger for no benefit.'
|
|
128
|
+
remediation: 'Add a [profile.release] with lto = true, strip = true, and opt-level = "z" or 3.'
|
|
129
|
+
references: ['Cargo-Profiles']
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
id: stacks/solidity
|
|
2
|
+
title: Blockchain / Smart Contracts
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when Solidity, Foundry, or Hardhat is detected. USAT is not a substitute for a third-party audit.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when: { fact: 'platform:evm', op: 'absent' }
|
|
8
|
+
|
|
9
|
+
rules:
|
|
10
|
+
- id: SOLID-001
|
|
11
|
+
title: Contracts are protected against reentrancy
|
|
12
|
+
section: S15
|
|
13
|
+
section_title: Platform-Specific
|
|
14
|
+
severity: CRITICAL
|
|
15
|
+
class: security
|
|
16
|
+
check: { kind: manual }
|
|
17
|
+
evidence: 'Every external call followed by a state change, or use of nonReentrant / checks-effects-interactions ordering.'
|
|
18
|
+
why: 'Reentrancy drained The DAO and has drained a great many contracts since. Funds are unrecoverable.'
|
|
19
|
+
remediation: 'Follow checks-effects-interactions; use OpenZeppelin ReentrancyGuard; never call external contracts before updating state.'
|
|
20
|
+
references: ['SWC-107', 'Consensys-Best-Practices']
|
|
21
|
+
|
|
22
|
+
- id: SOLID-002
|
|
23
|
+
title: Compiler version is 0.8+ (or SafeMath is used)
|
|
24
|
+
section: S15
|
|
25
|
+
section_title: Platform-Specific
|
|
26
|
+
severity: HIGH
|
|
27
|
+
class: security
|
|
28
|
+
check:
|
|
29
|
+
kind: grep_deprecated
|
|
30
|
+
pattern: 'pragma solidity\s+\^?0\.[0-7]\.'
|
|
31
|
+
include: ['**/*.sol']
|
|
32
|
+
why: 'Pre-0.8 Solidity wraps silently on overflow. That is how balances go negative.'
|
|
33
|
+
remediation: 'Use Solidity ^0.8.x, which reverts on overflow/underflow by default.'
|
|
34
|
+
references: ['SWC-101', 'Solidity-0.8-Breaking-Changes']
|
|
35
|
+
|
|
36
|
+
- id: SOLID-003
|
|
37
|
+
title: Access control is explicit on privileged functions
|
|
38
|
+
section: S15
|
|
39
|
+
section_title: Platform-Specific
|
|
40
|
+
severity: CRITICAL
|
|
41
|
+
class: security
|
|
42
|
+
check: { kind: manual }
|
|
43
|
+
evidence: 'For every state-changing external/public function: the modifier or require that restricts the caller.'
|
|
44
|
+
why: 'An unprotected initialise or setOwner function is a complete takeover, irrevocably.'
|
|
45
|
+
remediation: 'Use Ownable/AccessControl; require(msg.sender == owner) on every privileged entry point.'
|
|
46
|
+
references: ['SWC-105', 'SWC-112']
|
|
47
|
+
|
|
48
|
+
- id: SOLID-004
|
|
49
|
+
title: tx.origin is not used for authorisation
|
|
50
|
+
section: S15
|
|
51
|
+
section_title: Platform-Specific
|
|
52
|
+
severity: CRITICAL
|
|
53
|
+
class: security
|
|
54
|
+
check:
|
|
55
|
+
kind: grep_absent
|
|
56
|
+
pattern: '(require\s*\(\s*tx\.origin|if\s*\(\s*tx\.origin|msg\.sender\s*==\s*tx\.origin)'
|
|
57
|
+
include: ['**/*.sol']
|
|
58
|
+
why: 'tx.origin is the EOA that started the transaction — a phishing contract can impersonate it.'
|
|
59
|
+
remediation: 'Always authorise on msg.sender.'
|
|
60
|
+
references: ['SWC-115', 'Consensys-Best-Practices']
|
|
61
|
+
|
|
62
|
+
- id: SOLID-005
|
|
63
|
+
title: External calls are checked and treated as untrusted
|
|
64
|
+
section: S15
|
|
65
|
+
section_title: Platform-Specific
|
|
66
|
+
severity: HIGH
|
|
67
|
+
class: security
|
|
68
|
+
check:
|
|
69
|
+
kind: grep_wrong
|
|
70
|
+
pattern: '(\.call\s*\(\s*|\.send\s*\(\s*|\.transfer\s*\(\s*)(?![^\n;]*require)'
|
|
71
|
+
include: ['**/*.sol']
|
|
72
|
+
why: 'A failed low-level call returns false; it does not revert. Ignoring the return value means silent failure.'
|
|
73
|
+
remediation: 'Check the return value and revert, or use a safe wrapper (Address.sendValue).'
|
|
74
|
+
references: ['SWC-104', 'SWC-113']
|
|
75
|
+
|
|
76
|
+
- id: SOLID-006
|
|
77
|
+
title: No block.timestamp dependence for critical logic
|
|
78
|
+
section: S15
|
|
79
|
+
section_title: Platform-Specific
|
|
80
|
+
severity: MEDIUM
|
|
81
|
+
class: security
|
|
82
|
+
check:
|
|
83
|
+
kind: grep_wrong
|
|
84
|
+
pattern: '(block\.timestamp|block\.number|now)\s*(%|/|\*|>|<)\s*\d'
|
|
85
|
+
include: ['**/*.sol']
|
|
86
|
+
why: 'Miners can nudge timestamps by seconds, which is enough to swing a block-number-based lottery.'
|
|
87
|
+
remediation: 'Use an oracle or a commit-reveal scheme for randomness; allow tolerance in time comparisons.'
|
|
88
|
+
references: ['SWC-116', 'SWC-120']
|
|
89
|
+
|
|
90
|
+
- id: SOLID-007
|
|
91
|
+
title: Events are emitted for state changes
|
|
92
|
+
section: S15
|
|
93
|
+
section_title: Platform-Specific
|
|
94
|
+
severity: MEDIUM
|
|
95
|
+
class: maintainability
|
|
96
|
+
check: { kind: manual }
|
|
97
|
+
evidence: 'Every function that mutates meaningful state emits an event (transfer, ownership, config).'
|
|
98
|
+
why: 'Off-chain consumers (and incident responders) rely on events; without them, reconstructing history is guesswork.'
|
|
99
|
+
remediation: 'Emit an indexed event for every state transition, including parameters that identify the affected entity.'
|
|
100
|
+
references: ['Consensys-Best-Practices']
|
|
101
|
+
|
|
102
|
+
- id: SOLID-008
|
|
103
|
+
title: An emergency pause mechanism exists
|
|
104
|
+
section: S15
|
|
105
|
+
section_title: Platform-Specific
|
|
106
|
+
severity: MEDIUM
|
|
107
|
+
class: operations
|
|
108
|
+
check:
|
|
109
|
+
kind: grep_present
|
|
110
|
+
pattern: '(Pausable|whenNotPaused|_pause\s*\(|emergencyStop|circuitBreaker)'
|
|
111
|
+
include: ['**/*.sol']
|
|
112
|
+
why: 'When an exploit is live, the difference between losing everything and losing a little is a pause button.'
|
|
113
|
+
remediation: 'Add OpenZeppelin Pausable to value-moving functions, with a documented, time-bounded pausing authority.'
|
|
114
|
+
references: ['Consensys-Best-Practices']
|
|
115
|
+
|
|
116
|
+
- id: SOLID-009
|
|
117
|
+
title: A test suite with fuzzing/invariants exists
|
|
118
|
+
section: S15
|
|
119
|
+
section_title: Platform-Specific
|
|
120
|
+
severity: HIGH
|
|
121
|
+
class: correctness
|
|
122
|
+
check: { kind: manual }
|
|
123
|
+
evidence: 'Foundry invariant/fuzz tests, Echidna properties, or a Hardhat suite covering the economic edge cases.'
|
|
124
|
+
why: 'Unit tests with hand-picked inputs miss the one input that drains the pool.'
|
|
125
|
+
remediation: 'Add invariant tests for the core accounting identity (e.g. sum(balances) == totalSupply).'
|
|
126
|
+
references: ['Foundry-Invariant-Testing']
|
|
127
|
+
|
|
128
|
+
- id: SOLID-010
|
|
129
|
+
title: Third-party security audit has been performed
|
|
130
|
+
section: S15
|
|
131
|
+
section_title: Platform-Specific
|
|
132
|
+
severity: HIGH
|
|
133
|
+
class: security
|
|
134
|
+
applies_when: { fact: 'maturity:production' }
|
|
135
|
+
check: { kind: manual }
|
|
136
|
+
evidence: 'Audit report, the version it covered, and confirmation that every finding was fixed or explicitly accepted.'
|
|
137
|
+
why: 'Automated tooling catches patterns; it does not catch economic design flaws. Deployed contract code is immutable.'
|
|
138
|
+
remediation: 'Commission an audit before mainnet; publish the report and the remediation status.'
|
|
139
|
+
references: ['OpenSSF-Criticality', 'Consensys-Best-Practices']
|