peer-ai-standards 1.0.0-next.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 +62 -0
- package/dist/core/ai-features.d.ts +65 -0
- package/dist/core/ai-features.js +113 -0
- package/dist/core/api-design.d.ts +41 -0
- package/dist/core/api-design.js +91 -0
- package/dist/core/architecture.d.ts +41 -0
- package/dist/core/architecture.js +93 -0
- package/dist/core/backend.d.ts +21 -0
- package/dist/core/backend.js +25 -0
- package/dist/core/code-quality.d.ts +91 -0
- package/dist/core/code-quality.js +179 -0
- package/dist/core/data.d.ts +45 -0
- package/dist/core/data.js +59 -0
- package/dist/core/delivery.d.ts +153 -0
- package/dist/core/delivery.js +149 -0
- package/dist/core/design-accessibility.d.ts +124 -0
- package/dist/core/design-accessibility.js +199 -0
- package/dist/core/frontend.d.ts +77 -0
- package/dist/core/frontend.js +109 -0
- package/dist/core/mobile.d.ts +46 -0
- package/dist/core/mobile.js +83 -0
- package/dist/core/money.d.ts +81 -0
- package/dist/core/money.js +135 -0
- package/dist/core/operations.d.ts +108 -0
- package/dist/core/operations.js +177 -0
- package/dist/core/performance.d.ts +53 -0
- package/dist/core/performance.js +85 -0
- package/dist/core/privacy-compliance.d.ts +68 -0
- package/dist/core/privacy-compliance.js +77 -0
- package/dist/core/reliability.d.ts +67 -0
- package/dist/core/reliability.js +105 -0
- package/dist/core/requirements.d.ts +41 -0
- package/dist/core/requirements.js +58 -0
- package/dist/core/safety-critical.d.ts +41 -0
- package/dist/core/safety-critical.js +71 -0
- package/dist/core/security.d.ts +281 -0
- package/dist/core/security.js +427 -0
- package/dist/core/system-design.d.ts +31 -0
- package/dist/core/system-design.js +69 -0
- package/dist/core/testing.d.ts +51 -0
- package/dist/core/testing.js +124 -0
- package/dist/domains.d.ts +6 -0
- package/dist/domains.js +53 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +118 -0
- package/dist/profile.d.ts +262 -0
- package/dist/profile.js +269 -0
- package/dist/profiles/express.d.ts +2 -0
- package/dist/profiles/express.js +93 -0
- package/dist/profiles/fastapi.d.ts +2 -0
- package/dist/profiles/fastapi.js +104 -0
- package/dist/profiles/fastify.d.ts +2 -0
- package/dist/profiles/fastify.js +56 -0
- package/dist/profiles/github-actions.d.ts +2 -0
- package/dist/profiles/github-actions.js +151 -0
- package/dist/profiles/nestjs.d.ts +2 -0
- package/dist/profiles/nestjs.js +77 -0
- package/dist/profiles/next.d.ts +2 -0
- package/dist/profiles/next.js +73 -0
- package/dist/profiles/node.d.ts +2 -0
- package/dist/profiles/node.js +79 -0
- package/dist/profiles/python.d.ts +2 -0
- package/dist/profiles/python.js +232 -0
- package/dist/profiles/react-native.d.ts +2 -0
- package/dist/profiles/react-native.js +133 -0
- package/dist/profiles/react.d.ts +2 -0
- package/dist/profiles/react.js +180 -0
- package/dist/profiles/typescript.d.ts +2 -0
- package/dist/profiles/typescript.js +214 -0
- package/dist/rule.d.ts +72 -0
- package/dist/rule.js +39 -0
- package/package.json +39 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export declare const designAccessibility: ({
|
|
2
|
+
id: string;
|
|
3
|
+
domain: "design-accessibility";
|
|
4
|
+
title: string;
|
|
5
|
+
rule: string;
|
|
6
|
+
why: string;
|
|
7
|
+
ask: string;
|
|
8
|
+
stage: "mvp";
|
|
9
|
+
check: "auto";
|
|
10
|
+
severity: "medium";
|
|
11
|
+
sources?: never;
|
|
12
|
+
} | {
|
|
13
|
+
id: string;
|
|
14
|
+
domain: "design-accessibility";
|
|
15
|
+
title: string;
|
|
16
|
+
rule: string;
|
|
17
|
+
why: string;
|
|
18
|
+
ask: string;
|
|
19
|
+
stage: "mvp";
|
|
20
|
+
check: "ai-review";
|
|
21
|
+
severity: "low";
|
|
22
|
+
sources?: never;
|
|
23
|
+
} | {
|
|
24
|
+
id: string;
|
|
25
|
+
domain: "design-accessibility";
|
|
26
|
+
title: string;
|
|
27
|
+
rule: string;
|
|
28
|
+
why: string;
|
|
29
|
+
ask: string;
|
|
30
|
+
stage: "mvp";
|
|
31
|
+
check: "ai-review";
|
|
32
|
+
severity: "medium";
|
|
33
|
+
sources?: never;
|
|
34
|
+
} | {
|
|
35
|
+
id: string;
|
|
36
|
+
domain: "design-accessibility";
|
|
37
|
+
title: string;
|
|
38
|
+
rule: string;
|
|
39
|
+
why: string;
|
|
40
|
+
ask: string;
|
|
41
|
+
stage: "mvp";
|
|
42
|
+
check: "ai-review";
|
|
43
|
+
severity: "medium";
|
|
44
|
+
sources: {
|
|
45
|
+
name: string;
|
|
46
|
+
ref: string;
|
|
47
|
+
url: string;
|
|
48
|
+
}[];
|
|
49
|
+
} | {
|
|
50
|
+
id: string;
|
|
51
|
+
domain: "design-accessibility";
|
|
52
|
+
title: string;
|
|
53
|
+
rule: string;
|
|
54
|
+
why: string;
|
|
55
|
+
ask: string;
|
|
56
|
+
stage: "prototype";
|
|
57
|
+
check: "auto";
|
|
58
|
+
severity: "medium";
|
|
59
|
+
sources: {
|
|
60
|
+
name: string;
|
|
61
|
+
ref: string;
|
|
62
|
+
url: string;
|
|
63
|
+
}[];
|
|
64
|
+
} | {
|
|
65
|
+
id: string;
|
|
66
|
+
domain: "design-accessibility";
|
|
67
|
+
title: string;
|
|
68
|
+
rule: string;
|
|
69
|
+
why: string;
|
|
70
|
+
ask: string;
|
|
71
|
+
stage: "prototype";
|
|
72
|
+
check: "ai-review";
|
|
73
|
+
severity: "medium";
|
|
74
|
+
sources: {
|
|
75
|
+
name: string;
|
|
76
|
+
ref: string;
|
|
77
|
+
url: string;
|
|
78
|
+
}[];
|
|
79
|
+
} | {
|
|
80
|
+
id: string;
|
|
81
|
+
domain: "design-accessibility";
|
|
82
|
+
title: string;
|
|
83
|
+
rule: string;
|
|
84
|
+
why: string;
|
|
85
|
+
ask: string;
|
|
86
|
+
stage: "mvp";
|
|
87
|
+
check: "ai-review";
|
|
88
|
+
severity: "high";
|
|
89
|
+
sources: {
|
|
90
|
+
name: string;
|
|
91
|
+
ref: string;
|
|
92
|
+
url: string;
|
|
93
|
+
}[];
|
|
94
|
+
} | {
|
|
95
|
+
id: string;
|
|
96
|
+
domain: "design-accessibility";
|
|
97
|
+
title: string;
|
|
98
|
+
rule: string;
|
|
99
|
+
why: string;
|
|
100
|
+
ask: string;
|
|
101
|
+
stage: "mvp";
|
|
102
|
+
check: "auto";
|
|
103
|
+
severity: "medium";
|
|
104
|
+
sources: {
|
|
105
|
+
name: string;
|
|
106
|
+
ref: string;
|
|
107
|
+
url: string;
|
|
108
|
+
}[];
|
|
109
|
+
} | {
|
|
110
|
+
id: string;
|
|
111
|
+
domain: "design-accessibility";
|
|
112
|
+
title: string;
|
|
113
|
+
rule: string;
|
|
114
|
+
why: string;
|
|
115
|
+
ask: string;
|
|
116
|
+
stage: "mvp";
|
|
117
|
+
check: "auto";
|
|
118
|
+
severity: "low";
|
|
119
|
+
sources: {
|
|
120
|
+
name: string;
|
|
121
|
+
ref: string;
|
|
122
|
+
url: string;
|
|
123
|
+
}[];
|
|
124
|
+
})[];
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Design tokens, shared components, and making the product work for everyone. Accessibility
|
|
2
|
+
// rules cite the WCAG 2.2 success criterion they come from, checked against the W3C's text. The
|
|
3
|
+
// numbers a platform sets, such as touch target sizes, are in the stack profiles.
|
|
4
|
+
const WCAG = "WCAG 2.2";
|
|
5
|
+
const sc = (anchor) => `https://www.w3.org/TR/WCAG22/#${anchor}`;
|
|
6
|
+
export const designAccessibility = [
|
|
7
|
+
{
|
|
8
|
+
id: "DES-01",
|
|
9
|
+
domain: "design-accessibility",
|
|
10
|
+
title: "Design values come from tokens",
|
|
11
|
+
rule: "Colours, spacing, corner radii and font sizes come from design tokens. None is written directly into a screen or component.",
|
|
12
|
+
why: "A hard-coded value is missed by every rebrand, dark theme and accessibility fix that changes the tokens.",
|
|
13
|
+
ask: "Does this change write a colour, spacing, radius or font size directly?",
|
|
14
|
+
stage: "mvp",
|
|
15
|
+
check: "auto",
|
|
16
|
+
severity: "medium",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "DES-02",
|
|
20
|
+
domain: "design-accessibility",
|
|
21
|
+
title: "Tokens come from one place, used by every app",
|
|
22
|
+
rule: "Design tokens have one source, independent of any framework, and every app reads from it: light and dark palettes, type scale, spacing, radii and elevation.",
|
|
23
|
+
why: "Two copies of the tokens drift, and the web and mobile apps stop looking like one product.",
|
|
24
|
+
ask: "Does this change define design values anywhere other than the one token source?",
|
|
25
|
+
stage: "mvp",
|
|
26
|
+
check: "auto",
|
|
27
|
+
severity: "medium",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "DES-03",
|
|
31
|
+
domain: "design-accessibility",
|
|
32
|
+
title: "Token names say what they're for",
|
|
33
|
+
rule: "Tokens are named for their purpose, such as `surface`, `accent` or `danger`, never for how they look, such as `blue500`.",
|
|
34
|
+
why: "Names that describe purpose survive a rebrand and a dark theme. Names that describe a colour are wrong the day the colour changes.",
|
|
35
|
+
ask: "Does any token in this change describe how it looks rather than what it's for?",
|
|
36
|
+
stage: "mvp",
|
|
37
|
+
check: "ai-review",
|
|
38
|
+
severity: "low",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "DES-04",
|
|
42
|
+
domain: "design-accessibility",
|
|
43
|
+
title: "Screens are built from the shared components",
|
|
44
|
+
rule: "Screens are built only from the shared set of interface components. A screen that needs a new one adds it to the set, rather than building it inline.",
|
|
45
|
+
why: "Inline one-offs are where inconsistency, missing states and accessibility gaps collect.",
|
|
46
|
+
ask: "Does any screen in this change build an interface element instead of using a shared component?",
|
|
47
|
+
stage: "mvp",
|
|
48
|
+
check: "auto",
|
|
49
|
+
severity: "medium",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "DES-05",
|
|
53
|
+
domain: "design-accessibility",
|
|
54
|
+
title: "Every shared component comes with all its states",
|
|
55
|
+
rule: "Every shared component has all its states: default, pressed, focused, disabled, loading, error and, where it applies, empty. One without them isn't finished.",
|
|
56
|
+
why: "A missing state is found by a person at the worst moment: a button that looks tappable while it's still saving.",
|
|
57
|
+
ask: "Does every shared component in this change have all its states?",
|
|
58
|
+
stage: "mvp",
|
|
59
|
+
check: "ai-review",
|
|
60
|
+
severity: "medium",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "DES-06",
|
|
64
|
+
domain: "design-accessibility",
|
|
65
|
+
title: "The danger colour means one thing",
|
|
66
|
+
rule: "The danger colour is kept for the one meaning the project gives it, such as money going wrong or a safety warning. It's never decoration, and never a general error.",
|
|
67
|
+
why: "A signal used for everything teaches people to ignore it, including the time it matters.",
|
|
68
|
+
ask: "Is the danger colour in this change used for anything but its one meaning?",
|
|
69
|
+
stage: "mvp",
|
|
70
|
+
check: "ai-review",
|
|
71
|
+
severity: "medium",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "DES-07",
|
|
75
|
+
domain: "design-accessibility",
|
|
76
|
+
title: "Information is never shown by colour alone",
|
|
77
|
+
rule: "Anything shown with colour, such as a status or an error, is also shown with text, a shape or an icon with a label.",
|
|
78
|
+
why: "About one man in twelve has some colour blindness, and screen readers don't read colours at all.",
|
|
79
|
+
ask: "Is any information in this change shown by colour alone?",
|
|
80
|
+
stage: "mvp",
|
|
81
|
+
check: "ai-review",
|
|
82
|
+
severity: "medium",
|
|
83
|
+
sources: [{ name: WCAG, ref: "1.4.1, level A", url: sc("use-of-color") }],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "DES-08",
|
|
87
|
+
domain: "design-accessibility",
|
|
88
|
+
title: "Every control has an accessible name",
|
|
89
|
+
rule: "Every button, link and control has a name a screen reader can announce, including buttons that show only an icon.",
|
|
90
|
+
why: 'An unlabelled control is announced as "button", and the person has no way to know what it does.',
|
|
91
|
+
ask: "Does every control in this change have an accessible name?",
|
|
92
|
+
stage: "prototype",
|
|
93
|
+
check: "auto",
|
|
94
|
+
severity: "medium",
|
|
95
|
+
sources: [{ name: WCAG, ref: "4.1.2, level A", url: sc("name-role-value") }],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "DES-09",
|
|
99
|
+
domain: "design-accessibility",
|
|
100
|
+
title: "Form fields have labels",
|
|
101
|
+
rule: "Every form field has a visible label that stays visible while the person types. A placeholder isn't a label.",
|
|
102
|
+
why: "A placeholder disappears as soon as someone types, and many screen readers don't announce it.",
|
|
103
|
+
ask: "Does every form field in this change have a label, not just a placeholder?",
|
|
104
|
+
stage: "prototype",
|
|
105
|
+
check: "ai-review",
|
|
106
|
+
severity: "medium",
|
|
107
|
+
sources: [
|
|
108
|
+
{ name: WCAG, ref: "3.3.2, level A", url: sc("labels-or-instructions") },
|
|
109
|
+
{ name: WCAG, ref: "1.3.1, level A", url: sc("info-and-relationships") },
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "DES-10",
|
|
114
|
+
domain: "design-accessibility",
|
|
115
|
+
title: "Everything works with a keyboard",
|
|
116
|
+
rule: "On the web, everything a person can do with a mouse or a tap, they can do with a keyboard alone.",
|
|
117
|
+
why: "Many people can't use a mouse, and a control a keyboard can't reach locks them out of the whole task.",
|
|
118
|
+
ask: "Can everything in this change be done with a keyboard alone?",
|
|
119
|
+
stage: "mvp",
|
|
120
|
+
check: "ai-review",
|
|
121
|
+
severity: "high",
|
|
122
|
+
sources: [{ name: WCAG, ref: "2.1.1, level A", url: sc("keyboard") }],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "DES-11",
|
|
126
|
+
domain: "design-accessibility",
|
|
127
|
+
title: "Focus is always visible",
|
|
128
|
+
rule: "The element that has keyboard focus is always clearly visible.",
|
|
129
|
+
why: "Someone using a keyboard can't act on what they can't see is selected.",
|
|
130
|
+
ask: "Is keyboard focus visible on everything in this change?",
|
|
131
|
+
stage: "mvp",
|
|
132
|
+
check: "auto",
|
|
133
|
+
severity: "medium",
|
|
134
|
+
sources: [{ name: WCAG, ref: "2.4.7, level AA", url: sc("focus-visible") }],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "DES-12",
|
|
138
|
+
domain: "design-accessibility",
|
|
139
|
+
title: "Dragging always has an alternative",
|
|
140
|
+
rule: "Anything done by dragging can also be done without dragging, such as with buttons to move an item up or down.",
|
|
141
|
+
why: "Dragging needs a steady hand and a pointer, which many people don't have.",
|
|
142
|
+
ask: "Can everything this change does by dragging also be done another way?",
|
|
143
|
+
stage: "mvp",
|
|
144
|
+
check: "ai-review",
|
|
145
|
+
severity: "medium",
|
|
146
|
+
sources: [{ name: WCAG, ref: "2.5.7, level AA", url: sc("dragging-movements") }],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "DES-13",
|
|
150
|
+
domain: "design-accessibility",
|
|
151
|
+
title: "Contrast meets WCAG AA in every theme",
|
|
152
|
+
rule: "Text, icons and control borders have enough contrast against their background in every theme: at least 4.5 to 1 for normal text.",
|
|
153
|
+
why: "Low contrast is unreadable in sunlight, on cheap screens, and for many people with low vision.",
|
|
154
|
+
ask: "Does every colour pair in this change meet WCAG AA contrast, in every theme?",
|
|
155
|
+
stage: "mvp",
|
|
156
|
+
check: "auto",
|
|
157
|
+
severity: "medium",
|
|
158
|
+
sources: [
|
|
159
|
+
{ name: WCAG, ref: "1.4.3, level AA", url: sc("contrast-minimum") },
|
|
160
|
+
{ name: WCAG, ref: "1.4.11, level AA", url: sc("non-text-contrast") },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "DES-14",
|
|
165
|
+
domain: "design-accessibility",
|
|
166
|
+
title: "Text follows the person's text size",
|
|
167
|
+
rule: "Text grows with the person's text size setting, and the layout still works when it does.",
|
|
168
|
+
why: "People who need larger text set it once, and an app that ignores it is one they can't read.",
|
|
169
|
+
ask: "Does all text in this change follow the person's text size setting?",
|
|
170
|
+
stage: "mvp",
|
|
171
|
+
check: "ai-review",
|
|
172
|
+
severity: "medium",
|
|
173
|
+
sources: [{ name: WCAG, ref: "1.4.4, level AA", url: sc("resize-text") }],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "DES-15",
|
|
177
|
+
domain: "design-accessibility",
|
|
178
|
+
title: "Touch targets are large enough",
|
|
179
|
+
rule: "Everything a person taps or clicks is large enough to hit reliably. Each platform's profile sets the size.",
|
|
180
|
+
why: "Small targets cause mis-taps for everyone, and for people with tremors they make an app unusable.",
|
|
181
|
+
ask: "Is every tap or click target in this change at least its platform's minimum size?",
|
|
182
|
+
stage: "mvp",
|
|
183
|
+
check: "auto",
|
|
184
|
+
severity: "medium",
|
|
185
|
+
sources: [{ name: WCAG, ref: "2.5.8, level AA", url: sc("target-size-minimum") }],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "DES-16",
|
|
189
|
+
domain: "design-accessibility",
|
|
190
|
+
title: "Animation respects the reduced-motion setting",
|
|
191
|
+
rule: "Every animation that isn't essential stops or softens when the person has asked for reduced motion.",
|
|
192
|
+
why: "Movement on screen can cause dizziness and nausea for people with vestibular disorders.",
|
|
193
|
+
ask: "Does every animation in this change respect the reduced-motion setting?",
|
|
194
|
+
stage: "mvp",
|
|
195
|
+
check: "auto",
|
|
196
|
+
severity: "low",
|
|
197
|
+
sources: [{ name: WCAG, ref: "2.3.3, level AAA", url: sc("animation-from-interactions") }],
|
|
198
|
+
},
|
|
199
|
+
];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export declare const frontend: ({
|
|
2
|
+
id: string;
|
|
3
|
+
domain: "frontend";
|
|
4
|
+
title: string;
|
|
5
|
+
rule: string;
|
|
6
|
+
why: string;
|
|
7
|
+
ask: string;
|
|
8
|
+
stage: "mvp";
|
|
9
|
+
check: "ai-review";
|
|
10
|
+
severity: "medium";
|
|
11
|
+
sources?: never;
|
|
12
|
+
when?: never;
|
|
13
|
+
} | {
|
|
14
|
+
id: string;
|
|
15
|
+
domain: "frontend";
|
|
16
|
+
title: string;
|
|
17
|
+
rule: string;
|
|
18
|
+
why: string;
|
|
19
|
+
ask: string;
|
|
20
|
+
stage: "prototype";
|
|
21
|
+
check: "ai-review";
|
|
22
|
+
severity: "medium";
|
|
23
|
+
sources?: never;
|
|
24
|
+
when?: never;
|
|
25
|
+
} | {
|
|
26
|
+
id: string;
|
|
27
|
+
domain: "frontend";
|
|
28
|
+
title: string;
|
|
29
|
+
rule: string;
|
|
30
|
+
why: string;
|
|
31
|
+
ask: string;
|
|
32
|
+
stage: "mvp";
|
|
33
|
+
check: "ai-review";
|
|
34
|
+
severity: "low";
|
|
35
|
+
sources?: never;
|
|
36
|
+
when?: never;
|
|
37
|
+
} | {
|
|
38
|
+
id: string;
|
|
39
|
+
domain: "frontend";
|
|
40
|
+
title: string;
|
|
41
|
+
rule: string;
|
|
42
|
+
why: string;
|
|
43
|
+
ask: string;
|
|
44
|
+
stage: "mvp";
|
|
45
|
+
check: "auto";
|
|
46
|
+
severity: "low";
|
|
47
|
+
sources?: never;
|
|
48
|
+
when?: never;
|
|
49
|
+
} | {
|
|
50
|
+
id: string;
|
|
51
|
+
domain: "frontend";
|
|
52
|
+
title: string;
|
|
53
|
+
rule: string;
|
|
54
|
+
why: string;
|
|
55
|
+
ask: string;
|
|
56
|
+
stage: "mvp";
|
|
57
|
+
check: "ai-review";
|
|
58
|
+
severity: "medium";
|
|
59
|
+
sources: {
|
|
60
|
+
name: string;
|
|
61
|
+
ref: string;
|
|
62
|
+
url: string;
|
|
63
|
+
}[];
|
|
64
|
+
when?: never;
|
|
65
|
+
} | {
|
|
66
|
+
id: string;
|
|
67
|
+
domain: "frontend";
|
|
68
|
+
title: string;
|
|
69
|
+
rule: string;
|
|
70
|
+
why: string;
|
|
71
|
+
ask: string;
|
|
72
|
+
stage: "mvp";
|
|
73
|
+
check: "ai-review";
|
|
74
|
+
severity: "high";
|
|
75
|
+
when: "offline"[];
|
|
76
|
+
sources?: never;
|
|
77
|
+
})[];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// How screens get, hold and show data, whatever the framework. Each stack profile says how.
|
|
2
|
+
const WCAG = "WCAG 2.2";
|
|
3
|
+
const sc = (anchor) => `https://www.w3.org/TR/WCAG22/#${anchor}`;
|
|
4
|
+
export const frontend = [
|
|
5
|
+
{
|
|
6
|
+
id: "FE-01",
|
|
7
|
+
domain: "frontend",
|
|
8
|
+
title: "Data from the server lives in one cache, not in screen state",
|
|
9
|
+
rule: "Data that came from a server or a stored source is read from one shared cache, never copied into a screen's own state.",
|
|
10
|
+
why: "A copy goes stale, sends duplicate requests, and races with the original when someone moves quickly between screens.",
|
|
11
|
+
ask: "Does this change copy server data into a screen's own state?",
|
|
12
|
+
stage: "mvp",
|
|
13
|
+
check: "ai-review",
|
|
14
|
+
severity: "medium",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "FE-02",
|
|
18
|
+
domain: "frontend",
|
|
19
|
+
title: "Values that follow from other data are calculated, never stored",
|
|
20
|
+
rule: "Totals, counts and statuses that follow from other data are calculated when they're read, never stored alongside it.",
|
|
21
|
+
why: "A stored copy of a calculated value is how two screens come to disagree.",
|
|
22
|
+
ask: "Does this change store a value that could be calculated from other data?",
|
|
23
|
+
stage: "prototype",
|
|
24
|
+
check: "ai-review",
|
|
25
|
+
severity: "medium",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "FE-03",
|
|
29
|
+
domain: "frontend",
|
|
30
|
+
title: "Truly global state lives in one store; everything else stays local",
|
|
31
|
+
rule: "State that the whole app shares lives in one store. Everything else stays in the component or feature that uses it.",
|
|
32
|
+
why: "Global state is shared by everything, so every piece of it is a way for one part of the app to break another.",
|
|
33
|
+
ask: "Is any state in this change made global when only one part of the app uses it?",
|
|
34
|
+
stage: "mvp",
|
|
35
|
+
check: "ai-review",
|
|
36
|
+
severity: "low",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "FE-04",
|
|
40
|
+
domain: "frontend",
|
|
41
|
+
title: "Values aren't passed down through layers that don't use them",
|
|
42
|
+
rule: "A value is passed through only a few components that don't use it; the profile sets how many. Past that, it's fixed with composition first, then shared context, then a store.",
|
|
43
|
+
why: '"No prop drilling" can\'t be enforced because nobody agrees where it starts. A number can be counted in review.',
|
|
44
|
+
ask: "Does any value in this change pass through more components that don't use it than the profile allows?",
|
|
45
|
+
stage: "mvp",
|
|
46
|
+
check: "ai-review",
|
|
47
|
+
severity: "low",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "FE-05",
|
|
51
|
+
domain: "frontend",
|
|
52
|
+
title: "A component fetches data or shows it, not both",
|
|
53
|
+
rule: "Data is fetched where a screen begins. Everything below it receives what it needs and only shows it.",
|
|
54
|
+
why: "A component that only shows what it's given can be tested and reused without a network.",
|
|
55
|
+
ask: "Does any component in this change both fetch data and show it?",
|
|
56
|
+
stage: "mvp",
|
|
57
|
+
check: "ai-review",
|
|
58
|
+
severity: "medium",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "FE-06",
|
|
62
|
+
domain: "frontend",
|
|
63
|
+
title: "Only screens know about navigation",
|
|
64
|
+
rule: "Only a screen knows how to move to another screen. Components below it receive what to do as a function.",
|
|
65
|
+
why: "A component that navigates on its own can't be reused anywhere the route is different.",
|
|
66
|
+
ask: "Does any component in this change, other than a screen, navigate?",
|
|
67
|
+
stage: "mvp",
|
|
68
|
+
check: "auto",
|
|
69
|
+
severity: "low",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "FE-07",
|
|
73
|
+
domain: "frontend",
|
|
74
|
+
title: "Every screen that waits handles loading, empty and error",
|
|
75
|
+
rule: "Every screen or component that waits for data shows loading, empty and error states explicitly. An endless spinner is a bug.",
|
|
76
|
+
why: "The happy path is the only one that gets built by default, and people meet the others first on a bad connection.",
|
|
77
|
+
ask: "Does everything in this change that waits for data handle loading, empty and error?",
|
|
78
|
+
stage: "mvp",
|
|
79
|
+
check: "ai-review",
|
|
80
|
+
severity: "medium",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "FE-08",
|
|
84
|
+
domain: "frontend",
|
|
85
|
+
title: "An error message says what happened and what to do next",
|
|
86
|
+
rule: 'An error message tells the person what went wrong and what they can do about it. A bare "Something went wrong" isn\'t enough.',
|
|
87
|
+
why: "A message that explains nothing leaves the person stuck, and turns into a support request.",
|
|
88
|
+
ask: "Does every error message in this change say what happened and what to do next?",
|
|
89
|
+
stage: "mvp",
|
|
90
|
+
check: "ai-review",
|
|
91
|
+
severity: "medium",
|
|
92
|
+
sources: [
|
|
93
|
+
{ name: WCAG, ref: "3.3.1, level A", url: sc("error-identification") },
|
|
94
|
+
{ name: WCAG, ref: "3.3.3, level AA", url: sc("error-suggestion") },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "FE-09",
|
|
99
|
+
domain: "frontend",
|
|
100
|
+
title: "Offline is a state, not an error",
|
|
101
|
+
rule: "With no connection, the app says so, keeps what the person did, and catches up when it's back online. Nothing typed is lost.",
|
|
102
|
+
why: "People lose connection in lifts, on trains and in basements, and losing their work there teaches them not to trust the app.",
|
|
103
|
+
ask: "Does this change keep working, and keep what the person did, with no connection?",
|
|
104
|
+
stage: "mvp",
|
|
105
|
+
check: "ai-review",
|
|
106
|
+
severity: "high",
|
|
107
|
+
when: ["offline"],
|
|
108
|
+
},
|
|
109
|
+
];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const mobile: ({
|
|
2
|
+
id: string;
|
|
3
|
+
domain: "mobile";
|
|
4
|
+
title: string;
|
|
5
|
+
rule: string;
|
|
6
|
+
why: string;
|
|
7
|
+
ask: string;
|
|
8
|
+
stage: "mvp";
|
|
9
|
+
check: "ai-review";
|
|
10
|
+
severity: "high";
|
|
11
|
+
sources: {
|
|
12
|
+
name: string;
|
|
13
|
+
ref: string;
|
|
14
|
+
url: string;
|
|
15
|
+
}[];
|
|
16
|
+
} | {
|
|
17
|
+
id: string;
|
|
18
|
+
domain: "mobile";
|
|
19
|
+
title: string;
|
|
20
|
+
rule: string;
|
|
21
|
+
why: string;
|
|
22
|
+
ask: string;
|
|
23
|
+
stage: "mvp";
|
|
24
|
+
check: "ai-review";
|
|
25
|
+
severity: "medium";
|
|
26
|
+
sources: {
|
|
27
|
+
name: string;
|
|
28
|
+
ref: string;
|
|
29
|
+
url: string;
|
|
30
|
+
}[];
|
|
31
|
+
} | {
|
|
32
|
+
id: string;
|
|
33
|
+
domain: "mobile";
|
|
34
|
+
title: string;
|
|
35
|
+
rule: string;
|
|
36
|
+
why: string;
|
|
37
|
+
ask: string;
|
|
38
|
+
stage: "production";
|
|
39
|
+
check: "ai-review";
|
|
40
|
+
severity: "medium";
|
|
41
|
+
sources: {
|
|
42
|
+
name: string;
|
|
43
|
+
ref: string;
|
|
44
|
+
url: string;
|
|
45
|
+
}[];
|
|
46
|
+
})[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// What phone apps need beyond the frontend rules. Security rules cite the OWASP Mobile
|
|
2
|
+
// Application Security Verification Standard (MASVS) 2.1.0 control they come from, checked
|
|
3
|
+
// against OWASP's own repository. Platform numbers, such as touch target sizes, are in the
|
|
4
|
+
// stack profiles.
|
|
5
|
+
const MASVS = "OWASP MASVS 2.1.0";
|
|
6
|
+
const control = (id) => `https://github.com/OWASP/masvs/blob/master/controls/${id}.md`;
|
|
7
|
+
export const mobile = [
|
|
8
|
+
{
|
|
9
|
+
id: "MOB-01",
|
|
10
|
+
domain: "mobile",
|
|
11
|
+
title: "Sensitive data on the phone is kept in secure storage",
|
|
12
|
+
rule: "Session tokens, keys and other sensitive data on the phone are kept in the platform's secure storage, such as the iOS Keychain or the Android Keystore, never in ordinary app storage.",
|
|
13
|
+
why: "Ordinary app storage can be read from a backup, a rooted phone or another app's exploit. Secure storage is encrypted and tied to the device.",
|
|
14
|
+
ask: "Does this change keep any token, key or sensitive data in ordinary storage on the phone?",
|
|
15
|
+
stage: "mvp",
|
|
16
|
+
check: "ai-review",
|
|
17
|
+
severity: "high",
|
|
18
|
+
sources: [{ name: MASVS, ref: "MASVS-STORAGE-1", url: control("MASVS-STORAGE-1") }],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "MOB-02",
|
|
22
|
+
domain: "mobile",
|
|
23
|
+
title: "Sensitive data doesn't leak from the phone",
|
|
24
|
+
rule: "Sensitive data doesn't leak through the device's logs, backups, clipboard, keyboard suggestions or the screenshot shown in the app switcher.",
|
|
25
|
+
why: "Each of these is read by something other than your app, and some of them are copied off the phone.",
|
|
26
|
+
ask: "Could sensitive data in this change leak through logs, backups, the clipboard, the keyboard or the app switcher?",
|
|
27
|
+
stage: "mvp",
|
|
28
|
+
check: "ai-review",
|
|
29
|
+
severity: "medium",
|
|
30
|
+
sources: [{ name: MASVS, ref: "MASVS-STORAGE-2", url: control("MASVS-STORAGE-2") }],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "MOB-03",
|
|
34
|
+
domain: "mobile",
|
|
35
|
+
title: "An in-app browser opens only pages you trust",
|
|
36
|
+
rule: "An in-app browser (a WebView) loads only the app's own pages or ones on an allow-list. JavaScript and bridges to the app's code are switched on only where needed, and a link from outside, such as a QR code or a deep link, is checked before it's opened.",
|
|
37
|
+
why: "A WebView that opens any page runs an attacker's page inside your app, with whatever access the app gives it.",
|
|
38
|
+
ask: "Can any in-app browser in this change open a page that isn't trusted?",
|
|
39
|
+
stage: "mvp",
|
|
40
|
+
check: "ai-review",
|
|
41
|
+
severity: "high",
|
|
42
|
+
sources: [{ name: MASVS, ref: "MASVS-PLATFORM-2", url: control("MASVS-PLATFORM-2") }],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "MOB-04",
|
|
46
|
+
domain: "mobile",
|
|
47
|
+
title: "Links into the app are untrusted input",
|
|
48
|
+
rule: "Anything that arrives from outside the app, through a deep link, a notification or another app, is validated like any other untrusted input before it's used.",
|
|
49
|
+
why: "Anyone can craft a link that opens your app, so a deep link is a door anyone can knock on.",
|
|
50
|
+
ask: "Is everything that reaches this change through a link, notification or another app validated?",
|
|
51
|
+
stage: "mvp",
|
|
52
|
+
check: "ai-review",
|
|
53
|
+
severity: "medium",
|
|
54
|
+
sources: [{ name: MASVS, ref: "MASVS-CODE-4", url: control("MASVS-CODE-4") }],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "MOB-05",
|
|
58
|
+
domain: "mobile",
|
|
59
|
+
title: "Permissions are asked for when needed, at the least level",
|
|
60
|
+
rule: "The app asks for each permission when the feature that needs it is used, says why, and asks for the least it can, such as location while the app is in use rather than all the time.",
|
|
61
|
+
why: "A permission asked for up front, or at a higher level than needed, is refused by careful people, rejected by app stores, and a privacy risk when granted.",
|
|
62
|
+
ask: "Does this change ask for a permission earlier, or at a higher level, than the feature needs?",
|
|
63
|
+
stage: "mvp",
|
|
64
|
+
check: "ai-review",
|
|
65
|
+
severity: "high",
|
|
66
|
+
sources: [
|
|
67
|
+
{ name: MASVS, ref: "MASVS-PRIVACY-1", url: control("MASVS-PRIVACY-1") },
|
|
68
|
+
{ name: MASVS, ref: "MASVS-PRIVACY-3", url: control("MASVS-PRIVACY-3") },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "MOB-06",
|
|
73
|
+
domain: "mobile",
|
|
74
|
+
title: "The app can require an update",
|
|
75
|
+
rule: "The app can ask people to update, and refuse to run a version that's no longer safe, so a fix reaches everyone.",
|
|
76
|
+
why: "Installed apps stay on old versions for years, and without a way to require an update, a security fix never reaches them.",
|
|
77
|
+
ask: "Can the app require people on an unsafe version to update?",
|
|
78
|
+
stage: "production",
|
|
79
|
+
check: "ai-review",
|
|
80
|
+
severity: "medium",
|
|
81
|
+
sources: [{ name: MASVS, ref: "MASVS-CODE-2", url: control("MASVS-CODE-2") }],
|
|
82
|
+
},
|
|
83
|
+
];
|