@yaawn/workflow 0.0.0-stage → 0.3.1
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/README.md +172 -2
- package/agents/instructions/output-styles/yaawn-eli5.instructions.md +45 -0
- package/agents/instructions/yaawn-accessibility.css.instructions.md +58 -0
- package/agents/instructions/yaawn-accessibility.html.instructions.md +90 -0
- package/agents/instructions/yaawn-accessibility.javascript.instructions.md +50 -0
- package/agents/instructions/yaawn-clean-code.typescript.instructions.md +74 -0
- package/agents/instructions/yaawn-function-coupling.typescript.instructions.md +100 -0
- package/agents/instructions/yaawn-html-css-js.html.instructions.md +32 -0
- package/agents/instructions/yaawn-open-source-first.instructions.md +38 -0
- package/agents/instructions/yaawn-personas.instructions.md +18 -0
- package/agents/instructions/yaawn-semantic-html.html.instructions.md +40 -0
- package/agents/instructions/yaawn-vanilla-css.css.instructions.md +50 -0
- package/agents/output-styles/yaawn-eli5.instructions.md +45 -0
- package/agents/personas/yaawn-developer.md +16 -0
- package/agents/personas/yaawn-product-owner.md +18 -0
- package/agents/personas/yaawn-reviewer.md +16 -0
- package/agents/skills/yaawn-clean-code/SKILL.md +61 -0
- package/agents/skills/yaawn-clean-code/references/classes.md +114 -0
- package/agents/skills/yaawn-clean-code/references/comments.md +164 -0
- package/agents/skills/yaawn-clean-code/references/error-handling.md +151 -0
- package/agents/skills/yaawn-clean-code/references/formatting.md +146 -0
- package/agents/skills/yaawn-clean-code/references/functions.md +261 -0
- package/agents/skills/yaawn-clean-code/references/meaningful-names.md +234 -0
- package/agents/skills/yaawn-clean-code/references/objects-and-data-structures.md +130 -0
- package/agents/skills/yaawn-clean-code/references/principles.md +76 -0
- package/agents/skills/yaawn-clean-code/references/simple-design-and-code-smells.md +130 -0
- package/agents/skills/yaawn-clean-code/references/unit-tests.md +114 -0
- package/agents/skills/yaawn-create-plan/SKILL.md +228 -0
- package/agents/skills/yaawn-grill-bug-report/SKILL.md +31 -0
- package/agents/skills/yaawn-grill-bug-report/references/bug-report-checklist.md +27 -0
- package/agents/skills/yaawn-grill-design/SKILL.md +68 -0
- package/agents/skills/yaawn-grill-design/references/design-checklist.md +36 -0
- package/agents/skills/yaawn-grill-other/SKILL.md +31 -0
- package/agents/skills/yaawn-grill-other/references/other-checklist.md +19 -0
- package/agents/skills/yaawn-grill-refactor/SKILL.md +31 -0
- package/agents/skills/yaawn-grill-refactor/references/refactor-checklist.md +26 -0
- package/agents/skills/yaawn-grill-spike/SKILL.md +31 -0
- package/agents/skills/yaawn-grill-spike/references/spike-checklist.md +25 -0
- package/agents/skills/yaawn-grill-story/SKILL.md +34 -0
- package/agents/skills/yaawn-grill-story/references/concept-challenge.md +67 -0
- package/agents/skills/yaawn-grill-story/references/story-checklist.md +43 -0
- package/agents/skills/yaawn-implement-plan/SKILL.md +196 -0
- package/agents/skills/yaawn-implement-plan/assets/fix-prompt.md +66 -0
- package/agents/skills/yaawn-implement-plan/assets/review-prompt.md +89 -0
- package/agents/skills/yaawn-implement-plan/assets/session-prompt.md +80 -0
- package/agents/skills/yaawn-implement-plan/scripts/agent.ts +218 -0
- package/agents/skills/yaawn-implement-plan/scripts/caffeinate.ts +56 -0
- package/agents/skills/yaawn-implement-plan/scripts/groups.ts +233 -0
- package/agents/skills/yaawn-implement-plan/scripts/overnight.ts +772 -0
- package/agents/skills/yaawn-implement-plan/scripts/parallel.ts +1176 -0
- package/agents/skills/yaawn-implement-plan/scripts/processes.ts +53 -0
- package/agents/skills/yaawn-implement-plan/scripts/stages.ts +65 -0
- package/agents/skills/yaawn-implement-plan/scripts/targets.ts +381 -0
- package/agents/skills/yaawn-implement-plan/scripts/worktrees.ts +431 -0
- package/agents/skills/yaawn-owasp-security/SKILL.md +106 -0
- package/agents/skills/yaawn-owasp-security/references/a01-broken-access-control.md +58 -0
- package/agents/skills/yaawn-owasp-security/references/a02-security-misconfiguration.md +56 -0
- package/agents/skills/yaawn-owasp-security/references/a03-software-supply-chain-failures.md +56 -0
- package/agents/skills/yaawn-owasp-security/references/a04-cryptographic-failures.md +67 -0
- package/agents/skills/yaawn-owasp-security/references/a05-injection.md +53 -0
- package/agents/skills/yaawn-owasp-security/references/a06-insecure-design.md +48 -0
- package/agents/skills/yaawn-owasp-security/references/a07-authentication-failures.md +68 -0
- package/agents/skills/yaawn-owasp-security/references/a08-software-data-integrity-failures.md +45 -0
- package/agents/skills/yaawn-owasp-security/references/a09-security-logging-alerting-failures.md +59 -0
- package/agents/skills/yaawn-owasp-security/references/a10-mishandling-exceptional-conditions.md +63 -0
- package/agents/skills/yaawn-prepare-plan/SKILL.md +110 -0
- package/agents/skills/yaawn-save-plan/SKILL.md +65 -0
- package/agents/skills/yaawn-save-plan/scripts/plan-body.ts +331 -0
- package/agents/skills/yaawn-save-plan/scripts/save-plan.ts +443 -0
- package/agents/skills/yaawn-split-story/SKILL.md +189 -0
- package/agents/skills/yaawn-split-story/references/spider.md +64 -0
- package/agents/skills/yaawn-split-story/scripts/create-issue.ts +162 -0
- package/agents/skills/yaawn-verify-executed-plan/SKILL.md +154 -0
- package/agents/skills/yaawn-verify-executed-plan/scripts/overnight.ts +362 -0
- package/agents/skills/yaawn-verify-executed-plan/scripts/pull-request.ts +131 -0
- package/agents/skills/yaawn-write-bug-report/SKILL.md +87 -0
- package/agents/skills/yaawn-write-other/SKILL.md +90 -0
- package/agents/skills/yaawn-write-refactor/SKILL.md +94 -0
- package/agents/skills/yaawn-write-spike/SKILL.md +94 -0
- package/agents/skills/yaawn-write-user-story/SKILL.md +187 -0
- package/agents/skills/yaawn-write-user-story/references/acceptance-criteria.md +186 -0
- package/agents/skills/yaawn-write-user-story/references/five-types-of-value.md +82 -0
- package/agents/skills/yaawn-write-user-story/references/user-story-fundamentals.md +75 -0
- package/agents/yaawn-glossary.md +146 -0
- package/agents/yaawn-grilling.md +75 -0
- package/agents/yaawn-worktrees.md +69 -0
- package/config/install-tasks.json +35 -0
- package/config/settings.json +23 -0
- package/config/wizard.json +128 -0
- package/github/ISSUE_TEMPLATE/yaawn-bug-report.md +59 -0
- package/github/ISSUE_TEMPLATE/yaawn-other.md +38 -0
- package/github/ISSUE_TEMPLATE/yaawn-refactor.md +38 -0
- package/github/ISSUE_TEMPLATE/yaawn-spike.md +16 -0
- package/github/ISSUE_TEMPLATE/yaawn-story.md +56 -0
- package/package.json +33 -4
- package/scripts/install.js +73 -0
- package/yaawn/gitignore +1 -0
- package/yaawn/yaawn-definition-of-done.md +5 -0
- package/yaawn/yaawn-project-glossary.md +7 -0
- package/yaawn/yaawn-user-roles.md +3 -0
- package/yaawn/yaawn-verification-commands.md +7 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { uncommittedChangesAsync } from "./targets.js";
|
|
5
|
+
|
|
6
|
+
// Shared by yaawn-implement-plan (creates one git worktree per issue) and
|
|
7
|
+
// yaawn-verify-executed-plan (checks it verifies in the issue's worktree).
|
|
8
|
+
//
|
|
9
|
+
// Layout: the project root holds a bare `.git` repository, the main working
|
|
10
|
+
// copy is a normal checkout at `<project>/main`, and every issue worktree is
|
|
11
|
+
// a sibling of it at `<project>/yaawn-<issue>-<slug>`.
|
|
12
|
+
|
|
13
|
+
export type WorktreeCheckStatus = "ok" | "warn" | "fail";
|
|
14
|
+
|
|
15
|
+
export interface WorktreeCheck {
|
|
16
|
+
status: WorktreeCheckStatus;
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WorktreeLayout {
|
|
21
|
+
issue: number;
|
|
22
|
+
mainCopy: string;
|
|
23
|
+
registered: Iterable<string>;
|
|
24
|
+
currentToplevel: string;
|
|
25
|
+
// Resolved by branch (yaawn/<issue>), not by the title slug — the slug in
|
|
26
|
+
// the directory name is for humans only.
|
|
27
|
+
ownWorktree?: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface WorktreeEntry {
|
|
31
|
+
path: string;
|
|
32
|
+
branch: string | null;
|
|
33
|
+
bare: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AheadBehind {
|
|
37
|
+
ahead: number;
|
|
38
|
+
behind: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// How the main copy's HEAD relates to its upstream branch.
|
|
42
|
+
export type MainCopyState =
|
|
43
|
+
| { kind: "up-to-date" }
|
|
44
|
+
| { kind: "ahead"; ahead: number }
|
|
45
|
+
| { kind: "behind"; behind: number }
|
|
46
|
+
| { kind: "diverged"; ahead: number; behind: number };
|
|
47
|
+
|
|
48
|
+
export type MainCopyCheck =
|
|
49
|
+
| { ok: true; state: MainCopyState }
|
|
50
|
+
| { ok: false; reason: string };
|
|
51
|
+
|
|
52
|
+
export type MainCopySync =
|
|
53
|
+
| { ok: true; state: MainCopyState; updated: boolean }
|
|
54
|
+
| { ok: false; reason: string };
|
|
55
|
+
|
|
56
|
+
const worktreePrefix = "worktree ";
|
|
57
|
+
const branchPrefix = "branch refs/heads/";
|
|
58
|
+
const maxSlugLength = 40;
|
|
59
|
+
const fallbackMainCopyName = "main";
|
|
60
|
+
const fallbackDefaultBranches = ["main", "master"];
|
|
61
|
+
|
|
62
|
+
const aheadBehindPattern = /^(\d+)\s+(\d+)$/;
|
|
63
|
+
const execFileAsync = promisify(execFile);
|
|
64
|
+
|
|
65
|
+
export const branchName = (issue: number): string => `yaawn/${issue}`;
|
|
66
|
+
|
|
67
|
+
export function git(args: string[]): string {
|
|
68
|
+
return execFileSync("git", args, {
|
|
69
|
+
encoding: "utf-8",
|
|
70
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- names
|
|
75
|
+
|
|
76
|
+
function assertIssueNumber(issue: number): void {
|
|
77
|
+
if (!Number.isSafeInteger(issue) || issue <= 0) {
|
|
78
|
+
throw new Error(`issue must be a positive integer, got ${issue}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Only [a-z0-9-] survives, so the title can never add `..`, `/`, or
|
|
83
|
+
// whitespace to a path.
|
|
84
|
+
export function titleSlug(title: string): string {
|
|
85
|
+
return title
|
|
86
|
+
.normalize("NFKD")
|
|
87
|
+
.replace(/\p{M}/gu, "")
|
|
88
|
+
.toLowerCase()
|
|
89
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
90
|
+
.replace(/^-+|-+$/g, "")
|
|
91
|
+
.slice(0, maxSlugLength)
|
|
92
|
+
.replace(/-+$/, "");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function worktreeName(issue: number, title: string): string {
|
|
96
|
+
assertIssueNumber(issue);
|
|
97
|
+
const slug = titleSlug(title);
|
|
98
|
+
return slug ? `yaawn-${issue}-${slug}` : `yaawn-${issue}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// --- porcelain
|
|
102
|
+
|
|
103
|
+
function entryFromBlock(lines: string[]): WorktreeEntry | null {
|
|
104
|
+
const pathLine = lines.find((line) => line.startsWith(worktreePrefix));
|
|
105
|
+
if (pathLine === undefined) return null;
|
|
106
|
+
const branchLine = lines.find((line) => line.startsWith(branchPrefix));
|
|
107
|
+
return {
|
|
108
|
+
path: pathLine.slice(worktreePrefix.length),
|
|
109
|
+
branch:
|
|
110
|
+
branchLine === undefined ? null : branchLine.slice(branchPrefix.length),
|
|
111
|
+
bare: lines.includes("bare"),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function entriesFromPorcelain(porcelain: string): WorktreeEntry[] {
|
|
116
|
+
return porcelain
|
|
117
|
+
.split(/\n\s*\n/)
|
|
118
|
+
.map((block) => entryFromBlock(block.split("\n")))
|
|
119
|
+
.filter((entry): entry is WorktreeEntry => entry !== null);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function worktreesFromPorcelain(porcelain: string): string[] {
|
|
123
|
+
return entriesFromPorcelain(porcelain).map((entry) => entry.path);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// git lists the bare repository first, whichever worktree it runs in.
|
|
127
|
+
export function isBareLayout(porcelain: string): boolean {
|
|
128
|
+
return entriesFromPorcelain(porcelain)[0]?.bare === true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// git names the bare repository after the project root; a `.git` suffix is
|
|
132
|
+
// stripped defensively.
|
|
133
|
+
export function projectRootFromPorcelain(porcelain: string): string {
|
|
134
|
+
const [first] = worktreesFromPorcelain(porcelain);
|
|
135
|
+
if (!first) throw new Error("git worktree list returned no worktree");
|
|
136
|
+
return path.basename(first) === ".git" ? path.dirname(first) : first;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The main working copy is the registered worktree on the default branch.
|
|
140
|
+
export function mainCopyFromPorcelain(
|
|
141
|
+
porcelain: string,
|
|
142
|
+
defaultBranches: string[] = fallbackDefaultBranches,
|
|
143
|
+
): string {
|
|
144
|
+
const checkouts = entriesFromPorcelain(porcelain).filter(
|
|
145
|
+
(entry) => !entry.bare,
|
|
146
|
+
);
|
|
147
|
+
const onDefault = defaultBranches
|
|
148
|
+
.map((branch) => checkouts.find((entry) => entry.branch === branch))
|
|
149
|
+
.find((entry) => entry !== undefined);
|
|
150
|
+
return (
|
|
151
|
+
onDefault?.path ??
|
|
152
|
+
path.join(projectRootFromPorcelain(porcelain), fallbackMainCopyName)
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function branchWorktreeFromPorcelain(
|
|
157
|
+
porcelain: string,
|
|
158
|
+
branch: string,
|
|
159
|
+
): string | null {
|
|
160
|
+
const checkout = entriesFromPorcelain(porcelain).find(
|
|
161
|
+
(entry) => !entry.bare && entry.branch === branch,
|
|
162
|
+
);
|
|
163
|
+
return checkout?.path ?? null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function worktreePathFor(
|
|
167
|
+
projectRoot: string,
|
|
168
|
+
issue: number,
|
|
169
|
+
title: string,
|
|
170
|
+
): string {
|
|
171
|
+
return path.join(projectRoot, worktreeName(issue, title));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// --- git
|
|
175
|
+
|
|
176
|
+
export const listPorcelain = (): string => git(["worktree", "list", "--porcelain"]);
|
|
177
|
+
|
|
178
|
+
export const currentToplevel = (): string =>
|
|
179
|
+
git(["rev-parse", "--show-toplevel"]).trim();
|
|
180
|
+
|
|
181
|
+
// The bare repository's HEAD names the default branch it was cloned with.
|
|
182
|
+
function bareHeadBranch(): string | null {
|
|
183
|
+
try {
|
|
184
|
+
const commonDir = git([
|
|
185
|
+
"rev-parse",
|
|
186
|
+
"--path-format=absolute",
|
|
187
|
+
"--git-common-dir",
|
|
188
|
+
]).trim();
|
|
189
|
+
const head = git([
|
|
190
|
+
"--git-dir",
|
|
191
|
+
commonDir,
|
|
192
|
+
"symbolic-ref",
|
|
193
|
+
"--quiet",
|
|
194
|
+
"--short",
|
|
195
|
+
"HEAD",
|
|
196
|
+
]).trim();
|
|
197
|
+
return head || null;
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function defaultBranches(): string[] {
|
|
204
|
+
const head = bareHeadBranch();
|
|
205
|
+
return [...new Set([...(head ? [head] : []), ...fallbackDefaultBranches])];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function usesBareLayout(): boolean {
|
|
209
|
+
return isBareLayout(listPorcelain());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function worktreePath(issue: number, title: string): string {
|
|
213
|
+
return worktreePathFor(
|
|
214
|
+
projectRootFromPorcelain(listPorcelain()),
|
|
215
|
+
issue,
|
|
216
|
+
title,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// The branch's existing checkout wins over the title-derived path, so a
|
|
221
|
+
// relaunch resumes the earlier worktree even after the issue was renamed.
|
|
222
|
+
export function laneWorktreePath(issue: number, title: string): string {
|
|
223
|
+
const porcelain = listPorcelain();
|
|
224
|
+
return (
|
|
225
|
+
branchWorktreeFromPorcelain(porcelain, branchName(issue)) ??
|
|
226
|
+
worktreePathFor(projectRootFromPorcelain(porcelain), issue, title)
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function registeredWorktrees(): Set<string> {
|
|
231
|
+
return new Set(worktreesFromPorcelain(listPorcelain()));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const normalized = (worktree: string): string => path.resolve(worktree);
|
|
235
|
+
|
|
236
|
+
const samePath = (left: string, right: string): boolean =>
|
|
237
|
+
normalized(left) === normalized(right);
|
|
238
|
+
|
|
239
|
+
export function runsInOwnWorktree(issue: number, title: string): boolean {
|
|
240
|
+
return samePath(worktreePath(issue, title), currentToplevel());
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// --- classification
|
|
244
|
+
|
|
245
|
+
function checkOwnWorktree(own: string, toplevel: string): WorktreeCheck {
|
|
246
|
+
return samePath(own, toplevel)
|
|
247
|
+
? { status: "ok", message: `verifying in the issue's worktree ${own}` }
|
|
248
|
+
: {
|
|
249
|
+
status: "fail",
|
|
250
|
+
message: `run verification in the issue's worktree ${own}, not in ${toplevel}`,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function checkMainCopy(
|
|
255
|
+
issue: number,
|
|
256
|
+
mainCopy: string,
|
|
257
|
+
toplevel: string,
|
|
258
|
+
): WorktreeCheck {
|
|
259
|
+
return samePath(mainCopy, toplevel)
|
|
260
|
+
? {
|
|
261
|
+
status: "warn",
|
|
262
|
+
message: `#${issue} has no git worktree of its own — verifying the main copy`,
|
|
263
|
+
}
|
|
264
|
+
: {
|
|
265
|
+
status: "fail",
|
|
266
|
+
message: `#${issue} has no git worktree of its own — run verification in the main copy ${mainCopy}, not in ${toplevel}`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function classifyWorktree(layout: WorktreeLayout): WorktreeCheck {
|
|
271
|
+
const { issue, mainCopy, registered, ownWorktree, currentToplevel } = layout;
|
|
272
|
+
const own =
|
|
273
|
+
ownWorktree != null &&
|
|
274
|
+
[...registered].some((worktree) => samePath(worktree, ownWorktree))
|
|
275
|
+
? ownWorktree
|
|
276
|
+
: null;
|
|
277
|
+
return own !== null
|
|
278
|
+
? checkOwnWorktree(own, currentToplevel)
|
|
279
|
+
: checkMainCopy(issue, mainCopy, currentToplevel);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function checkWorktree(issue: number): WorktreeCheck {
|
|
283
|
+
const porcelain = listPorcelain();
|
|
284
|
+
return classifyWorktree({
|
|
285
|
+
issue,
|
|
286
|
+
mainCopy: mainCopyFromPorcelain(porcelain, defaultBranches()),
|
|
287
|
+
registered: worktreesFromPorcelain(porcelain),
|
|
288
|
+
currentToplevel: currentToplevel(),
|
|
289
|
+
ownWorktree: branchWorktreeFromPorcelain(porcelain, branchName(issue)),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// --- main copy sync
|
|
294
|
+
|
|
295
|
+
// Parses `git rev-list --left-right --count HEAD...@{u}`: "<ahead>\t<behind>".
|
|
296
|
+
export function parseAheadBehind(output: string): AheadBehind | null {
|
|
297
|
+
const match = aheadBehindPattern.exec(output.trim());
|
|
298
|
+
if (match === null) return null;
|
|
299
|
+
return { ahead: Number(match[1]), behind: Number(match[2]) };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function classifyMainCopyState({
|
|
303
|
+
ahead,
|
|
304
|
+
behind,
|
|
305
|
+
}: AheadBehind): MainCopyState {
|
|
306
|
+
if (ahead > 0 && behind > 0) return { kind: "diverged", ahead, behind };
|
|
307
|
+
if (behind > 0) return { kind: "behind", behind };
|
|
308
|
+
if (ahead > 0) return { kind: "ahead", ahead };
|
|
309
|
+
return { kind: "up-to-date" };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const commits = (count: number): string =>
|
|
313
|
+
count === 1 ? "1 commit" : `${count} commits`;
|
|
314
|
+
|
|
315
|
+
export function describeMainCopyState(state: MainCopyState): string {
|
|
316
|
+
switch (state.kind) {
|
|
317
|
+
case "up-to-date":
|
|
318
|
+
return "main copy is up to date";
|
|
319
|
+
case "ahead":
|
|
320
|
+
return `main copy is ${commits(state.ahead)} ahead — lanes start from it`;
|
|
321
|
+
case "behind":
|
|
322
|
+
return `main copy is ${commits(state.behind)} behind — the launch will bring it up to date`;
|
|
323
|
+
case "diverged":
|
|
324
|
+
return `main copy has diverged (${state.ahead} ahead, ${state.behind} behind) — reconcile it by hand before launching`;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// git's stderr may echo a remote URL with credentials in it.
|
|
329
|
+
function redactedGitError(error: unknown): string {
|
|
330
|
+
const stderr =
|
|
331
|
+
typeof error === "object" && error !== null && "stderr" in error
|
|
332
|
+
? String(error.stderr)
|
|
333
|
+
: "";
|
|
334
|
+
const line = stderr
|
|
335
|
+
.split("\n")
|
|
336
|
+
.map((text) => text.trim())
|
|
337
|
+
.find(Boolean);
|
|
338
|
+
return line ? `: ${line.replace(/\/\/[^/\s@]+@/g, "//***@")}` : "";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Never waits for a credential prompt: an unattended launch would hang.
|
|
342
|
+
async function gitAsync(args: string[]): Promise<string> {
|
|
343
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
344
|
+
encoding: "utf-8",
|
|
345
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
346
|
+
});
|
|
347
|
+
return stdout;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function hasUpstream(): Promise<boolean> {
|
|
351
|
+
try {
|
|
352
|
+
await gitAsync(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
|
|
353
|
+
return true;
|
|
354
|
+
} catch {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function mainCopyAheadBehind(): Promise<AheadBehind | null> {
|
|
360
|
+
try {
|
|
361
|
+
return parseAheadBehind(
|
|
362
|
+
await gitAsync(["rev-list", "--left-right", "--count", "HEAD...@{u}"]),
|
|
363
|
+
);
|
|
364
|
+
} catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Fetches the main copy's upstream and compares; it never changes the main copy.
|
|
370
|
+
export async function fetchMainCopy(): Promise<MainCopyCheck> {
|
|
371
|
+
if (!(await hasUpstream())) {
|
|
372
|
+
return {
|
|
373
|
+
ok: false,
|
|
374
|
+
reason:
|
|
375
|
+
"main copy's branch has no upstream — set one with `git branch --set-upstream-to`",
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
await gitAsync(["fetch", "--quiet"]);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
return {
|
|
382
|
+
ok: false,
|
|
383
|
+
reason: `could not fetch the main copy's upstream — remote unreachable${redactedGitError(error)}`,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
const counts = await mainCopyAheadBehind();
|
|
387
|
+
return counts === null
|
|
388
|
+
? { ok: false, reason: "could not compare the main copy with its upstream" }
|
|
389
|
+
: { ok: true, state: classifyMainCopyState(counts) };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function blockedUpdateReason(behind: number): string {
|
|
393
|
+
return `main copy is ${commits(behind)} behind and uncommitted changes block the update — commit or stash them first`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async function uncommittedChangesFailure(behind: number): Promise<string | null> {
|
|
397
|
+
try {
|
|
398
|
+
return (await uncommittedChangesAsync()).trim()
|
|
399
|
+
? blockedUpdateReason(behind)
|
|
400
|
+
: null;
|
|
401
|
+
} catch {
|
|
402
|
+
return "could not list uncommitted changes in the main copy";
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function fastForwardMainCopy(): Promise<string | null> {
|
|
407
|
+
try {
|
|
408
|
+
await gitAsync(["merge", "--ff-only", "--quiet", "@{u}"]);
|
|
409
|
+
return null;
|
|
410
|
+
} catch (error) {
|
|
411
|
+
return `could not fast-forward the main copy${redactedGitError(error)}`;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Fails closed: the main copy is only fast-forwarded when it is behind and
|
|
416
|
+
// clean; any unknown state stops the launch before a worktree exists.
|
|
417
|
+
export async function syncMainCopy(): Promise<MainCopySync> {
|
|
418
|
+
const check = await fetchMainCopy();
|
|
419
|
+
if (!check.ok) return check;
|
|
420
|
+
const { state } = check;
|
|
421
|
+
if (state.kind === "diverged") {
|
|
422
|
+
return { ok: false, reason: describeMainCopyState(state) };
|
|
423
|
+
}
|
|
424
|
+
if (state.kind !== "behind") return { ok: true, state, updated: false };
|
|
425
|
+
const failure =
|
|
426
|
+
(await uncommittedChangesFailure(state.behind)) ??
|
|
427
|
+
(await fastForwardMainCopy());
|
|
428
|
+
return failure === null
|
|
429
|
+
? { ok: true, state, updated: true }
|
|
430
|
+
: { ok: false, reason: failure };
|
|
431
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yaawn-owasp-security
|
|
3
|
+
description: "Background security lens based on the OWASP Top 10:2025. Read silently when proposing designs, plans, or code — bake mitigations in autonomously. Only escalate to the developer when a risk cannot be mitigated without their input. Use when: creating an implementation plan (/yaawn-create-plan), grilling a design (/yaawn-grill-design), implementing a plan (/yaawn-implement-plan), reviewing code for security risks, or when the user asks to make something secure, check for vulnerabilities, harden, or audit code."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# OWASP Top 10:2025 Security Lens
|
|
8
|
+
|
|
9
|
+
Applies the [OWASP Top 10:2025](https://top10.owasp.org/2025/) as a background security lens across the yaawn plan pipeline. This is **not** a checklist to grill through. The agent reads the relevant risks silently, bakes mitigations into every design decision, plan step, and line of code it proposes — and only surfaces a risk to the developer when it cannot resolve it on its own.
|
|
10
|
+
|
|
11
|
+
This skill **complements** the pipeline skills. Load it alongside `/yaawn-create-plan`, `/yaawn-grill-design`, or `/yaawn-implement-plan`.
|
|
12
|
+
|
|
13
|
+
## How It Works
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Agent reads the issue / design / code being proposed
|
|
17
|
+
│
|
|
18
|
+
▼
|
|
19
|
+
Match against the routing table below → load ONLY the relevant reference files
|
|
20
|
+
│
|
|
21
|
+
▼
|
|
22
|
+
Silently check the proposal against each loaded risk
|
|
23
|
+
│
|
|
24
|
+
┌────┴────┐
|
|
25
|
+
▼ ▼
|
|
26
|
+
Covered Cannot mitigate alone
|
|
27
|
+
│ │
|
|
28
|
+
▼ ▼
|
|
29
|
+
Bake the Escalate with a recommended
|
|
30
|
+
mitigation mitigation — never ask an
|
|
31
|
+
into the open-ended "how do you want
|
|
32
|
+
proposal to handle security?"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Routing Table
|
|
36
|
+
|
|
37
|
+
Read the issue, design decisions, or code being proposed. Match what it touches against the keywords below. Load **only** the reference files that match. Each reference file is self-contained — it has the OWASP risk details, design-phase guidance, and implementation checks for that risk.
|
|
38
|
+
|
|
39
|
+
| What the plan or code touches | Keywords to match | Load |
|
|
40
|
+
| ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| New API endpoints, routes, permissions, roles, data access by ID, cross-domain calls, CORS, redirects | endpoint, route, permission, role, authorize, IDOR, ownership, CORS, redirect, URL routing | [a01-broken-access-control.md](./references/a01-broken-access-control.md) |
|
|
42
|
+
| New config, env vars, dependencies, services, error messages, cookies, headers, deployment, debug | config, env, cookie, header, CSP, HSTS, debug, sample, default, hardening, secret | [a02-security-misconfiguration.md](./references/a02-security-misconfiguration.md) |
|
|
43
|
+
| Adding dependencies, CI/CD changes, build scripts, Docker images, package registries | dependency, npm, package, CI, pipeline, action, Docker, image, registry, lockfile, SBOM | [a03-software-supply-chain-failures.md](./references/a03-software-supply-chain-failures.md) |
|
|
44
|
+
| Passwords, tokens, encryption, hashing, randomness, sensitive data storage, TLS, keys | password, token, encrypt, hash, random, salt, key, TLS, HSTS, PII, sensitive data, credential | [a04-cryptographic-failures.md](./references/a04-cryptographic-failures.md) |
|
|
45
|
+
| User input, database queries, HTML rendering, file uploads, shell commands, redirects, ORM | input, query, SQL, ORM, render, XSS, upload, file, eval, shell, command, sanitize, escape, validate | [a05-injection.md](./references/a05-injection.md) |
|
|
46
|
+
| New trust boundaries, auth flows, payment flows, business logic, tenant isolation, client-side enforcement | trust boundary, auth flow, payment, business logic, tenant, isolation, client-side, misuse case, abuse case | [a06-insecure-design.md](./references/a06-insecure-design.md) |
|
|
47
|
+
| Login, registration, sessions, password reset, MFA, JWT, SSO, account enumeration | login, signin, register, session, password reset, MFA, 2FA, JWT, SSO, token, brute force, lockout | [a07-authentication-failures.md](./references/a07-authentication-failures.md) |
|
|
48
|
+
| External data sources, webhooks, deserialization, auto-updates, plugins, CI/CD integrity | webhook, callback, external data, deserialize, serialize, auto-update, plugin, extension, signature, checksum | [a08-software-data-integrity-failures.md](./references/a08-software-data-integrity-failures.md) |
|
|
49
|
+
| Any code path handling security events, errors, or transactions | log, audit, alert, monitor, security event, auth failure, access denied, validation failure | [a09-security-logging-alerting-failures.md](./references/a09-security-logging-alerting-failures.md) |
|
|
50
|
+
| Error handling, multi-step transactions, resource cleanup, rate limiting, concurrency | error, exception, catch, throw, rollback, transaction, finally, cleanup, rate limit, throttle, concurrency, race | [a10-mishandling-exceptional-conditions.md](./references/a10-mishandling-exceptional-conditions.md) |
|
|
51
|
+
|
|
52
|
+
### Always consider
|
|
53
|
+
|
|
54
|
+
These risks apply broadly — load them whenever the plan or code has the corresponding shape, even if no specific keyword matches:
|
|
55
|
+
|
|
56
|
+
- **A06 (Insecure Design)** — load whenever the proposal introduces a trust boundary, a security-critical flow, or client-side enforcement of a security control.
|
|
57
|
+
- **A09 (Logging)** — load whenever the code handles authentication, authorization, input validation, or transactions. These events must be logged.
|
|
58
|
+
- **A10 (Exception Handling)** — load whenever the code has error paths, multi-step operations, or resource management. Errors must fail closed and not leak details.
|
|
59
|
+
|
|
60
|
+
### Loading strategy
|
|
61
|
+
|
|
62
|
+
- **Plan creation / grilling:** scan the issue and design decisions, load 2–4 matching references, apply silently when proposing decisions.
|
|
63
|
+
- **Implementation:** scan the step being implemented, load 1–3 matching references, verify code against the implementation checks before marking done.
|
|
64
|
+
- **Code review:** scan the diff, load matching references, check the changed code.
|
|
65
|
+
- **Don't load all 10.** A plan that adds a search endpoint needs A01, A05, A09, and A10 — not A04 or A07.
|
|
66
|
+
|
|
67
|
+
## The 10 Risks at a Glance
|
|
68
|
+
|
|
69
|
+
| # | Risk | One-line summary |
|
|
70
|
+
| --- | -------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
71
|
+
| A01 | Broken Access Control | Users can act outside their intended permissions — enforce deny-by-default on the server. |
|
|
72
|
+
| A02 | Security Misconfiguration | Unnecessary features, default credentials, missing headers, verbose errors. |
|
|
73
|
+
| A03 | Software Supply Chain Failures | Vulnerable, unmaintained, or untrusted dependencies and build tooling. |
|
|
74
|
+
| A04 | Cryptographic Failures | Weak algorithms, missing encryption, hard-coded keys, poor randomness. |
|
|
75
|
+
| A05 | Injection | Untrusted input executed as commands — SQL, OS, XSS, LDAP, expression language. |
|
|
76
|
+
| A06 | Insecure Design | Missing or ineffective security controls in the architecture and business logic. |
|
|
77
|
+
| A07 | Authentication Failures | Weak credentials, no MFA, session fixation, brute-force exposure. |
|
|
78
|
+
| A08 | Software or Data Integrity Failures | Unverified updates, unsigned artifacts, insecure deserialization. |
|
|
79
|
+
| A09 | Security Logging and Alerting Failures | Missing logs for security events, no alerting, sensitive data in logs. |
|
|
80
|
+
| A10 | Mishandling of Exceptional Conditions | Errors leak data, fail open, crash the system, or corrupt state. |
|
|
81
|
+
|
|
82
|
+
## Rules
|
|
83
|
+
|
|
84
|
+
- **Silent by default.** The agent applies security autonomously. The developer should not be asked about risks the agent can handle.
|
|
85
|
+
- **Deny by default.** When in doubt, the secure option wins. The burden of proof is on the insecure option.
|
|
86
|
+
- **Bake, don't bolt on.** Mitigations go into the design and plan from the start — not as a post-hoc review layer.
|
|
87
|
+
- **Escalate with a recommendation.** When escalation is unavoidable, present the risk and a recommended mitigation. The developer confirms or adjusts — they are not asked to solve it from scratch.
|
|
88
|
+
- **Never log or commit secrets.** This applies to the agent's own output — no credentials, tokens, or keys in plan files, decision logs, code, or issue comments.
|
|
89
|
+
- **Record security decisions in the decision log.** When the agent bakes a mitigation into a proposal, log it briefly: `- Security (Axx): <what was done> — <why>`. One line. This keeps the plan auditable without bloating the conversation.
|
|
90
|
+
|
|
91
|
+
## Gotchas
|
|
92
|
+
|
|
93
|
+
- **A06 Insecure Design is the meta-risk.** If the proposed design itself is insecure (missing trust boundaries, client-side-only enforcement, no threat model for the flow), no amount of careful coding fixes it. The agent should revise the design proposal, not just the implementation. Only escalate if the design constraint comes from the developer.
|
|
94
|
+
- **A03 and A08 overlap.** A03 covers the supply chain (dependencies, build tools, CI/CD). A08 covers integrity verification (signatures, deserialization, auto-updates). The agent checks both when a proposal adds a dependency.
|
|
95
|
+
- **A09 is easy to skip silently.** The agent should proactively include logging for security-relevant events in its proposals — not wait to be asked. Auth failures, access denials, and input validation failures are always in scope.
|
|
96
|
+
- **A10 is new in 2025.** Error handling is a security control: fail closed, don't leak stack traces, roll back partial transactions. The agent bakes this into every proposal that touches error paths.
|
|
97
|
+
- **Don't over-escalate.** If the agent can propose a secure default and move on, it should. Escalation is for genuine decisions, not for every risk that crosses its desk.
|
|
98
|
+
|
|
99
|
+
## Done When
|
|
100
|
+
|
|
101
|
+
Before finishing a plan, grill, implementation step, or code review with this lens loaded:
|
|
102
|
+
|
|
103
|
+
- [ ] Every reference matching the routing table was loaded and checked against the proposal
|
|
104
|
+
- [ ] Mitigations are baked into the proposal itself — not listed as follow-up actions
|
|
105
|
+
- [ ] Each baked mitigation is recorded in the decision log as `- Security (Axx): <what was done> — <why>`
|
|
106
|
+
- [ ] Anything the agent cannot mitigate is escalated with a recommended mitigation
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# A01:2025 — Broken Access Control
|
|
2
|
+
|
|
3
|
+
**Rank:** #1 (100% of applications tested had some form of broken access control)
|
|
4
|
+
|
|
5
|
+
Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside the user's limits.
|
|
6
|
+
|
|
7
|
+
## You are vulnerable if
|
|
8
|
+
|
|
9
|
+
- Violation of the principle of least privilege (deny by default) — access available to anyone
|
|
10
|
+
- Bypassing access control checks by modifying the URL (parameter tampering, force browsing), internal application state, or the HTML page
|
|
11
|
+
- Permitting viewing or editing someone else's account by providing its unique identifier (insecure direct object references / IDOR)
|
|
12
|
+
- APIs with missing access controls for POST, PUT, and DELETE
|
|
13
|
+
- Elevation of privilege — acting as a user without being logged in, or gaining admin access
|
|
14
|
+
- Metadata manipulation — replaying or tampering with JWT tokens, cookies, or hidden fields to elevate privileges
|
|
15
|
+
- CORS misconfiguration allowing API access from unauthorized or untrusted origins
|
|
16
|
+
- Force browsing (guessing URLs) to authenticated or privileged pages
|
|
17
|
+
|
|
18
|
+
## How to prevent (OWASP)
|
|
19
|
+
|
|
20
|
+
- Access control is only effective in trusted server-side code or serverless APIs
|
|
21
|
+
- Except for public resources, **deny by default**
|
|
22
|
+
- Implement access control mechanisms once and reuse them throughout the application; minimize CORS usage
|
|
23
|
+
- Enforce record ownership rather than allowing users to CRUD any record
|
|
24
|
+
- Enforce unique business limit requirements via domain models
|
|
25
|
+
- Disable web server directory listing; ensure file metadata (e.g. `.git`) and backup files are not in web roots
|
|
26
|
+
- Log access control failures; alert admins on repeated failures
|
|
27
|
+
- Implement rate limits on API and controller access
|
|
28
|
+
- Invalidate stateful session identifiers on logout; use short-lived JWTs
|
|
29
|
+
- Use well-established toolkits or patterns for simple, declarative access controls
|
|
30
|
+
- Include functional access control in unit and integration tests
|
|
31
|
+
|
|
32
|
+
## Design phase — silently check and bake in
|
|
33
|
+
|
|
34
|
+
- Default to deny: new routes, endpoints, and data access are restricted unless the proposal explicitly makes them public.
|
|
35
|
+
- If the proposal reads or modifies records by ID, bake in an ownership check.
|
|
36
|
+
- If the proposal adds an API endpoint, bake in server-side authorization — not just authentication.
|
|
37
|
+
- If the proposal crosses a domain boundary, bake in the access control at the target domain's public API.
|
|
38
|
+
- If the proposal touches CORS, URL routing, or redirects, bake in a review step.
|
|
39
|
+
- If the proposal introduces client-side access control (hiding buttons, route guards), flag it as insufficient and bake in the server-side equivalent.
|
|
40
|
+
|
|
41
|
+
## Design phase — escalate when
|
|
42
|
+
|
|
43
|
+
The proposal requires a permission model the agent cannot infer (e.g. a new role hierarchy, custom RBAC rules) — propose a default role model and ask for confirmation.
|
|
44
|
+
|
|
45
|
+
## Implementation checks
|
|
46
|
+
|
|
47
|
+
- Every new endpoint/action checks authorization server-side — not just authentication
|
|
48
|
+
- Record access by ID includes an ownership check
|
|
49
|
+
- New routes, endpoints, and data access are denied by default unless explicitly public
|
|
50
|
+
- Privileged URLs return 403/404 for unauthorized users — no force-browsing
|
|
51
|
+
- CORS is not widened to untrusted origins
|
|
52
|
+
- Access control failures are logged
|
|
53
|
+
- Rate limits are in place on new API endpoints
|
|
54
|
+
- Tests cover: unauthorized access denied, cross-user access denied, privilege escalation blocked
|
|
55
|
+
|
|
56
|
+
## Key CWEs
|
|
57
|
+
|
|
58
|
+
CWE-200, CWE-201, CWE-352 (CSRF), CWE-639 (IDOR), CWE-862 (Missing Authorization), CWE-863 (Incorrect Authorization), CWE-918 (SSRF), CWE-22 (Path Traversal), CWE-601 (Open Redirect)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# A02:2025 — Security Misconfiguration
|
|
2
|
+
|
|
3
|
+
**Rank:** #2 (moved up from #5; 100% of applications tested had some form of misconfiguration)
|
|
4
|
+
|
|
5
|
+
Security misconfiguration is when a system, application, or cloud service is set up incorrectly from a security perspective, creating vulnerabilities.
|
|
6
|
+
|
|
7
|
+
## You are vulnerable if
|
|
8
|
+
|
|
9
|
+
- Missing appropriate security hardening across any part of the application stack, or improperly configured permissions on cloud services
|
|
10
|
+
- Unnecessary features are enabled or installed (ports, services, pages, accounts, testing frameworks, privileges)
|
|
11
|
+
- Default accounts and their passwords are still enabled and unchanged
|
|
12
|
+
- Error handling reveals stack traces or overly informative error messages to users
|
|
13
|
+
- For upgraded systems, the latest security features are disabled or not configured securely
|
|
14
|
+
- Excessive prioritization of backward compatibility leading to insecure configuration
|
|
15
|
+
- Security settings in application servers, frameworks, libraries, databases are not set to secure values
|
|
16
|
+
- The server does not send security headers or directives, or they are not set to secure values
|
|
17
|
+
|
|
18
|
+
## How to prevent (OWASP)
|
|
19
|
+
|
|
20
|
+
- Implement a repeatable hardening process for fast, easy deployment of locked-down environments
|
|
21
|
+
- Use a minimal platform without unnecessary features, components, documentation, or samples
|
|
22
|
+
- Review and update configurations as part of patch management; review cloud storage permissions
|
|
23
|
+
- Use a segmented application architecture with separation between components or tenants
|
|
24
|
+
- Send security directives to clients (security headers)
|
|
25
|
+
- Automate verification of configurations and settings in all environments
|
|
26
|
+
- Proactively add central configuration to intercept excessive error messages
|
|
27
|
+
- Use identity federation, short-lived credentials, or role-based access — never embed static keys or secrets in code, configuration files, or pipelines
|
|
28
|
+
|
|
29
|
+
## Design phase — silently check and bake in
|
|
30
|
+
|
|
31
|
+
- New env vars or config values: bake in secure defaults and document them.
|
|
32
|
+
- New error messages: bake in generic user-facing messages with detailed logs.
|
|
33
|
+
- New dependencies or services: bake in a hardening step (remove defaults, disable debug, set secure headers).
|
|
34
|
+
- Any secret the proposal needs: bake in environment variable or secret manager usage — never hard-coded.
|
|
35
|
+
- Cookie settings: bake in `Secure`, `HttpOnly`, `SameSite`.
|
|
36
|
+
- Security headers: if the proposal touches HTTP responses, bake in CSP, HSTS, X-Content-Type-Options.
|
|
37
|
+
|
|
38
|
+
## Design phase — escalate when
|
|
39
|
+
|
|
40
|
+
The proposal requires infrastructure configuration the agent cannot make (e.g. cloud IAM, CDN settings) — list it as a manual step in the plan.
|
|
41
|
+
|
|
42
|
+
## Implementation checks
|
|
43
|
+
|
|
44
|
+
- No default credentials in any environment, config file, or seed data
|
|
45
|
+
- No unnecessary features, debug endpoints, or sample data in the build
|
|
46
|
+
- Security headers are set (CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy)
|
|
47
|
+
- Error responses do not include stack traces, file paths, or system details
|
|
48
|
+
- No secrets in source code, config files, or committed environment files
|
|
49
|
+
- Secrets come from environment variables or a secret manager
|
|
50
|
+
- Cookie settings: `Secure`, `HttpOnly`, `SameSite` set appropriately
|
|
51
|
+
- Directory listing is disabled
|
|
52
|
+
- `.git`, `.env`, and backup files are not accessible from the web root
|
|
53
|
+
|
|
54
|
+
## Key CWEs
|
|
55
|
+
|
|
56
|
+
CWE-16 (Configuration), CWE-611 (XXE), CWE-260 (Password in Configuration File), CWE-547 (Hard-coded Constants), CWE-489 (Active Debug Code), CWE-942 (Permissive Cross-domain Policy)
|