thuban 0.4.12 → 0.4.13
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/dist/cli.js +1 -1
- package/dist/package.json +1 -1
- package/dist/packages/crucible/rules/code-scanner-core.json +11 -0
- package/dist/packages/scanner/ghost-code-detector.js +1 -1
- package/package.json +1 -1
- package/dist/packages/crucible/seeds/python/seed-021 2.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-022 2.py +0 -34
- package/dist/packages/crucible/seeds/python/seed-023 2.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-024 2.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-025 2.py +0 -40
- package/dist/packages/crucible/seeds/python/seed-026 2.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-027 2.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-028 2.py +0 -42
- package/dist/packages/crucible/seeds/python/seed-030 2.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-031 2.py +0 -34
- package/dist/packages/crucible/seeds/python/seed-036 2.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-037 2.py +0 -41
- package/dist/packages/crucible/seeds/python/seed-038 2.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-039 2.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-040 2.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-041 2.py +0 -37
- package/dist/packages/crucible/seeds/python/seed-042 2.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-043 2.py +0 -32
- package/dist/packages/crucible/seeds/python/seed-044 2.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-045 2.py +0 -36
- package/dist/packages/crucible/seeds/python/seed-046 2.py +0 -33
- package/dist/packages/crucible/seeds/python/seed-047 2.py +0 -44
- package/dist/packages/crucible/seeds/python/seed-048 2.py +0 -35
- package/dist/packages/crucible/seeds/python/seed-049 2.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-050 2.py +0 -39
- package/dist/packages/crucible/seeds/python/seed-051 2.py +0 -38
- package/dist/packages/crucible/seeds/python/seed-052 2.py +0 -41
- package/dist/packages/crucible/seeds/ruby/seed-001 2.rb +0 -15
- package/dist/packages/crucible/seeds/ruby/seed-002 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-003 2.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-004 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-005 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-006 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-007 2.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-008 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-009 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-010 2.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-011 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-012 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-013 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-014 2.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-015 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-016 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-017 2.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-018 2.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-019 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-020 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-021 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-022 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-023 2.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-024 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-025 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-026 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-027 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-028 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-029 2.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-030 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-031 2.rb +0 -17
- package/dist/packages/crucible/seeds/ruby/seed-032 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-033 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-034 2.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-035 2.rb +0 -19
- package/dist/packages/crucible/seeds/ruby/seed-036 2.rb +0 -18
- package/dist/packages/crucible/seeds/ruby/seed-037 2.rb +0 -21
- package/dist/packages/crucible/seeds/ruby/seed-038 2.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-039 2.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-040 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-041 2.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-042 2.rb +0 -25
- package/dist/packages/crucible/seeds/ruby/seed-043 2.rb +0 -23
- package/dist/packages/crucible/seeds/ruby/seed-044 2.rb +0 -16
- package/dist/packages/crucible/seeds/ruby/seed-045 2.rb +0 -22
- package/dist/packages/crucible/seeds/ruby/seed-046 2.rb +0 -27
- package/dist/packages/crucible/seeds/ruby/seed-047 2.rb +0 -26
- package/dist/packages/crucible/seeds/ruby/seed-048 2.rb +0 -24
- package/dist/packages/crucible/seeds/ruby/seed-049 2.rb +0 -20
- package/dist/packages/crucible/seeds/ruby/seed-050 2.rb +0 -27
- package/dist/packages/crucible/seeds/rust/seed-001 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-002 2.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-003 2.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-004 2.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-005 2.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-006 2.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-007 2.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-008 2.rs +0 -19
- package/dist/packages/crucible/seeds/rust/seed-009 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-010 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-011 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-012 2.rs +0 -31
- package/dist/packages/crucible/seeds/rust/seed-013 2.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-014 2.rs +0 -30
- package/dist/packages/crucible/seeds/rust/seed-015 2.rs +0 -33
- package/dist/packages/crucible/seeds/rust/seed-016 2.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-017 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-018 2.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-019 2.rs +0 -36
- package/dist/packages/crucible/seeds/rust/seed-020 2.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-021 2.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-022 2.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-023 2.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-024 2.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-025 2.rs +0 -29
- package/dist/packages/crucible/seeds/rust/seed-026 2.rs +0 -23
- package/dist/packages/crucible/seeds/rust/seed-027 2.rs +0 -24
- package/dist/packages/crucible/seeds/rust/seed-028 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-029 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-030 2.rs +0 -30
- package/dist/packages/crucible/seeds/rust/seed-031 2.rs +0 -22
- package/dist/packages/crucible/seeds/rust/seed-032 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-033 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-034 2.rs +0 -20
- package/dist/packages/crucible/seeds/rust/seed-035 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-036 2.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-037 2.rs +0 -31
- package/dist/packages/crucible/seeds/rust/seed-038 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-039 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-040 2.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-041 2.rs +0 -32
- package/dist/packages/crucible/seeds/rust/seed-042 2.rs +0 -27
- package/dist/packages/crucible/seeds/rust/seed-043 2.rs +0 -29
- package/dist/packages/crucible/seeds/rust/seed-044 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-045 2.rs +0 -28
- package/dist/packages/crucible/seeds/rust/seed-046 2.rs +0 -25
- package/dist/packages/crucible/seeds/rust/seed-047 2.rs +0 -34
- package/dist/packages/crucible/seeds/rust/seed-048 2.rs +0 -21
- package/dist/packages/crucible/seeds/rust/seed-049 2.rs +0 -26
- package/dist/packages/crucible/seeds/rust/seed-050 2.rs +0 -23
- package/dist/packages/crucible/seeds/ts/seed-001 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-002 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-003 2.ts +0 -28
- package/dist/packages/crucible/seeds/ts/seed-004 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-005 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-006 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-007 2.ts +0 -28
- package/dist/packages/crucible/seeds/ts/seed-008 2.ts +0 -40
- package/dist/packages/crucible/seeds/ts/seed-009 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-010 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-011 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-012 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-013 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-014 2.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-015 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-016 2.ts +0 -37
- package/dist/packages/crucible/seeds/ts/seed-017 2.ts +0 -44
- package/dist/packages/crucible/seeds/ts/seed-018 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-019 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-020 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-021 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-022 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-023 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-024 2.ts +0 -35
- package/dist/packages/crucible/seeds/ts/seed-025 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-026 2.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-027 2.ts +0 -30
- package/dist/packages/crucible/seeds/ts/seed-028 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-029 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-030 2.ts +0 -37
- package/dist/packages/crucible/seeds/ts/seed-031 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-032 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-033 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-034 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-035 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-036 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-037 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-038 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-039 2.ts +0 -35
- package/dist/packages/crucible/seeds/ts/seed-040 2.ts +0 -31
- package/dist/packages/crucible/seeds/ts/seed-041 2.ts +0 -29
- package/dist/packages/crucible/seeds/ts/seed-042 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-043 2.ts +0 -32
- package/dist/packages/crucible/seeds/ts/seed-044 2.ts +0 -36
- package/dist/packages/crucible/seeds/ts/seed-045 2.ts +0 -39
- package/dist/packages/crucible/seeds/ts/seed-046 2.ts +0 -41
- package/dist/packages/crucible/seeds/ts/seed-047 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-048 2.ts +0 -33
- package/dist/packages/crucible/seeds/ts/seed-049 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-050 2.ts +0 -41
- package/dist/packages/crucible/seeds/ts/seed-051 2.ts +0 -34
- package/dist/packages/crucible/seeds/ts/seed-052 2.ts +0 -36
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection in NestJS service using raw pg query with string interpolation
|
|
6
|
-
|
|
7
|
-
import { Injectable } from '@nestjs/common';
|
|
8
|
-
import { InjectConnection } from '@nestjs/typeorm';
|
|
9
|
-
import { Connection } from 'typeorm';
|
|
10
|
-
|
|
11
|
-
@Injectable()
|
|
12
|
-
export class ReportService {
|
|
13
|
-
constructor(@InjectConnection() private connection: Connection) {}
|
|
14
|
-
|
|
15
|
-
async getRevenueReport(fromDate: string, toDate: string, groupBy: string): Promise<any[]> {
|
|
16
|
-
const sql = `
|
|
17
|
-
SELECT
|
|
18
|
-
${groupBy} as period,
|
|
19
|
-
SUM(amount) as total_revenue,
|
|
20
|
-
COUNT(*) as transaction_count
|
|
21
|
-
FROM transactions
|
|
22
|
-
WHERE created_at BETWEEN '${fromDate}' AND '${toDate}'
|
|
23
|
-
GROUP BY ${groupBy}
|
|
24
|
-
ORDER BY period ASC
|
|
25
|
-
`;
|
|
26
|
-
return this.connection.query(sql);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async getUserActivity(userId: string, action: string): Promise<any[]> {
|
|
30
|
-
return this.connection.query(
|
|
31
|
-
`SELECT * FROM audit_log WHERE user_id = '` + userId + `' AND action = '` + action + `'`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection via dynamic ORDER BY column name from user input
|
|
6
|
-
|
|
7
|
-
import { Pool } from 'pg';
|
|
8
|
-
|
|
9
|
-
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
10
|
-
|
|
11
|
-
interface ListOptions {
|
|
12
|
-
sortField: string; // user-controlled
|
|
13
|
-
sortDir: 'asc' | 'desc'; // type claim but not validated
|
|
14
|
-
limit: number;
|
|
15
|
-
offset: number;
|
|
16
|
-
filter?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function listProducts(opts: ListOptions) {
|
|
20
|
-
// sortField and sortDir inserted verbatim — type annotations don't prevent injection
|
|
21
|
-
const query = `
|
|
22
|
-
SELECT id, name, price, stock, created_at
|
|
23
|
-
FROM products
|
|
24
|
-
WHERE archived = false
|
|
25
|
-
${opts.filter ? `AND name ILIKE '%${opts.filter}%'` : ''}
|
|
26
|
-
ORDER BY ${opts.sortField} ${opts.sortDir}
|
|
27
|
-
LIMIT ${opts.limit} OFFSET ${opts.offset}
|
|
28
|
-
`;
|
|
29
|
-
const { rows } = await pool.query(query);
|
|
30
|
-
return rows;
|
|
31
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection in authentication middleware via raw query with login credentials
|
|
6
|
-
|
|
7
|
-
import { Request, Response, NextFunction } from 'express';
|
|
8
|
-
import { Pool } from 'pg';
|
|
9
|
-
|
|
10
|
-
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
11
|
-
|
|
12
|
-
export async function authenticateMiddleware(
|
|
13
|
-
req: Request,
|
|
14
|
-
res: Response,
|
|
15
|
-
next: NextFunction
|
|
16
|
-
): Promise<void> {
|
|
17
|
-
const { username, password } = req.body as { username: string; password: string };
|
|
18
|
-
|
|
19
|
-
// Classic auth bypass: username = "admin'--"
|
|
20
|
-
const query = `
|
|
21
|
-
SELECT id, username, role, email
|
|
22
|
-
FROM users
|
|
23
|
-
WHERE username = '${username}'
|
|
24
|
-
AND password_hash = crypt('${password}', password_hash)
|
|
25
|
-
AND active = true
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
const { rows } = await pool.query(query);
|
|
29
|
-
if (!rows.length) {
|
|
30
|
-
res.status(401).json({ error: 'Invalid credentials' });
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
req.user = rows[0];
|
|
35
|
-
next();
|
|
36
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: sql_injection
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: SQL injection via Sequelize literal() with user-controlled search input
|
|
6
|
-
|
|
7
|
-
import { Sequelize, QueryTypes } from 'sequelize';
|
|
8
|
-
|
|
9
|
-
const sequelize = new Sequelize(process.env.DATABASE_URL!);
|
|
10
|
-
|
|
11
|
-
export async function fullTextSearch(
|
|
12
|
-
query: string,
|
|
13
|
-
table: 'products' | 'articles' | 'users',
|
|
14
|
-
fields: string[]
|
|
15
|
-
): Promise<Record<string, unknown>[]> {
|
|
16
|
-
const fieldList = fields.join(', ');
|
|
17
|
-
const whereClause = fields
|
|
18
|
-
.map(f => `${f} ILIKE '%${query}%'`)
|
|
19
|
-
.join(' OR ');
|
|
20
|
-
|
|
21
|
-
// Both fieldList and whereClause derived from user input — injection possible
|
|
22
|
-
const sql = `SELECT ${fieldList} FROM "${table}" WHERE ${whereClause} LIMIT 50`;
|
|
23
|
-
return sequelize.query(sql, { type: QueryTypes.SELECT });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function bulkUpdate(ids: number[], status: string): Promise<void> {
|
|
27
|
-
const idList = ids.join(',');
|
|
28
|
-
await sequelize.query(
|
|
29
|
-
`UPDATE orders SET status = '${status}' WHERE id IN (${idList})`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: xss
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: XSS via React dangerouslySetInnerHTML with user-controlled content
|
|
6
|
-
|
|
7
|
-
import React from 'react';
|
|
8
|
-
|
|
9
|
-
interface Comment {
|
|
10
|
-
id: string;
|
|
11
|
-
author: string;
|
|
12
|
-
body: string;
|
|
13
|
-
avatarHtml: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface CommentCardProps {
|
|
17
|
-
comment: Comment;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const CommentCard: React.FC<CommentCardProps> = ({ comment }) => {
|
|
21
|
-
return (
|
|
22
|
-
<div className="comment-card">
|
|
23
|
-
{/* avatarHtml and body come from server/user — XSS if unsanitized */}
|
|
24
|
-
<div className="avatar" dangerouslySetInnerHTML={{ __html: comment.avatarHtml }} />
|
|
25
|
-
<div className="author">{comment.author}</div>
|
|
26
|
-
<div className="body" dangerouslySetInnerHTML={{ __html: comment.body }} />
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
interface RichTextProps {
|
|
32
|
-
content: string; // user-submitted rich text
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const RichTextRenderer: React.FC<RichTextProps> = ({ content }) => (
|
|
36
|
-
<article dangerouslySetInnerHTML={{ __html: content }} />
|
|
37
|
-
);
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: xss
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: XSS via Angular [innerHTML] binding with user-controlled data
|
|
6
|
-
|
|
7
|
-
import { Component, OnInit } from '@angular/core';
|
|
8
|
-
import { ActivatedRoute } from '@angular/router';
|
|
9
|
-
import { UserService } from '../services/user.service';
|
|
10
|
-
|
|
11
|
-
interface UserProfile {
|
|
12
|
-
displayName: string;
|
|
13
|
-
bio: string; // Rich HTML from user
|
|
14
|
-
signature: string; // HTML signature from user
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@Component({
|
|
18
|
-
selector: 'app-user-profile',
|
|
19
|
-
template: `
|
|
20
|
-
<div class="profile">
|
|
21
|
-
<h1>{{ user?.displayName }}</h1>
|
|
22
|
-
<!-- [innerHTML] bypasses Angular's sanitization when DomSanitizer.bypassSecurityTrustHtml is used -->
|
|
23
|
-
<div class="bio" [innerHTML]="trustedBio"></div>
|
|
24
|
-
<footer [innerHTML]="trustedSignature"></footer>
|
|
25
|
-
</div>
|
|
26
|
-
`,
|
|
27
|
-
})
|
|
28
|
-
export class UserProfileComponent implements OnInit {
|
|
29
|
-
user: UserProfile | null = null;
|
|
30
|
-
trustedBio: any;
|
|
31
|
-
trustedSignature: any;
|
|
32
|
-
|
|
33
|
-
constructor(private route: ActivatedRoute, private userService: UserService) {}
|
|
34
|
-
|
|
35
|
-
ngOnInit(): void {
|
|
36
|
-
const id = this.route.snapshot.paramMap.get('id')!;
|
|
37
|
-
this.userService.getProfile(id).subscribe(user => {
|
|
38
|
-
this.user = user;
|
|
39
|
-
// Bypasses Angular's built-in XSS protection
|
|
40
|
-
this.trustedBio = user.bio;
|
|
41
|
-
this.trustedSignature = user.signature;
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: xss
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: XSS via innerHTML in typed DOM manipulation utility
|
|
6
|
-
|
|
7
|
-
export class DomRenderer {
|
|
8
|
-
private container: HTMLElement;
|
|
9
|
-
|
|
10
|
-
constructor(containerId: string) {
|
|
11
|
-
this.container = document.getElementById(containerId) as HTMLElement;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
renderUserContent(content: string): void {
|
|
15
|
-
// Direct innerHTML assignment — content from user input
|
|
16
|
-
this.container.innerHTML = content;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
appendItem(title: string, description: string): void {
|
|
20
|
-
// Template literal with user data written to innerHTML
|
|
21
|
-
this.container.innerHTML += `
|
|
22
|
-
<div class="item">
|
|
23
|
-
<h3>${title}</h3>
|
|
24
|
-
<p>${description}</p>
|
|
25
|
-
</div>
|
|
26
|
-
`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
showBanner(message: string, type: 'info' | 'warning' | 'error'): void {
|
|
30
|
-
const banner = document.querySelector('.banner') as HTMLElement;
|
|
31
|
-
banner.innerHTML = `<div class="alert alert-${type}">${message}</div>`;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: xss
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: XSS via Express response with unsanitized template literal containing user input
|
|
6
|
-
|
|
7
|
-
import { Request, Response, Router } from 'express';
|
|
8
|
-
|
|
9
|
-
const router = Router();
|
|
10
|
-
|
|
11
|
-
router.get('/search', (req: Request, res: Response) => {
|
|
12
|
-
const query = (req.query.q as string) || '';
|
|
13
|
-
const category = (req.query.cat as string) || 'all';
|
|
14
|
-
|
|
15
|
-
// Both query and category embedded directly in HTML without encoding
|
|
16
|
-
res.send(`
|
|
17
|
-
<!DOCTYPE html>
|
|
18
|
-
<html lang="en">
|
|
19
|
-
<head><title>Search - ${category}</title></head>
|
|
20
|
-
<body>
|
|
21
|
-
<h1>Results for: <em>${query}</em></h1>
|
|
22
|
-
<p>Category: ${category}</p>
|
|
23
|
-
<script>
|
|
24
|
-
const searchQuery = "${query}";
|
|
25
|
-
const searchCategory = "${category}";
|
|
26
|
-
</script>
|
|
27
|
-
</body>
|
|
28
|
-
</html>
|
|
29
|
-
`);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export default router;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: xss
|
|
3
|
-
// severity: medium
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: XSS via Next.js page that renders query param directly into dangerouslySetInnerHTML
|
|
6
|
-
|
|
7
|
-
import { GetServerSideProps, NextPage } from 'next';
|
|
8
|
-
|
|
9
|
-
interface PageProps {
|
|
10
|
-
name: string;
|
|
11
|
-
description: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const ProfilePage: NextPage<PageProps> = ({ name, description }) => {
|
|
15
|
-
return (
|
|
16
|
-
<div>
|
|
17
|
-
<h1>Profile: {name}</h1>
|
|
18
|
-
{/* description from query string — dangerouslySetInnerHTML without sanitization */}
|
|
19
|
-
<div dangerouslySetInnerHTML={{ __html: description }} />
|
|
20
|
-
</div>
|
|
21
|
-
);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const getServerSideProps: GetServerSideProps = async ({ query }) => {
|
|
25
|
-
return {
|
|
26
|
-
props: {
|
|
27
|
-
name: query.name as string ?? 'Unknown',
|
|
28
|
-
description: query.description as string ?? '',
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export default ProfilePage;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: xss
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: XSS via Vue.js v-html directive with user-supplied content
|
|
6
|
-
|
|
7
|
-
import { defineComponent, ref, onMounted } from 'vue';
|
|
8
|
-
import axios from 'axios';
|
|
9
|
-
|
|
10
|
-
export default defineComponent({
|
|
11
|
-
name: 'ArticleViewer',
|
|
12
|
-
props: {
|
|
13
|
-
articleId: { type: String, required: true },
|
|
14
|
-
},
|
|
15
|
-
setup(props) {
|
|
16
|
-
const article = ref<{ title: string; content: string; authorBio: string } | null>(null);
|
|
17
|
-
|
|
18
|
-
onMounted(async () => {
|
|
19
|
-
const { data } = await axios.get(`/api/articles/${props.articleId}`);
|
|
20
|
-
article.value = data;
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
return { article };
|
|
24
|
-
},
|
|
25
|
-
// v-html renders raw HTML — XSS if content contains malicious script tags
|
|
26
|
-
template: `
|
|
27
|
-
<div class="article" v-if="article">
|
|
28
|
-
<h1>{{ article.title }}</h1>
|
|
29
|
-
<div class="content" v-html="article.content"></div>
|
|
30
|
-
<aside class="author-bio" v-html="article.authorBio"></aside>
|
|
31
|
-
</div>
|
|
32
|
-
`,
|
|
33
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: xss
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: XSS via Marked.js rendering user markdown without sanitization
|
|
6
|
-
|
|
7
|
-
import marked from 'marked';
|
|
8
|
-
import { Request, Response, Router } from 'express';
|
|
9
|
-
|
|
10
|
-
const router = Router();
|
|
11
|
-
|
|
12
|
-
interface MarkdownRequest {
|
|
13
|
-
markdown: string;
|
|
14
|
-
title?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
router.post('/preview', (req: Request, res: Response) => {
|
|
18
|
-
const { markdown, title } = req.body as MarkdownRequest;
|
|
19
|
-
|
|
20
|
-
// marked() renders to HTML without XSS protection — <script> tags in markdown execute
|
|
21
|
-
const renderedHtml = marked(markdown);
|
|
22
|
-
|
|
23
|
-
res.send(`
|
|
24
|
-
<html>
|
|
25
|
-
<head><title>${title ?? 'Preview'}</title></head>
|
|
26
|
-
<body>
|
|
27
|
-
<h1>${title}</h1>
|
|
28
|
-
<div class="content">${renderedHtml}</div>
|
|
29
|
-
</body>
|
|
30
|
-
</html>
|
|
31
|
-
`);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export default router;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: command_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: command injection via child_process.exec with typed but unsanitized user input
|
|
6
|
-
|
|
7
|
-
import { exec } from 'child_process';
|
|
8
|
-
import { promisify } from 'util';
|
|
9
|
-
import { Request, Response, Router } from 'express';
|
|
10
|
-
|
|
11
|
-
const execAsync = promisify(exec);
|
|
12
|
-
const router = Router();
|
|
13
|
-
|
|
14
|
-
interface PingRequest {
|
|
15
|
-
host: string;
|
|
16
|
-
count?: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
router.post('/tools/ping', async (req: Request, res: Response) => {
|
|
20
|
-
const { host, count = 4 } = req.body as PingRequest;
|
|
21
|
-
// TypeScript types don't prevent injection — host can be "8.8.8.8; id"
|
|
22
|
-
const cmd = `ping -c ${count} ${host}`;
|
|
23
|
-
const { stdout, stderr } = await execAsync(cmd);
|
|
24
|
-
res.json({ output: stdout, errors: stderr });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
router.post('/tools/traceroute', async (req: Request, res: Response) => {
|
|
28
|
-
const target: string = req.body.target;
|
|
29
|
-
const { stdout } = await execAsync(`traceroute ${target}`);
|
|
30
|
-
res.json({ hops: stdout });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export default router;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: command_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: command injection via execSync in typed file processing service
|
|
6
|
-
|
|
7
|
-
import { execSync } from 'child_process';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
|
|
10
|
-
interface ConversionOptions {
|
|
11
|
-
inputPath: string; // user-controlled filename
|
|
12
|
-
outputFormat: 'pdf' | 'png' | 'jpg' | 'webp';
|
|
13
|
-
quality?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class FileConverter {
|
|
17
|
-
private readonly outputDir: string;
|
|
18
|
-
|
|
19
|
-
constructor(outputDir: string) {
|
|
20
|
-
this.outputDir = outputDir;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
convert(opts: ConversionOptions): string {
|
|
24
|
-
const outputName = path.basename(opts.inputPath, path.extname(opts.inputPath));
|
|
25
|
-
const outputPath = path.join(this.outputDir, `${outputName}.${opts.outputFormat}`);
|
|
26
|
-
// opts.inputPath is user-controlled — no sanitization before execSync
|
|
27
|
-
const cmd = `convert "${opts.inputPath}" -quality ${opts.quality ?? 85} "${outputPath}"`;
|
|
28
|
-
execSync(cmd, { timeout: 30000 });
|
|
29
|
-
return outputPath;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
extractText(pdfPath: string): string {
|
|
33
|
-
return execSync(`pdftotext ${pdfPath} -`, { encoding: 'utf8' });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: command_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: command injection via spawn with shell:true and user-controlled arguments
|
|
6
|
-
|
|
7
|
-
import { spawn } from 'child_process';
|
|
8
|
-
import { Request, Response, Router } from 'express';
|
|
9
|
-
|
|
10
|
-
const router = Router();
|
|
11
|
-
|
|
12
|
-
function runCommand(cmd: string, args: string[]): Promise<string> {
|
|
13
|
-
return new Promise((resolve, reject) => {
|
|
14
|
-
// shell: true means the full command string is interpreted by /bin/sh
|
|
15
|
-
const proc = spawn(`${cmd} ${args.join(' ')}`, { shell: true, timeout: 15000 });
|
|
16
|
-
let output = '';
|
|
17
|
-
proc.stdout.on('data', (d: Buffer) => (output += d.toString()));
|
|
18
|
-
proc.stderr.on('data', (d: Buffer) => (output += d.toString()));
|
|
19
|
-
proc.on('close', (code: number) => (code === 0 ? resolve(output) : reject(new Error(output))));
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
router.post('/scripts/run', async (req: Request, res: Response) => {
|
|
24
|
-
const { script, params }: { script: string; params: string[] } = req.body;
|
|
25
|
-
const result = await runCommand(`node scripts/${script}`, params);
|
|
26
|
-
res.json({ output: result });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export default router;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: command_injection
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: command injection via git operations with user-controlled branch/tag names
|
|
6
|
-
|
|
7
|
-
import { execSync } from 'child_process';
|
|
8
|
-
|
|
9
|
-
interface GitOptions {
|
|
10
|
-
repoPath: string;
|
|
11
|
-
branch: string; // user-controlled
|
|
12
|
-
remote?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class GitService {
|
|
16
|
-
clone(repoUrl: string, targetDir: string): void {
|
|
17
|
-
// repoUrl from user — can include shell metacharacters
|
|
18
|
-
execSync(`git clone ${repoUrl} ${targetDir}`, { encoding: 'utf8', stdio: 'pipe' });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
checkout(opts: GitOptions): string {
|
|
22
|
-
// opts.branch may be "main; cat /etc/shadow"
|
|
23
|
-
return execSync(`git -C ${opts.repoPath} checkout ${opts.branch}`, { encoding: 'utf8' });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
createBranch(repoPath: string, branchName: string, fromBranch: string): string {
|
|
27
|
-
return execSync(
|
|
28
|
-
`git -C ${repoPath} checkout -b ${branchName} origin/${fromBranch}`,
|
|
29
|
-
{ encoding: 'utf8' }
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
tag(repoPath: string, tagName: string, message: string): string {
|
|
34
|
-
return execSync(`git -C ${repoPath} tag -a ${tagName} -m "${message}"`, { encoding: 'utf8' });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: command_injection
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: command injection in NestJS service using exec with user-provided report parameters
|
|
6
|
-
|
|
7
|
-
import { Injectable } from '@nestjs/common';
|
|
8
|
-
import { exec } from 'child_process';
|
|
9
|
-
import { promisify } from 'util';
|
|
10
|
-
|
|
11
|
-
const execAsync = promisify(exec);
|
|
12
|
-
|
|
13
|
-
interface ReportParams {
|
|
14
|
-
format: string; // user-controlled
|
|
15
|
-
dateRange: string; // user-controlled
|
|
16
|
-
recipient: string; // user-controlled email
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
@Injectable()
|
|
20
|
-
export class ReportingService {
|
|
21
|
-
async generateAndSendReport(params: ReportParams): Promise<void> {
|
|
22
|
-
const { format, dateRange, recipient } = params;
|
|
23
|
-
// All three params go directly into shell command
|
|
24
|
-
const generateCmd = `python3 /scripts/gen_report.py --format ${format} --range ${dateRange}`;
|
|
25
|
-
const { stdout: reportPath } = await execAsync(generateCmd);
|
|
26
|
-
|
|
27
|
-
const sendCmd = `sendmail -s "Your Report" ${recipient.trim()} < ${reportPath.trim()}`;
|
|
28
|
-
await execAsync(sendCmd);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: command_injection
|
|
3
|
-
// severity: critical
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: command injection via execa with shell:true and template literal from user data
|
|
6
|
-
|
|
7
|
-
import execa from 'execa';
|
|
8
|
-
import { Request, Response, Router } from 'express';
|
|
9
|
-
|
|
10
|
-
const router = Router();
|
|
11
|
-
|
|
12
|
-
interface ScanRequest {
|
|
13
|
-
target: string; // IP or hostname — user-controlled
|
|
14
|
-
ports?: string; // port range — user-controlled
|
|
15
|
-
flags?: string; // nmap flags — user-controlled
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
router.post('/security/scan', async (req: Request, res: Response) => {
|
|
19
|
-
const { target, ports = '1-1000', flags = '' } = req.body as ScanRequest;
|
|
20
|
-
try {
|
|
21
|
-
// All fields from user body — injection via shell metacharacters
|
|
22
|
-
const { stdout } = await execa.command(
|
|
23
|
-
`nmap ${flags} -p ${ports} ${target}`,
|
|
24
|
-
{ shell: true, timeout: 60000 }
|
|
25
|
-
);
|
|
26
|
-
res.json({ results: stdout });
|
|
27
|
-
} catch (err: any) {
|
|
28
|
-
res.status(500).json({ error: err.message });
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export default router;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: path_traversal
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: path traversal via unvalidated user path in fs.readFile
|
|
6
|
-
|
|
7
|
-
import * as fs from 'fs/promises';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
import { Request, Response, Router } from 'express';
|
|
10
|
-
|
|
11
|
-
const router = Router();
|
|
12
|
-
const PUBLIC_DIR = path.resolve(__dirname, '..', 'public');
|
|
13
|
-
|
|
14
|
-
router.get('/static/:file', async (req: Request, res: Response) => {
|
|
15
|
-
const requestedFile: string = req.params.file;
|
|
16
|
-
// path.join does NOT prevent traversal — "../../etc/passwd" resolves outside PUBLIC_DIR
|
|
17
|
-
const filePath = path.join(PUBLIC_DIR, requestedFile);
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const data = await fs.readFile(filePath, 'utf-8');
|
|
21
|
-
res.type('text/plain').send(data);
|
|
22
|
-
} catch {
|
|
23
|
-
res.status(404).send('Not found');
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
router.get('/download', async (req: Request, res: Response) => {
|
|
28
|
-
const file = req.query.file as string;
|
|
29
|
-
// Completely unvalidated — reads arbitrary path on filesystem
|
|
30
|
-
const content = await fs.readFile(file);
|
|
31
|
-
res.send(content);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export default router;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// [CRUCIBLE-SEED]
|
|
2
|
-
// category: path_traversal
|
|
3
|
-
// severity: high
|
|
4
|
-
// language: typescript
|
|
5
|
-
// expected: path traversal via typed file service with user-controlled subdirectory
|
|
6
|
-
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
|
|
10
|
-
interface FileServiceConfig {
|
|
11
|
-
baseDir: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class FileService {
|
|
15
|
-
private readonly baseDir: string;
|
|
16
|
-
|
|
17
|
-
constructor(config: FileServiceConfig) {
|
|
18
|
-
this.baseDir = config.baseDir;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
readUserFile(userId: string, fileName: string): string {
|
|
22
|
-
// No normalization check — fileName can be "../../../etc/passwd"
|
|
23
|
-
const filePath = path.join(this.baseDir, userId, fileName);
|
|
24
|
-
return fs.readFileSync(filePath, 'utf-8');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
listDirectory(userId: string, subDir: string): string[] {
|
|
28
|
-
// subDir from user — directory traversal possible
|
|
29
|
-
const dirPath = path.join(this.baseDir, userId, subDir);
|
|
30
|
-
return fs.readdirSync(dirPath);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
deleteFile(userId: string, fileName: string): void {
|
|
34
|
-
const filePath = path.join(this.baseDir, userId, fileName);
|
|
35
|
-
fs.unlinkSync(filePath);
|
|
36
|
-
}
|
|
37
|
-
}
|