guardlink 1.0.0 → 1.2.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/CHANGELOG.md +65 -0
- package/README.md +14 -0
- package/dist/agents/config.d.ts +8 -0
- package/dist/agents/config.d.ts.map +1 -1
- package/dist/agents/config.js +28 -5
- package/dist/agents/config.js.map +1 -1
- package/dist/agents/index.d.ts +2 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +1 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/launcher.d.ts +14 -0
- package/dist/agents/launcher.d.ts.map +1 -1
- package/dist/agents/launcher.js +126 -1
- package/dist/agents/launcher.js.map +1 -1
- package/dist/agents/prompts.d.ts +2 -2
- package/dist/agents/prompts.d.ts.map +1 -1
- package/dist/agents/prompts.js +251 -31
- package/dist/agents/prompts.js.map +1 -1
- package/dist/analyze/index.d.ts +34 -1
- package/dist/analyze/index.d.ts.map +1 -1
- package/dist/analyze/index.js +281 -8
- package/dist/analyze/index.js.map +1 -1
- package/dist/analyze/llm.d.ts +54 -3
- package/dist/analyze/llm.d.ts.map +1 -1
- package/dist/analyze/llm.js +418 -97
- package/dist/analyze/llm.js.map +1 -1
- package/dist/analyze/prompts.d.ts +3 -2
- package/dist/analyze/prompts.d.ts.map +1 -1
- package/dist/analyze/prompts.js +227 -111
- package/dist/analyze/prompts.js.map +1 -1
- package/dist/analyze/tools.d.ts +22 -0
- package/dist/analyze/tools.d.ts.map +1 -0
- package/dist/analyze/tools.js +230 -0
- package/dist/analyze/tools.js.map +1 -0
- package/dist/analyzer/sarif.js +1 -1
- package/dist/cli/index.d.ts +15 -7
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +290 -150
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/data.d.ts +5 -0
- package/dist/dashboard/data.d.ts.map +1 -1
- package/dist/dashboard/data.js +24 -12
- package/dist/dashboard/data.js.map +1 -1
- package/dist/dashboard/diagrams.d.ts.map +1 -1
- package/dist/dashboard/diagrams.js +310 -37
- package/dist/dashboard/diagrams.js.map +1 -1
- package/dist/dashboard/generate.d.ts.map +1 -1
- package/dist/dashboard/generate.js +197 -64
- package/dist/dashboard/generate.js.map +1 -1
- package/dist/init/picker.d.ts.map +1 -1
- package/dist/init/picker.js +2 -2
- package/dist/init/picker.js.map +1 -1
- package/dist/init/templates.d.ts.map +1 -1
- package/dist/init/templates.js +52 -32
- package/dist/init/templates.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +14 -28
- package/dist/mcp/server.js.map +1 -1
- package/dist/parser/index.d.ts +1 -0
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +1 -0
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/parse-line.js +3 -3
- package/dist/parser/parse-line.js.map +1 -1
- package/dist/parser/parse-project.js +1 -1
- package/dist/parser/validate.d.ts +31 -0
- package/dist/parser/validate.d.ts.map +1 -0
- package/dist/parser/validate.js +149 -0
- package/dist/parser/validate.js.map +1 -0
- package/dist/report/report.d.ts.map +1 -1
- package/dist/report/report.js +64 -0
- package/dist/report/report.js.map +1 -1
- package/dist/tui/commands.d.ts +3 -3
- package/dist/tui/commands.d.ts.map +1 -1
- package/dist/tui/commands.js +390 -206
- package/dist/tui/commands.js.map +1 -1
- package/dist/tui/config.d.ts +2 -0
- package/dist/tui/config.d.ts.map +1 -1
- package/dist/tui/config.js.map +1 -1
- package/dist/tui/format.d.ts +7 -0
- package/dist/tui/format.d.ts.map +1 -1
- package/dist/tui/format.js +59 -0
- package/dist/tui/format.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +32 -19
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/input.d.ts +2 -2
- package/dist/tui/input.js +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/agents/prompts.js
CHANGED
|
@@ -8,8 +8,8 @@ import { resolve } from 'node:path';
|
|
|
8
8
|
/**
|
|
9
9
|
* Build a prompt for annotation agents.
|
|
10
10
|
*
|
|
11
|
-
* Includes the GuardLink reference doc
|
|
12
|
-
*
|
|
11
|
+
* Includes the GuardLink reference doc, current model summary with flows and exposures,
|
|
12
|
+
* flow-first threat modeling methodology, and precise GAL syntax rules.
|
|
13
13
|
*/
|
|
14
14
|
export function buildAnnotatePrompt(userPrompt, root, model) {
|
|
15
15
|
// Read the reference doc if available
|
|
@@ -18,8 +18,17 @@ export function buildAnnotatePrompt(userPrompt, root, model) {
|
|
|
18
18
|
if (existsSync(refPath)) {
|
|
19
19
|
refDoc = readFileSync(refPath, 'utf-8');
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
// Fall back to docs/GUARDLINK_REFERENCE.md
|
|
22
|
+
if (!refDoc) {
|
|
23
|
+
const docsRefPath = resolve(root, 'docs', 'GUARDLINK_REFERENCE.md');
|
|
24
|
+
if (existsSync(docsRefPath)) {
|
|
25
|
+
refDoc = readFileSync(docsRefPath, 'utf-8');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
let modelSummary = 'No threat model parsed yet. This may be a fresh project — define assets, threats, and controls first.';
|
|
22
29
|
let existingIds = '';
|
|
30
|
+
let existingFlows = '';
|
|
31
|
+
let existingExposures = '';
|
|
23
32
|
if (model) {
|
|
24
33
|
const parts = [
|
|
25
34
|
`${model.annotations_parsed} annotations`,
|
|
@@ -28,6 +37,8 @@ export function buildAnnotatePrompt(userPrompt, root, model) {
|
|
|
28
37
|
`${model.threats.length} threats`,
|
|
29
38
|
`${model.controls.length} controls`,
|
|
30
39
|
`${model.mitigations.length} mitigations`,
|
|
40
|
+
`${model.flows.length} flows`,
|
|
41
|
+
`${model.boundaries.length} boundaries`,
|
|
31
42
|
];
|
|
32
43
|
modelSummary = `Current model: ${parts.join(', ')}.`;
|
|
33
44
|
// Include existing IDs so the agent doesn't create duplicates or dangling refs
|
|
@@ -36,45 +47,235 @@ export function buildAnnotatePrompt(userPrompt, root, model) {
|
|
|
36
47
|
const controlIds = model.controls.filter(c => c.id).map(c => `#${c.id}`);
|
|
37
48
|
if (threatIds.length + assetIds.length + controlIds.length > 0) {
|
|
38
49
|
const sections = [];
|
|
39
|
-
if (threatIds.length)
|
|
40
|
-
sections.push(`Threats: ${threatIds.join(', ')}`);
|
|
41
50
|
if (assetIds.length)
|
|
42
51
|
sections.push(`Assets: ${assetIds.join(', ')}`);
|
|
52
|
+
if (threatIds.length)
|
|
53
|
+
sections.push(`Threats: ${threatIds.join(', ')}`);
|
|
43
54
|
if (controlIds.length)
|
|
44
55
|
sections.push(`Controls: ${controlIds.join(', ')}`);
|
|
45
|
-
existingIds = `\n\nExisting defined IDs (
|
|
56
|
+
existingIds = `\n\nExisting defined IDs (REUSE these — do NOT redefine):\n${sections.join('\n')}`;
|
|
57
|
+
}
|
|
58
|
+
// Include existing flows so agent understands the current flow graph
|
|
59
|
+
if (model.flows.length > 0) {
|
|
60
|
+
const flowLines = model.flows.slice(0, 30).map(f => ` ${f.source} -> ${f.target}${f.mechanism ? ` via ${f.mechanism}` : ''} (${f.location.file}:${f.location.line})`);
|
|
61
|
+
existingFlows = `\n\nExisting data flows (extend these, don't duplicate):\n${flowLines.join('\n')}`;
|
|
62
|
+
if (model.flows.length > 30)
|
|
63
|
+
existingFlows += `\n ... and ${model.flows.length - 30} more`;
|
|
64
|
+
}
|
|
65
|
+
// Include unmitigated exposures so agent knows what still needs attention
|
|
66
|
+
// NOTE: Do NOT filter out @accepts — agents should see ALL exposures without real mitigations
|
|
67
|
+
const unmitigatedExposures = model.exposures.filter(e => {
|
|
68
|
+
return !model.mitigations.some(m => m.asset === e.asset && m.threat === e.threat);
|
|
69
|
+
});
|
|
70
|
+
if (unmitigatedExposures.length > 0) {
|
|
71
|
+
const expLines = unmitigatedExposures.slice(0, 20).map(e => ` ${e.asset} exposed to ${e.threat} [${e.severity || 'unrated'}] (${e.location.file}:${e.location.line})`);
|
|
72
|
+
existingExposures = `\n\nOpen exposures (no mitigation in code — add @mitigates if a control exists, or @audit to flag for human review):\n${expLines.join('\n')}`;
|
|
73
|
+
if (unmitigatedExposures.length > 20)
|
|
74
|
+
existingExposures += `\n ... and ${unmitigatedExposures.length - 20} more`;
|
|
46
75
|
}
|
|
47
76
|
}
|
|
48
|
-
return `You are
|
|
77
|
+
return `You are an expert security engineer performing threat modeling as code.
|
|
78
|
+
Your job is to read this codebase deeply, understand how code flows between components, and annotate it with GuardLink (GAL) security annotations that accurately represent the security posture.
|
|
49
79
|
|
|
50
|
-
|
|
51
|
-
|
|
80
|
+
This is NOT a vulnerability scanner. You are building a living threat model embedded in the code itself.
|
|
81
|
+
Annotations capture what COULD go wrong, what controls exist, and how data moves — not just confirmed bugs.
|
|
52
82
|
|
|
53
|
-
##
|
|
83
|
+
${refDoc ? '## GuardLink Annotation Language Reference\n\n' + refDoc.slice(0, 4000) + '\n\n' : ''}## Current State
|
|
84
|
+
${modelSummary}${existingIds}${existingFlows}${existingExposures}
|
|
85
|
+
|
|
86
|
+
## Your Task
|
|
54
87
|
${userPrompt}
|
|
55
88
|
|
|
56
|
-
##
|
|
89
|
+
## HOW TO THINK — Flow-First Threat Modeling
|
|
90
|
+
|
|
91
|
+
Before writing ANY annotation, you MUST understand the code deeply:
|
|
92
|
+
|
|
93
|
+
### Step 1: Map the Architecture
|
|
94
|
+
Read ALL source files related to the area you're annotating. Trace:
|
|
95
|
+
- Entry points (HTTP handlers, CLI commands, message consumers, event listeners)
|
|
96
|
+
- Data paths (how user input flows through functions, classes, middleware, to storage or output)
|
|
97
|
+
- Exit points (database writes, API calls, file I/O, rendered templates, responses)
|
|
98
|
+
- Class hierarchies, inherited methods, shared utilities, middleware chains
|
|
99
|
+
- Configuration and environment variable usage
|
|
100
|
+
|
|
101
|
+
### Step 2: Identify Trust Boundaries
|
|
102
|
+
Look for where trust changes:
|
|
103
|
+
- External user → application code (HTTP boundary)
|
|
104
|
+
- Application → database (data layer boundary)
|
|
105
|
+
- Service → service (network boundary)
|
|
106
|
+
- Frontend → backend (client/server boundary)
|
|
107
|
+
- Application → third-party API (vendor boundary)
|
|
108
|
+
- Internal code → spawned process (process boundary)
|
|
57
109
|
|
|
58
|
-
|
|
110
|
+
### Step 3: Identify What Could Go Wrong
|
|
111
|
+
At each boundary crossing and data transformation, ask:
|
|
112
|
+
- What if this input is malicious? (@exposes)
|
|
113
|
+
- What validation/sanitization exists? (@mitigates)
|
|
114
|
+
- What sensitive data passes through here? (@handles)
|
|
115
|
+
- Is there an assumption that could be violated? (@assumes)
|
|
116
|
+
- Does this need human security review? (@audit)
|
|
117
|
+
- Is this risk handled by someone else? (@transfers)
|
|
118
|
+
|
|
119
|
+
### Step 4: Write Coupled Annotation Blocks
|
|
120
|
+
NEVER write a single annotation in isolation. Every annotated location should tell a complete story.
|
|
121
|
+
|
|
122
|
+
## ANNOTATION STYLE GUIDE — Write Like a Developer
|
|
123
|
+
|
|
124
|
+
### Always Couple Annotations Together
|
|
125
|
+
A file's doc-block should paint the full security picture of that module. Group annotations logically:
|
|
59
126
|
|
|
60
|
-
### Definitions
|
|
61
127
|
\`\`\`
|
|
62
|
-
// @shield:begin -- "Example
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// @
|
|
128
|
+
// @shield:begin -- "Example annotation block for reference, excluded from parsing"
|
|
129
|
+
//
|
|
130
|
+
// GOOD — Complete story at a single code location:
|
|
131
|
+
// @exposes #auth-api to #sqli [P1] cwe:CWE-89 -- "User-supplied email passed to findUser() query builder"
|
|
132
|
+
// @mitigates #auth-api against #sqli using #input-validation -- "Zod schema validates email format before query"
|
|
133
|
+
// @flows User_Input -> #auth-api via POST./login -- "Login form submits credentials"
|
|
134
|
+
// @flows #auth-api -> #user-db via TypeORM.findOne -- "Authenticated user lookup"
|
|
135
|
+
// @handles pii on #auth-api -- "Processes email, password, session tokens"
|
|
136
|
+
// @comment -- "Password comparison uses bcrypt.compare with timing-safe equality"
|
|
137
|
+
//
|
|
138
|
+
// BAD — Isolated annotation with no context:
|
|
139
|
+
// @exposes #auth-api to #sqli -- "SQL injection possible"
|
|
140
|
+
//
|
|
66
141
|
// @shield:end
|
|
67
142
|
\`\`\`
|
|
68
143
|
|
|
69
|
-
###
|
|
144
|
+
### Description Style — Reference Actual Code
|
|
145
|
+
Descriptions must reference the real code: function names, variable names, libraries, mechanisms.
|
|
146
|
+
|
|
147
|
+
\`\`\`
|
|
148
|
+
// @shield:begin -- "Description examples, excluded from parsing"
|
|
149
|
+
//
|
|
150
|
+
// GOOD: -- "req.body.token passed to jwt.verify() without audience check"
|
|
151
|
+
// GOOD: -- "bcrypt rounds set to 12 via BCRYPT_COST env var"
|
|
152
|
+
// GOOD: -- "Rate limiter uses express-rate-limit at 100req/15min on /api/*"
|
|
153
|
+
//
|
|
154
|
+
// BAD: -- "Input not validated" (too vague — WHICH input? WHERE?)
|
|
155
|
+
// BAD: -- "Uses encryption" (WHAT encryption? On WHAT data?)
|
|
156
|
+
// BAD: -- "Security vulnerability exists" (meaningless — be specific)
|
|
157
|
+
//
|
|
158
|
+
// @shield:end
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
### @flows — Stitch the Complete Data Path
|
|
162
|
+
@flows is the backbone of the threat model. Trace data movement accurately:
|
|
163
|
+
|
|
164
|
+
\`\`\`
|
|
165
|
+
// @shield:begin -- "Flow examples, excluded from parsing"
|
|
166
|
+
//
|
|
167
|
+
// Trace a request through the full stack:
|
|
168
|
+
// @flows User_Browser -> #api-gateway via HTTPS -- "Client sends auth request"
|
|
169
|
+
// @flows #api-gateway -> #auth-service via internal.gRPC -- "Gateway forwards to auth microservice"
|
|
170
|
+
// @flows #auth-service -> #user-db via pg.query -- "Looks up user record by email"
|
|
171
|
+
// @flows #auth-service -> #session-store via redis.set -- "Stores session token with TTL"
|
|
172
|
+
// @flows #auth-service -> User_Browser via Set-Cookie -- "Returns session cookie to client"
|
|
173
|
+
//
|
|
174
|
+
// @shield:end
|
|
175
|
+
\`\`\`
|
|
176
|
+
|
|
177
|
+
### @boundary — Mark Every Trust Zone Crossing
|
|
178
|
+
Place @boundary annotations where trust level changes between two components:
|
|
179
|
+
|
|
180
|
+
\`\`\`
|
|
181
|
+
// @shield:begin -- "Boundary examples, excluded from parsing"
|
|
182
|
+
//
|
|
183
|
+
// @boundary between #api-gateway and External_Internet (#public-boundary) -- "TLS termination, rate limiting at edge"
|
|
184
|
+
// @boundary between #backend and #database (#data-boundary) -- "Application to persistence layer, connection pooling via pgBouncer"
|
|
185
|
+
// @boundary between #app and #payment-provider (#vendor-boundary) -- "PCI-DSS scope boundary, tokenized card data only"
|
|
186
|
+
//
|
|
187
|
+
// @shield:end
|
|
188
|
+
\`\`\`
|
|
189
|
+
|
|
190
|
+
### Where to Place Annotations
|
|
191
|
+
Annotations go in the file's top doc-block comment OR directly above the security-relevant code:
|
|
192
|
+
|
|
70
193
|
\`\`\`
|
|
71
|
-
// @shield:begin -- "
|
|
194
|
+
// @shield:begin -- "Placement examples, excluded from parsing"
|
|
195
|
+
//
|
|
196
|
+
// FILE-LEVEL (top doc-block) — for module-wide security properties:
|
|
197
|
+
// Place @exposes, @mitigates, @flows, @handles, @boundary that describe the module as a whole
|
|
198
|
+
//
|
|
199
|
+
// INLINE (above specific functions/methods) — for function-specific concerns:
|
|
200
|
+
// Place @exposes, @mitigates above the exact function where the risk or control lives
|
|
201
|
+
// Place @comment above tricky security-relevant code to explain intent
|
|
202
|
+
//
|
|
203
|
+
// @shield:end
|
|
204
|
+
\`\`\`
|
|
205
|
+
|
|
206
|
+
### Severity — Be Honest, Not Alarmist
|
|
207
|
+
Annotations capture what COULD go wrong, calibrated to realistic risk:
|
|
208
|
+
- **[P0] / [critical]**: Directly exploitable by external attacker, severe impact (RCE, auth bypass, data breach)
|
|
209
|
+
- **[P1] / [high]**: Exploitable with some conditions, significant impact (privilege escalation, data leak)
|
|
210
|
+
- **[P2] / [medium]**: Requires specific conditions or insider access (SSRF, info disclosure)
|
|
211
|
+
- **[P3] / [low]**: Minor impact or very difficult to exploit (timing side-channels, verbose errors)
|
|
212
|
+
|
|
213
|
+
Don't rate everything P0. A SQL injection in an admin-only internal tool is different from one in a public API.
|
|
214
|
+
|
|
215
|
+
### @comment — Always Add Context
|
|
216
|
+
Every annotation block should include at least one @comment explaining non-obvious security decisions, assumptions, or context that helps future developers (and AI tools) understand the "why".
|
|
217
|
+
|
|
218
|
+
### @accepts — NEVER USE (Human-Only Decision)
|
|
219
|
+
@accepts marks a risk as intentionally unmitigated. This is a **human-only governance decision** — it requires conscious risk ownership by a person or team.
|
|
220
|
+
As an AI agent, you MUST NEVER write @accepts annotations. You cannot accept risk on behalf of humans.
|
|
221
|
+
|
|
222
|
+
Instead, when you find an exposure with no mitigation in the code:
|
|
223
|
+
1. Write the @exposes annotation to document the risk
|
|
224
|
+
2. Add @audit to flag it for human security review
|
|
225
|
+
3. Add @comment explaining what controls COULD be added
|
|
226
|
+
4. Optionally add @assumes to document any assumptions the code makes
|
|
227
|
+
|
|
228
|
+
Example — what to do when no mitigation exists:
|
|
229
|
+
\`\`\`
|
|
230
|
+
// @shield:begin -- "@accepts alternative examples, excluded from parsing"
|
|
231
|
+
//
|
|
232
|
+
// WRONG (AI rubber-stamping risk):
|
|
233
|
+
// @accepts #prompt-injection on #ai-endpoint -- "Relying on model safety filters"
|
|
234
|
+
//
|
|
235
|
+
// RIGHT (flag for human review):
|
|
236
|
+
// @exposes #ai-endpoint to #prompt-injection [P1] cwe:CWE-77 -- "User prompt passed directly to LLM API without sanitization"
|
|
237
|
+
// @audit #ai-endpoint -- "No prompt sanitization — needs human review to decide: add input filter or accept risk"
|
|
238
|
+
// @comment -- "Potential controls: #prompt-filter (input sanitization), #output-validator (response filtering)"
|
|
239
|
+
//
|
|
240
|
+
// @shield:end
|
|
241
|
+
\`\`\`
|
|
242
|
+
|
|
243
|
+
Leaving exposures unmitigated is HONEST. The dashboard and reports will surface them as open risks for humans to triage.
|
|
244
|
+
|
|
245
|
+
### @shield — DO NOT USE Unless Explicitly Asked
|
|
246
|
+
@shield and @shield:begin/@shield:end block AI coding assistants from reading the annotated code.
|
|
247
|
+
This means any shielded code becomes invisible to AI tools — they cannot analyze, refactor, or annotate it.
|
|
248
|
+
Do NOT add @shield annotations unless the user has EXPLICITLY requested it (e.g., "shield the crypto module").
|
|
249
|
+
Adding @shield on your own initiative would actively harm the threat model by creating blind spots where AI cannot help.
|
|
250
|
+
|
|
251
|
+
## PRECISE GAL Syntax
|
|
252
|
+
|
|
253
|
+
Definitions go in .guardlink/definitions.{ts,js,py,rs}. Source files use only relationship verbs.
|
|
254
|
+
|
|
255
|
+
### Definitions (in .guardlink/definitions file)
|
|
256
|
+
\`\`\`
|
|
257
|
+
// @shield:begin -- "Definition syntax examples, excluded from parsing"
|
|
258
|
+
// @asset Server.Auth (#auth) -- "Authentication service handling login and session management"
|
|
259
|
+
// @threat SQL_Injection (#sqli) [P0] cwe:CWE-89 -- "Unsanitized input reaches SQL query builder"
|
|
260
|
+
// @control Prepared_Statements (#prepared-stmts) -- "Parameterized queries via ORM or driver placeholders"
|
|
261
|
+
// @shield:end
|
|
262
|
+
\`\`\`
|
|
263
|
+
|
|
264
|
+
### Relationships (in source files)
|
|
265
|
+
\`\`\`
|
|
266
|
+
// @shield:begin -- "Relationship syntax examples, excluded from parsing"
|
|
72
267
|
// @exposes #auth to #sqli [P0] cwe:CWE-89 owasp:A03:2021 -- "User input concatenated into query"
|
|
73
|
-
// @mitigates #auth against #sqli using #prepared-stmts -- "Uses parameterized queries"
|
|
268
|
+
// @mitigates #auth against #sqli using #prepared-stmts -- "Uses parameterized queries via sqlx"
|
|
269
|
+
// @audit #auth -- "Timing attack risk — needs human review to decide if bcrypt constant-time comparison is sufficient"
|
|
270
|
+
// @transfers #ddos from #api to #cdn -- "Cloudflare handles L7 DDoS mitigation"
|
|
74
271
|
// @flows req.body.username -> db.query via string-concat -- "User input flows to SQL"
|
|
75
|
-
// @boundary between #frontend and #api (#
|
|
76
|
-
// @handles pii on #auth -- "Processes
|
|
77
|
-
// @
|
|
272
|
+
// @boundary between #frontend and #api (#web-boundary) -- "TLS-terminated public/private boundary"
|
|
273
|
+
// @handles pii on #auth -- "Processes email, password, session tokens"
|
|
274
|
+
// @validates #prepared-stmts for #auth -- "Integration test sqlInjectionTest.ts confirms parameterized queries block SQLi payloads"
|
|
275
|
+
// @audit #auth -- "Session token rotation logic needs cryptographic review"
|
|
276
|
+
// @assumes #auth -- "Upstream API gateway has already validated TLS and rate-limited requests"
|
|
277
|
+
// @owns security-team for #auth -- "Security team reviews all auth PRs"
|
|
278
|
+
// @comment -- "Password hashing uses bcrypt with cost factor 12, migration from SHA256 completed in v2.1"
|
|
78
279
|
// @shield:end
|
|
79
280
|
\`\`\`
|
|
80
281
|
|
|
@@ -84,13 +285,13 @@ Definitions go in .guardlink/definitions.js (or .py/.rs). Source files use only
|
|
|
84
285
|
WRONG: \`@boundary api -- "desc"\` (only one argument — will NOT parse)
|
|
85
286
|
RIGHT: \`@boundary between #api and #client (#api-boundary) -- "Trust boundary"\`
|
|
86
287
|
|
|
87
|
-
2. **@flows is ONE source
|
|
288
|
+
2. **@flows is ONE source -> ONE target per line**: \`@flows <source> -> <target> via <mechanism>\`.
|
|
88
289
|
WRONG: \`@flows A -> B, C -> D -- "desc"\` (commas not supported)
|
|
89
290
|
RIGHT: \`@flows A -> B via mechanism -- "desc"\` (one per line, repeat for multiple)
|
|
90
291
|
|
|
91
292
|
3. **@exposes / @mitigates require DEFINED #id refs**: Every \`#id\` you reference must exist as a definition.
|
|
92
293
|
Before using \`@exposes #app to #sqli\`, ensure \`@threat SQL_Injection (#sqli)\` exists in definitions.
|
|
93
|
-
Add new definitions to .guardlink/definitions
|
|
294
|
+
Add new definitions to the .guardlink/definitions file FIRST, then reference them in source files.
|
|
94
295
|
|
|
95
296
|
4. **Severity in square brackets**: \`[P0]\` \`[P1]\` \`[P2]\` \`[P3]\` or \`[critical]\` \`[high]\` \`[medium]\` \`[low]\`.
|
|
96
297
|
Goes AFTER the threat ref in @exposes: \`@exposes #app to #sqli [P0] cwe:CWE-89\`
|
|
@@ -108,13 +309,32 @@ Definitions go in .guardlink/definitions.js (or .py/.rs). Source files use only
|
|
|
108
309
|
|
|
109
310
|
8. **External refs are space-separated after severity**: \`cwe:CWE-89 owasp:A03:2021 capec:CAPEC-66\`
|
|
110
311
|
|
|
312
|
+
9. **@comment always needs -- and quotes**: \`@comment -- "your note here"\`.
|
|
313
|
+
A bare \`@comment\` without description is valid but useless. Always include context.
|
|
314
|
+
|
|
315
|
+
10. **One annotation per comment line.** Do NOT put two @verbs on the same line.
|
|
316
|
+
|
|
111
317
|
## Workflow
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
318
|
+
|
|
319
|
+
1. **Read first, annotate second.** Read ALL related source files before writing any annotation.
|
|
320
|
+
Trace the full call chain: entry point → middleware → handler → service → repository → database.
|
|
321
|
+
Understand class hierarchies, shared utilities, and configuration.
|
|
322
|
+
|
|
323
|
+
2. **Read existing definitions** in the .guardlink/definitions file — reuse existing IDs, never duplicate.
|
|
324
|
+
|
|
325
|
+
3. **Add NEW definitions FIRST** if you need new assets, threats, or controls.
|
|
326
|
+
Group related definitions together with section comments.
|
|
327
|
+
|
|
328
|
+
4. **Annotate in coupled blocks.** For each security-relevant location, write the complete story:
|
|
329
|
+
@exposes + @mitigates (or @audit if no mitigation exists) + @flows + @comment at minimum.
|
|
330
|
+
Think: "what's the risk, what's the defense, how does data flow here, and what should the next developer know?"
|
|
331
|
+
NEVER write @accepts — that is a human-only governance decision. Use @audit to flag unmitigated risks for review.
|
|
332
|
+
|
|
333
|
+
5. **Use the project's comment style** (// for JS/TS/Go/Rust, # for Python/Ruby/Shell, etc.)
|
|
334
|
+
|
|
335
|
+
6. **Run validation** via guardlink_validate (MCP) or \`guardlink validate\` to check for errors.
|
|
336
|
+
|
|
337
|
+
7. **Fix any validation errors** before finishing — especially dangling refs and malformed syntax.
|
|
118
338
|
`;
|
|
119
339
|
}
|
|
120
340
|
//# sourceMappingURL=prompts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/agents/prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,UAAkB,EAClB,IAAY,EACZ,KAAyB;IAEzB,sCAAsC;IACtC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,wBAAwB,CAAC,CAAC;IACtE,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,MAAM,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,IAAI,YAAY,GAAG,
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/agents/prompts.ts"],"names":[],"mappings":"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"}
|
package/dist/analyze/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ import { type AnalysisFramework } from './prompts.js';
|
|
|
18
18
|
import { type LLMConfig } from './llm.js';
|
|
19
19
|
export { type AnalysisFramework, FRAMEWORK_LABELS, FRAMEWORK_PROMPTS, buildUserMessage } from './prompts.js';
|
|
20
20
|
export { type LLMConfig, type LLMProvider, buildConfig, autoDetectConfig } from './llm.js';
|
|
21
|
+
export { GUARDLINK_TOOLS, createToolExecutor } from './tools.js';
|
|
22
|
+
export type { ToolDefinition, ToolCall, ToolResult, ToolExecutor } from './llm.js';
|
|
21
23
|
export interface ThreatReportOptions {
|
|
22
24
|
root: string;
|
|
23
25
|
model: ThreatModel;
|
|
@@ -26,6 +28,18 @@ export interface ThreatReportOptions {
|
|
|
26
28
|
customPrompt?: string;
|
|
27
29
|
stream?: boolean;
|
|
28
30
|
onChunk?: (text: string) => void;
|
|
31
|
+
/** Max lines of context to include around each annotated line (default: 8) */
|
|
32
|
+
snippetContext?: number;
|
|
33
|
+
/** Max total characters for all code snippets combined (default: 40000) */
|
|
34
|
+
snippetBudget?: number;
|
|
35
|
+
/** Enable web search grounding (OpenAI Responses API) */
|
|
36
|
+
webSearch?: boolean;
|
|
37
|
+
/** Enable extended thinking (Anthropic) / reasoning (DeepSeek) */
|
|
38
|
+
extendedThinking?: boolean;
|
|
39
|
+
/** Token budget for thinking (default: 10000) */
|
|
40
|
+
thinkingBudget?: number;
|
|
41
|
+
/** Enable agentic tool use (CVE lookup, model validation, codebase search) */
|
|
42
|
+
enableTools?: boolean;
|
|
29
43
|
}
|
|
30
44
|
export interface ThreatReportResult {
|
|
31
45
|
framework: AnalysisFramework;
|
|
@@ -36,10 +50,29 @@ export interface ThreatReportResult {
|
|
|
36
50
|
savedTo?: string;
|
|
37
51
|
inputTokens?: number;
|
|
38
52
|
outputTokens?: number;
|
|
53
|
+
/** Thinking/reasoning content (if extended thinking was enabled) */
|
|
54
|
+
thinking?: string;
|
|
55
|
+
thinkingTokens?: number;
|
|
39
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Collect project-level context for the LLM: language/framework, key
|
|
59
|
+
* dependencies, and deployment signals (Dockerfile, CI, etc.).
|
|
60
|
+
* Keeps output compact — targets ~2-4 KB.
|
|
61
|
+
*/
|
|
62
|
+
export declare function buildProjectContext(root: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Extract source code snippets around annotated lines.
|
|
65
|
+
*
|
|
66
|
+
* For each annotation that has a file + line location, reads the
|
|
67
|
+
* surrounding `contextLines` lines from disk and returns a formatted
|
|
68
|
+
* block. Deduplicates overlapping ranges within the same file.
|
|
69
|
+
* Respects a total character budget to keep token usage bounded.
|
|
70
|
+
*/
|
|
71
|
+
export declare function extractCodeSnippets(root: string, model: ThreatModel, contextLines?: number, budgetChars?: number): string;
|
|
40
72
|
/**
|
|
41
73
|
* Serialize the threat model to a compact representation for LLM context.
|
|
42
|
-
*
|
|
74
|
+
* Includes file:line locations for all security-relevant annotations so
|
|
75
|
+
* the LLM can cross-reference with code snippets.
|
|
43
76
|
*/
|
|
44
77
|
export declare function serializeModel(model: ThreatModel): string;
|
|
45
78
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analyze/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,KAAK,iBAAiB,EAAyD,MAAM,cAAc,CAAC;AAC7G,OAAO,EAAE,KAAK,SAAS,EAA+B,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analyze/index.ts"],"names":[],"mappings":"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"}
|