@@ -165,6 +165,21 @@ var SECRET_PATTERNS = [
165165 name : "mailchimp-key" ,
166166 pattern : / [ a - f 0 - 9 ] { 32 } - u s \d { 1 , 2 } / g,
167167 description : "Mailchimp API key"
168+ } ,
169+ {
170+ name : "huggingface-token" ,
171+ pattern : / h f _ [ a - z A - Z 0 - 9 ] { 20 , } / g,
172+ description : "Hugging Face access token"
173+ } ,
174+ {
175+ name : "databricks-token" ,
176+ pattern : / d a p i [ a - f 0 - 9 ] { 32 } / g,
177+ description : "Databricks personal access token"
178+ } ,
179+ {
180+ name : "digitalocean-token" ,
181+ pattern : / d o p _ v 1 _ [ a - f 0 - 9 ] { 64 } / g,
182+ description : "DigitalOcean personal access token"
168183 }
169184] ;
170185function findLineNumber ( content , matchIndex ) {
@@ -702,6 +717,49 @@ var permissionRules = [
702717 }
703718 return findings ;
704719 }
720+ } ,
721+ {
722+ id : "permissions-wildcard-root-paths" ,
723+ name : "Wildcard Root Path in Allow List" ,
724+ description : "Checks if the allow list uses wildcards on root-level or home-level directories" ,
725+ severity : "high" ,
726+ category : "permissions" ,
727+ check ( file ) {
728+ if ( file . type !== "settings-json" ) return [ ] ;
729+ const perms = parsePermissionLists ( file . content ) ;
730+ if ( ! perms ) return [ ] ;
731+ const findings = [ ] ;
732+ const broadPathPatterns = [
733+ { pattern : / \( \/ \* \) / , description : "root filesystem wildcard" } ,
734+ { pattern : / \( ~ \/ \* \) / , description : "home directory wildcard" } ,
735+ { pattern : / \( \/ h o m e \/ \* \) / , description : "all users home directories" } ,
736+ { pattern : / \( \/ u s r \/ \* \) / , description : "system programs directory" } ,
737+ { pattern : / \( \/ o p t \/ \* \) / , description : "optional software directory" }
738+ ] ;
739+ for ( const entry of perms . allow ) {
740+ for ( const { pattern, description } of broadPathPatterns ) {
741+ if ( pattern . test ( entry ) ) {
742+ findings . push ( {
743+ id : `permissions-wildcard-root-${ findings . length } ` ,
744+ severity : "high" ,
745+ category : "permissions" ,
746+ title : `Broad wildcard path in allow list: ${ entry } ` ,
747+ description : `The allow entry "${ entry } " uses a ${ description } . This grants the agent access to far more files than typically needed. Restrict to project-specific paths.` ,
748+ file : file . path ,
749+ evidence : entry ,
750+ fix : {
751+ description : "Restrict to project-specific directories" ,
752+ before : entry ,
753+ after : entry . replace ( / \( .* \) / , "(./src/*)" ) ,
754+ auto : false
755+ }
756+ } ) ;
757+ break ;
758+ }
759+ }
760+ }
761+ return findings ;
762+ }
705763 }
706764] ;
707765function findLineNumber2 ( content , matchIndex ) {
@@ -1268,6 +1326,93 @@ var hookRules = [
12681326 }
12691327 return findings ;
12701328 }
1329+ } ,
1330+ {
1331+ id : "hooks-output-to-world-readable" ,
1332+ name : "Hook Writes to World-Readable Path" ,
1333+ description : "Checks for hooks that redirect output to world-readable directories like /tmp" ,
1334+ severity : "high" ,
1335+ category : "hooks" ,
1336+ check ( file ) {
1337+ if ( file . type !== "settings-json" && file . type !== "hook-script" ) return [ ] ;
1338+ const findings = [ ] ;
1339+ const worldReadablePatterns = [
1340+ {
1341+ pattern : / > \s * \/ t m p \/ / g,
1342+ description : "Redirects output to /tmp \u2014 readable by all users on the system"
1343+ } ,
1344+ {
1345+ pattern : / \b t e e \s + \/ t m p \/ / g,
1346+ description : "Uses tee to write to /tmp \u2014 creates world-readable file"
1347+ } ,
1348+ {
1349+ pattern : / > \s * \/ v a r \/ t m p \/ / g,
1350+ description : "Redirects output to /var/tmp \u2014 persistent and world-readable"
1351+ } ,
1352+ {
1353+ pattern : / \b m k t e m p \b / g,
1354+ description : "Creates temporary file \u2014 ensure secure permissions (mktemp is generally safe but verify cleanup)"
1355+ }
1356+ ] ;
1357+ for ( const { pattern, description } of worldReadablePatterns ) {
1358+ const matches = findAllMatches2 ( file . content , pattern ) ;
1359+ for ( const match of matches ) {
1360+ if ( pattern . source . includes ( "mktemp" ) ) continue ;
1361+ findings . push ( {
1362+ id : `hooks-world-readable-${ match . index } ` ,
1363+ severity : "high" ,
1364+ category : "exposure" ,
1365+ title : `Hook writes to world-readable path: ${ match [ 0 ] . trim ( ) } ` ,
1366+ description : `${ description } . Other users or processes on the system can read the output, which may contain secrets, code, or session data.` ,
1367+ file : file . path ,
1368+ line : findLineNumber3 ( file . content , match . index ?? 0 ) ,
1369+ evidence : match [ 0 ] . trim ( )
1370+ } ) ;
1371+ }
1372+ }
1373+ return findings ;
1374+ }
1375+ } ,
1376+ {
1377+ id : "hooks-source-from-env" ,
1378+ name : "Hook Sources Script from Environment Path" ,
1379+ description : "Checks for hooks that source scripts from environment variable paths" ,
1380+ severity : "high" ,
1381+ category : "injection" ,
1382+ check ( file ) {
1383+ if ( file . type !== "settings-json" && file . type !== "hook-script" ) return [ ] ;
1384+ const findings = [ ] ;
1385+ const sourcePatterns = [
1386+ {
1387+ pattern : / \b s o u r c e \s + \$ \{ ? \w + \} ? \/ / g,
1388+ description : "Sources a script from an environment variable path"
1389+ } ,
1390+ {
1391+ pattern : / \. \s + \$ \{ ? \w + \} ? \/ / g,
1392+ description : "Dot-sources a script from an environment variable path"
1393+ } ,
1394+ {
1395+ pattern : / \b e v a l \s + \$ \{ ? \w + / g,
1396+ description : "Evaluates content from an environment variable"
1397+ }
1398+ ] ;
1399+ for ( const { pattern, description } of sourcePatterns ) {
1400+ const matches = findAllMatches2 ( file . content , pattern ) ;
1401+ for ( const match of matches ) {
1402+ findings . push ( {
1403+ id : `hooks-source-env-${ match . index } ` ,
1404+ severity : "high" ,
1405+ category : "injection" ,
1406+ title : `Hook sources script from environment path: ${ match [ 0 ] . trim ( ) } ` ,
1407+ description : `${ description } . If the environment variable is attacker-controlled, this enables arbitrary code execution through the sourced script.` ,
1408+ file : file . path ,
1409+ line : findLineNumber3 ( file . content , match . index ?? 0 ) ,
1410+ evidence : match [ 0 ] . trim ( )
1411+ } ) ;
1412+ }
1413+ }
1414+ return findings ;
1415+ }
12711416 }
12721417] ;
12731418
@@ -1757,6 +1902,45 @@ var mcpRules = [
17571902 }
17581903 return [ ] ;
17591904 }
1905+ } ,
1906+ {
1907+ id : "mcp-shell-wrapper" ,
1908+ name : "MCP Server Uses Shell Wrapper" ,
1909+ description : "Checks for MCP servers that use sh/bash -c as command, which defeats argument separation safety" ,
1910+ severity : "high" ,
1911+ category : "mcp" ,
1912+ check ( file ) {
1913+ if ( file . type !== "mcp-json" && file . type !== "settings-json" ) return [ ] ;
1914+ const findings = [ ] ;
1915+ try {
1916+ const config = JSON . parse ( file . content ) ;
1917+ const servers = config . mcpServers ?? { } ;
1918+ for ( const [ name , server ] of Object . entries ( servers ) ) {
1919+ const serverConfig = server ;
1920+ const command = serverConfig . command ?? "" ;
1921+ const args = serverConfig . args ?? [ ] ;
1922+ if ( / ^ ( s h | b a s h | z s h | c m d ) $ / . test ( command ) && args . includes ( "-c" ) ) {
1923+ findings . push ( {
1924+ id : `mcp-shell-wrapper-${ name } ` ,
1925+ severity : "high" ,
1926+ category : "mcp" ,
1927+ title : `MCP server "${ name } " uses shell wrapper (${ command } -c)` ,
1928+ description : `The MCP server "${ name } " uses "${ command } -c" as its command. This passes all arguments through a shell interpreter, defeating the security benefits of argument separation. Shell metacharacters in args become live injection vectors. Use the target binary directly as the command instead.` ,
1929+ file : file . path ,
1930+ evidence : `command: ${ command } , args: ${ JSON . stringify ( args ) . substring ( 0 , 80 ) } ` ,
1931+ fix : {
1932+ description : "Use the target binary directly instead of wrapping in sh -c" ,
1933+ before : `"command": "${ command } ", "args": ["-c", ...]` ,
1934+ after : '"command": "node", "args": ["./server.js"]' ,
1935+ auto : false
1936+ }
1937+ } ) ;
1938+ }
1939+ }
1940+ } catch {
1941+ }
1942+ return findings ;
1943+ }
17601944 }
17611945] ;
17621946
@@ -2088,16 +2272,28 @@ var agentRules = [
20882272 const findings = [ ] ;
20892273 const autoRunPatterns = [
20902274 {
2091- pattern : / a l w a y s \s + (?: r u n | i n s t a l l | d o w n l o a d ) / gi,
2275+ pattern : / a l w a y s \s + (?: r u n | i n s t a l l | d o w n l o a d | e x e c u t e ) / gi,
20922276 desc : "Auto-run instructions"
20932277 } ,
20942278 {
2095- pattern : / a u t o m a t i c a l l y \s + (?: r u n | i n s t a l l | c l o n e ) / gi,
2279+ pattern : / a u t o m a t i c a l l y \s + (?: r u n | i n s t a l l | c l o n e | e x e c u t e | d o w n l o a d ) / gi,
20962280 desc : "Automatic running"
20972281 } ,
20982282 {
2099- pattern : / w i t h o u t \s + (?: a s k i n g | c o n f i r m a t i o n | p r o m p t i n g ) / gi,
2283+ pattern : / w i t h o u t \s + (?: a s k i n g | c o n f i r m a t i o n | p r o m p t i n g | u s e r \s + i n p u t ) / gi,
21002284 desc : "Bypasses confirmation"
2285+ } ,
2286+ {
2287+ pattern : / \b s i l e n t l y \s + (?: r u n | i n s t a l l | e x e c u t e | d o w n l o a d | c l o n e ) / gi,
2288+ desc : "Silent execution"
2289+ } ,
2290+ {
2291+ pattern : / \b r u n \s + u n a t t e n d e d \b / gi,
2292+ desc : "Unattended execution"
2293+ } ,
2294+ {
2295+ pattern : / \b e x e c u t e \s + w i t h o u t \s + (?: c o n f i r m a t i o n | r e v i e w | a p p r o v a l ) / gi,
2296+ desc : "Execution without review"
21012297 }
21022298 ] ;
21032299 for ( const { pattern, desc } of autoRunPatterns ) {
0 commit comments