agentic-qe 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/qx-partner.md +17 -4
- package/.claude/skills/accessibility-testing/SKILL.md +144 -692
- package/.claude/skills/agentic-quality-engineering/SKILL.md +176 -529
- package/.claude/skills/api-testing-patterns/SKILL.md +180 -560
- package/.claude/skills/brutal-honesty-review/SKILL.md +113 -603
- package/.claude/skills/bug-reporting-excellence/SKILL.md +116 -517
- package/.claude/skills/chaos-engineering-resilience/SKILL.md +127 -72
- package/.claude/skills/cicd-pipeline-qe-orchestrator/SKILL.md +209 -404
- package/.claude/skills/code-review-quality/SKILL.md +158 -608
- package/.claude/skills/compatibility-testing/SKILL.md +148 -38
- package/.claude/skills/compliance-testing/SKILL.md +132 -63
- package/.claude/skills/consultancy-practices/SKILL.md +114 -446
- package/.claude/skills/context-driven-testing/SKILL.md +117 -381
- package/.claude/skills/contract-testing/SKILL.md +176 -141
- package/.claude/skills/database-testing/SKILL.md +137 -130
- package/.claude/skills/exploratory-testing-advanced/SKILL.md +160 -629
- package/.claude/skills/holistic-testing-pact/SKILL.md +140 -188
- package/.claude/skills/localization-testing/SKILL.md +145 -33
- package/.claude/skills/mobile-testing/SKILL.md +132 -448
- package/.claude/skills/mutation-testing/SKILL.md +147 -41
- package/.claude/skills/performance-testing/SKILL.md +200 -546
- package/.claude/skills/quality-metrics/SKILL.md +164 -519
- package/.claude/skills/refactoring-patterns/SKILL.md +132 -699
- package/.claude/skills/regression-testing/SKILL.md +120 -926
- package/.claude/skills/risk-based-testing/SKILL.md +157 -660
- package/.claude/skills/security-testing/SKILL.md +199 -538
- package/.claude/skills/sherlock-review/SKILL.md +163 -699
- package/.claude/skills/shift-left-testing/SKILL.md +161 -465
- package/.claude/skills/shift-right-testing/SKILL.md +161 -519
- package/.claude/skills/six-thinking-hats/SKILL.md +175 -1110
- package/.claude/skills/skills-manifest.json +71 -20
- package/.claude/skills/tdd-london-chicago/SKILL.md +131 -448
- package/.claude/skills/technical-writing/SKILL.md +103 -154
- package/.claude/skills/test-automation-strategy/SKILL.md +166 -772
- package/.claude/skills/test-data-management/SKILL.md +126 -910
- package/.claude/skills/test-design-techniques/SKILL.md +179 -89
- package/.claude/skills/test-environment-management/SKILL.md +136 -91
- package/.claude/skills/test-reporting-analytics/SKILL.md +169 -92
- package/.claude/skills/testability-scoring/SKILL.md +172 -538
- package/.claude/skills/testability-scoring/scripts/generate-html-report.js +0 -0
- package/.claude/skills/visual-testing-advanced/SKILL.md +155 -78
- package/.claude/skills/xp-practices/SKILL.md +151 -587
- package/CHANGELOG.md +48 -0
- package/README.md +23 -16
- package/dist/agents/QXPartnerAgent.d.ts +8 -1
- package/dist/agents/QXPartnerAgent.d.ts.map +1 -1
- package/dist/agents/QXPartnerAgent.js +1174 -112
- package/dist/agents/QXPartnerAgent.js.map +1 -1
- package/dist/agents/lifecycle/AgentLifecycleManager.d.ts.map +1 -1
- package/dist/agents/lifecycle/AgentLifecycleManager.js +34 -31
- package/dist/agents/lifecycle/AgentLifecycleManager.js.map +1 -1
- package/dist/cli/commands/init-claude-md-template.d.ts.map +1 -1
- package/dist/cli/commands/init-claude-md-template.js +14 -0
- package/dist/cli/commands/init-claude-md-template.js.map +1 -1
- package/dist/core/SwarmCoordinator.d.ts +180 -0
- package/dist/core/SwarmCoordinator.d.ts.map +1 -0
- package/dist/core/SwarmCoordinator.js +473 -0
- package/dist/core/SwarmCoordinator.js.map +1 -0
- package/dist/core/metrics/MetricsAggregator.d.ts +228 -0
- package/dist/core/metrics/MetricsAggregator.d.ts.map +1 -0
- package/dist/core/metrics/MetricsAggregator.js +482 -0
- package/dist/core/metrics/MetricsAggregator.js.map +1 -0
- package/dist/core/metrics/index.d.ts +5 -0
- package/dist/core/metrics/index.d.ts.map +1 -0
- package/dist/core/metrics/index.js +11 -0
- package/dist/core/metrics/index.js.map +1 -0
- package/dist/core/optimization/SwarmOptimizer.d.ts +5 -0
- package/dist/core/optimization/SwarmOptimizer.d.ts.map +1 -1
- package/dist/core/optimization/SwarmOptimizer.js +17 -0
- package/dist/core/optimization/SwarmOptimizer.js.map +1 -1
- package/dist/core/orchestration/AdaptiveScheduler.d.ts +190 -0
- package/dist/core/orchestration/AdaptiveScheduler.d.ts.map +1 -0
- package/dist/core/orchestration/AdaptiveScheduler.js +460 -0
- package/dist/core/orchestration/AdaptiveScheduler.js.map +1 -0
- package/dist/core/orchestration/WorkflowOrchestrator.d.ts +13 -0
- package/dist/core/orchestration/WorkflowOrchestrator.d.ts.map +1 -1
- package/dist/core/orchestration/WorkflowOrchestrator.js +32 -0
- package/dist/core/orchestration/WorkflowOrchestrator.js.map +1 -1
- package/dist/core/recovery/CircuitBreaker.d.ts +176 -0
- package/dist/core/recovery/CircuitBreaker.d.ts.map +1 -0
- package/dist/core/recovery/CircuitBreaker.js +382 -0
- package/dist/core/recovery/CircuitBreaker.js.map +1 -0
- package/dist/core/recovery/RecoveryOrchestrator.d.ts +186 -0
- package/dist/core/recovery/RecoveryOrchestrator.d.ts.map +1 -0
- package/dist/core/recovery/RecoveryOrchestrator.js +476 -0
- package/dist/core/recovery/RecoveryOrchestrator.js.map +1 -0
- package/dist/core/recovery/RetryStrategy.d.ts +127 -0
- package/dist/core/recovery/RetryStrategy.d.ts.map +1 -0
- package/dist/core/recovery/RetryStrategy.js +314 -0
- package/dist/core/recovery/RetryStrategy.js.map +1 -0
- package/dist/core/recovery/index.d.ts +8 -0
- package/dist/core/recovery/index.d.ts.map +1 -0
- package/dist/core/recovery/index.js +27 -0
- package/dist/core/recovery/index.js.map +1 -0
- package/dist/core/skills/DependencyResolver.d.ts +99 -0
- package/dist/core/skills/DependencyResolver.d.ts.map +1 -0
- package/dist/core/skills/DependencyResolver.js +260 -0
- package/dist/core/skills/DependencyResolver.js.map +1 -0
- package/dist/core/skills/ManifestGenerator.d.ts +114 -0
- package/dist/core/skills/ManifestGenerator.d.ts.map +1 -0
- package/dist/core/skills/ManifestGenerator.js +449 -0
- package/dist/core/skills/ManifestGenerator.js.map +1 -0
- package/dist/core/skills/index.d.ts +9 -0
- package/dist/core/skills/index.d.ts.map +1 -0
- package/dist/core/skills/index.js +24 -0
- package/dist/core/skills/index.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -9
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +1 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/types/qx.d.ts +39 -7
- package/dist/types/qx.d.ts.map +1 -1
- package/dist/types/qx.js.map +1 -1
- package/dist/visualization/api/RestEndpoints.js +1 -1
- package/dist/visualization/api/RestEndpoints.js.map +1 -1
- package/package.json +13 -55
|
@@ -1,623 +1,272 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: security-testing
|
|
3
|
-
description: Test for security vulnerabilities using OWASP principles
|
|
3
|
+
description: "Test for security vulnerabilities using OWASP principles. Use when conducting security audits, testing auth, or implementing security practices."
|
|
4
|
+
category: specialized-testing
|
|
5
|
+
priority: critical
|
|
6
|
+
tokenEstimate: 1200
|
|
7
|
+
agents: [qe-security-scanner, qe-api-contract-validator, qe-quality-analyzer]
|
|
8
|
+
implementation_status: optimized
|
|
9
|
+
optimization_version: 1.0
|
|
10
|
+
last_optimized: 2025-12-02
|
|
11
|
+
dependencies: []
|
|
12
|
+
quick_reference_card: true
|
|
13
|
+
tags: [security, owasp, sast, dast, vulnerabilities, auth, injection]
|
|
4
14
|
---
|
|
5
15
|
|
|
6
16
|
# Security Testing
|
|
7
17
|
|
|
8
|
-
|
|
18
|
+
<default_to_action>
|
|
19
|
+
When testing security or conducting audits:
|
|
20
|
+
1. TEST OWASP Top 10 vulnerabilities systematically
|
|
21
|
+
2. VALIDATE authentication and authorization on every endpoint
|
|
22
|
+
3. SCAN dependencies for known vulnerabilities (npm audit)
|
|
23
|
+
4. CHECK for injection attacks (SQL, XSS, command)
|
|
24
|
+
5. VERIFY secrets aren't exposed in code/logs
|
|
25
|
+
|
|
26
|
+
**Quick Security Checks:**
|
|
27
|
+
- Access control → Test horizontal/vertical privilege escalation
|
|
28
|
+
- Crypto → Verify password hashing, HTTPS, no sensitive data exposed
|
|
29
|
+
- Injection → Test SQL injection, XSS, command injection
|
|
30
|
+
- Auth → Test weak passwords, session fixation, MFA enforcement
|
|
31
|
+
- Config → Check error messages don't leak info
|
|
32
|
+
|
|
33
|
+
**Critical Success Factors:**
|
|
34
|
+
- Think like an attacker, build like a defender
|
|
35
|
+
- Security is built in, not added at the end
|
|
36
|
+
- Test continuously in CI/CD, not just before release
|
|
37
|
+
</default_to_action>
|
|
38
|
+
|
|
39
|
+
## Quick Reference Card
|
|
40
|
+
|
|
41
|
+
### When to Use
|
|
42
|
+
- Security audits and penetration testing
|
|
43
|
+
- Testing authentication/authorization
|
|
44
|
+
- Validating input sanitization
|
|
45
|
+
- Reviewing security configuration
|
|
46
|
+
|
|
47
|
+
### OWASP Top 10 (2021)
|
|
48
|
+
| # | Vulnerability | Key Test |
|
|
49
|
+
|---|---------------|----------|
|
|
50
|
+
| 1 | Broken Access Control | User A accessing User B's data |
|
|
51
|
+
| 2 | Cryptographic Failures | Plaintext passwords, HTTP |
|
|
52
|
+
| 3 | Injection | SQL/XSS/command injection |
|
|
53
|
+
| 4 | Insecure Design | Rate limiting, session timeout |
|
|
54
|
+
| 5 | Security Misconfiguration | Verbose errors, exposed /admin |
|
|
55
|
+
| 6 | Vulnerable Components | npm audit, outdated packages |
|
|
56
|
+
| 7 | Auth Failures | Weak passwords, no MFA |
|
|
57
|
+
| 8 | Integrity Failures | Unsigned updates, malware |
|
|
58
|
+
| 9 | Logging Failures | No audit trail for breaches |
|
|
59
|
+
| 10 | SSRF | Server fetching internal URLs |
|
|
60
|
+
|
|
61
|
+
### Tools
|
|
62
|
+
| Type | Tool | Purpose |
|
|
63
|
+
|------|------|---------|
|
|
64
|
+
| SAST | SonarQube, Semgrep | Static code analysis |
|
|
65
|
+
| DAST | OWASP ZAP, Burp | Dynamic scanning |
|
|
66
|
+
| Deps | npm audit, Snyk | Dependency vulnerabilities |
|
|
67
|
+
| Secrets | git-secrets, TruffleHog | Secret scanning |
|
|
68
|
+
|
|
69
|
+
### Agent Coordination
|
|
70
|
+
- `qe-security-scanner`: Multi-layer SAST/DAST scanning
|
|
71
|
+
- `qe-api-contract-validator`: API security testing
|
|
72
|
+
- `qe-quality-analyzer`: Security code review
|
|
9
73
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
**Key principle:** Think like an attacker, build like a defender.
|
|
74
|
+
---
|
|
13
75
|
|
|
14
|
-
##
|
|
76
|
+
## Key Vulnerability Tests
|
|
15
77
|
|
|
16
78
|
### 1. Broken Access Control
|
|
17
|
-
**Risk:** Users accessing resources they shouldn't
|
|
18
|
-
|
|
19
|
-
**Test Scenarios:**
|
|
20
79
|
```javascript
|
|
21
|
-
// Horizontal
|
|
80
|
+
// Horizontal escalation - User A accessing User B's data
|
|
22
81
|
test('user cannot access another user\'s order', async () => {
|
|
23
82
|
const userAToken = await login('userA');
|
|
24
83
|
const userBOrder = await createOrder('userB');
|
|
25
|
-
|
|
84
|
+
|
|
26
85
|
const response = await api.get(`/orders/${userBOrder.id}`, {
|
|
27
86
|
headers: { Authorization: `Bearer ${userAToken}` }
|
|
28
87
|
});
|
|
29
|
-
|
|
30
|
-
expect(response.status).toBe(403); // Forbidden
|
|
88
|
+
expect(response.status).toBe(403);
|
|
31
89
|
});
|
|
32
90
|
|
|
33
|
-
// Vertical
|
|
34
|
-
test('regular user cannot access admin
|
|
91
|
+
// Vertical escalation - Regular user accessing admin
|
|
92
|
+
test('regular user cannot access admin', async () => {
|
|
35
93
|
const userToken = await login('regularUser');
|
|
36
|
-
|
|
37
|
-
const response = await api.get('/admin/users', {
|
|
94
|
+
expect((await api.get('/admin/users', {
|
|
38
95
|
headers: { Authorization: `Bearer ${userToken}` }
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
expect(response.status).toBe(403);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Missing authorization check
|
|
45
|
-
test('unauthenticated user cannot create order', async () => {
|
|
46
|
-
const response = await api.post('/orders', orderData);
|
|
47
|
-
expect(response.status).toBe(401); // Unauthorized
|
|
96
|
+
})).status).toBe(403);
|
|
48
97
|
});
|
|
49
98
|
```
|
|
50
99
|
|
|
51
|
-
### 2.
|
|
52
|
-
**Risk:** Sensitive data exposed due to weak encryption
|
|
53
|
-
|
|
54
|
-
**Test Scenarios:**
|
|
55
|
-
```javascript
|
|
56
|
-
test('passwords are hashed, not stored in plaintext', async () => {
|
|
57
|
-
const user = await db.users.create({
|
|
58
|
-
email: 'test@example.com',
|
|
59
|
-
password: 'MyPassword123'
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const storedUser = await db.users.findById(user.id);
|
|
63
|
-
expect(storedUser.password).not.toBe('MyPassword123');
|
|
64
|
-
expect(storedUser.password).toMatch(/^\$2[aby]\$\d{2}\$/); // bcrypt format
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('sensitive data encrypted in transit', async () => {
|
|
68
|
-
const response = await fetch('https://api.example.com/profile');
|
|
69
|
-
expect(response.url).toStartWith('https://'); // Not http://
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('API does not return sensitive data unnecessarily', async () => {
|
|
73
|
-
const response = await api.get('/users/me');
|
|
74
|
-
expect(response.body).not.toHaveProperty('password');
|
|
75
|
-
expect(response.body).not.toHaveProperty('ssn');
|
|
76
|
-
expect(response.body).not.toHaveProperty('creditCard');
|
|
77
|
-
});
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### 3. Injection
|
|
81
|
-
**Risk:** SQL injection, command injection, XSS
|
|
82
|
-
|
|
83
|
-
**Test Scenarios:**
|
|
100
|
+
### 2. Injection Attacks
|
|
84
101
|
```javascript
|
|
85
102
|
// SQL Injection
|
|
86
|
-
test('prevents SQL injection
|
|
87
|
-
const
|
|
88
|
-
const response = await api.get(`/products?search=${
|
|
89
|
-
|
|
90
|
-
// Should return empty or sanitized results, not all products
|
|
91
|
-
expect(response.body.length).toBeLessThan(100);
|
|
103
|
+
test('prevents SQL injection', async () => {
|
|
104
|
+
const malicious = "' OR '1'='1";
|
|
105
|
+
const response = await api.get(`/products?search=${malicious}`);
|
|
106
|
+
expect(response.body.length).toBeLessThan(100); // Not all products
|
|
92
107
|
});
|
|
93
108
|
|
|
94
|
-
//
|
|
95
|
-
test('
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
filename: maliciousFilename,
|
|
99
|
-
content: 'test'
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
expect(response.status).toBe(400); // Rejected
|
|
103
|
-
});
|
|
109
|
+
// XSS
|
|
110
|
+
test('sanitizes HTML output', async () => {
|
|
111
|
+
const xss = '<script>alert("XSS")</script>';
|
|
112
|
+
await api.post('/comments', { text: xss });
|
|
104
113
|
|
|
105
|
-
|
|
106
|
-
test('sanitizes user input in HTML output', async () => {
|
|
107
|
-
const maliciousInput = '<script>alert("XSS")</script>';
|
|
108
|
-
await api.post('/comments', { text: maliciousInput });
|
|
109
|
-
|
|
110
|
-
const response = await api.get('/comments');
|
|
111
|
-
const html = response.body;
|
|
112
|
-
|
|
113
|
-
// Should be escaped, not executable
|
|
114
|
+
const html = (await api.get('/comments')).body;
|
|
114
115
|
expect(html).toContain('<script>');
|
|
115
116
|
expect(html).not.toContain('<script>');
|
|
116
117
|
});
|
|
117
118
|
```
|
|
118
119
|
|
|
119
|
-
###
|
|
120
|
-
**Risk:** Fundamental security flaws in architecture
|
|
121
|
-
|
|
122
|
-
**Review Checklist:**
|
|
123
|
-
- [ ] Principle of least privilege (minimal permissions)
|
|
124
|
-
- [ ] Defense in depth (multiple security layers)
|
|
125
|
-
- [ ] Fail securely (errors don't expose info)
|
|
126
|
-
- [ ] Secure defaults (secure by default, not opt-in)
|
|
127
|
-
|
|
128
|
-
**Test Scenarios:**
|
|
120
|
+
### 3. Cryptographic Failures
|
|
129
121
|
```javascript
|
|
130
|
-
test('
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
email: 'test@example.com',
|
|
137
|
-
password: 'wrong'
|
|
138
|
-
}));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const rateLimited = responses.filter(r => r.status === 429);
|
|
142
|
-
expect(rateLimited.length).toBeGreaterThan(0);
|
|
122
|
+
test('passwords are hashed', async () => {
|
|
123
|
+
await db.users.create({ email: 'test@example.com', password: 'MyPassword123' });
|
|
124
|
+
const user = await db.users.findByEmail('test@example.com');
|
|
125
|
+
|
|
126
|
+
expect(user.password).not.toBe('MyPassword123');
|
|
127
|
+
expect(user.password).toMatch(/^\$2[aby]\$\d{2}\$/); // bcrypt
|
|
143
128
|
});
|
|
144
129
|
|
|
145
|
-
test('
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
await sleep(31 * 60 * 1000);
|
|
150
|
-
|
|
151
|
-
const response = await api.get('/profile', {
|
|
152
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
expect(response.status).toBe(401); // Expired
|
|
130
|
+
test('no sensitive data in API response', async () => {
|
|
131
|
+
const response = await api.get('/users/me');
|
|
132
|
+
expect(response.body).not.toHaveProperty('password');
|
|
133
|
+
expect(response.body).not.toHaveProperty('ssn');
|
|
156
134
|
});
|
|
157
135
|
```
|
|
158
136
|
|
|
159
|
-
###
|
|
160
|
-
**Risk:** Default configs, exposed admin panels, verbose errors
|
|
161
|
-
|
|
162
|
-
**Test Scenarios:**
|
|
137
|
+
### 4. Security Misconfiguration
|
|
163
138
|
```javascript
|
|
164
|
-
test('
|
|
165
|
-
const response = await api.post('/login', {
|
|
166
|
-
|
|
167
|
-
password: 'wrong'
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// Should be generic, not "user doesn't exist" vs "wrong password"
|
|
171
|
-
expect(response.body.error).toBe('Invalid credentials');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test('admin panel not accessible without auth', async () => {
|
|
175
|
-
const response = await fetch('https://example.com/admin');
|
|
176
|
-
expect(response.status).toBe(401);
|
|
139
|
+
test('errors don\'t leak sensitive info', async () => {
|
|
140
|
+
const response = await api.post('/login', { email: 'nonexistent@test.com', password: 'wrong' });
|
|
141
|
+
expect(response.body.error).toBe('Invalid credentials'); // Generic message
|
|
177
142
|
});
|
|
178
143
|
|
|
179
144
|
test('sensitive endpoints not exposed', async () => {
|
|
180
|
-
const endpoints = [
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
];
|
|
184
|
-
|
|
185
|
-
for (let endpoint of endpoints) {
|
|
186
|
-
const response = await fetch(`https://example.com${endpoint}`);
|
|
187
|
-
expect(response.status).not.toBe(200);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### 6. Vulnerable and Outdated Components
|
|
193
|
-
**Risk:** Using libraries with known vulnerabilities
|
|
194
|
-
|
|
195
|
-
**Prevention:**
|
|
196
|
-
```bash
|
|
197
|
-
# Check for vulnerabilities regularly
|
|
198
|
-
npm audit
|
|
199
|
-
npm audit fix
|
|
200
|
-
|
|
201
|
-
# Or with Yarn
|
|
202
|
-
yarn audit
|
|
203
|
-
|
|
204
|
-
# Use Snyk, Dependabot, or similar
|
|
205
|
-
snyk test
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
**CI/CD Integration:**
|
|
209
|
-
```yaml
|
|
210
|
-
# GitHub Actions example
|
|
211
|
-
- name: Security audit
|
|
212
|
-
run: npm audit --audit-level=high
|
|
213
|
-
|
|
214
|
-
- name: Check for outdated packages
|
|
215
|
-
run: npm outdated
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### 7. Identification and Authentication Failures
|
|
219
|
-
**Risk:** Weak passwords, poor session management
|
|
220
|
-
|
|
221
|
-
**Test Scenarios:**
|
|
222
|
-
```javascript
|
|
223
|
-
test('rejects weak passwords', async () => {
|
|
224
|
-
const weakPasswords = ['123456', 'password', 'abc123'];
|
|
225
|
-
|
|
226
|
-
for (let pwd of weakPasswords) {
|
|
227
|
-
const response = await api.post('/register', {
|
|
228
|
-
email: 'test@example.com',
|
|
229
|
-
password: pwd
|
|
230
|
-
});
|
|
231
|
-
expect(response.status).toBe(400);
|
|
145
|
+
const endpoints = ['/debug', '/.env', '/.git', '/admin'];
|
|
146
|
+
for (let ep of endpoints) {
|
|
147
|
+
expect((await fetch(`https://example.com${ep}`)).status).not.toBe(200);
|
|
232
148
|
}
|
|
233
149
|
});
|
|
234
|
-
|
|
235
|
-
test('enforces multi-factor authentication for sensitive ops', async () => {
|
|
236
|
-
const token = await login('user@example.com', 'password');
|
|
237
|
-
|
|
238
|
-
// Try to change email without MFA
|
|
239
|
-
const response = await api.put('/profile/email', {
|
|
240
|
-
newEmail: 'new@example.com'
|
|
241
|
-
}, {
|
|
242
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
expect(response.status).toBe(403); // Requires MFA
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test('prevents session fixation', async () => {
|
|
249
|
-
const sessionBefore = await getSessionId();
|
|
250
|
-
|
|
251
|
-
await login('user@example.com', 'password');
|
|
252
|
-
|
|
253
|
-
const sessionAfter = await getSessionId();
|
|
254
|
-
|
|
255
|
-
// Session ID should change after login
|
|
256
|
-
expect(sessionAfter).not.toBe(sessionBefore);
|
|
257
|
-
});
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### 8. Software and Data Integrity Failures
|
|
261
|
-
**Risk:** Unsigned updates, untrusted CI/CD pipeline
|
|
262
|
-
|
|
263
|
-
**Test Scenarios:**
|
|
264
|
-
```javascript
|
|
265
|
-
test('API responses include integrity check', async () => {
|
|
266
|
-
const response = await api.get('/config');
|
|
267
|
-
|
|
268
|
-
// Should include checksum or signature
|
|
269
|
-
expect(response.headers['x-content-signature']).toBeDefined();
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test('uploaded files are scanned for malware', async () => {
|
|
273
|
-
const maliciousFile = createTestVirusFile(); // EICAR test file
|
|
274
|
-
|
|
275
|
-
const response = await api.post('/upload', maliciousFile);
|
|
276
|
-
|
|
277
|
-
expect(response.status).toBe(400);
|
|
278
|
-
expect(response.body.error).toMatch(/malware|virus/i);
|
|
279
|
-
});
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### 9. Security Logging and Monitoring Failures
|
|
283
|
-
**Risk:** Breaches not detected, no audit trail
|
|
284
|
-
|
|
285
|
-
**Test Scenarios:**
|
|
286
|
-
```javascript
|
|
287
|
-
test('failed login attempts are logged', async () => {
|
|
288
|
-
await api.post('/login', { email: 'test@example.com', password: 'wrong' });
|
|
289
|
-
|
|
290
|
-
const logs = await getLogs('authentication');
|
|
291
|
-
const failedLogin = logs.find(l => l.event === 'login_failed');
|
|
292
|
-
|
|
293
|
-
expect(failedLogin).toBeDefined();
|
|
294
|
-
expect(failedLogin.ip).toBeDefined();
|
|
295
|
-
expect(failedLogin.timestamp).toBeDefined();
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('sensitive operations are audited', async () => {
|
|
299
|
-
const adminToken = await login('admin@example.com', 'password');
|
|
300
|
-
|
|
301
|
-
await api.delete('/users/123', {
|
|
302
|
-
headers: { Authorization: `Bearer ${adminToken}` }
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
const auditLog = await getAuditLog();
|
|
306
|
-
const deletion = auditLog.find(l => l.action === 'user_deleted');
|
|
307
|
-
|
|
308
|
-
expect(deletion.actor).toBe('admin@example.com');
|
|
309
|
-
expect(deletion.target).toBe('123');
|
|
310
|
-
});
|
|
311
150
|
```
|
|
312
151
|
|
|
313
|
-
###
|
|
314
|
-
**Risk:** Attacker makes server request internal resources
|
|
315
|
-
|
|
316
|
-
**Test Scenarios:**
|
|
152
|
+
### 5. Rate Limiting
|
|
317
153
|
```javascript
|
|
318
|
-
test('
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
url: internalUrl
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
expect(response.status).toBe(400); // Rejected
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test('validates and sanitizes URL inputs', async () => {
|
|
329
|
-
const maliciousUrls = [
|
|
330
|
-
'file:///etc/passwd',
|
|
331
|
-
'http://169.254.169.254/latest/meta-data/', // AWS metadata
|
|
332
|
-
'http://metadata.google.internal/', // GCP metadata
|
|
333
|
-
];
|
|
334
|
-
|
|
335
|
-
for (let url of maliciousUrls) {
|
|
336
|
-
const response = await api.post('/fetch-url', { url });
|
|
337
|
-
expect(response.status).toBe(400);
|
|
154
|
+
test('rate limiting prevents brute force', async () => {
|
|
155
|
+
const responses = [];
|
|
156
|
+
for (let i = 0; i < 20; i++) {
|
|
157
|
+
responses.push(await api.post('/login', { email: 'test@example.com', password: 'wrong' }));
|
|
338
158
|
}
|
|
159
|
+
expect(responses.filter(r => r.status === 429).length).toBeGreaterThan(0);
|
|
339
160
|
});
|
|
340
161
|
```
|
|
341
162
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
### Static Analysis (SAST)
|
|
345
|
-
- **SonarQube** - Code quality + security
|
|
346
|
-
- **Semgrep** - Fast, customizable rules
|
|
347
|
-
- **ESLint security plugins** - JavaScript
|
|
348
|
-
- **Bandit** - Python security linter
|
|
349
|
-
|
|
350
|
-
### Dynamic Analysis (DAST)
|
|
351
|
-
- **OWASP ZAP** - Web app security scanner
|
|
352
|
-
- **Burp Suite** - Security testing platform
|
|
353
|
-
- **Nikto** - Web server scanner
|
|
354
|
-
|
|
355
|
-
### Dependency Scanning
|
|
356
|
-
- **npm audit / yarn audit** - Node.js
|
|
357
|
-
- **Snyk** - Multi-language
|
|
358
|
-
- **Dependabot** - GitHub integration
|
|
359
|
-
- **OWASP Dependency-Check** - Multi-language
|
|
360
|
-
|
|
361
|
-
### Secret Scanning
|
|
362
|
-
- **git-secrets** - Prevent secrets in commits
|
|
363
|
-
- **TruffleHog** - Find secrets in git history
|
|
364
|
-
- **GitGuardian** - Real-time secret detection
|
|
365
|
-
|
|
366
|
-
## Penetration Testing Basics
|
|
367
|
-
|
|
368
|
-
### Manual Testing Approach
|
|
369
|
-
|
|
370
|
-
1. **Reconnaissance**
|
|
371
|
-
- Identify attack surface
|
|
372
|
-
- Map endpoints and functionality
|
|
373
|
-
- Note technologies used
|
|
374
|
-
|
|
375
|
-
2. **Enumeration**
|
|
376
|
-
- Discover hidden endpoints
|
|
377
|
-
- Test common paths (/admin, /.env, /api)
|
|
378
|
-
- Check for information disclosure
|
|
379
|
-
|
|
380
|
-
3. **Exploitation**
|
|
381
|
-
- Test for OWASP Top 10
|
|
382
|
-
- Try auth bypasses
|
|
383
|
-
- Test input validation
|
|
384
|
-
|
|
385
|
-
4. **Reporting**
|
|
386
|
-
- Document findings
|
|
387
|
-
- Rate severity
|
|
388
|
-
- Provide remediation steps
|
|
389
|
-
|
|
390
|
-
### Automated Scanning
|
|
391
|
-
|
|
392
|
-
```bash
|
|
393
|
-
# OWASP ZAP baseline scan
|
|
394
|
-
docker run -t owasp/zap2docker-stable zap-baseline.py \
|
|
395
|
-
-t https://example.com \
|
|
396
|
-
-r report.html
|
|
163
|
+
---
|
|
397
164
|
|
|
398
|
-
|
|
399
|
-
nikto -h https://example.com
|
|
165
|
+
## Security Checklist
|
|
400
166
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
167
|
+
### Authentication
|
|
168
|
+
- [ ] Strong password requirements (12+ chars)
|
|
169
|
+
- [ ] Password hashing (bcrypt, scrypt, Argon2)
|
|
170
|
+
- [ ] MFA for sensitive operations
|
|
171
|
+
- [ ] Account lockout after failed attempts
|
|
172
|
+
- [ ] Session ID changes after login
|
|
173
|
+
- [ ] Session timeout
|
|
404
174
|
|
|
405
|
-
|
|
175
|
+
### Authorization
|
|
176
|
+
- [ ] Check authorization on every request
|
|
177
|
+
- [ ] Least privilege principle
|
|
178
|
+
- [ ] No horizontal escalation
|
|
179
|
+
- [ ] No vertical escalation
|
|
406
180
|
|
|
407
|
-
###
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
181
|
+
### Data Protection
|
|
182
|
+
- [ ] HTTPS everywhere
|
|
183
|
+
- [ ] Encrypted at rest
|
|
184
|
+
- [ ] Secrets not in code/logs
|
|
185
|
+
- [ ] PII compliance (GDPR)
|
|
411
186
|
|
|
412
|
-
|
|
413
|
-
|
|
187
|
+
### Input Validation
|
|
188
|
+
- [ ] Server-side validation
|
|
189
|
+
- [ ] Parameterized queries (no SQL injection)
|
|
190
|
+
- [ ] Output encoding (no XSS)
|
|
191
|
+
- [ ] Rate limiting
|
|
414
192
|
|
|
415
|
-
|
|
416
|
-
npm run lint:security
|
|
193
|
+
---
|
|
417
194
|
|
|
418
|
-
|
|
419
|
-
if [ $? -ne 0 ]; then
|
|
420
|
-
echo "Security issues found. Commit aborted."
|
|
421
|
-
exit 1
|
|
422
|
-
fi
|
|
423
|
-
```
|
|
195
|
+
## CI/CD Integration
|
|
424
196
|
|
|
425
|
-
### CI Pipeline
|
|
426
197
|
```yaml
|
|
427
|
-
# GitHub Actions
|
|
198
|
+
# GitHub Actions
|
|
428
199
|
security-checks:
|
|
429
|
-
runs-on: ubuntu-latest
|
|
430
200
|
steps:
|
|
431
|
-
- uses: actions/checkout@v2
|
|
432
|
-
|
|
433
201
|
- name: Dependency audit
|
|
434
202
|
run: npm audit --audit-level=high
|
|
435
|
-
|
|
203
|
+
|
|
436
204
|
- name: SAST scan
|
|
437
205
|
run: npm run sast
|
|
438
|
-
|
|
206
|
+
|
|
439
207
|
- name: Secret scan
|
|
440
208
|
uses: trufflesecurity/trufflehog@main
|
|
441
|
-
|
|
442
|
-
- name: DAST scan
|
|
209
|
+
|
|
210
|
+
- name: DAST scan
|
|
443
211
|
if: github.ref == 'refs/heads/main'
|
|
444
|
-
run:
|
|
445
|
-
docker run owasp/zap2docker-stable \
|
|
446
|
-
zap-baseline.py -t https://staging.example.com
|
|
212
|
+
run: docker run owasp/zap2docker-stable zap-baseline.py -t https://staging.example.com
|
|
447
213
|
```
|
|
448
214
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
### ❌ Client-Side Validation Only
|
|
456
|
-
**Problem:** JavaScript validation can be bypassed
|
|
457
|
-
**Better:** Always validate on server side
|
|
458
|
-
|
|
459
|
-
### ❌ Trusting User Input
|
|
460
|
-
**Problem:** Assuming input is safe
|
|
461
|
-
**Better:** Sanitize, validate, escape all input
|
|
462
|
-
|
|
463
|
-
### ❌ Hardcoded Secrets
|
|
464
|
-
**Problem:** API keys in code
|
|
465
|
-
**Better:** Environment variables, secret management
|
|
466
|
-
|
|
467
|
-
### ❌ Insufficient Logging
|
|
468
|
-
**Problem:** Can't detect or investigate breaches
|
|
469
|
-
**Better:** Log security events, monitor for anomalies
|
|
470
|
-
|
|
471
|
-
## Security Testing Checklist
|
|
472
|
-
|
|
473
|
-
### Authentication
|
|
474
|
-
- [ ] Strong password requirements
|
|
475
|
-
- [ ] Password hashing (bcrypt, scrypt, Argon2)
|
|
476
|
-
- [ ] MFA for sensitive operations
|
|
477
|
-
- [ ] Account lockout after failed attempts
|
|
478
|
-
- [ ] Secure password reset flow
|
|
479
|
-
- [ ] Session timeout
|
|
480
|
-
- [ ] Session ID changes after login
|
|
481
|
-
|
|
482
|
-
### Authorization
|
|
483
|
-
- [ ] Check authorization on every request
|
|
484
|
-
- [ ] Principle of least privilege
|
|
485
|
-
- [ ] No horizontal privilege escalation
|
|
486
|
-
- [ ] No vertical privilege escalation
|
|
487
|
-
- [ ] Resource-level authorization
|
|
215
|
+
**Pre-commit hooks:**
|
|
216
|
+
```bash
|
|
217
|
+
#!/bin/sh
|
|
218
|
+
git-secrets --scan
|
|
219
|
+
npm run lint:security
|
|
220
|
+
```
|
|
488
221
|
|
|
489
|
-
|
|
490
|
-
- [ ] HTTPS everywhere
|
|
491
|
-
- [ ] Sensitive data encrypted at rest
|
|
492
|
-
- [ ] Secrets not in code or logs
|
|
493
|
-
- [ ] PII handling compliance (GDPR, etc.)
|
|
494
|
-
- [ ] Secure file uploads
|
|
495
|
-
- [ ] Safe data deletion
|
|
222
|
+
---
|
|
496
223
|
|
|
497
|
-
|
|
498
|
-
- [ ] Validate all input server-side
|
|
499
|
-
- [ ] Whitelist, not blacklist
|
|
500
|
-
- [ ] Parameterized queries (no SQL injection)
|
|
501
|
-
- [ ] Output encoding (no XSS)
|
|
502
|
-
- [ ] File upload restrictions
|
|
503
|
-
- [ ] Rate limiting
|
|
224
|
+
## Agent-Assisted Security Testing
|
|
504
225
|
|
|
505
|
-
### API Security
|
|
506
|
-
- [ ] Authentication required
|
|
507
|
-
- [ ] Authorization per endpoint
|
|
508
|
-
- [ ] CORS configured properly
|
|
509
|
-
- [ ] Rate limiting
|
|
510
|
-
- [ ] Input validation
|
|
511
|
-
- [ ] Error handling (no info leakage)
|
|
512
|
-
|
|
513
|
-
### Infrastructure
|
|
514
|
-
- [ ] Keep dependencies updated
|
|
515
|
-
- [ ] Remove unnecessary services
|
|
516
|
-
- [ ] Secure defaults
|
|
517
|
-
- [ ] Regular security scans
|
|
518
|
-
- [ ] Secrets management
|
|
519
|
-
- [ ] Security headers configured
|
|
520
|
-
|
|
521
|
-
## Real-World Example: API Security Audit
|
|
522
|
-
|
|
523
|
-
**Scenario:** E-commerce API security review
|
|
524
|
-
|
|
525
|
-
**Findings:**
|
|
526
|
-
|
|
527
|
-
1. **Critical: Authorization Bypass**
|
|
528
|
-
```javascript
|
|
529
|
-
// Vulnerable code
|
|
530
|
-
app.get('/orders/:id', (req, res) => {
|
|
531
|
-
const order = db.orders.findById(req.params.id);
|
|
532
|
-
res.json(order); // No ownership check!
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
// Fixed
|
|
536
|
-
app.get('/orders/:id', auth, (req, res) => {
|
|
537
|
-
const order = db.orders.findById(req.params.id);
|
|
538
|
-
if (order.userId !== req.user.id) {
|
|
539
|
-
return res.status(403).json({ error: 'Forbidden' });
|
|
540
|
-
}
|
|
541
|
-
res.json(order);
|
|
542
|
-
});
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
2. **High: Weak Password Policy**
|
|
546
|
-
- No minimum length
|
|
547
|
-
- No complexity requirements
|
|
548
|
-
- **Fix:** Require 12+ chars, mixed case, numbers
|
|
549
|
-
|
|
550
|
-
3. **Medium: Verbose Error Messages**
|
|
551
|
-
- Stack traces in production
|
|
552
|
-
- **Fix:** Generic errors for clients, detailed logs server-side
|
|
553
|
-
|
|
554
|
-
4. **Low: Missing Security Headers**
|
|
555
|
-
- No HSTS, CSP, X-Frame-Options
|
|
556
|
-
- **Fix:** Add helmet.js middleware
|
|
557
|
-
|
|
558
|
-
**Result:** 4 vulnerabilities fixed before production launch.
|
|
559
|
-
|
|
560
|
-
## Using with QE Agents
|
|
561
|
-
|
|
562
|
-
### Multi-Layer Security Scanning
|
|
563
|
-
|
|
564
|
-
**qe-security-scanner** performs comprehensive security testing:
|
|
565
226
|
```typescript
|
|
566
|
-
//
|
|
567
|
-
|
|
227
|
+
// Comprehensive multi-layer scan
|
|
228
|
+
await Task("Security Scan", {
|
|
568
229
|
target: 'src/',
|
|
569
|
-
layers: {
|
|
570
|
-
sast: true, // Static analysis
|
|
571
|
-
dast: true, // Dynamic analysis
|
|
572
|
-
dependencies: true, // npm audit
|
|
573
|
-
secrets: true, // Secret scanning
|
|
574
|
-
containers: true // Docker image scanning
|
|
575
|
-
},
|
|
230
|
+
layers: { sast: true, dast: true, dependencies: true, secrets: true },
|
|
576
231
|
severity: ['critical', 'high', 'medium']
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
// Returns categorized vulnerabilities
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
### OWASP Top 10 Automated Testing
|
|
232
|
+
}, "qe-security-scanner");
|
|
583
233
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
categories: [
|
|
588
|
-
'broken-access-control',
|
|
589
|
-
'cryptographic-failures',
|
|
590
|
-
'injection',
|
|
591
|
-
'insecure-design',
|
|
592
|
-
'security-misconfiguration'
|
|
593
|
-
],
|
|
234
|
+
// OWASP Top 10 testing
|
|
235
|
+
await Task("OWASP Scan", {
|
|
236
|
+
categories: ['broken-access-control', 'injection', 'cryptographic-failures'],
|
|
594
237
|
depth: 'comprehensive'
|
|
595
|
-
});
|
|
596
|
-
```
|
|
238
|
+
}, "qe-security-scanner");
|
|
597
239
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
```typescript
|
|
601
|
-
// Agent validates security fix
|
|
602
|
-
const validation = await agent.validateFix({
|
|
240
|
+
// Validate fix
|
|
241
|
+
await Task("Validate Fix", {
|
|
603
242
|
vulnerability: 'CVE-2024-12345',
|
|
604
243
|
expectedResolution: 'upgrade package to v2.0.0',
|
|
605
244
|
retestAfterFix: true
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
// Returns: { fixed: true, retestPassed: true, residualRisk: 'low' }
|
|
245
|
+
}, "qe-security-scanner");
|
|
609
246
|
```
|
|
610
247
|
|
|
611
|
-
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Agent Coordination Hints
|
|
251
|
+
|
|
252
|
+
### Memory Namespace
|
|
253
|
+
```
|
|
254
|
+
aqe/security/
|
|
255
|
+
├── scans/* - Scan results
|
|
256
|
+
├── vulnerabilities/* - Found vulnerabilities
|
|
257
|
+
├── fixes/* - Remediation tracking
|
|
258
|
+
└── compliance/* - Compliance status
|
|
259
|
+
```
|
|
612
260
|
|
|
261
|
+
### Fleet Coordination
|
|
613
262
|
```typescript
|
|
614
263
|
const securityFleet = await FleetManager.coordinate({
|
|
615
264
|
strategy: 'security-testing',
|
|
616
265
|
agents: [
|
|
617
|
-
'qe-security-scanner',
|
|
618
|
-
'qe-api-contract-validator',
|
|
619
|
-
'qe-quality-analyzer',
|
|
620
|
-
'qe-deployment-readiness'
|
|
266
|
+
'qe-security-scanner',
|
|
267
|
+
'qe-api-contract-validator',
|
|
268
|
+
'qe-quality-analyzer',
|
|
269
|
+
'qe-deployment-readiness'
|
|
621
270
|
],
|
|
622
271
|
topology: 'parallel'
|
|
623
272
|
});
|
|
@@ -625,21 +274,33 @@ const securityFleet = await FleetManager.coordinate({
|
|
|
625
274
|
|
|
626
275
|
---
|
|
627
276
|
|
|
628
|
-
##
|
|
277
|
+
## Common Mistakes
|
|
629
278
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
- [api-testing-patterns](../api-testing-patterns/) - API security testing
|
|
279
|
+
### ❌ Security by Obscurity
|
|
280
|
+
Hiding admin at `/super-secret-admin` → **Use proper auth**
|
|
633
281
|
|
|
634
|
-
|
|
635
|
-
|
|
282
|
+
### ❌ Client-Side Validation Only
|
|
283
|
+
JavaScript validation can be bypassed → **Always validate server-side**
|
|
636
284
|
|
|
637
|
-
|
|
638
|
-
|
|
285
|
+
### ❌ Trusting User Input
|
|
286
|
+
Assuming input is safe → **Sanitize, validate, escape all input**
|
|
639
287
|
|
|
640
|
-
|
|
288
|
+
### ❌ Hardcoded Secrets
|
|
289
|
+
API keys in code → **Environment variables, secret management**
|
|
290
|
+
|
|
291
|
+
---
|
|
641
292
|
|
|
642
|
-
|
|
293
|
+
## Related Skills
|
|
294
|
+
- [agentic-quality-engineering](../agentic-quality-engineering/) - Security with agents
|
|
295
|
+
- [api-testing-patterns](../api-testing-patterns/) - API security testing
|
|
296
|
+
- [compliance-testing](../compliance-testing/) - GDPR, HIPAA, SOC2
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Remember
|
|
643
301
|
|
|
644
302
|
**Think like an attacker:** What would you try to break? Test that.
|
|
645
303
|
**Build like a defender:** Assume input is malicious until proven otherwise.
|
|
304
|
+
**Test continuously:** Security testing is ongoing, not one-time.
|
|
305
|
+
|
|
306
|
+
**With Agents:** Agents automate vulnerability scanning, track remediation, and validate fixes. Use agents to maintain security posture at scale.
|