@qball-inc/the-bulwark 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/.claude-plugin/plugin.json +43 -0
- package/agents/bulwark-fix-validator.md +633 -0
- package/agents/bulwark-implementer.md +391 -0
- package/agents/bulwark-issue-analyzer.md +308 -0
- package/agents/bulwark-standards-reviewer.md +221 -0
- package/agents/plan-creation-architect.md +323 -0
- package/agents/plan-creation-eng-lead.md +352 -0
- package/agents/plan-creation-po.md +300 -0
- package/agents/plan-creation-qa-critic.md +334 -0
- package/agents/product-ideation-competitive-analyzer.md +298 -0
- package/agents/product-ideation-idea-validator.md +268 -0
- package/agents/product-ideation-market-researcher.md +292 -0
- package/agents/product-ideation-pattern-documenter.md +308 -0
- package/agents/product-ideation-segment-analyzer.md +303 -0
- package/agents/product-ideation-strategist.md +259 -0
- package/agents/statusline-setup.md +97 -0
- package/hooks/hooks.json +59 -0
- package/package.json +45 -0
- package/scripts/hooks/cleanup-stale.sh +13 -0
- package/scripts/hooks/enforce-quality.sh +166 -0
- package/scripts/hooks/implementer-quality.sh +256 -0
- package/scripts/hooks/inject-protocol.sh +52 -0
- package/scripts/hooks/suggest-pipeline.sh +175 -0
- package/scripts/hooks/track-pipeline-start.sh +37 -0
- package/scripts/hooks/track-pipeline-stop.sh +52 -0
- package/scripts/init-rules.sh +35 -0
- package/scripts/init.sh +151 -0
- package/skills/anthropic-validator/SKILL.md +607 -0
- package/skills/anthropic-validator/references/agents-checklist.md +131 -0
- package/skills/anthropic-validator/references/commands-checklist.md +102 -0
- package/skills/anthropic-validator/references/hooks-checklist.md +151 -0
- package/skills/anthropic-validator/references/mcp-checklist.md +136 -0
- package/skills/anthropic-validator/references/plugins-checklist.md +148 -0
- package/skills/anthropic-validator/references/skills-checklist.md +85 -0
- package/skills/assertion-patterns/SKILL.md +296 -0
- package/skills/bug-magnet-data/SKILL.md +284 -0
- package/skills/bug-magnet-data/context/cli-args.md +91 -0
- package/skills/bug-magnet-data/context/db-query.md +104 -0
- package/skills/bug-magnet-data/context/file-contents.md +103 -0
- package/skills/bug-magnet-data/context/http-body.md +91 -0
- package/skills/bug-magnet-data/context/process-spawn.md +123 -0
- package/skills/bug-magnet-data/data/booleans/boundaries.yaml +143 -0
- package/skills/bug-magnet-data/data/collections/arrays.yaml +114 -0
- package/skills/bug-magnet-data/data/collections/objects.yaml +123 -0
- package/skills/bug-magnet-data/data/concurrency/race-conditions.yaml +118 -0
- package/skills/bug-magnet-data/data/concurrency/state-machines.yaml +115 -0
- package/skills/bug-magnet-data/data/dates/boundaries.yaml +137 -0
- package/skills/bug-magnet-data/data/dates/invalid.yaml +132 -0
- package/skills/bug-magnet-data/data/dates/timezone.yaml +118 -0
- package/skills/bug-magnet-data/data/encoding/charset.yaml +79 -0
- package/skills/bug-magnet-data/data/encoding/normalization.yaml +105 -0
- package/skills/bug-magnet-data/data/formats/email.yaml +154 -0
- package/skills/bug-magnet-data/data/formats/json.yaml +187 -0
- package/skills/bug-magnet-data/data/formats/url.yaml +165 -0
- package/skills/bug-magnet-data/data/language-specific/javascript.yaml +182 -0
- package/skills/bug-magnet-data/data/language-specific/python.yaml +174 -0
- package/skills/bug-magnet-data/data/language-specific/rust.yaml +148 -0
- package/skills/bug-magnet-data/data/numbers/boundaries.yaml +161 -0
- package/skills/bug-magnet-data/data/numbers/precision.yaml +89 -0
- package/skills/bug-magnet-data/data/numbers/special.yaml +69 -0
- package/skills/bug-magnet-data/data/strings/boundaries.yaml +109 -0
- package/skills/bug-magnet-data/data/strings/injection.yaml +208 -0
- package/skills/bug-magnet-data/data/strings/special-chars.yaml +190 -0
- package/skills/bug-magnet-data/data/strings/unicode.yaml +139 -0
- package/skills/bug-magnet-data/references/external-lists.md +115 -0
- package/skills/bulwark-brainstorm/SKILL.md +563 -0
- package/skills/bulwark-brainstorm/references/at-teammate-prompts.md +60 -0
- package/skills/bulwark-brainstorm/references/role-critical-analyst.md +78 -0
- package/skills/bulwark-brainstorm/references/role-development-lead.md +66 -0
- package/skills/bulwark-brainstorm/references/role-product-delivery-lead.md +79 -0
- package/skills/bulwark-brainstorm/references/role-product-manager.md +62 -0
- package/skills/bulwark-brainstorm/references/role-project-sme.md +59 -0
- package/skills/bulwark-brainstorm/references/role-technical-architect.md +66 -0
- package/skills/bulwark-research/SKILL.md +298 -0
- package/skills/bulwark-research/references/viewpoint-contrarian.md +63 -0
- package/skills/bulwark-research/references/viewpoint-direct-investigation.md +62 -0
- package/skills/bulwark-research/references/viewpoint-first-principles.md +65 -0
- package/skills/bulwark-research/references/viewpoint-practitioner.md +62 -0
- package/skills/bulwark-research/references/viewpoint-prior-art.md +66 -0
- package/skills/bulwark-scaffold/SKILL.md +330 -0
- package/skills/bulwark-statusline/SKILL.md +161 -0
- package/skills/bulwark-statusline/scripts/statusline.sh +144 -0
- package/skills/bulwark-verify/SKILL.md +519 -0
- package/skills/code-review/SKILL.md +428 -0
- package/skills/code-review/examples/anti-patterns/linting.ts +181 -0
- package/skills/code-review/examples/anti-patterns/security.ts +91 -0
- package/skills/code-review/examples/anti-patterns/standards.ts +195 -0
- package/skills/code-review/examples/anti-patterns/type-safety.ts +108 -0
- package/skills/code-review/examples/recommended/linting.ts +195 -0
- package/skills/code-review/examples/recommended/security.ts +154 -0
- package/skills/code-review/examples/recommended/standards.ts +231 -0
- package/skills/code-review/examples/recommended/type-safety.ts +181 -0
- package/skills/code-review/frameworks/angular.md +218 -0
- package/skills/code-review/frameworks/django.md +235 -0
- package/skills/code-review/frameworks/express.md +207 -0
- package/skills/code-review/frameworks/flask.md +298 -0
- package/skills/code-review/frameworks/generic.md +146 -0
- package/skills/code-review/frameworks/react.md +152 -0
- package/skills/code-review/frameworks/vue.md +244 -0
- package/skills/code-review/references/linting-patterns.md +221 -0
- package/skills/code-review/references/security-patterns.md +125 -0
- package/skills/code-review/references/standards-patterns.md +246 -0
- package/skills/code-review/references/type-safety-patterns.md +130 -0
- package/skills/component-patterns/SKILL.md +131 -0
- package/skills/component-patterns/references/pattern-cli-command.md +118 -0
- package/skills/component-patterns/references/pattern-database.md +166 -0
- package/skills/component-patterns/references/pattern-external-api.md +139 -0
- package/skills/component-patterns/references/pattern-file-parser.md +168 -0
- package/skills/component-patterns/references/pattern-http-server.md +162 -0
- package/skills/component-patterns/references/pattern-process-spawner.md +133 -0
- package/skills/continuous-feedback/SKILL.md +327 -0
- package/skills/continuous-feedback/references/collect-instructions.md +81 -0
- package/skills/continuous-feedback/references/specialize-code-review.md +82 -0
- package/skills/continuous-feedback/references/specialize-general.md +98 -0
- package/skills/continuous-feedback/references/specialize-test-audit.md +81 -0
- package/skills/create-skill/SKILL.md +359 -0
- package/skills/create-skill/references/agent-conventions.md +194 -0
- package/skills/create-skill/references/agent-template.md +195 -0
- package/skills/create-skill/references/content-guidance.md +291 -0
- package/skills/create-skill/references/decision-framework.md +124 -0
- package/skills/create-skill/references/template-pipeline.md +217 -0
- package/skills/create-skill/references/template-reference-heavy.md +111 -0
- package/skills/create-skill/references/template-research.md +210 -0
- package/skills/create-skill/references/template-script-driven.md +172 -0
- package/skills/create-skill/references/template-simple.md +80 -0
- package/skills/create-subagent/SKILL.md +353 -0
- package/skills/create-subagent/references/agent-conventions.md +268 -0
- package/skills/create-subagent/references/content-guidance.md +232 -0
- package/skills/create-subagent/references/decision-framework.md +134 -0
- package/skills/create-subagent/references/template-single-agent.md +192 -0
- package/skills/fix-bug/SKILL.md +241 -0
- package/skills/governance-protocol/SKILL.md +116 -0
- package/skills/init/SKILL.md +341 -0
- package/skills/issue-debugging/SKILL.md +385 -0
- package/skills/issue-debugging/references/anti-patterns.md +245 -0
- package/skills/issue-debugging/references/debug-report-schema.md +227 -0
- package/skills/mock-detection/SKILL.md +511 -0
- package/skills/mock-detection/references/false-positive-prevention.md +402 -0
- package/skills/mock-detection/references/stub-patterns.md +236 -0
- package/skills/pipeline-templates/SKILL.md +215 -0
- package/skills/pipeline-templates/references/code-change-workflow.md +277 -0
- package/skills/pipeline-templates/references/code-review.md +336 -0
- package/skills/pipeline-templates/references/fix-validation.md +421 -0
- package/skills/pipeline-templates/references/new-feature.md +335 -0
- package/skills/pipeline-templates/references/research-brainstorm.md +161 -0
- package/skills/pipeline-templates/references/research-planning.md +257 -0
- package/skills/pipeline-templates/references/test-audit.md +389 -0
- package/skills/pipeline-templates/references/test-execution-fix.md +238 -0
- package/skills/plan-creation/SKILL.md +497 -0
- package/skills/product-ideation/SKILL.md +372 -0
- package/skills/product-ideation/references/analysis-frameworks.md +161 -0
- package/skills/session-handoff/SKILL.md +139 -0
- package/skills/session-handoff/references/examples.md +223 -0
- package/skills/setup-lsp/SKILL.md +312 -0
- package/skills/setup-lsp/references/server-registry.md +85 -0
- package/skills/setup-lsp/references/troubleshooting.md +135 -0
- package/skills/subagent-output-templating/SKILL.md +415 -0
- package/skills/subagent-output-templating/references/examples.md +440 -0
- package/skills/subagent-prompting/SKILL.md +364 -0
- package/skills/subagent-prompting/references/examples.md +342 -0
- package/skills/test-audit/SKILL.md +531 -0
- package/skills/test-audit/references/known-limitations.md +41 -0
- package/skills/test-audit/references/priority-classification.md +30 -0
- package/skills/test-audit/references/prompts/deep-mode-detection.md +83 -0
- package/skills/test-audit/references/prompts/synthesis.md +57 -0
- package/skills/test-audit/references/rewrite-instructions.md +46 -0
- package/skills/test-audit/references/schemas/audit-output.yaml +100 -0
- package/skills/test-audit/references/schemas/diagnostic-output.yaml +49 -0
- package/skills/test-audit/scripts/data-flow-analyzer.ts +509 -0
- package/skills/test-audit/scripts/integration-mock-detector.ts +462 -0
- package/skills/test-audit/scripts/package.json +20 -0
- package/skills/test-audit/scripts/skip-detector.ts +211 -0
- package/skills/test-audit/scripts/verification-counter.ts +295 -0
- package/skills/test-classification/SKILL.md +310 -0
- package/skills/test-fixture-creation/SKILL.md +295 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coding Standards Anti-Patterns
|
|
3
|
+
*
|
|
4
|
+
* DO NOT use these patterns in production code.
|
|
5
|
+
* Each example violates coding standards.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// CS1: Multiple Responsibilities
|
|
9
|
+
// BAD: Function does validation, transformation, persistence, AND notification
|
|
10
|
+
async function processOrderViolation(order: Order) {
|
|
11
|
+
// Validate
|
|
12
|
+
if (!order.items.length) throw new Error('Empty order');
|
|
13
|
+
|
|
14
|
+
// Transform
|
|
15
|
+
order.total = order.items.reduce((sum, i) => sum + i.price, 0);
|
|
16
|
+
|
|
17
|
+
// Persist
|
|
18
|
+
await database.orders.save(order);
|
|
19
|
+
|
|
20
|
+
// Notify
|
|
21
|
+
await emailService.send(order.customer.email, 'Order confirmed');
|
|
22
|
+
|
|
23
|
+
// Track
|
|
24
|
+
await analytics.track('order_processed', order.id);
|
|
25
|
+
|
|
26
|
+
return order;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// CS2: Magic Values
|
|
30
|
+
// BAD: Hardcoded values without explanation
|
|
31
|
+
function calculateDiscountViolation(total: number, customerType: string): number {
|
|
32
|
+
if (total > 100) {
|
|
33
|
+
if (customerType === 'gold') {
|
|
34
|
+
return total * 0.2;
|
|
35
|
+
} else if (customerType === 'silver') {
|
|
36
|
+
return total * 0.1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return total * 0.05;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// CS2: Hidden Mutation
|
|
43
|
+
// BAD: Function modifies input object without indicating it
|
|
44
|
+
function normalizeUserViolation(user: User) {
|
|
45
|
+
user.email = user.email.toLowerCase();
|
|
46
|
+
user.name = user.name.trim();
|
|
47
|
+
user.createdAt = new Date();
|
|
48
|
+
// Returns void but mutated input
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// CS2: Implicit Global State
|
|
52
|
+
// BAD: Function depends on undeclared global
|
|
53
|
+
let currentTransaction: Transaction;
|
|
54
|
+
|
|
55
|
+
function processPaymentViolation(amount: number) {
|
|
56
|
+
// Uses global state without indicating dependency
|
|
57
|
+
currentTransaction.amount = amount;
|
|
58
|
+
currentTransaction.status = 'processing';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// CS2: Side Effect in Getter
|
|
62
|
+
// BAD: Property access triggers external action
|
|
63
|
+
class UserProfileViolation {
|
|
64
|
+
private _views = 0;
|
|
65
|
+
|
|
66
|
+
get viewCount(): number {
|
|
67
|
+
// Side effect: logs to analytics
|
|
68
|
+
analytics.track('profile_viewed', this.id);
|
|
69
|
+
this._views++;
|
|
70
|
+
return this._views;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// CS3: Silent Failure
|
|
75
|
+
// BAD: Catches and swallows error
|
|
76
|
+
async function fetchDataViolation(id: string) {
|
|
77
|
+
try {
|
|
78
|
+
return await api.getData(id);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// Silent failure - no logging, no re-throw
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// CS3: Default on Error
|
|
86
|
+
// BAD: Returns default instead of failing
|
|
87
|
+
function parseConfigViolation(raw: string): Config {
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(raw);
|
|
90
|
+
} catch {
|
|
91
|
+
// Returns invalid default, masking the error
|
|
92
|
+
return { apiUrl: 'http://localhost', timeout: 5000 };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// CS3: Missing Input Validation
|
|
97
|
+
// BAD: Public function without validation
|
|
98
|
+
function calculateAgeViolation(birthYear: number): number {
|
|
99
|
+
// No validation - negative years, future years accepted
|
|
100
|
+
return new Date().getFullYear() - birthYear;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// CS4: Unused Imports
|
|
104
|
+
import { unusedHelper, anotherUnused } from './utils';
|
|
105
|
+
import * as everythingUnused from './everything';
|
|
106
|
+
|
|
107
|
+
// CS4: Unused Variables
|
|
108
|
+
function processItemsViolation(items: Item[]) {
|
|
109
|
+
const unusedConfig = loadConfig();
|
|
110
|
+
const unusedHelper = createHelper();
|
|
111
|
+
|
|
112
|
+
return items.map((item) => item.name);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// CS4: Commented-Out Code
|
|
116
|
+
function handleRequestViolation(req: Request) {
|
|
117
|
+
// const oldWay = processOldWay(req);
|
|
118
|
+
// if (oldWay.success) {
|
|
119
|
+
// return oldWay.result;
|
|
120
|
+
// }
|
|
121
|
+
|
|
122
|
+
// TODO: Remove old code after migration
|
|
123
|
+
// const legacyResult = legacyHandler(req);
|
|
124
|
+
// legacyResult.transformed = true;
|
|
125
|
+
// return legacyResult;
|
|
126
|
+
|
|
127
|
+
return processNewWay(req);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// CS4: Dead Code
|
|
131
|
+
function processWithDeadCode(value: number): number {
|
|
132
|
+
if (value > 0) {
|
|
133
|
+
return value * 2;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return value;
|
|
137
|
+
|
|
138
|
+
// Dead code - unreachable
|
|
139
|
+
console.log('This never executes');
|
|
140
|
+
return value * 3;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Missing Documentation on Public API
|
|
144
|
+
export function calculateComplexMetric(
|
|
145
|
+
data: MetricData[],
|
|
146
|
+
weights: number[],
|
|
147
|
+
normalize: boolean,
|
|
148
|
+
threshold: number
|
|
149
|
+
): ComplexResult {
|
|
150
|
+
// No documentation for public function with many parameters
|
|
151
|
+
// Users have to read the code to understand behavior
|
|
152
|
+
return internalCalculation(data, weights, normalize, threshold);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Outdated Documentation
|
|
156
|
+
/**
|
|
157
|
+
* Processes user data
|
|
158
|
+
* @param user - The user to process
|
|
159
|
+
* @deprecated Use newProcessUser instead
|
|
160
|
+
*/
|
|
161
|
+
function processUserViolation(user: User, options: ProcessOptions): Result {
|
|
162
|
+
// Function signature changed but docs not updated
|
|
163
|
+
// 'options' parameter not documented
|
|
164
|
+
// Not actually deprecated
|
|
165
|
+
return doProcessing(user, options);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Inconsistent Error Handling
|
|
169
|
+
async function fetchAllViolation() {
|
|
170
|
+
// Mix of error handling styles in same function
|
|
171
|
+
const users = await fetch('/users').catch(() => []);
|
|
172
|
+
|
|
173
|
+
let orders;
|
|
174
|
+
try {
|
|
175
|
+
orders = await fetch('/orders');
|
|
176
|
+
} catch (e) {
|
|
177
|
+
orders = [];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const products = await fetch('/products'); // No error handling
|
|
181
|
+
|
|
182
|
+
return { users, orders, products };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Inconsistent Async Style
|
|
186
|
+
async function loadDataViolation() {
|
|
187
|
+
// Mix of .then() and await in same function
|
|
188
|
+
const config = await loadConfig();
|
|
189
|
+
|
|
190
|
+
return fetch(config.url)
|
|
191
|
+
.then((res) => res.json())
|
|
192
|
+
.then((data) => {
|
|
193
|
+
return processData(data);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Safety Anti-Patterns
|
|
3
|
+
*
|
|
4
|
+
* DO NOT use these patterns in production code.
|
|
5
|
+
* Each example demonstrates a type safety hole.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Explicit `any` - bypasses type checking entirely
|
|
9
|
+
function processDataUnsafe(data: any) {
|
|
10
|
+
// No type checking, any access is allowed
|
|
11
|
+
return data.deeply.nested.property.that.might.not.exist;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Return type `any` - loses type information for callers
|
|
15
|
+
async function fetchUserUnsafe(id: string): Promise<any> {
|
|
16
|
+
const response = await fetch(`/api/users/${id}`);
|
|
17
|
+
return response.json();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Variable typed as `any`
|
|
21
|
+
const configUnsafe: any = JSON.parse(rawConfig);
|
|
22
|
+
console.log(configUnsafe.setting.that.might.not.exist);
|
|
23
|
+
|
|
24
|
+
// Implicit `any` from missing parameter type (with noImplicitAny: false)
|
|
25
|
+
function calculateUnsafe(a, b) {
|
|
26
|
+
return a + b; // Could be string concatenation or number addition
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Non-null assertion abuse
|
|
30
|
+
interface User {
|
|
31
|
+
name: string;
|
|
32
|
+
profile?: {
|
|
33
|
+
avatar?: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getAvatarUnsafe(user: User | null): string {
|
|
38
|
+
// BAD: Assumes user and profile exist
|
|
39
|
+
return user!.profile!.avatar!;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Optional chaining gap - mixed `.` and `?.`
|
|
43
|
+
function getNameUnsafe(user: User | null): string {
|
|
44
|
+
// BAD: `profile` could be undefined, but we use `.avatar`
|
|
45
|
+
return user?.profile.avatar ?? 'default';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Truthy check when 0 or "" is valid
|
|
49
|
+
function getCountUnsafe(items?: number): number {
|
|
50
|
+
// BAD: Returns 10 when items is 0
|
|
51
|
+
if (!items) {
|
|
52
|
+
return 10;
|
|
53
|
+
}
|
|
54
|
+
return items;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Double assertion - almost always wrong
|
|
58
|
+
function convertUnsafe(value: string): number {
|
|
59
|
+
// BAD: Forces incompatible type conversion
|
|
60
|
+
return value as unknown as number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Type assertion without validation
|
|
64
|
+
interface Config {
|
|
65
|
+
apiUrl: string;
|
|
66
|
+
timeout: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function loadConfigUnsafe(): Config {
|
|
70
|
+
const raw = JSON.parse(process.env.CONFIG!);
|
|
71
|
+
// BAD: No validation that raw matches Config shape
|
|
72
|
+
return raw as Config;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Widening to `any` then narrowing
|
|
76
|
+
function parseResponseUnsafe(response: Response): UserData {
|
|
77
|
+
const data = response.json() as any;
|
|
78
|
+
// BAD: Loses all type safety
|
|
79
|
+
return data as UserData;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Type assertion on non-overlapping types (via unknown)
|
|
83
|
+
function stringToNumberUnsafe(str: string): number {
|
|
84
|
+
// This compiles but is nonsensical
|
|
85
|
+
return str as unknown as number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Ignoring null/undefined with assertion
|
|
89
|
+
async function getFirstUserUnsafe(): Promise<User> {
|
|
90
|
+
const users = await fetchUsers();
|
|
91
|
+
// BAD: Array could be empty
|
|
92
|
+
return users[0]!;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Untyped catch clause
|
|
96
|
+
async function fetchDataUnsafe() {
|
|
97
|
+
try {
|
|
98
|
+
return await fetch('/api/data');
|
|
99
|
+
} catch (e) {
|
|
100
|
+
// BAD: `e` is `unknown` but used as Error
|
|
101
|
+
console.log(e.message); // TypeScript error in strict mode
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Generic constraint `any` - defeats purpose of generics
|
|
106
|
+
function wrapUnsafe<T extends any>(value: T): { wrapped: T } {
|
|
107
|
+
return { wrapped: value };
|
|
108
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linting Recommended Patterns
|
|
3
|
+
*
|
|
4
|
+
* These examples demonstrate clean code practices.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Early returns instead of deep nesting
|
|
8
|
+
function processOrderSafe(order: Order | null) {
|
|
9
|
+
if (!order) return;
|
|
10
|
+
if (!order.items?.length) return;
|
|
11
|
+
|
|
12
|
+
for (const item of order.items) {
|
|
13
|
+
if (item.quantity <= 0) continue;
|
|
14
|
+
if (item.price <= 0) continue;
|
|
15
|
+
|
|
16
|
+
console.log(item);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Small, focused functions
|
|
21
|
+
async function handleCheckoutSafe(cart: Cart, user: User): Promise<Order> {
|
|
22
|
+
validateCart(cart);
|
|
23
|
+
|
|
24
|
+
const totals = calculateTotals(cart, user);
|
|
25
|
+
const order = createOrder(cart, user, totals);
|
|
26
|
+
|
|
27
|
+
await saveOrder(order, cart);
|
|
28
|
+
await sendNotifications(order, user);
|
|
29
|
+
await updateUserStats(user, totals.total);
|
|
30
|
+
|
|
31
|
+
return order;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function validateCart(cart: Cart): void {
|
|
35
|
+
if (!cart.items.length) throw new Error('Empty cart');
|
|
36
|
+
|
|
37
|
+
for (const item of cart.items) {
|
|
38
|
+
if (!item.available) throw new Error('Item unavailable');
|
|
39
|
+
if (item.quantity > item.stock) throw new Error('Insufficient stock');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Totals {
|
|
44
|
+
subtotal: number;
|
|
45
|
+
discount: number;
|
|
46
|
+
tax: number;
|
|
47
|
+
shipping: number;
|
|
48
|
+
total: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function calculateTotals(cart: Cart, user: User): Totals {
|
|
52
|
+
const subtotal = cart.items.reduce(
|
|
53
|
+
(sum, item) => sum + item.price * item.quantity,
|
|
54
|
+
0
|
|
55
|
+
);
|
|
56
|
+
const discount = user.isPremium ? subtotal * 0.1 : 0;
|
|
57
|
+
const tax = (subtotal - discount) * 0.08;
|
|
58
|
+
const shipping = subtotal > 100 ? 0 : 10;
|
|
59
|
+
const total = subtotal - discount + tax + shipping;
|
|
60
|
+
|
|
61
|
+
return { subtotal, discount, tax, shipping, total };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Descriptive naming
|
|
65
|
+
function transformProductData(rawProducts: RawProduct[]): TransformedProduct[] {
|
|
66
|
+
return rawProducts.map((product) => {
|
|
67
|
+
const discountedPrice = product.basePrice * 2;
|
|
68
|
+
return { ...product, discountedPrice };
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Meaningful variable names
|
|
73
|
+
function calculateRectangleArea(
|
|
74
|
+
width: number,
|
|
75
|
+
height: number,
|
|
76
|
+
margin: number,
|
|
77
|
+
padding: number
|
|
78
|
+
): number {
|
|
79
|
+
const innerWidth = width - margin * 2;
|
|
80
|
+
const innerHeight = height - padding * 2;
|
|
81
|
+
const area = innerWidth * innerHeight;
|
|
82
|
+
return area;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Accurate naming
|
|
86
|
+
const currentUser = getUser(); // Singular, clear
|
|
87
|
+
const validationResult = validate(data); // Describes what it returns
|
|
88
|
+
const permanentDiscountRate = 0.15; // Accurate description
|
|
89
|
+
|
|
90
|
+
// Single responsibility functions
|
|
91
|
+
function parseInput(input: string): ParsedData {
|
|
92
|
+
return JSON.parse(input);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateData(data: ParsedData): void {
|
|
96
|
+
if (!data.name) throw new Error('Missing name');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function transformData(data: ParsedData): TransformedData {
|
|
100
|
+
return { ...data, name: data.name.toUpperCase() };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function saveData(data: TransformedData): void {
|
|
104
|
+
localStorage.setItem('data', JSON.stringify(data));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function notifyDataUpdate(data: TransformedData): void {
|
|
108
|
+
dispatchEvent(new CustomEvent('dataUpdated', { detail: data }));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function processInput(input: string): TransformedData {
|
|
112
|
+
const parsed = parseInput(input);
|
|
113
|
+
validateData(parsed);
|
|
114
|
+
const transformed = transformData(parsed);
|
|
115
|
+
saveData(transformed);
|
|
116
|
+
notifyDataUpdate(transformed);
|
|
117
|
+
return transformed;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Named conditions for complex logic
|
|
121
|
+
function canAccessResource(user: User, resource: Resource): boolean {
|
|
122
|
+
const isAdmin = user.role === 'admin';
|
|
123
|
+
const isManagerOfDepartment =
|
|
124
|
+
user.role === 'manager' && user.department === resource.department;
|
|
125
|
+
const isEmployeeWithPublicAccess =
|
|
126
|
+
user.role === 'employee' &&
|
|
127
|
+
user.department === resource.department &&
|
|
128
|
+
resource.accessLevel === 'public';
|
|
129
|
+
const hasDirectPermission = user.permissions.includes(resource.id);
|
|
130
|
+
const hasSharedAccess =
|
|
131
|
+
resource.sharedWith.includes(user.id) && !resource.revoked;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
isAdmin ||
|
|
135
|
+
isManagerOfDepartment ||
|
|
136
|
+
isEmployeeWithPublicAccess ||
|
|
137
|
+
hasDirectPermission ||
|
|
138
|
+
hasSharedAccess
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Extract shared logic to avoid duplication
|
|
143
|
+
function validateEmail(email: string): void {
|
|
144
|
+
if (!email) throw new Error('Email required');
|
|
145
|
+
if (!email.includes('@')) throw new Error('Invalid email');
|
|
146
|
+
if (email.length > 255) throw new Error('Email too long');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function createUserSafe(data: UserData) {
|
|
150
|
+
validateEmail(data.email);
|
|
151
|
+
// ...create user
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function updateUserSafe(id: string, data: UserData) {
|
|
155
|
+
validateEmail(data.email);
|
|
156
|
+
// ...update user
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Consistent async style
|
|
160
|
+
async function fetchDataConsistent() {
|
|
161
|
+
const response = await fetch('/api/data');
|
|
162
|
+
const data = await response.json();
|
|
163
|
+
return processData(data);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function fetchOtherDataConsistent() {
|
|
167
|
+
const response = await fetch('/api/other');
|
|
168
|
+
const data = await response.json();
|
|
169
|
+
return processData(data);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Named constants instead of magic numbers
|
|
173
|
+
const SHIPPING_RATES = {
|
|
174
|
+
LIGHT_PER_MILE: 0.5,
|
|
175
|
+
LIGHT_BASE: 3.99,
|
|
176
|
+
MEDIUM_PER_MILE: 0.75,
|
|
177
|
+
MEDIUM_BASE: 7.99,
|
|
178
|
+
HEAVY_PER_MILE: 1.25,
|
|
179
|
+
HEAVY_BASE: 14.99,
|
|
180
|
+
} as const;
|
|
181
|
+
|
|
182
|
+
const WEIGHT_THRESHOLDS = {
|
|
183
|
+
LIGHT: 5,
|
|
184
|
+
MEDIUM: 20,
|
|
185
|
+
} as const;
|
|
186
|
+
|
|
187
|
+
function calculateShippingSafe(weight: number, distance: number): number {
|
|
188
|
+
if (weight < WEIGHT_THRESHOLDS.LIGHT) {
|
|
189
|
+
return distance * SHIPPING_RATES.LIGHT_PER_MILE + SHIPPING_RATES.LIGHT_BASE;
|
|
190
|
+
}
|
|
191
|
+
if (weight < WEIGHT_THRESHOLDS.MEDIUM) {
|
|
192
|
+
return distance * SHIPPING_RATES.MEDIUM_PER_MILE + SHIPPING_RATES.MEDIUM_BASE;
|
|
193
|
+
}
|
|
194
|
+
return distance * SHIPPING_RATES.HEAVY_PER_MILE + SHIPPING_RATES.HEAVY_BASE;
|
|
195
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Recommended Patterns
|
|
3
|
+
*
|
|
4
|
+
* These examples demonstrate secure coding practices.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// A03:2021 - SQL Injection Prevention
|
|
8
|
+
// GOOD: Parameterized query
|
|
9
|
+
async function getUserByIdSafe(userId: string) {
|
|
10
|
+
const query = 'SELECT * FROM users WHERE id = ?';
|
|
11
|
+
return db.query(query, [userId]);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A03:2021 - Command Injection Prevention
|
|
15
|
+
// GOOD: Use array-based spawn, validate input
|
|
16
|
+
function processFileSafe(filename: string) {
|
|
17
|
+
const { spawnSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
// Validate filename contains only safe characters
|
|
20
|
+
if (!/^[\w.-]+$/.test(filename)) {
|
|
21
|
+
throw new Error('Invalid filename');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Use array form to prevent shell interpretation
|
|
25
|
+
spawnSync('grep', ['pattern', filename]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// A03:2021 - XSS Prevention
|
|
29
|
+
// GOOD: Use textContent or proper escaping
|
|
30
|
+
function renderCommentSafe(comment: string) {
|
|
31
|
+
const element = document.getElementById('comments');
|
|
32
|
+
element.textContent = comment; // Safe: treated as text, not HTML
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Alternative: Use a sanitization library
|
|
36
|
+
function renderCommentWithSanitization(comment: string) {
|
|
37
|
+
const DOMPurify = require('dompurify');
|
|
38
|
+
const clean = DOMPurify.sanitize(comment);
|
|
39
|
+
document.getElementById('comments').innerHTML = clean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// A02:2021 - Secrets Management
|
|
43
|
+
// GOOD: Use environment variables
|
|
44
|
+
const API_KEY = process.env.API_KEY;
|
|
45
|
+
const DB_PASSWORD = process.env.DB_PASSWORD;
|
|
46
|
+
|
|
47
|
+
// Validate secrets are present at startup
|
|
48
|
+
if (!API_KEY || !DB_PASSWORD) {
|
|
49
|
+
throw new Error('Required environment variables not set');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A02:2021 - Strong Cryptography
|
|
53
|
+
// GOOD: bcrypt for password hashing
|
|
54
|
+
import * as bcrypt from 'bcrypt';
|
|
55
|
+
|
|
56
|
+
async function hashPasswordSafe(password: string): Promise<string> {
|
|
57
|
+
const saltRounds = 12;
|
|
58
|
+
return bcrypt.hash(password, saltRounds);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
62
|
+
return bcrypt.compare(password, hash);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A02:2021 - Secure Random
|
|
66
|
+
// GOOD: crypto.randomBytes for security-sensitive operations
|
|
67
|
+
import * as crypto from 'crypto';
|
|
68
|
+
|
|
69
|
+
function generateTokenSafe(): string {
|
|
70
|
+
return crypto.randomBytes(32).toString('hex');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// A01:2021 - Authorization Check
|
|
74
|
+
// GOOD: Verify ownership before returning data
|
|
75
|
+
async function getDocumentSafe(documentId: string, userId: string) {
|
|
76
|
+
const document = await db.documents.findById(documentId);
|
|
77
|
+
|
|
78
|
+
if (!document) {
|
|
79
|
+
throw new NotFoundError('Document not found');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (document.ownerId !== userId) {
|
|
83
|
+
throw new ForbiddenError('Access denied');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return document;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// A01:2021 - Path Traversal Prevention
|
|
90
|
+
// GOOD: Validate and normalize path
|
|
91
|
+
import * as path from 'path';
|
|
92
|
+
|
|
93
|
+
function readFileSafe(userPath: string) {
|
|
94
|
+
const uploadsDir = path.resolve('./uploads');
|
|
95
|
+
const requestedPath = path.resolve(uploadsDir, userPath);
|
|
96
|
+
|
|
97
|
+
// Ensure path is within uploads directory
|
|
98
|
+
if (!requestedPath.startsWith(uploadsDir)) {
|
|
99
|
+
throw new Error('Invalid path');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const fs = require('fs');
|
|
103
|
+
return fs.readFileSync(requestedPath);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// A07:2021 - Session Regeneration
|
|
107
|
+
// GOOD: Regenerate session after authentication
|
|
108
|
+
async function loginSafe(req: Request, res: Response) {
|
|
109
|
+
const user = await authenticate(req.body);
|
|
110
|
+
|
|
111
|
+
// Regenerate session to prevent fixation
|
|
112
|
+
req.session.regenerate((err) => {
|
|
113
|
+
if (err) throw err;
|
|
114
|
+
req.session.userId = user.id;
|
|
115
|
+
res.json({ success: true });
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// A09:2021 - Safe Logging
|
|
120
|
+
// GOOD: Never log sensitive data
|
|
121
|
+
function authenticateSafe(username: string, password: string) {
|
|
122
|
+
console.log(`Login attempt for user: ${username}`);
|
|
123
|
+
// Password is NOT logged
|
|
124
|
+
const result = verifyCredentials(username, password);
|
|
125
|
+
console.log(`Login result for ${username}: ${result ? 'success' : 'failure'}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// A10:2021 - SSRF Prevention
|
|
129
|
+
// GOOD: Allowlist for external URLs
|
|
130
|
+
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];
|
|
131
|
+
|
|
132
|
+
async function fetchUrlSafe(url: string) {
|
|
133
|
+
const parsedUrl = new URL(url);
|
|
134
|
+
|
|
135
|
+
if (!ALLOWED_HOSTS.includes(parsedUrl.hostname)) {
|
|
136
|
+
throw new Error('URL not in allowlist');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const response = await fetch(url);
|
|
140
|
+
return response.text();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// A05:2021 - Production Error Handling
|
|
144
|
+
// GOOD: Generic error message, log details server-side
|
|
145
|
+
function handleErrorSafe(error: Error, res: Response) {
|
|
146
|
+
// Log full details server-side
|
|
147
|
+
console.error('Internal error:', error);
|
|
148
|
+
|
|
149
|
+
// Return generic message to client
|
|
150
|
+
res.status(500).json({
|
|
151
|
+
error: 'An internal error occurred',
|
|
152
|
+
requestId: generateRequestId(), // For support correlation
|
|
153
|
+
});
|
|
154
|
+
}
|