Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@profullstack/threatcrush",
"version": "0.7.1",
"version": "0.7.2",
"description": "All-in-one security agent daemon — monitor, detect, scan, and protect servers in real-time",
"bin": {
"threatcrush": "./dist/index.js"
Expand Down Expand Up @@ -36,7 +36,6 @@
"dependencies": {
"@iarna/toml": "^2.2.5",
"@sentry/node": "^8.45.0",
"@threatcrush/scan": "workspace:*",
"better-sqlite3": "^11.7.0",
"blessed": "^0.1.81",
"blessed-contrib": "^4.11.0",
Expand All @@ -49,6 +48,7 @@
"react-blessed-contrib": "^0.2.1"
},
"devDependencies": {
"@threatcrush/scan": "workspace:*",
"@types/better-sqlite3": "^7.6.12",
"@types/blessed": "^0.1.25",
"@types/node": "^22.19.17",
Expand Down
2 changes: 1 addition & 1 deletion packages/scan/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@threatcrush/scan",
"version": "0.7.1",
"version": "0.7.2",
"description": "ThreatCrush scan rules and engine, shared by the CLI, web, desktop and extension.",
"license": "MIT",
"type": "module",
Expand Down
22 changes: 22 additions & 0 deletions packages/scan/src/__tests__/code-rules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,28 @@ describe('SQL injection', () => {
expect(ruleIds('a.rb', `User.where("id = '#{id}'")`)).toContain('rb-sql-interpolation');
expect(ruleIds('a.rb', `User.where('id = ?', params[:id])`)).toHaveLength(0);
});

// A SQL verb is not SQL until it has the clause that makes it a statement.
// These are template literals whose only SQL-ness is an English word that
// happens to be a verb — real findings from ShortsStudio and capacitor.
it('does not read a verb-shaped English word as SQL', () => {
// `insert` in a React key; `INSERT` alone no longer qualifies — it needs INTO.
expect(ruleIds('a.jsx', 'key={`insert-${insertIndex}`}')).toHaveLength(0);
// `Update`/`Delete`/`drop` as prose or identifiers.
expect(ruleIds('a.js', 'log.info(`Update finished in ${ms}ms`);')).toHaveLength(0);
expect(ruleIds('a.js', 'const cls = `dropdown-${open ? "open" : "shut"}`;')).toHaveLength(0);
expect(ruleIds('a.ts', 'const label = `Delete ${count} items?`;')).toHaveLength(0);
});

it('still flags a genuine INSERT INTO and DROP TABLE built by interpolation', () => {
// The structured forms must survive — the fix narrows, it does not disable.
expect(ruleIds('a.js', 'db.query(`INSERT INTO users (name) VALUES (\'${name}\')`);')).toContain(
'sql-template-interpolation',
);
expect(ruleIds('a.js', 'db.query(`DROP TABLE ${table}`);')).toContain(
'sql-template-interpolation',
);
});
});

describe('command injection', () => {
Expand Down
23 changes: 22 additions & 1 deletion packages/scan/src/code-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,28 @@ const EXFIL_SINK = /\bconsole\s*\.\s*(?:log|debug|info|warn|error)\s*\(|\bfetch\
* as an argument) leaves a comma after the closing quote and matches none of
* them — which is exactly how the safe counterparts stay unflagged.
*/
const SQL_KEYWORDS = 'SELECT|INSERT\\s+INTO|INSERT|UPDATE|DELETE\\s+FROM|DELETE|DROP|UNION\\s+SELECT';
// Each verb requires the clause that makes it SQL rather than an English word.
//
// The bare forms — `INSERT`, `UPDATE`, `DELETE`, `DROP`, a lone `SELECT` — were
// the source of this rule's false positives: `\bINSERT\b` matches the `insert`
// in a React key `` `insert-${i}` ``, and `\bUPDATE\b` matches `Update` in a
// log line `` `Update finished in ${ms}ms` ``. Both read as SQL injection.
//
// Real SQL pairs the verb with structure — `SELECT … FROM`, `INSERT INTO`,
// `UPDATE … SET`, `DELETE FROM`, `DROP TABLE`. Requiring it keeps every
// injection shape the corpus and the unit tests exercise (all of which are
// `SELECT … FROM` or `DELETE FROM`) while a verb standing alone as prose no
// longer qualifies. The `SELECT`/`UPDATE` look-aheads stay inside one string
// literal — the character class excludes quotes and backticks — so the clause
// must live in the same statement, not merely somewhere later on the line.
const SQL_KEYWORDS =
"SELECT\\b(?=[^`'\"\\n]*\\bFROM\\b)" +
"|INSERT\\s+INTO" +
"|UPDATE\\b(?=[^`'\"\\n]*\\bSET\\b)" +
"|DELETE\\s+FROM" +
"|DROP\\s+(?:TABLE|DATABASE|INDEX|VIEW|SCHEMA)" +
"|TRUNCATE\\s+TABLE" +
"|UNION\\s+SELECT";

/**
* A quoted string containing a SQL verb.
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading