@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,128 @@
|
|
|
1
|
+
id: stacks/iac
|
|
2
|
+
title: Infrastructure as Code & Cloud
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when Terraform/OpenTofu, Pulumi, Ansible, or Kubernetes manifests are detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when:
|
|
8
|
+
all:
|
|
9
|
+
- { fact: 'has:terraform', op: 'absent' }
|
|
10
|
+
- { fact: 'has:kubernetes', op: 'absent' }
|
|
11
|
+
- { fact: 'project:iac', op: 'absent' }
|
|
12
|
+
|
|
13
|
+
rules:
|
|
14
|
+
- id: IAC-001
|
|
15
|
+
title: No public exposure of storage buckets or databases
|
|
16
|
+
section: S15
|
|
17
|
+
section_title: Platform-Specific
|
|
18
|
+
severity: CRITICAL
|
|
19
|
+
class: security
|
|
20
|
+
applies_when: { fact: 'has:terraform' }
|
|
21
|
+
check:
|
|
22
|
+
kind: grep_wrong
|
|
23
|
+
pattern: '(acl\s*=\s*[''"]public-read|publicly_accessible\s*=\s*true|0\.0\.0\.0/0|cidr_blocks\s*=\s*\[\s*[''"]0\.0\.0\.0/0|Effect\s*=\s*[''"]Allow[\s\S]{0,120}Principal\s*=\s*[''"]\*)'
|
|
24
|
+
include: ['**/*.tf', '**/*.tfvars', '**/*.{yaml,yml,json}']
|
|
25
|
+
exclude: ['**/node_modules/**']
|
|
26
|
+
why: 'This is the single most common cause of cloud data breaches, and it is one line of config.'
|
|
27
|
+
remediation: 'Default to private; allowlist specific principals/CIDRs; add an automated check (Checkov/tfsec) in CI.'
|
|
28
|
+
references: ['CWE-284', 'CIS-AWS-2.1.5', 'ASVS-14.1.3']
|
|
29
|
+
|
|
30
|
+
- id: IAC-002
|
|
31
|
+
title: Encryption at rest is enabled
|
|
32
|
+
section: S15
|
|
33
|
+
section_title: Platform-Specific
|
|
34
|
+
severity: HIGH
|
|
35
|
+
class: security
|
|
36
|
+
applies_when: { fact: 'has:terraform' }
|
|
37
|
+
check:
|
|
38
|
+
kind: grep_present
|
|
39
|
+
pattern: '(encrypted\s*=\s*true|storage_encrypted\s*=\s*true|sse_algorithm|kms_key_id|enable_disk_encryption|customer_managed_key)'
|
|
40
|
+
include: ['**/*.tf', '**/*.tfvars', '**/*.{yaml,yml}']
|
|
41
|
+
exclude: ['**/node_modules/**']
|
|
42
|
+
why: 'Unencrypted volumes and buckets fail every compliance framework you will ever be asked about.'
|
|
43
|
+
remediation: 'Enable encryption with a customer-managed KMS key on all storage, databases, and queues.'
|
|
44
|
+
references: ['CIS-AWS-2.2.1', 'ASVS-8.1.1', 'GDPR-Art.32']
|
|
45
|
+
|
|
46
|
+
- id: IAC-003
|
|
47
|
+
title: IaC is validated and scanned before apply
|
|
48
|
+
section: S15
|
|
49
|
+
section_title: Platform-Specific
|
|
50
|
+
severity: MEDIUM
|
|
51
|
+
class: supply-chain
|
|
52
|
+
applies_when: { fact: 'has:ci' }
|
|
53
|
+
check:
|
|
54
|
+
kind: grep_present
|
|
55
|
+
pattern: '(terraform validate|terraform plan|tflint|checkov|tfsec|trivy config|kubeconform|kubeval|pulumi preview|ansible-lint)'
|
|
56
|
+
include:
|
|
57
|
+
[
|
|
58
|
+
'.github/workflows/*.yml',
|
|
59
|
+
'.github/workflows/*.yaml',
|
|
60
|
+
'.gitlab-ci.yml',
|
|
61
|
+
'Makefile',
|
|
62
|
+
'justfile',
|
|
63
|
+
'.pre-commit-config.yaml',
|
|
64
|
+
]
|
|
65
|
+
flags: i
|
|
66
|
+
why: '`terraform apply` on unreviewed HCL is a production change with no diff shown to anybody.'
|
|
67
|
+
remediation: 'Run validate → plan → policy scan on every PR; require approval before apply.'
|
|
68
|
+
references: ['NIST-SSDF-PO.5', 'CIS']
|
|
69
|
+
|
|
70
|
+
- id: IAC-004
|
|
71
|
+
title: State is stored remotely and locked
|
|
72
|
+
section: S15
|
|
73
|
+
section_title: Platform-Specific
|
|
74
|
+
severity: HIGH
|
|
75
|
+
class: operations
|
|
76
|
+
applies_when: { fact: 'has:terraform' }
|
|
77
|
+
check:
|
|
78
|
+
kind: grep_present
|
|
79
|
+
pattern: '(backend\s+"(s3|azurerm|gcs|remote|http|cos)|dynamodb_table|use_lockfile|lock_table)'
|
|
80
|
+
include: ['**/*.tf', '**/backend.tf', '**/*.hcl']
|
|
81
|
+
why: 'Local state means a laptop is the source of truth for your infrastructure, and concurrent applies corrupt it.'
|
|
82
|
+
remediation: 'Configure a remote backend with locking (S3 + DynamoDB, GCS, or Terraform Cloud).'
|
|
83
|
+
references: ['Terraform-Backends']
|
|
84
|
+
|
|
85
|
+
- id: IAC-005
|
|
86
|
+
title: IAM follows least privilege
|
|
87
|
+
section: S15
|
|
88
|
+
section_title: Platform-Specific
|
|
89
|
+
severity: HIGH
|
|
90
|
+
class: security
|
|
91
|
+
applies_when: { fact: 'has:terraform' }
|
|
92
|
+
check:
|
|
93
|
+
kind: grep_absent
|
|
94
|
+
pattern: '(Action\s*=\s*[''"]\*[''"]|actions\s*=\s*\[\s*[''"]\*[''"]|Resource\s*=\s*[''"]\*[''"]\s*$)'
|
|
95
|
+
include: ['**/*.tf', '**/*.{yaml,yml,json}']
|
|
96
|
+
exclude: ['**/node_modules/**']
|
|
97
|
+
why: '`Action: "*"` on `Resource: "*"` is administrator access with a nicer YAML indent.'
|
|
98
|
+
remediation: 'Enumerate the exact actions and resources; separate read and write roles; review quarterly.'
|
|
99
|
+
references: ['CIS-AWS-1.16', 'ASVS-4.1.1']
|
|
100
|
+
|
|
101
|
+
- id: IAC-006
|
|
102
|
+
title: Cloud spend is tagged and budgeted
|
|
103
|
+
section: S15
|
|
104
|
+
section_title: Platform-Specific
|
|
105
|
+
severity: LOW
|
|
106
|
+
class: operations
|
|
107
|
+
applies_when: { fact: 'has:terraform' }
|
|
108
|
+
check: { kind: manual }
|
|
109
|
+
evidence: 'A required-tags policy (owner, service, environment) and budget alerts at 50/80/100%.'
|
|
110
|
+
why: 'Untagged resources are how a forgotten load balancer costs $4,000 for eleven months.'
|
|
111
|
+
remediation: 'Enforce tags in policy (OPA/Sentinel); set per-environment budgets with alerts.'
|
|
112
|
+
references: ['FinOps', 'AWS-Well-Architected:Cost']
|
|
113
|
+
|
|
114
|
+
- id: IAC-007
|
|
115
|
+
title: Kubernetes manifests avoid privileged containers
|
|
116
|
+
section: S15
|
|
117
|
+
section_title: Platform-Specific
|
|
118
|
+
severity: HIGH
|
|
119
|
+
class: security
|
|
120
|
+
applies_when: { fact: 'has:kubernetes' }
|
|
121
|
+
check:
|
|
122
|
+
kind: grep_absent
|
|
123
|
+
pattern: '(privileged\s*:\s*true|hostPID\s*:\s*true|hostNetwork\s*:\s*true|allowPrivilegeEscalation\s*:\s*true|capabilities\s*:[\s\S]{0,60}add\s*:[\s\S]{0,40}\[\s*[''"]?ALL)'
|
|
124
|
+
include: ['**/*.{yaml,yml}']
|
|
125
|
+
exclude: ['**/node_modules/**']
|
|
126
|
+
why: 'A privileged container is root on the node. There is almost always a narrower capability that works.'
|
|
127
|
+
remediation: 'Drop ALL capabilities and add back only what is needed; use Pod Security Admission "restricted".'
|
|
128
|
+
references: ['CIS-K8s-5.2.1', 'NSA-K8s-Hardening']
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
id: stacks/jvm
|
|
2
|
+
title: Java / Kotlin / JVM
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when Java, Kotlin, or Scala is detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when:
|
|
8
|
+
all:
|
|
9
|
+
- { fact: 'lang:java', op: 'absent' }
|
|
10
|
+
- { fact: 'lang:kotlin', op: 'absent' }
|
|
11
|
+
- { fact: 'lang:scala', op: 'absent' }
|
|
12
|
+
|
|
13
|
+
rules:
|
|
14
|
+
- id: JVM-001
|
|
15
|
+
title: Dependency versions are managed centrally
|
|
16
|
+
section: S15
|
|
17
|
+
section_title: Platform-Specific
|
|
18
|
+
severity: MEDIUM
|
|
19
|
+
class: supply-chain
|
|
20
|
+
check:
|
|
21
|
+
kind: grep_present
|
|
22
|
+
pattern: '(dependencyManagement|<dependencyManagement>|versions?\.toml|catalog|constraints\s*\{|dependencyResolutionManagement)'
|
|
23
|
+
include:
|
|
24
|
+
[
|
|
25
|
+
'pom.xml',
|
|
26
|
+
'build.gradle*',
|
|
27
|
+
'settings.gradle*',
|
|
28
|
+
'build.gradle.kts',
|
|
29
|
+
'gradle/libs.versions.toml',
|
|
30
|
+
]
|
|
31
|
+
flags: i
|
|
32
|
+
why: 'Without a BOM or version catalog, five modules drift into five versions of the same library.'
|
|
33
|
+
remediation: 'Maven: dependencyManagement. Gradle: a version catalog in gradle/libs.versions.toml.'
|
|
34
|
+
references: ['Maven-BOM', 'Gradle-Version-Catalogs']
|
|
35
|
+
|
|
36
|
+
- id: JVM-002
|
|
37
|
+
title: No hardcoded credentials in configuration
|
|
38
|
+
section: S15
|
|
39
|
+
section_title: Platform-Specific
|
|
40
|
+
severity: CRITICAL
|
|
41
|
+
class: security
|
|
42
|
+
check:
|
|
43
|
+
kind: grep_absent
|
|
44
|
+
pattern: '(password|passwd|secret|token|api[_-]?key)\s*[:=]\s*[''"]?(?!\$\{|#\{|\{\{)[A-Za-z0-9/+_-]{8,}'
|
|
45
|
+
include:
|
|
46
|
+
['**/*.{properties,yml,yaml,xml,conf}', '**/application*.yml', '**/application*.properties']
|
|
47
|
+
exclude: ['**/src/test/**', '**/test/**']
|
|
48
|
+
flags: i
|
|
49
|
+
why: 'application.properties is committed. Anything in it is public.'
|
|
50
|
+
remediation: 'Externalise to environment variables or a secret manager; reference with ${ENV_VAR}.'
|
|
51
|
+
references: ['CWE-798', 'ASVS-14.3.2']
|
|
52
|
+
|
|
53
|
+
- id: JVM-003
|
|
54
|
+
title: Exception handling does not swallow stack traces
|
|
55
|
+
section: S15
|
|
56
|
+
section_title: Platform-Specific
|
|
57
|
+
severity: MEDIUM
|
|
58
|
+
class: correctness
|
|
59
|
+
check:
|
|
60
|
+
kind: grep_wrong
|
|
61
|
+
pattern: '(catch\s*\(\s*\w+\s+\w+\s*\)\s*\{\s*\}|printStackTrace\s*\(\s*\)|e\.printStackTrace)'
|
|
62
|
+
include: ['**/*.{java,kt,scala}']
|
|
63
|
+
exclude: ['**/src/test/**', '**/test/**']
|
|
64
|
+
why: 'printStackTrace writes to stderr, which usually goes nowhere useful, and an empty catch loses the error entirely.'
|
|
65
|
+
remediation: 'Log with a logger at the right level, including the context; rethrow or handle.'
|
|
66
|
+
references: ['CWE-390', 'ASVS-7.4.1']
|
|
67
|
+
|
|
68
|
+
- id: JVM-004
|
|
69
|
+
title: Parameterised SQL / JPA criteria instead of concatenation
|
|
70
|
+
section: S15
|
|
71
|
+
section_title: Platform-Specific
|
|
72
|
+
severity: CRITICAL
|
|
73
|
+
class: security
|
|
74
|
+
applies_when: { fact: 'has:database' }
|
|
75
|
+
check:
|
|
76
|
+
kind: grep_absent
|
|
77
|
+
pattern: '(createQuery\(\s*[''"][^''"]*[''"]\s*\+|createNativeQuery\(\s*.*\+|Statement\.execute(Query)?\s*\(\s*.*\+|\+?\s*[''"]\s*(SELECT|INSERT|UPDATE|DELETE)[^''"]*[''"]\s*\+)'
|
|
78
|
+
include: ['**/*.{java,kt}']
|
|
79
|
+
exclude: ['**/src/test/**', '**/test/**']
|
|
80
|
+
flags: i
|
|
81
|
+
why: 'String-concatenated JPQL is exactly as injectable as raw SQL.'
|
|
82
|
+
remediation: 'Use named parameters (:name) or the Criteria API. Never build a query string from input.'
|
|
83
|
+
references: ['CWE-89', 'ASVS-5.3.4']
|
|
84
|
+
|
|
85
|
+
- id: JVM-005
|
|
86
|
+
title: Nullability is explicit
|
|
87
|
+
section: S15
|
|
88
|
+
section_title: Platform-Specific
|
|
89
|
+
severity: LOW
|
|
90
|
+
class: correctness
|
|
91
|
+
applies_when: { fact: 'lang:kotlin' }
|
|
92
|
+
check: { kind: manual }
|
|
93
|
+
evidence: 'Count of `!!` force-unwraps and platform types crossing into Kotlin from Java.'
|
|
94
|
+
why: 'Every !! is a NullPointerException with the compiler silenced.'
|
|
95
|
+
remediation: 'Prefer ?: with a default, requireNotNull with a message, or make the Java API annotated.'
|
|
96
|
+
references: ['Kotlin-Null-Safety']
|
|
97
|
+
|
|
98
|
+
- id: JVM-006
|
|
99
|
+
title: Static analysis runs in CI
|
|
100
|
+
section: S15
|
|
101
|
+
section_title: Platform-Specific
|
|
102
|
+
severity: MEDIUM
|
|
103
|
+
class: supply-chain
|
|
104
|
+
applies_when: { fact: 'has:ci' }
|
|
105
|
+
check:
|
|
106
|
+
kind: grep_present
|
|
107
|
+
pattern: '(spotbugs|pmd|checkstyle|errorprone|detekt|ktlint|sonar|snyk)'
|
|
108
|
+
include:
|
|
109
|
+
[
|
|
110
|
+
'pom.xml',
|
|
111
|
+
'build.gradle*',
|
|
112
|
+
'.github/workflows/*.yml',
|
|
113
|
+
'.github/workflows/*.yaml',
|
|
114
|
+
'.gitlab-ci.yml',
|
|
115
|
+
]
|
|
116
|
+
flags: i
|
|
117
|
+
why: 'SpotBugs and Error Prone find real bugs, not just style nits, and they are free.'
|
|
118
|
+
remediation: 'Add SpotBugs/ErrorProne (Java) or detekt/ktlint (Kotlin) and fail on new high-priority findings.'
|
|
119
|
+
references: ['OpenSSF-Scorecard:SAST']
|
|
120
|
+
|
|
121
|
+
- id: JVM-007
|
|
122
|
+
title: Insecure deserialisation is avoided
|
|
123
|
+
section: S15
|
|
124
|
+
section_title: Platform-Specific
|
|
125
|
+
severity: HIGH
|
|
126
|
+
class: security
|
|
127
|
+
check:
|
|
128
|
+
kind: grep_absent
|
|
129
|
+
pattern: '(ObjectInputStream|readObject\s*\(|XMLDecoder|enableDefaultTyping|activateDefaultTyping|readValue\s*\(\s*.*,\s*Object\.class)'
|
|
130
|
+
include: ['**/*.{java,kt}']
|
|
131
|
+
exclude: ['**/src/test/**', '**/test/**']
|
|
132
|
+
why: 'Java deserialisation gadget chains are one of the most reliable RCE vectors in existence.'
|
|
133
|
+
remediation: 'Use JSON with an explicit schema and no polymorphic default typing; validate before binding.'
|
|
134
|
+
references: ['CWE-502', 'ASVS-5.5.1']
|
|
135
|
+
|
|
136
|
+
- id: JVM-008
|
|
137
|
+
title: JVM/container memory limits are respected
|
|
138
|
+
section: S15
|
|
139
|
+
section_title: Platform-Specific
|
|
140
|
+
severity: LOW
|
|
141
|
+
class: performance
|
|
142
|
+
applies_when: { fact: 'has:containers' }
|
|
143
|
+
check:
|
|
144
|
+
kind: grep_present
|
|
145
|
+
pattern: '(MaxRAMPercentage|UseContainerSupport|XX:\+UseContainerSupport|Xmx)'
|
|
146
|
+
include:
|
|
147
|
+
['Dockerfile*', '**/*.sh', 'docker-compose*.yml', '**/*.{yaml,yml}', 'JAVA_TOOL_OPTIONS']
|
|
148
|
+
why: 'Without it the JVM sizes its heap from the host, not the cgroup, and gets OOM-killed.'
|
|
149
|
+
remediation: 'Set -XX:MaxRAMPercentage=75 (and leave the rest for off-heap).'
|
|
150
|
+
references: ['JVM-Container-Support']
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
id: stacks/ml-ai
|
|
2
|
+
title: ML / AI Systems
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when PyTorch/TensorFlow/JAX, notebooks, or model artefacts are detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when: { fact: 'project:ml', op: 'absent' }
|
|
8
|
+
|
|
9
|
+
rules:
|
|
10
|
+
- id: ML-001
|
|
11
|
+
title: Training data is versioned
|
|
12
|
+
section: S15
|
|
13
|
+
section_title: Platform-Specific
|
|
14
|
+
severity: MEDIUM
|
|
15
|
+
class: maintainability
|
|
16
|
+
check:
|
|
17
|
+
kind: grep_present
|
|
18
|
+
pattern: '(dvc|lfs|lakefs|wandb\.Artifact|mlflow|dataset.*version|\.dvc/|data/raw)'
|
|
19
|
+
include: ['**/*.{py,yaml,yml,toml,md}', '.gitattributes', '.dvc/config']
|
|
20
|
+
exclude: ['**/node_modules/**']
|
|
21
|
+
flags: i
|
|
22
|
+
why: 'A model you cannot reproduce from its data is a result you cannot defend or debug.'
|
|
23
|
+
remediation: 'Version datasets with DVC or Git LFS; record the exact snapshot hash with every training run.'
|
|
24
|
+
references: ['DVC', 'Google-Rules-of-ML']
|
|
25
|
+
|
|
26
|
+
- id: ML-002
|
|
27
|
+
title: Model artefacts are versioned and registered
|
|
28
|
+
section: S15
|
|
29
|
+
section_title: Platform-Specific
|
|
30
|
+
severity: MEDIUM
|
|
31
|
+
class: operations
|
|
32
|
+
check: { kind: manual }
|
|
33
|
+
evidence: 'A model registry (MLflow, Weights & Biases, SageMaker) mapping deployed model → training run → data snapshot.'
|
|
34
|
+
why: 'When production degrades, the first question is "which model is running and what was it trained on?"'
|
|
35
|
+
remediation: 'Register every candidate model with metadata; deploy by registry version, never by copying files.'
|
|
36
|
+
references: ['MLflow', 'Google-Rules-of-ML']
|
|
37
|
+
|
|
38
|
+
- id: ML-003
|
|
39
|
+
title: Training is reproducible (seeded and pinned)
|
|
40
|
+
section: S15
|
|
41
|
+
section_title: Platform-Specific
|
|
42
|
+
severity: MEDIUM
|
|
43
|
+
class: correctness
|
|
44
|
+
check:
|
|
45
|
+
kind: grep_present
|
|
46
|
+
pattern: '(random_seed|manual_seed|set_seed|seed\s*=\s*\d+|deterministic\s*=\s*True|torch\.use_deterministic)'
|
|
47
|
+
include: ['**/*.py', '**/*.ipynb', '**/*.{yaml,yml}']
|
|
48
|
+
exclude: ['**/node_modules/**']
|
|
49
|
+
flags: i
|
|
50
|
+
why: 'Unseeded training means you cannot tell an improvement from noise.'
|
|
51
|
+
remediation: 'Seed every RNG (python, numpy, torch), pin the library versions, and record the environment.'
|
|
52
|
+
references: ['PyTorch-Reproducibility']
|
|
53
|
+
|
|
54
|
+
- id: ML-004
|
|
55
|
+
title: Model/data drift is monitored in production
|
|
56
|
+
section: S15
|
|
57
|
+
section_title: Platform-Specific
|
|
58
|
+
severity: MEDIUM
|
|
59
|
+
class: operations
|
|
60
|
+
applies_when: { fact: 'maturity:production' }
|
|
61
|
+
check: { kind: manual }
|
|
62
|
+
evidence: 'Input distribution monitoring and a performance/label-feedback loop with alert thresholds.'
|
|
63
|
+
why: 'Models do not crash when they degrade. They just get quietly worse, and nobody notices for a quarter.'
|
|
64
|
+
remediation: 'Log input feature distributions and prediction confidence; alert on drift beyond a threshold.'
|
|
65
|
+
references: ['Google-Rules-of-ML', 'NIST-AI-RMF']
|
|
66
|
+
|
|
67
|
+
- id: ML-005
|
|
68
|
+
title: Predictions are logged for audit
|
|
69
|
+
section: S15
|
|
70
|
+
section_title: Platform-Specific
|
|
71
|
+
severity: MEDIUM
|
|
72
|
+
class: compliance
|
|
73
|
+
applies_when: { fact: 'maturity:production' }
|
|
74
|
+
check: { kind: manual }
|
|
75
|
+
evidence: 'Per-request log of model version, input features (or a hash), output, and latency, retained per policy.'
|
|
76
|
+
why: 'You cannot investigate a harmful prediction, a bias complaint, or a regression without the record.'
|
|
77
|
+
remediation: 'Log predictions with a request ID and model version; keep PII out or pseudonymised.'
|
|
78
|
+
references: ['NIST-AI-RMF', 'EU-AI-Act-Art.12']
|
|
79
|
+
|
|
80
|
+
- id: ML-006
|
|
81
|
+
title: Bias/fairness has been evaluated
|
|
82
|
+
section: S15
|
|
83
|
+
section_title: Platform-Specific
|
|
84
|
+
severity: MEDIUM
|
|
85
|
+
class: compliance
|
|
86
|
+
check: { kind: manual }
|
|
87
|
+
evidence: 'Evaluation sliced across relevant protected or sensitive attributes, with the results recorded.'
|
|
88
|
+
why: 'Aggregate accuracy hides per-group failure. "95% accurate" can still mean one group is systematically wrong.'
|
|
89
|
+
remediation: 'Slice metrics by relevant cohorts before launch; document known limitations in a model card.'
|
|
90
|
+
references: ['NIST-AI-RMF', 'EU-AI-Act']
|
|
91
|
+
|
|
92
|
+
- id: ML-007
|
|
93
|
+
title: Notebooks are not the deployment path
|
|
94
|
+
section: S15
|
|
95
|
+
section_title: Platform-Specific
|
|
96
|
+
severity: MEDIUM
|
|
97
|
+
class: maintainability
|
|
98
|
+
check: { kind: manual }
|
|
99
|
+
evidence: "Whether training/inference runs from scripts or pipelines, or still from someone's notebook."
|
|
100
|
+
why: '"Run cells 1 through 14, then the one I added" is not a reproducible pipeline.'
|
|
101
|
+
remediation: 'Extract to versioned scripts and a pipeline definition; keep notebooks for exploration only.'
|
|
102
|
+
references: ['Google-Rules-of-ML']
|
|
103
|
+
|
|
104
|
+
- id: ML-008
|
|
105
|
+
title: Inference cost and latency are tracked
|
|
106
|
+
section: S15
|
|
107
|
+
section_title: Platform-Specific
|
|
108
|
+
severity: LOW
|
|
109
|
+
class: performance
|
|
110
|
+
applies_when: { fact: 'maturity:production' }
|
|
111
|
+
check: { kind: manual }
|
|
112
|
+
evidence: 'p50/p95 inference latency and cost per 1k predictions (GPU hours or API spend).'
|
|
113
|
+
why: 'LLM and GPU inference bills are the fastest-growing line item in most 2026 stacks.'
|
|
114
|
+
remediation: 'Instrument per-request token/compute cost; set budgets; cache and batch where possible.'
|
|
115
|
+
references: ['FinOps', 'OWASP-LLM06:2026']
|
|
116
|
+
|
|
117
|
+
- id: ML-009
|
|
118
|
+
title: Model files are not committed to git
|
|
119
|
+
section: S15
|
|
120
|
+
section_title: Platform-Specific
|
|
121
|
+
severity: MEDIUM
|
|
122
|
+
class: maintainability
|
|
123
|
+
check:
|
|
124
|
+
kind: tracked_absent
|
|
125
|
+
patterns:
|
|
126
|
+
[
|
|
127
|
+
'**/*.pt',
|
|
128
|
+
'**/*.pth',
|
|
129
|
+
'**/*.ckpt',
|
|
130
|
+
'**/*.h5',
|
|
131
|
+
'**/*.safetensors',
|
|
132
|
+
'**/*.onnx',
|
|
133
|
+
'**/*.bin',
|
|
134
|
+
'**/*.pb',
|
|
135
|
+
'**/checkpoints/**',
|
|
136
|
+
'**/*.gguf',
|
|
137
|
+
]
|
|
138
|
+
why: 'Model weights are hundreds of megabytes to tens of gigabytes. Git will remember every version, forever.'
|
|
139
|
+
remediation: 'Store weights in a model registry or object storage (DVC, S3, Hugging Face Hub) and reference by digest.'
|
|
140
|
+
references: ['DVC', 'OpenSSF-Scorecard:Binary-Artifacts']
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
id: stacks/mobile
|
|
2
|
+
title: Mobile (iOS / Android / Cross-platform)
|
|
3
|
+
section: S15
|
|
4
|
+
section_title: Platform-Specific
|
|
5
|
+
description: 'Activated when iOS, Android, React Native, Expo, or Flutter is detected.'
|
|
6
|
+
version: '1.0'
|
|
7
|
+
skip_when:
|
|
8
|
+
all:
|
|
9
|
+
- { fact: 'platform:mobile', op: 'absent' }
|
|
10
|
+
- { fact: 'platform:ios', op: 'absent' }
|
|
11
|
+
- { fact: 'platform:android', op: 'absent' }
|
|
12
|
+
|
|
13
|
+
rules:
|
|
14
|
+
- id: MOB-001
|
|
15
|
+
title: Crash reporting is integrated
|
|
16
|
+
section: S15
|
|
17
|
+
section_title: Platform-Specific
|
|
18
|
+
severity: HIGH
|
|
19
|
+
class: operations
|
|
20
|
+
check:
|
|
21
|
+
kind: grep_present
|
|
22
|
+
pattern: '(Crashlytics|Sentry|BugSnag|Instabug|@sentry/react-native|firebase/crashlytics|crashReporting)'
|
|
23
|
+
include: ['**/*.{ts,tsx,js,json,plist,xml,gradle,kts,swift,kt,dart,yaml,yml}']
|
|
24
|
+
exclude: ['**/node_modules/**', '**/Pods/**']
|
|
25
|
+
flags: i
|
|
26
|
+
why: "On mobile you cannot watch the user's screen. Without crash reporting, your bug reports are one-star reviews."
|
|
27
|
+
remediation: 'Add Crashlytics or Sentry with symbol/dSYM upload so stack traces are readable.'
|
|
28
|
+
references: ['DORA:Monitoring']
|
|
29
|
+
|
|
30
|
+
- id: MOB-002
|
|
31
|
+
title: No hardcoded API secrets in app bundles
|
|
32
|
+
section: S15
|
|
33
|
+
section_title: Platform-Specific
|
|
34
|
+
severity: CRITICAL
|
|
35
|
+
class: security
|
|
36
|
+
check:
|
|
37
|
+
kind: grep_absent
|
|
38
|
+
pattern: '(api[_-]?key|secret|token|password)\s*[:=]\s*[''"][A-Za-z0-9._-]{16,}[''"]'
|
|
39
|
+
include: ['**/*.{swift,kt,java,dart,ts,tsx,js,plist,xml,gradle,json}']
|
|
40
|
+
exclude:
|
|
41
|
+
['**/node_modules/**', '**/Pods/**', '**/*Test*', '**/android/build/**', '**/ios/Pods/**']
|
|
42
|
+
flags: i
|
|
43
|
+
why: 'Anything in an app bundle is extractable in about four minutes with apktool or Hopper.'
|
|
44
|
+
remediation: 'Keep secrets server-side; use attestation (App Check / DeviceCheck) and short-lived tokens.'
|
|
45
|
+
references: ['OWASP-MASVS-STORAGE-1', 'CWE-798']
|
|
46
|
+
|
|
47
|
+
- id: MOB-003
|
|
48
|
+
title: Sensitive data is not stored insecurely on device
|
|
49
|
+
section: S15
|
|
50
|
+
section_title: Platform-Specific
|
|
51
|
+
severity: HIGH
|
|
52
|
+
class: security
|
|
53
|
+
check: { kind: manual }
|
|
54
|
+
evidence: 'Where tokens/PII live: Keychain/Keystore/EncryptedSharedPreferences, not AsyncStorage, UserDefaults, or a plaintext SQLite file.'
|
|
55
|
+
why: 'On a rooted or backed-up device, plaintext local storage is a data breach with extra steps.'
|
|
56
|
+
remediation: 'Use Keychain (iOS) / Keystore-backed EncryptedSharedPreferences (Android); never AsyncStorage for secrets.'
|
|
57
|
+
references: ['OWASP-MASVS-STORAGE-2', 'CWE-311']
|
|
58
|
+
|
|
59
|
+
- id: MOB-004
|
|
60
|
+
title: Network security configuration is explicit
|
|
61
|
+
section: S15
|
|
62
|
+
section_title: Platform-Specific
|
|
63
|
+
severity: MEDIUM
|
|
64
|
+
class: security
|
|
65
|
+
check: { kind: manual }
|
|
66
|
+
evidence: 'ATS settings (iOS Info.plist) and network-security-config / cleartextTrafficPermitted (Android).'
|
|
67
|
+
why: 'Both platforms default to permissive in older templates, which means plaintext HTTP and MITM.'
|
|
68
|
+
remediation: 'Disallow cleartext traffic; pin certificates only if you can commit to a rotation process.'
|
|
69
|
+
references: ['OWASP-MASVS-NETWORK-1', 'CWE-319']
|
|
70
|
+
|
|
71
|
+
- id: MOB-005
|
|
72
|
+
title: Permissions are requested minimally and with rationale
|
|
73
|
+
section: S15
|
|
74
|
+
section_title: Platform-Specific
|
|
75
|
+
severity: MEDIUM
|
|
76
|
+
class: compliance
|
|
77
|
+
check: { kind: manual }
|
|
78
|
+
evidence: 'Declared permissions vs. permissions actually used; where and when each is requested.'
|
|
79
|
+
why: 'Over-broad permissions are the fastest route to an app-store rejection and a privacy headline.'
|
|
80
|
+
remediation: 'Request at the moment of use with an explanation; drop anything unused.'
|
|
81
|
+
references: ['OWASP-MASVS-PLATFORM-1', 'App-Store-Review']
|
|
82
|
+
|
|
83
|
+
- id: MOB-006
|
|
84
|
+
title: Offline behaviour is handled
|
|
85
|
+
section: S15
|
|
86
|
+
section_title: Platform-Specific
|
|
87
|
+
severity: MEDIUM
|
|
88
|
+
class: correctness
|
|
89
|
+
check: { kind: manual }
|
|
90
|
+
evidence: 'Behaviour when the network is unavailable mid-action: queued, retried, or failed with a clear message?'
|
|
91
|
+
why: 'Mobile networks drop constantly. An app that assumes connectivity corrupts data.'
|
|
92
|
+
remediation: 'Queue mutations locally and reconcile on reconnect; show a clear offline state.'
|
|
93
|
+
references: ['OWASP-MASVS']
|
|
94
|
+
|
|
95
|
+
- id: MOB-007
|
|
96
|
+
title: Privacy manifests / data collection disclosure are present
|
|
97
|
+
section: S15
|
|
98
|
+
section_title: Platform-Specific
|
|
99
|
+
severity: HIGH
|
|
100
|
+
class: compliance
|
|
101
|
+
applies_when:
|
|
102
|
+
any:
|
|
103
|
+
- { fact: 'platform:ios' }
|
|
104
|
+
- { fact: 'platform:android' }
|
|
105
|
+
check: { kind: manual }
|
|
106
|
+
evidence: 'PrivacyInfo.xcprivacy (Apple) and the Play Data Safety declaration, both matching what the app actually collects.'
|
|
107
|
+
why: 'Apple rejects apps with missing or inaccurate privacy manifests. The declaration is also legally binding.'
|
|
108
|
+
remediation: 'Enumerate every SDK that collects data, then fill in the manifest to match reality.'
|
|
109
|
+
references: ['Apple-Privacy-Manifest', 'Play-Data-Safety']
|
|
110
|
+
|
|
111
|
+
- id: MOB-008
|
|
112
|
+
title: Tested on multiple screen sizes and OS versions
|
|
113
|
+
section: S15
|
|
114
|
+
section_title: Platform-Specific
|
|
115
|
+
severity: MEDIUM
|
|
116
|
+
class: correctness
|
|
117
|
+
check: { kind: manual }
|
|
118
|
+
evidence: 'Device matrix you test against, and whether the min-supported OS version is still patched upstream.'
|
|
119
|
+
why: '"Works on my iPhone 15 Pro" is not a test matrix.'
|
|
120
|
+
remediation: 'Test at least: smallest supported phone, a tablet, one dark-mode run, and the oldest supported OS.'
|
|
121
|
+
references: ['OWASP-MASVS']
|