trace-sec 2.1.13 → 2.1.15

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/package.json CHANGED
@@ -1,12 +1,9 @@
1
1
  {
2
2
  "name": "trace-sec",
3
- "version": "2.1.13",
3
+ "version": "2.1.15",
4
4
  "description": "Threat Reconnaissance & Attack-path Correlation Engine — Security Verification Layer for Coding Agents with Fine-Tuned SecureBERT 2.0 & Laya AI",
5
5
  "type": "module",
6
6
  "main": "bin/trace.js",
7
- "scripts": {
8
- "postinstall": "node bin/postinstall.js"
9
- },
10
7
  "bin": {
11
8
  "trace": "bin/trace.js",
12
9
  "trace-sec": "bin/trace.js",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "trace-sec"
7
- version = "2.1.13"
7
+ version = "2.1.15"
8
8
  description = "Threat Reconnaissance & Attack-path Correlation Engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -3,6 +3,6 @@
3
3
  Local-first application security analysis and runtime validation engine.
4
4
  """
5
5
 
6
- __version__ = "2.1.13"
6
+ __version__ = "2.1.15"
7
7
  __app_name__ = "TRACE"
8
8
  __description__ = "Threat Reconnaissance & Attack-path Correlation Engine"
@@ -3,7 +3,7 @@
3
3
  from trace_engine.findings.model import Finding, Severity, FindingConfidence
4
4
  from trace_engine.findings.correlate import EvidenceCorrelator
5
5
  from trace_engine.findings.store import FindingStore
6
- from trace_engine.findings.recommendations import get_remediation_for_category
6
+ from trace_engine.findings.recommendations import get_remediation_for_category, get_detailed_remediation_and_root_cause
7
7
 
8
8
  __all__ = [
9
9
  "Finding",
@@ -12,4 +12,5 @@ __all__ = [
12
12
  "EvidenceCorrelator",
13
13
  "FindingStore",
14
14
  "get_remediation_for_category",
15
+ "get_detailed_remediation_and_root_cause",
15
16
  ]
@@ -4,7 +4,7 @@ from typing import List, Optional
4
4
  from trace_engine.security.hypotheses import SecurityHypothesis
5
5
  from trace_engine.testpacks.base import TestExecutionResult
6
6
  from trace_engine.findings.model import Finding, Severity, FindingConfidence
7
- from trace_engine.findings.recommendations import get_remediation_for_category
7
+ from trace_engine.findings.recommendations import get_detailed_remediation_and_root_cause, get_remediation_for_category
8
8
  from trace_engine.apm.model import AttackPathModel
9
9
 
10
10
 
@@ -91,6 +91,14 @@ class EvidenceCorrelator:
91
91
 
92
92
  file_path = source_loc.file if source_loc else ""
93
93
 
94
+ root_cause, remediation, short_action = get_detailed_remediation_and_root_cause(
95
+ title=hypothesis.title,
96
+ category=category_str,
97
+ endpoint=hypothesis.endpoint_display,
98
+ static_evidence=hypothesis.static_evidence,
99
+ filepath=file_path,
100
+ )
101
+
94
102
  return Finding(
95
103
  id=finding_id,
96
104
  title=hypothesis.title,
@@ -103,7 +111,9 @@ class EvidenceCorrelator:
103
111
  static_evidence=hypothesis.static_evidence,
104
112
  runtime_evidence=runtime_evidence,
105
113
  correlation_notes=correlation_notes,
106
- remediation=get_remediation_for_category(category_str, filepath=file_path),
114
+ remediation=remediation,
115
+ root_cause=root_cause,
116
+ short_action=short_action,
107
117
  reproduction_steps=test_result.reproduction_steps if test_result else [],
108
118
  observations=test_result.observations if test_result else [],
109
119
  )
@@ -38,3 +38,5 @@ class Finding(BaseModel):
38
38
  remediation: str
39
39
  reproduction_steps: List[str] = Field(default_factory=list)
40
40
  observations: List[RuntimeObservation] = Field(default_factory=list)
41
+ root_cause: Optional[str] = None
42
+ short_action: Optional[str] = None
@@ -1,152 +1,456 @@
1
- """Remediation recommendations for security findings."""
2
-
3
- from typing import Optional
4
-
5
-
6
- REMEDIATION_MAP_PYTHON = {
7
- "BOLA": (
8
- "Enforce strict object ownership validation before returning records. "
9
- "Query using both the object ID and the authenticated user/tenant ID (e.g. `WHERE id = :id AND user_id = :current_user.id`)."
10
- ),
11
- "BFLA": (
12
- "Implement declarative role-based access control (RBAC) dependencies (e.g. `Security(require_role('admin'))`). "
13
- "Validate user privileges on the backend for every privileged/administrative route."
14
- ),
15
- "AUTHENTICATION": (
16
- "Protect this route with authentication dependencies (e.g. `Depends(get_current_user)` or security middleware) "
17
- "to ensure unauthorized users cannot trigger state-changing or sensitive operations."
18
- ),
19
- "SSRF": (
20
- "Avoid making arbitrary outbound HTTP requests with user-controlled URLs. "
21
- "Implement a strict destination domain whitelist and block loopback/internal IP addresses (RFC 1918)."
22
- ),
23
- "INJECTION": (
24
- "Use parameterized queries, prepared statements, or ORM abstraction for all database operations. "
25
- "Never format or concatenate raw client inputs into SQL/command strings."
26
- ),
27
- "MASS_ASSIGNMENT": (
28
- "Define explicit request DTO schemas (e.g. Pydantic models with only allowed mutable fields). "
29
- "Do not allow raw dictionary binding directly into database models."
30
- ),
31
- "PATH_TRAVERSAL": (
32
- "Sanitize file path inputs using `os.path.basename()` or `pathlib.Path.resolve()`, "
33
- "and verify that the resolved path is strictly within the allowed directory. Disallow path traversal sequences (`..`)."
34
- ),
35
- "SSTI": (
36
- "Avoid rendering untrusted user inputs directly inside template engines. "
37
- "Use contextual output encoding or sandbox template evaluation environments."
38
- ),
39
- "CORS": (
40
- "Do not reflect arbitrary Origin headers or use wildcard `*` with credentials. "
41
- "Configure an explicit allowlist of trusted origins."
42
- ),
43
- "DESERIALIZATION": (
44
- "Avoid deserializing untrusted payloads (e.g. `pickle.loads`). "
45
- "Use safe serialization formats like standard JSON with strict schema validation."
46
- ),
47
- }
48
-
49
- REMEDIATION_MAP_NODE = {
50
- "BOLA": (
51
- "Enforce strict object ownership validation before returning or modifying records. "
52
- "Scope queries using both the object ID and the authenticated user/tenant identity (e.g. `findByIdAndUserId(req.params.id, req.user.id)` or `WHERE id = ? AND tenant_id = ?`)."
53
- ),
54
- "BFLA": (
55
- "Implement role-based access control (RBAC) middleware (e.g. `requireRole('admin')` or `checkPermission(...)`). "
56
- "Validate `req.user.role` on the backend before executing privileged operations."
57
- ),
58
- "AUTHENTICATION": (
59
- "Mount authentication middleware (e.g. `router.use(verifyToken)` or `app.use('/api/...', verifyToken, router)`) "
60
- "to ensure unauthorized users cannot trigger state-changing or sensitive operations."
61
- ),
62
- "SSRF": (
63
- "Avoid making outbound HTTP requests (e.g. with `axios` or `fetch`) using user-controlled URLs. "
64
- "Implement a strict destination domain allowlist and block loopback (`127.0.0.1`, `localhost`) and private RFC-1918 IP addresses."
65
- ),
66
- "INJECTION": (
67
- "Use parameterized queries, prepared statements, or ORM abstractions (e.g. Prisma, Mongoose, TypeORM, Knex). "
68
- "Never concatenate or interpolate raw `req.body` or `req.query` into database queries or shell commands."
69
- ),
70
- "MASS_ASSIGNMENT": (
71
- "Define explicit request DTO schemas (e.g. using Zod, Joi, or TypeScript DTOs) and pick allowlisted fields from `req.body`. "
72
- "Never pass raw `req.body` directly into database creation or update functions."
73
- ),
74
- "PATH_TRAVERSAL": (
75
- "Sanitize file path inputs using `path.basename()` or `path.resolve()`, "
76
- "and verify that the target path strictly resides within the intended root directory (e.g. `resolvedPath.startsWith(BASE_DIR)`). Disallow directory traversal sequences (`..`)."
77
- ),
78
- "SSTI": (
79
- "Avoid rendering untrusted user inputs directly inside template engines. "
80
- "Use contextual output encoding or sandbox template evaluation environments."
81
- ),
82
- "CORS": (
83
- "Do not reflect the `Origin` request header or allow `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true`. "
84
- "Configure an explicit allowlist of trusted origins."
85
- ),
86
- "DESERIALIZATION": (
87
- "Avoid deserializing untrusted payloads (e.g. `node-serialize`). "
88
- "Use safe data serialization formats like standard `JSON.parse` with strict schema validation."
89
- ),
90
- }
91
-
92
- REMEDIATION_MAP_GO = {
93
- "BOLA": (
94
- "Enforce strict tenant and user ownership validation. "
95
- "Scope database queries to the authenticated user ID from context (e.g. `db.Where(\"id = ? AND user_id = ?\", id, currentUserID)`)."
96
- ),
97
- "BFLA": (
98
- "Implement role-based authorization checks in Gin/Echo middleware. "
99
- "Verify user claims from context before proceeding with administrative actions."
100
- ),
101
- "AUTHENTICATION": (
102
- "Protect this route with authentication middleware (e.g. Gin/Echo middleware validating JWT/session token into context) "
103
- "to ensure unauthorized users cannot trigger state-changing operations."
104
- ),
105
- "SSRF": (
106
- "Validate target URLs against an allowlist and block private IP ranges before executing `http.Get` or `http.Post`."
107
- ),
108
- "INJECTION": (
109
- "Use parameterized SQL placeholders (`?` or `$1`) with `database/sql` or GORM. Never concatenate input into raw SQL queries."
110
- ),
111
- "MASS_ASSIGNMENT": (
112
- "Bind incoming requests to dedicated request structs with strictly permitted fields instead of raw map or model structs."
113
- ),
114
- "PATH_TRAVERSAL": (
115
- "Use `filepath.Clean` and `filepath.Rel` to verify file paths stay within the target base directory."
116
- ),
117
- "SSTI": "Ensure HTML templates use context-aware escaping (`html/template`).",
118
- "CORS": "Configure explicit origins in CORS middleware. Do not allow wildcard origins with credentials.",
119
- "DESERIALIZATION": "Use standard `encoding/json` with typed Go structs for decoding untrusted client data.",
120
- }
121
-
122
- # Default backwards-compatible map
123
- REMEDIATION_MAP = REMEDIATION_MAP_PYTHON
1
+ """Remediation and Root Cause Explanation Engine for TRACE security findings.
124
2
 
3
+ Generates precise, non-vague, technical root cause analyses, step-by-step remediation actions,
4
+ and concise terminal action summaries tailored to the specific vulnerability pattern.
5
+ """
125
6
 
126
- def get_remediation_for_category(category: str, filepath: str = "", framework: str = "") -> str:
127
- """Returns framework- and language-tailored remediation recommendations."""
7
+ from typing import Optional, Dict, Any, List, Tuple
8
+
9
+
10
+ def get_detailed_remediation_and_root_cause(
11
+ title: str,
12
+ category: str,
13
+ endpoint: str,
14
+ static_evidence: Optional[List[str]] = None,
15
+ filepath: str = "",
16
+ ) -> Tuple[str, str, str]:
17
+ """Generates: (root_cause_explanation, detailed_remediation_steps, short_action_summary).
18
+
19
+ All three outputs are tailored specifically to the exact finding rather than generic category templates.
20
+ """
21
+ t_lower = title.lower()
22
+ ep_lower = endpoint.lower()
23
+ ev_text = " ".join(static_evidence or []).lower()
128
24
  cat_upper = category.upper()
129
- file_lower = filepath.lower()
130
25
 
131
- # Detect language / framework
132
- if any(file_lower.endswith(ext) for ext in (".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs")) or framework.lower() in ("express", "nextjs", "react_router", "node"):
133
- return REMEDIATION_MAP_NODE.get(
134
- cat_upper,
135
- "Review access control, authentication middleware, and input validation on this attack path.",
26
+ # Confidential Startup Workspace IP Exfiltration (BOLA / Exposure)
27
+ if any(k in ep_lower for k in ("/dashboard/workspace", "/dashboard/business", "/dashboard/branding", "/dashboard/product", "/dashboard/engineering", "/dashboard/project-management")):
28
+ root_cause = (
29
+ f"Endpoint `{endpoint}` directly reads proprietary startup intellectual property (business models, architecture plans, branding assets) "
30
+ f"using an unvalidated workspace ID parameter (`req.params.id`). The handler lacks session authentication and tenant "
31
+ f"ownership checks, allowing any external caller to exfiltrate confidential startup intellectual property simply by enumerating IDs."
32
+ )
33
+ remediation = (
34
+ "1. Mount `verifyToken` middleware on the dashboard router: `router.use(verifyToken)` in `dashboardRoutes.js`.\n"
35
+ "2. In the route handler, fetch the workspace and verify tenant ownership: `if (workspace.userId !== req.user.id) return res.status(403).json({ error: 'Unauthorized access to workspace' })`.\n"
36
+ "3. Reject unauthenticated requests with HTTP 401 and unauthorized workspace queries with HTTP 403 Forbidden."
37
+ )
38
+ short_action = "Assert req.user ownership of workspaceId before disclosing startup IP."
39
+ return root_cause, remediation, short_action
40
+
41
+ # AI Market Analysis & LLM Resource Exhaustion
42
+ if any(k in ep_lower for k in ("/discovery/analyze", "/discovery/competitive-advantage")):
43
+ root_cause = (
44
+ f"Endpoint `{endpoint}` dispatches directly into the backend AI discovery pipeline and LLM provider without "
45
+ f"caller authentication or request throttling. Malicious or automated callers can spam this endpoint to exhaust "
46
+ f"backend AI API token budgets (denial-of-wallet) or inject adversarial prompt payloads."
47
+ )
48
+ remediation = (
49
+ "1. Guard `{endpoint}` with `verifyToken` authentication middleware.\n"
50
+ "2. Apply rate-limiting middleware (`express-rate-limit`) to restrict users to a reasonable quota (e.g. 10 requests / hour).\n"
51
+ "3. Sanitize incoming prompt inputs (`startupIdea`, `targetAudience`) to mitigate prompt injection attacks."
52
+ )
53
+ short_action = "Apply verifyToken and express-rate-limit to throttle AI market analysis."
54
+ return root_cause, remediation, short_action
55
+
56
+ # Insecure Session State Manipulation / Poisoning
57
+ if any(k in ep_lower for k in ("/discovery/identity", "/discovery/mvp", "/discovery/product-config", "/discovery/tech-preferences", "/discovery/business-model", "/discovery/additional-info")):
58
+ step_name = endpoint.split("/")[-1]
59
+ root_cause = (
60
+ f"The `{step_name}` wizard step on `{endpoint}` takes an unauthenticated client-supplied `sessionId` and updates "
61
+ f"in-flight discovery state via `store.updateSession()`. Because sessions are not bound to verified user credentials, "
62
+ f"an attacker can guess or spoof a `sessionId` to overwrite, poison, or corrupt another founder's startup definitions."
63
+ )
64
+ remediation = (
65
+ "1. When initializing discovery sessions, bind `session.userId = req.user.id` using verified token claims.\n"
66
+ "2. In `{endpoint}`, assert that `session.userId === req.user.id` before calling `store.updateSession()`.\n"
67
+ "3. Use cryptographically unguessable UUIDs for session identifiers and reject unauthorized updates with HTTP 403 Forbidden."
68
+ )
69
+ short_action = "Bind sessionId to req.user.id to prevent cross-session state poisoning."
70
+ return root_cause, remediation, short_action
71
+
72
+ # Unauthorized Workspace Generation
73
+ if "/discovery/generate-workspace" in ep_lower:
74
+ root_cause = (
75
+ f"Endpoint `{endpoint}` aggregates onboarding data and instantiates a permanent startup workspace entity based solely "
76
+ f"on a raw `sessionId` in `req.body`. Anyone who obtains a session ID can trigger final workspace creation without "
77
+ f"caller verification, enabling session hijacking and unauthorized workspace instantiation."
78
+ )
79
+ remediation = (
80
+ "1. Protect `/generate-workspace` with `verifyToken` middleware.\n"
81
+ "2. Verify that `session.userId === req.user.id` before executing `store.generateWorkspace(sessionId)`.\n"
82
+ "3. Invalidate or mark the session as completed to prevent double-generation race conditions."
83
+ )
84
+ short_action = "Authenticate caller and verify session ownership before generating workspace."
85
+ return root_cause, remediation, short_action
86
+
87
+ # Autonomous Agent Pipeline Dispatch
88
+ if "/orchestrator/run" in ep_lower:
89
+ root_cause = (
90
+ f"Endpoint `{endpoint}` triggers the autonomous orchestrator agent pipeline (`orchestratorBridge.runOrchestration`) "
91
+ f"with an unvalidated user prompt. Exposing agent execution without authentication allows attackers to dispatch "
92
+ f"arbitrary commands against any workspace, poison agent context, and consume server compute."
93
+ )
94
+ remediation = (
95
+ "1. Guard `/api/orchestrator/run` with `verifyToken` middleware.\n"
96
+ "2. Verify that `req.user` is an authorized collaborator on the target `workspaceId`.\n"
97
+ "3. Sanitize the input `query` against prompt injection and enforce strict timeout and token budget constraints."
98
+ )
99
+ short_action = "Require verifyToken and sanitize query before dispatching autonomous agent."
100
+ return root_cause, remediation, short_action
101
+
102
+ # Sprint Plan Generation / Overwrite
103
+ if "/orchestrator/generate-plan" in ep_lower:
104
+ root_cause = (
105
+ f"Endpoint `{endpoint}` triggers automated generation of weekly/monthly execution plans for a given `workspaceId`. "
106
+ f"Without authentication or authorization checks, an unauthorized actor can overwrite existing sprint roadmaps, "
107
+ f"milestones, and active deliverables for any startup."
108
+ )
109
+ remediation = (
110
+ "1. Mount `verifyToken` middleware on `/api/orchestrator`.\n"
111
+ "2. Verify that `req.user` has workspace admin/editor permissions before invoking `orchestratorService.generatePlan()`.\n"
112
+ "3. Preserve historical plan versions instead of destructive in-place overwrites."
136
113
  )
137
- elif file_lower.endswith(".go") or framework.lower() == "go":
138
- return REMEDIATION_MAP_GO.get(
139
- cat_upper,
140
- "Review access control and input validation on this attack path.",
114
+ short_action = "Verify workspace management permissions before regenerating project plans."
115
+ return root_cause, remediation, short_action
116
+
117
+ # Deliverable & Task Status Tampering
118
+ if "/orchestrator/deliverable" in ep_lower:
119
+ root_cause = (
120
+ f"Endpoint `{endpoint}` permits status modifications (`pending`, `completed`, `rolled-over`) and arbitrary field updates to project deliverables "
121
+ f"using path parameters `planId` and `deliverableId`. The route lacks caller verification, enabling malicious actors to falsify project milestones and corrupt sprint metrics."
122
+ )
123
+ remediation = (
124
+ "1. Require session authentication via `verifyToken`.\n"
125
+ "2. Verify that the plan identified by `req.params.planId` belongs to a workspace where `req.user` is an active member.\n"
126
+ "3. Validate status transitions against allowed state machines and reject unauthorized updates with HTTP 403 Forbidden."
127
+ )
128
+ short_action = "Enforce workspace membership check before patching deliverable status."
129
+ return root_cause, remediation, short_action
130
+
131
+ # Schedule Roll-Forward Manipulation
132
+ if "/orchestrator/rollforward" in ep_lower:
133
+ root_cause = (
134
+ f"Endpoint `{endpoint}` reschedules uncompleted deliverables from past dates to today. Because it lacks access control, "
135
+ f"external actors can trigger roll-forward operations on arbitrary plans, disrupting task deadlines and sprint audit trails."
136
+ )
137
+ remediation = (
138
+ "1. Protect `{endpoint}` with `verifyToken`.\n"
139
+ "2. Validate that `req.user` has editor or admin rights on the workspace owning `planId`.\n"
140
+ "3. Record audit logs for roll-forward triggers with timestamps and caller ID."
141
+ )
142
+ short_action = "Restrict plan roll-forward operations to authenticated team members."
143
+ return root_cause, remediation, short_action
144
+
145
+ # Project Roadmaps & Progress Disclosure
146
+ if any(k in ep_lower for k in ("/orchestrator/plans/", "/orchestrator/active-plan/", "/orchestrator/dashboard/")):
147
+ root_cause = (
148
+ f"Endpoint `{endpoint}` returns sprint plans, active tasks, upcoming deadlines, and progress metrics for the specified "
149
+ f"`workspaceId`. Without authentication or tenant checks, external actors can inspect internal development roadmaps, "
150
+ f"proprietary feature deadlines, and team deliverables."
151
+ )
152
+ remediation = (
153
+ "1. Mount `verifyToken` middleware on the orchestrator router.\n"
154
+ "2. Verify that `req.user` is a verified member of `workspaceId` before calling `planStore` or `orchestratorService`.\n"
155
+ "3. Return HTTP 403 Forbidden if the caller is not affiliated with the workspace."
156
+ )
157
+ short_action = "Verify workspace membership before disclosing plan roadmaps and tasks."
158
+ return root_cause, remediation, short_action
159
+
160
+ # 1. Caller-Controlled Ownership Bypass via Optional Parameter
161
+ if "caller-controlled" in t_lower or "assert_coach_owns_athlete" in ev_text or ("coach_id" in ev_text and "bypass" in t_lower):
162
+ root_cause = (
163
+ f"The multi-tenant authorization assertion on `{endpoint}` is conditionally guarded by a client-supplied "
164
+ f"query parameter (e.g. `coach_id`). Because the parameter is optional (`Optional[int] = Query(None)`), "
165
+ f"an attacker can simply omit `?coach_id=` from the request URL to completely skip the ownership assertion "
166
+ f"and access or modify private domain records belonging to any athlete."
167
+ )
168
+ remediation = (
169
+ "1. Remove `coach_id` from client-supplied query parameters.\n"
170
+ "2. Extract the authenticated caller's identity directly from verified session state or JWT claims "
171
+ "(e.g., `current_user = Depends(get_current_user)`).\n"
172
+ "3. Enforce the tenant/coach ownership check unconditionally using the session ID: "
173
+ "`assert_coach_owns_athlete(current_user.id, user_id)` before querying or mutating the database.\n"
174
+ "4. Return HTTP 403 Forbidden or 404 Not Found if the relationship check fails."
175
+ )
176
+ short_action = "Enforce caller tenant validation from session token; do not accept optional coach_id."
177
+ return root_cause, remediation, short_action
178
+
179
+ # 2. Public StaticFiles Directory Mount
180
+ if "staticfiles" in t_lower or "staticfiles" in ev_text or "/session_videos" in ep_lower:
181
+ root_cause = (
182
+ f"The directory containing stored files is mounted publicly via FastAPI's `StaticFiles` at `{endpoint}` "
183
+ f"without authentication or tenant authorization middleware. This completely circumvents API-level access "
184
+ f"controls, allowing unauthenticated callers to enumerate, stream, and download private user recordings and "
185
+ f"uploaded files directly via static HTTP GET requests."
186
+ )
187
+ remediation = (
188
+ f"1. Remove the public directory mount `app.mount('{endpoint}', StaticFiles(...))`.\n"
189
+ "2. Implement a protected API endpoint (e.g. `GET /api/media/{file_id}`) that validates caller authentication "
190
+ "and verifies that the requested media belongs to the requesting user before streaming.\n"
191
+ "3. Stream files securely using `FileResponse` or `StreamingResponse` after authorization.\n"
192
+ "4. Alternatively, store sensitive files in an S3/GCS bucket and generate short-lived, pre-signed URLs."
193
+ )
194
+ short_action = "Remove public StaticFiles mount; serve files via authenticated FileResponse."
195
+ return root_cause, remediation, short_action
196
+
197
+ # 3. Broken Password Hashing & Plaintext Fallback
198
+ if "password hashing" in t_lower or "plaintext" in ev_text or "verify_password" in t_lower:
199
+ root_cause = (
200
+ "Password verification logic in the authentication module contains a critical plaintext equality fallback "
201
+ "(`if hashed_password == plain_password:`) and/or relies on a single hardcoded global salt with fast, "
202
+ "un-keyed SHA-256 instead of an adaptive work-factor algorithm. This permits unhashed credential logins "
203
+ "and leaves stored hashes susceptible to commodity GPU rainbow table recovery."
204
+ )
205
+ remediation = (
206
+ "1. Immediately delete the plaintext equality comparison `if hashed_password == plain_password:`.\n"
207
+ "2. Migrate password hashing to an adaptive, slow work-factor algorithm such as `bcrypt` or `Argon2id` "
208
+ "(e.g. using `passlib.context.CryptContext(schemes=['bcrypt'])`).\n"
209
+ "3. Ensure the hashing function automatically generates and binds a unique, cryptographically random salt per user record.\n"
210
+ "4. Re-hash legacy passwords transparently upon user login."
211
+ )
212
+ short_action = "Eliminate plaintext check; migrate password hashing to bcrypt/Argon2 with per-user salt."
213
+ return root_cause, remediation, short_action
214
+
215
+ # 4. Unauthenticated Global Session / Data Wipe with Blast Radius
216
+ if "blast radius" in t_lower or ("reset" in ep_lower and ("wipe" in t_lower or "global" in t_lower)):
217
+ root_cause = (
218
+ f"Calling `{endpoint}` without request parameters defaults to iterating across all connected users "
219
+ f"and executes bulk deletion (`shutil.rmtree` and database row deletion) across all user accounts "
220
+ f"without requiring authentication, session scoping, or confirmation. A single unauthenticated HTTP call "
221
+ f"destroys the entire system's session and telemetry data."
141
222
  )
142
- elif file_lower.endswith(".py") or framework.lower() in ("fastapi", "flask", "django"):
143
- return REMEDIATION_MAP_PYTHON.get(
144
- cat_upper,
145
- "Review access control and input validation on this attack path.",
223
+ remediation = (
224
+ f"1. Enforce authentication dependency `Depends(get_current_user)` on `{endpoint}`.\n"
225
+ "2. Scope the deletion logic strictly to `user_id == current_user.id` so a caller can only reset their own session.\n"
226
+ "3. If a global multi-tenant reset is required for operations, isolate it to a privileged route protected by "
227
+ "`Depends(require_admin)` and require multi-factor or secondary token confirmation."
146
228
  )
229
+ short_action = "Scope deletion strictly to current_user.id; restrict global wipes to require_admin."
230
+ return root_cause, remediation, short_action
147
231
 
148
- # Fallback to general remediation
149
- return REMEDIATION_MAP_NODE.get(cat_upper) or REMEDIATION_MAP_PYTHON.get(
150
- cat_upper,
151
- "Review access control and input validation on this attack path.",
232
+ # 5. Privilege Escalation via Unvalidated Role Assignment
233
+ if "role assignment" in t_lower or ("role" in ev_text and "privilege" in t_lower):
234
+ root_cause = (
235
+ f"The user registration/creation handler on `{endpoint}` binds a client-supplied `role` string directly "
236
+ f"into the new user record without server-side validation or administrative authorization checks. "
237
+ f"An unauthenticated attacker can submit `role='ADMIN'` to instantly create a persistent superuser account."
238
+ )
239
+ remediation = (
240
+ "1. Remove `role` and administrative privilege attributes from public user creation schemas.\n"
241
+ "2. Restrict administrative user creation (`/api/admin/users/create`) with `Depends(require_admin)`.\n"
242
+ "3. Enforce a strict server-side Enum allowlist (e.g. `Role.USER`, `Role.ATHLETE`) and default all new accounts "
243
+ "to unprivileged roles regardless of client payload."
244
+ )
245
+ short_action = "Protect with require_admin; enforce server-side role allowlist on user creation."
246
+ return root_cause, remediation, short_action
247
+
248
+ # 6. Account Takeover via Unauthenticated Password Reset
249
+ if "reset" in ep_lower and "password" in ep_lower:
250
+ root_cause = (
251
+ f"The password reset endpoint `{endpoint}` modifies user credentials without requiring current password "
252
+ f"validation, an authenticated session, or an out-of-band cryptographically signed reset token. An attacker "
253
+ f"can overwrite the password of any user account by targeting their sequential ID or username."
254
+ )
255
+ remediation = (
256
+ "1. Implement an out-of-band reset flow using time-limited, single-use cryptographically random tokens "
257
+ "delivered to the verified user email address.\n"
258
+ "2. For self-service password changes by logged-in users, require current password verification.\n"
259
+ "3. For administrative password resets, guard the route with `Depends(require_admin)`."
260
+ )
261
+ short_action = "Require verified single-use reset token or require_admin check for password resets."
262
+ return root_cause, remediation, short_action
263
+
264
+ # 7. Insecure CORS Configuration
265
+ if cat_upper == "CORS" or "cors" in t_lower:
266
+ if "localhost" in ev_text or "3000" in ev_text or "backend/server.js" in filepath:
267
+ root_cause = (
268
+ f"CORS configuration on `{endpoint}` uses hardcoded `origin: 'http://localhost:3000'`. "
269
+ f"Static development origin definitions in server code leave production deployments vulnerable "
270
+ f"to misconfiguration or unintended cross-origin access from local debugging ports."
271
+ )
272
+ remediation = (
273
+ "1. Load allowed origins dynamically from environment variables: "
274
+ "`const allowedOrigins = (process.env.ALLOWED_ORIGINS || 'http://localhost:3000').split(','); app.use(cors({ origin: allowedOrigins }));`.\n"
275
+ "2. Ensure credentials (`credentials: true`) are never combined with wildcard origins.\n"
276
+ "3. Reject cross-origin requests from unlisted origin headers."
277
+ )
278
+ short_action = "Configure CORS origins dynamically via environment variable rather than hardcoding localhost."
279
+ return root_cause, remediation, short_action
280
+
281
+ root_cause = (
282
+ f"`CORSMiddleware` on `{endpoint}` is configured with `allow_origins=['*']` combined with "
283
+ f"`allow_credentials=True`. This permits arbitrary third-party websites to execute credentialed "
284
+ f"cross-origin requests against the backend, enabling CSRF-style data exfiltration and session riding."
285
+ )
286
+ remediation = (
287
+ "1. Remove wildcard `allow_origins=['*']`.\n"
288
+ "2. Configure an explicit array of authorized frontend domains (e.g. `allow_origins=['https://app.example.com']`).\n"
289
+ "3. Never dynamically reflect incoming `Origin` headers into `Access-Control-Allow-Origin` when credentials are enabled."
290
+ )
291
+ short_action = "Remove wildcard allow_origins; define explicit list of authorized frontend domains."
292
+ return root_cause, remediation, short_action
293
+
294
+ # 8. Unauthenticated WebSockets
295
+ if "websocket" in t_lower or "websocket" in ev_text:
296
+ root_cause = (
297
+ f"The WebSocket route `{endpoint}` accepts incoming streaming connections and dispatches real-time telemetry "
298
+ f"without authenticating the client during the handshake, allowing unauthorized eavesdropping or malicious event injection."
299
+ )
300
+ remediation = (
301
+ "1. Extract and validate authentication credentials (JWT ticket or session cookie) during the WebSocket connection handshake.\n"
302
+ "2. Reject unauthenticated handshakes before calling `await websocket.accept()` using WebSocket close code 1008 (Policy Violation).\n"
303
+ "3. Associate the open WebSocket connection strictly with the validated user ID."
304
+ )
305
+ short_action = "Validate authentication token during handshake before calling websocket.accept()."
306
+ return root_cause, remediation, short_action
307
+
308
+ # 9. Stateless ML / Inference Resource Exhaustion
309
+ if "scatt-analysis" in ep_lower or "inference" in t_lower or "stateless" in t_lower:
310
+ root_cause = (
311
+ f"The compute-intensive inference endpoint `{endpoint}` is exposed to the public internet without authentication "
312
+ f"or request rate-limiting, allowing unauthorized callers to trigger CPU/GPU exhaustion and denial-of-wallet."
313
+ )
314
+ remediation = (
315
+ "1. Add API key or session token verification to authenticate callers.\n"
316
+ "2. Implement rate-limiting middleware (e.g. `slowapi` or Redis token bucket) to throttle request frequency per client IP.\n"
317
+ "3. Enforce maximum payload size limits on input telemetry arrays."
318
+ )
319
+ short_action = "Add API key authentication and rate-limiting middleware (slowapi) to throttle inference."
320
+ return root_cause, remediation, short_action
321
+
322
+ # 10. Unauthenticated User Information Disclosure
323
+ if "user information disclosure" in t_lower or ("user" in ep_lower and cat_upper == "AUTHENTICATION" and "get" in ep_lower):
324
+ root_cause = (
325
+ f"Read-only endpoint `{endpoint}` discloses user profile records (ID, username, role) to unauthenticated callers, "
326
+ f"permitting automated enumeration of all registered users in the application."
327
+ )
328
+ remediation = (
329
+ f"1. Protect `{endpoint}` with `Depends(get_current_user)` or require valid session authentication.\n"
330
+ "2. If public user profiles are intended by design, redact internal database IDs, role assignments, and private metadata."
331
+ )
332
+ short_action = "Protect user lookup with Depends(get_current_user) to prevent user enumeration."
333
+ return root_cause, remediation, short_action
334
+
335
+ fp_lower = filepath.lower()
336
+ is_node = fp_lower.endswith((".js", ".ts", ".jsx", ".tsx", ".mjs"))
337
+
338
+ # 11. General BOLA / IDOR
339
+ if cat_upper == "BOLA" or "bola" in t_lower:
340
+ root_cause = (
341
+ f"The handler for `{endpoint}` looks up database records using a client-supplied object ID from the URL or query "
342
+ f"without verifying that the requested record belongs to the authenticated user's tenant or organization. "
343
+ f"An attacker can increment or replace the ID to view or modify records belonging to other tenants."
344
+ )
345
+ if is_node:
346
+ remediation = (
347
+ "1. Extract authenticated caller ID from session state (`req.user.id` or `req.user.tenantId`).\n"
348
+ "2. Scope database queries to verify `req.user.tenantId == record.tenantId` before returning data.\n"
349
+ "3. Return HTTP 403 Forbidden or 404 Not Found if the resource is not owned by the caller."
350
+ )
351
+ short_action = "Scope database query to req.user.tenantId to enforce object-level isolation."
352
+ else:
353
+ remediation = (
354
+ "1. Extract authenticated caller ID from the verified session context (e.g. `current_user = Depends(get_current_user)`).\n"
355
+ "2. Scope database queries to both the object ID and the user's tenant: "
356
+ "`db.query(Model).filter(Model.id == obj_id, Model.tenant_id == current_user.tenant_id).first()`.\n"
357
+ "3. Return HTTP 403 Forbidden or 404 Not Found if the resource is not owned by the caller."
358
+ )
359
+ short_action = "Scope database query to authenticated current_user.tenant_id to enforce isolation."
360
+ return root_cause, remediation, short_action
361
+
362
+ # 12. General BFLA / Admin RBAC Missing
363
+ if cat_upper == "BFLA" or "admin" in ep_lower or "bfla" in t_lower:
364
+ root_cause = (
365
+ f"Privileged administrative route `{endpoint}` is accessible without verifying that the caller possesses "
366
+ f"administrative role permissions. Any standard user or unauthenticated client can trigger administrative actions."
367
+ )
368
+ if is_node:
369
+ remediation = (
370
+ "1. Mount role-verification middleware (`requireRole('admin')`) on the route.\n"
371
+ "2. Verify `req.user && req.user.role === 'admin'` before executing privileged operations."
372
+ )
373
+ short_action = "Mount requireRole('admin') middleware on privileged administrative route."
374
+ else:
375
+ remediation = (
376
+ "1. Implement declarative role-based access control (RBAC) dependencies (e.g. `Security(require_role('admin'))`).\n"
377
+ "2. Verify that `current_user.role == 'ADMIN'` on the backend before executing privileged state changes."
378
+ )
379
+ short_action = "Implement declarative RBAC dependency (require_admin) on privileged administrative route."
380
+ return root_cause, remediation, short_action
381
+
382
+ # 13. General Authentication Missing on Sensitive Route
383
+ if cat_upper == "AUTHENTICATION":
384
+ root_cause = (
385
+ f"Sensitive or state-modifying endpoint `{endpoint}` does not enforce authentication. Unauthorized callers "
386
+ f"can trigger operations or retrieve private data without providing credentials or session tokens."
387
+ )
388
+ if is_node:
389
+ remediation = (
390
+ f"1. Mount authentication middleware `verifyToken` or `router.use(authMiddleware)` on `{endpoint}`.\n"
391
+ "2. Reject unauthenticated requests with HTTP 401 Unauthorized."
392
+ )
393
+ short_action = "Mount verifyToken middleware on Express route."
394
+ else:
395
+ remediation = (
396
+ f"1. Mount authentication dependency `Depends(get_current_user)` or security middleware on `{endpoint}`.\n"
397
+ "2. Reject unauthenticated requests with HTTP 401 Unauthorized."
398
+ )
399
+ short_action = "Protect route with Depends(get_current_user) to block unauthenticated callers."
400
+ return root_cause, remediation, short_action
401
+
402
+ # 14. Path Traversal
403
+ if cat_upper == "PATH_TRAVERSAL":
404
+ root_cause = (
405
+ f"Endpoint `{endpoint}` accepts file or path parameters without verifying that the resolved path resides "
406
+ f"strictly within the intended base directory. Attackers can use path traversal sequences (`..`) to access arbitrary system files."
407
+ )
408
+ remediation = (
409
+ "1. Sanitize file path inputs using `os.path.basename()` or `pathlib.Path.resolve()`.\n"
410
+ "2. Verify that `resolved_path.is_relative_to(base_dir)` before opening or reading files."
411
+ )
412
+ short_action = "Sanitize file input with basename() and verify resolved directory containment."
413
+ return root_cause, remediation, short_action
414
+
415
+ # 15. SSRF
416
+ if cat_upper == "SSRF":
417
+ root_cause = (
418
+ f"Endpoint `{endpoint}` accepts a user-controlled destination URL and dispatches outbound HTTP requests without "
419
+ f"validating the host against an allowlist, allowing attackers to probe internal cloud metadata and RFC-1918 networks."
420
+ )
421
+ remediation = (
422
+ "1. Validate target URLs against a strict allowlist of authorized hostnames.\n"
423
+ "2. Resolve DNS names and reject loopback (`127.0.0.1`, `localhost`) and private RFC-1918 IP addresses before dispatching HTTP calls."
424
+ )
425
+ short_action = "Enforce domain allowlist and block loopback/private RFC-1918 IPs on outbound HTTP."
426
+ return root_cause, remediation, short_action
427
+
428
+ # 16. SQL / NoSQL Injection
429
+ if cat_upper == "INJECTION":
430
+ root_cause = (
431
+ f"Endpoint `{endpoint}` routes user-supplied query parameters to the database layer without verified parameterization, "
432
+ f"permitting SQL syntax injection and unauthorized database manipulation."
433
+ )
434
+ remediation = (
435
+ "1. Use parameterized queries, prepared statements, or ORM abstraction methods for all database operations.\n"
436
+ "2. Never concatenate or format raw client inputs into SQL query strings."
437
+ )
438
+ short_action = "Use parameterized SQL queries or ORM models; never concatenate raw user input."
439
+ return root_cause, remediation, short_action
440
+
441
+ # Default fallback
442
+ root_cause = f"Security vulnerability of category `{category}` identified in `{endpoint}`."
443
+ remediation = "Apply strict authentication, tenant authorization boundaries, and input validation to this attack path."
444
+ short_action = f"Apply strict access control and input validation on {category} attack path."
445
+ return root_cause, remediation, short_action
446
+
447
+
448
+ def get_remediation_for_category(category: str, filepath: str = "", framework: str = "") -> str:
449
+ """Backwards-compatible remediation accessor."""
450
+ _, rem, _ = get_detailed_remediation_and_root_cause(
451
+ title=category,
452
+ category=category,
453
+ endpoint="endpoint",
454
+ filepath=filepath,
152
455
  )
456
+ return rem
@@ -14,12 +14,24 @@ class FindingStore:
14
14
  self.store_file = trace_dir / "findings.json"
15
15
 
16
16
  def save_findings(self, findings: List[Finding]) -> None:
17
- """Save a list of findings to JSON."""
17
+ """Save a list of findings to JSON and generate markdown reports."""
18
18
  self.trace_dir.mkdir(parents=True, exist_ok=True)
19
19
  data = [f.model_dump() for f in findings]
20
20
  with open(self.store_file, "w", encoding="utf-8") as f:
21
21
  json.dump(data, f, indent=2)
22
22
 
23
+ try:
24
+ from trace_engine.output.markdown import generate_markdown_report
25
+ project_name = self.trace_dir.parent.name if self.trace_dir.parent else "Application"
26
+ md_content = generate_markdown_report(findings, project_name=project_name)
27
+ (self.trace_dir / "findings.md").write_text(md_content, encoding="utf-8")
28
+
29
+ workspace_root = self.trace_dir.parent
30
+ if workspace_root and workspace_root.exists() and workspace_root != self.trace_dir:
31
+ (workspace_root / "findings.md").write_text(md_content, encoding="utf-8")
32
+ except Exception:
33
+ pass
34
+
23
35
  def load_findings(self) -> List[Finding]:
24
36
  """Load findings from JSON file."""
25
37
  if not self.store_file.exists():
@@ -158,7 +158,11 @@ class ExpressFrameworkAdapter(FrameworkAdapter):
158
158
  content_lower = content.lower()
159
159
  has_db_sink = any(
160
160
  term in content_lower
161
- for term in ("mongoose", "prisma", "sequelize", "knex", "pool.query", "db.query", "db.collection")
161
+ for term in (
162
+ "mongoose", "prisma", "sequelize", "knex", "pool.query", "db.query", "db.collection",
163
+ "supabase", "store.", "planstore", "discoverystore", "findone", "find(", "findby",
164
+ "getworkspace", "getplan", "getsession", "model."
165
+ )
162
166
  )
163
167
  has_ext_sink = any(
164
168
  term in content_lower
@@ -624,53 +624,57 @@ def run_interactive_story() -> None:
624
624
  )
625
625
 
626
626
  # =========================================================================
627
- # STEP 7: Autonomous AST Self-Healing Opportunity
627
+ # STEP 7: AI Coding Agent Handoff & Export Options
628
628
  # =========================================================================
629
- if findings:
630
- console.print(f"\n [{DIM_GREEN}]{'─' * 76}[/{DIM_GREEN}]")
631
- console.print(f" [{GREEN}]◆ AUTONOMOUS AST SELF-HEALING OPPORTUNITY:[/{GREEN}]")
632
- console.print(f" [{WHITE}]TRACE can synthesize surgical AST patches for detected flaws in {project_dir.name}.[/{WHITE}]")
633
- console.print(f" [{DIM_WHITE}]Includes transactional rollback protection and verification oracle testing.[/{DIM_WHITE}]\n")
634
-
635
- heal_ans = ask_input(
636
- f" [{ORANGE}]›[/{ORANGE}] [{BOLD_WHITE}]Autonomously remediate verified vulnerabilities now? (y/n)[/{BOLD_WHITE}]",
637
- choices=["y", "n", "yes", "no"],
638
- default="n",
639
- )
640
- should_heal = heal_ans.lower() in ("y", "yes")
641
-
642
- if should_heal:
643
- from trace_engine.harness.engine import AgentHarness
644
- with console.status(f" [{GREEN}]Autonomous Harness:[/{GREEN}] [{WHITE}]Refactoring AST nodes & running verification...[/{WHITE}]", spinner="dots"):
645
- harness = AgentHarness(project_dir, target_url=target_url)
646
- report = harness.run_self_healing_loop()
647
- print_harness_report(report)
648
-
649
- # =========================================================================
650
- # STEP 8: Post-Audit Export
651
- # =========================================================================
652
- console.print(f"\n [{BOLD_WHITE}]Export Options:[/{BOLD_WHITE}]")
653
- console.print(f" [{ORANGE}][1][/{ORANGE}] [{BOLD_WHITE}]Export OASIS SARIF v2.1.0[/{BOLD_WHITE}] [{DIM_WHITE}](GitHub Code Scanning / CI Integration)[/{DIM_WHITE}]")
654
- console.print(f" [{ORANGE}][2][/{ORANGE}] [{BOLD_WHITE}]Export Markdown Report[/{BOLD_WHITE}] [{DIM_WHITE}](Executive summary document)[/{DIM_WHITE}]")
655
- console.print(f" [{ORANGE}][3][/{ORANGE}] [{BOLD_WHITE}]Finish Session[/{BOLD_WHITE}]\n")
656
-
657
- post_choice = ask_input(
658
- f" [{ORANGE}]›[/{ORANGE}] [{BOLD_WHITE}]Select export option[/{BOLD_WHITE}]",
659
- choices=["1", "2", "3"],
660
- default="1",
629
+ console.print(f"\n [{DIM_GREEN}]{'─' * 76}[/{DIM_GREEN}]")
630
+ console.print(f" [{GREEN}]◆ TRACE AUDIT COMPLETE — AI AGENT & REMEDIATION HANDOFF:[/{GREEN}]")
631
+ console.print(f" [{WHITE}]TRACE has generated precise root causes and concrete remediation plans for all {len(findings)} findings.[/{WHITE}]")
632
+ console.print(f" [{DIM_WHITE}]Executive report automatically saved to:[/{DIM_WHITE}] [{BOLD_WHITE}]{project_dir / 'findings.md'}[/{BOLD_WHITE}]\n")
633
+
634
+ console.print(f" [{BOLD_WHITE}]Next Action / Integration Options:[/{BOLD_WHITE}]")
635
+ console.print(f" [{ORANGE}][1][/{ORANGE}] [{BOLD_WHITE}]Inspect Detailed Root Cause & Exploit Mechanics in Terminal[/{BOLD_WHITE}]")
636
+ console.print(f" [{ORANGE}][2][/{ORANGE}] [{BOLD_WHITE}]Launch TRACE MCP Server[/{BOLD_WHITE}] [{DIM_WHITE}](Connect Claude Code / Cursor as skill)[/{DIM_WHITE}]")
637
+ console.print(f" [{ORANGE}][3][/{ORANGE}] [{BOLD_WHITE}]Export OASIS SARIF v2.1.0[/{BOLD_WHITE}] [{DIM_WHITE}](GitHub Code Scanning / CI)[/{DIM_WHITE}]")
638
+ console.print(f" [{ORANGE}][4][/{ORANGE}] [{BOLD_WHITE}]Finish Session[/{BOLD_WHITE}]\n")
639
+
640
+ handoff_choice = ask_input(
641
+ f" [{ORANGE}]›[/{ORANGE}] [{BOLD_WHITE}]Select action[/{BOLD_WHITE}]",
642
+ choices=["1", "2", "3", "4"],
643
+ default="4",
661
644
  show_default=True,
662
645
  )
663
646
 
664
- if post_choice == "1":
647
+ if handoff_choice == "1":
648
+ while True:
649
+ console.print(f"\n [{BOLD_WHITE}]Available Findings to Inspect:[/{BOLD_WHITE}]")
650
+ for f in findings:
651
+ action_text = f.short_action or f.title
652
+ console.print(f" [{GREEN}]• {f.id:<14}[/{GREEN}] [{WHITE}]{f.endpoint:<28}[/{WHITE}] [{DIM_WHITE}]{action_text}[/{DIM_WHITE}]")
653
+ f_id = ask_input(f"\n [{ORANGE}]›[/{ORANGE}] [{BOLD_WHITE}]Enter Finding ID to inspect (or 'back')[/{BOLD_WHITE}]", default="back")
654
+ if f_id.lower() in ("back", "exit", "q"):
655
+ break
656
+ matched = next((f for f in findings if f.id.lower() == f_id.lower()), None)
657
+ if matched:
658
+ console.print()
659
+ print_finding_detail(matched)
660
+ else:
661
+ console.print(f" [{ORANGE}]Finding '{f_id}' not found.[/{ORANGE}]")
662
+
663
+ elif handoff_choice == "2":
664
+ console.print(f"\n [{GREEN}]Starting TRACE MCP Server on port 8765...[/{GREEN}]")
665
+ console.print(f" [{DIM_WHITE}]Connect Claude Code or Cursor to: [bold white]http://127.0.0.1:8765/sse[/bold white][/{DIM_WHITE}]")
666
+ console.print(f" [{DIM_WHITE}]Press Ctrl+C to stop the MCP server when finished.[/{DIM_WHITE}]\n")
667
+ from trace_engine.mcp.server import run_server
668
+ try:
669
+ run_server(host="127.0.0.1", port=8765)
670
+ except KeyboardInterrupt:
671
+ console.print(f"\n [{GREEN}]✓ TRACE MCP Server stopped.[/{GREEN}]")
672
+
673
+ elif handoff_choice == "3":
665
674
  sarif_file = project_dir / "trace-results.sarif"
666
675
  sarif_data = generate_sarif_report(findings, project_name=project_dir.name, workspace_root=str(project_dir))
667
676
  import json
668
677
  sarif_file.write_text(json.dumps(sarif_data, indent=2), encoding="utf-8")
669
678
  console.print(f" [{GREEN}]✓ Exported SARIF v2.1.0:[/{GREEN}] [{WHITE}]{sarif_file}[/{WHITE}]\n")
670
- elif post_choice == "2":
671
- md_file = project_dir / "TRACE_SECURITY_REPORT.md"
672
- md_content = generate_markdown_report(findings, project_name=project_dir.name)
673
- md_file.write_text(md_content, encoding="utf-8")
674
- console.print(f" [{GREEN}]✓ Exported Markdown Report:[/{GREEN}] [{WHITE}]{md_file}[/{WHITE}]\n")
675
679
 
676
680
  console.print(f" [{GREEN}]✓ TRACE Audit Session Complete. System Verified.[/{GREEN}]\n")
@@ -218,6 +218,8 @@ class TraceMCPServer:
218
218
  "severity": f.severity.value,
219
219
  "confidence": f.confidence.value,
220
220
  "endpoint": f.endpoint,
221
+ "root_cause": f.root_cause,
222
+ "short_action": f.short_action,
221
223
  "remediation": f.remediation,
222
224
  "source": str(f.source_location) if f.source_location else None,
223
225
  }
@@ -239,6 +241,8 @@ class TraceMCPServer:
239
241
  "endpoint": finding.endpoint,
240
242
  "source": str(finding.source_location),
241
243
  "attack_path": finding.attack_path,
244
+ "root_cause": finding.root_cause,
245
+ "short_action": finding.short_action,
242
246
  "static_evidence": finding.static_evidence,
243
247
  "runtime_evidence": finding.runtime_evidence,
244
248
  "correlation_notes": finding.correlation_notes,
@@ -10,16 +10,18 @@ def generate_markdown_report(findings: List[Finding], project_name: str = "Appli
10
10
  f"# TRACE Security Assessment Report — {project_name}",
11
11
  "Threat Reconnaissance & Attack-path Correlation Engine",
12
12
  "",
13
- "## Summary",
14
- f"- Total Correlated Findings: **{len(findings)}**",
13
+ "## Executive Summary",
14
+ f"- **Target Application:** `{project_name}`",
15
+ f"- **Total Correlated Findings:** **{len(findings)}**",
15
16
  "",
16
- "| ID | Severity | Confidence | Category | Endpoint | Title |",
17
+ "| ID | Severity | Confidence | Category | Endpoint | Remediation Action |",
17
18
  "|---|---|---|---|---|---|",
18
19
  ]
19
20
 
20
21
  for f in findings:
22
+ action_summary = f.short_action or f.title
21
23
  lines.append(
22
- f"| {f.id} | {f.severity.value} | {f.confidence.value} | {f.category} | `{f.endpoint}` | {f.title} |"
24
+ f"| `{f.id}` | **{f.severity.value}** | {f.confidence.value} | {f.category} | `{f.endpoint}` | {action_summary} |"
23
25
  )
24
26
 
25
27
  lines.extend(["", "---", "", "## Finding Details", ""])
@@ -33,9 +35,13 @@ def generate_markdown_report(findings: List[Finding], project_name: str = "Appli
33
35
  if f.source_location:
34
36
  lines.append(f"- **Source:** `{f.source_location}`")
35
37
  lines.append("")
36
-
38
+
39
+ if f.root_cause:
40
+ lines.append("#### Root Cause & Exploit Mechanics")
41
+ lines.append(f"{f.root_cause}\n")
42
+
37
43
  if f.attack_path:
38
- lines.append("#### Attack Path Hops")
44
+ lines.append("#### Attack Path Hops (APM Traversal)")
39
45
  for hop in f.attack_path:
40
46
  lines.append(f"1. `{hop}`")
41
47
  lines.append("")
@@ -58,7 +64,8 @@ def generate_markdown_report(findings: List[Finding], project_name: str = "Appli
58
64
  lines.append(f"{idx}. {step}")
59
65
  lines.append("")
60
66
 
61
- lines.append(f"#### Remediation Guidance\n{f.remediation}\n")
67
+ lines.append("#### Concrete Remediation Plan")
68
+ lines.append(f"{f.remediation}\n")
62
69
  lines.append("---")
63
70
  lines.append("")
64
71
 
@@ -220,6 +220,10 @@ def print_finding_detail(f: Finding) -> None:
220
220
  prefix = " +- "
221
221
  content.append(f"{prefix}{hop}\n", style="dim white")
222
222
 
223
+ if f.root_cause:
224
+ content.append("\nRoot Cause & Exploit Mechanics:\n", style="bold green")
225
+ content.append(f" {f.root_cause}\n", style="bright_white")
226
+
223
227
  if f.static_evidence:
224
228
  content.append("\nStatic Code Evidence:\n", style="bold green")
225
229
  for ev in f.static_evidence:
@@ -235,8 +239,10 @@ def print_finding_detail(f: Finding) -> None:
235
239
  for idx, step in enumerate(f.reproduction_steps, 1):
236
240
  content.append(f" {idx}. {step}\n", style="white")
237
241
 
238
- content.append("\nRemediation Guidance:\n", style="bold green")
239
- content.append(f" {f.remediation}\n", style="dim white")
242
+ content.append("\nConcrete Remediation Action Plan:\n", style="bold green")
243
+ for line in f.remediation.split("\n"):
244
+ if line.strip():
245
+ content.append(f" {line}\n", style="white")
240
246
 
241
247
  console.print(
242
248
  Panel(
@@ -351,7 +357,7 @@ def print_test_all_report(
351
357
  for idx, f in enumerate(sorted_findings, 1):
352
358
  prio = priority_labels.get(f.severity, str(f.severity.value))
353
359
  loc_str = str(f.source_location) if f.source_location else "-"
354
- remediation_snippet = f.remediation.strip().split(".")[0] + "." if f.remediation else "Apply strict authorization check."
360
+ remediation_snippet = f.short_action or (f.remediation.strip().split(".")[0] + "." if f.remediation else "Apply strict authorization check.")
355
361
 
356
362
  table.add_row(
357
363
  f"#{idx}",
@@ -267,6 +267,69 @@ class HypothesisEngine:
267
267
  )
268
268
  )
269
269
  hypo_idx += 1
270
+ elif any(term in ep.path.lower() for term in ("analyze", "competitive-advantage", "generate-workspace", "generate-plan", "orchestrator/run")):
271
+ # Case: Unauthenticated AI Agent / LLM Inference Pipeline
272
+ hypotheses.append(
273
+ SecurityHypothesis(
274
+ id=f"HYP-AUTH-{hypo_idx:03d}",
275
+ category=VulnerabilityCategory.AUTHENTICATION,
276
+ endpoint_id=ep.id,
277
+ endpoint_display=ep_disp,
278
+ title=f"Unauthenticated AI Agent Pipeline / Token Depletion on {ep_disp}",
279
+ description="Endpoint invokes expensive backend AI agent or LLM pipelines without authentication or rate-limiting, exposing the application to token exhaustion and prompt injection.",
280
+ recommended_test_pack="authentication",
281
+ confidence_prior=0.85,
282
+ static_evidence=[
283
+ f"AI Agent / LLM reasoning endpoint: {ep.method} {ep.path}",
284
+ "No authentication or caller identity verification detected",
285
+ "Risk: Denial-of-wallet, unbounded LLM token consumption, prompt injection",
286
+ ],
287
+ severity=Severity.HIGH,
288
+ )
289
+ )
290
+ hypo_idx += 1
291
+ elif any(term in ep.path.lower() for term in ("identity", "mvp", "product-config", "tech-preferences", "business-model", "additional-info")):
292
+ # Case: Multi-step wizard session state mutation
293
+ hypotheses.append(
294
+ SecurityHypothesis(
295
+ id=f"HYP-AUTH-{hypo_idx:03d}",
296
+ category=VulnerabilityCategory.AUTHENTICATION,
297
+ endpoint_id=ep.id,
298
+ endpoint_display=ep_disp,
299
+ title=f"Insecure Discovery Session State Mutation on {ep_disp}",
300
+ description="Endpoint updates onboarding discovery session data using client-supplied sessionId without session tenant verification, enabling session hijacking and state poisoning.",
301
+ recommended_test_pack="authentication",
302
+ confidence_prior=0.80,
303
+ static_evidence=[
304
+ f"Discovery session state modification: {ep.method} {ep.path}",
305
+ "Client-controlled sessionId parameter without verified caller ownership",
306
+ "Risk: Cross-session data corruption and poisoning of startup configuration",
307
+ ],
308
+ severity=Severity.HIGH,
309
+ )
310
+ )
311
+ hypo_idx += 1
312
+ elif any(term in ep.path.lower() for term in ("deliverable", "rollforward")):
313
+ # Case: Unauthorized task status / sprint roadmap mutation
314
+ hypotheses.append(
315
+ SecurityHypothesis(
316
+ id=f"HYP-AUTH-{hypo_idx:03d}",
317
+ category=VulnerabilityCategory.AUTHENTICATION,
318
+ endpoint_id=ep.id,
319
+ endpoint_display=ep_disp,
320
+ title=f"Unauthorized Sprint Deliverable & Schedule Manipulation on {ep_disp}",
321
+ description="Endpoint updates deliverable statuses or rolls forward sprint deadlines without verifying workspace membership or caller permissions.",
322
+ recommended_test_pack="authentication",
323
+ confidence_prior=0.80,
324
+ static_evidence=[
325
+ f"Deliverable/schedule mutation: {ep.method} {ep.path}",
326
+ "Missing workspace collaborator or editor role check",
327
+ "Risk: Tampering with project sprint audit trails and deliverable completion status",
328
+ ],
329
+ severity=Severity.HIGH,
330
+ )
331
+ )
332
+ hypo_idx += 1
270
333
  elif any(term in ep.path.lower() for term in ("scatt-analysis", "inference", "predict", "telemetry/analyze")) and not ep.database_access:
271
334
  # Case: Stateless ML/inference compute endpoint (low impact)
272
335
  hypotheses.append(
@@ -316,7 +379,7 @@ class HypothesisEngine:
316
379
  category=VulnerabilityCategory.AUTHENTICATION,
317
380
  endpoint_id=ep.id,
318
381
  endpoint_display=ep_disp,
319
- title=f"Unauthenticated sensitive endpoint {ep_disp}",
382
+ title=f"Unauthenticated Sensitive Route Exposure on {ep_disp}",
320
383
  description="Sensitive or state-modifying endpoint does not declare authentication requirement.",
321
384
  recommended_test_pack="authentication",
322
385
  confidence_prior=0.80,