@qlucent/fishi-core 0.11.0 → 0.12.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/dist/index.d.ts +47 -1
- package/dist/index.js +416 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -772,10 +772,56 @@ declare function getAllPermissionSummary(): Record<AgentRole, {
|
|
|
772
772
|
*/
|
|
773
773
|
declare function generatePermissionBlock(role: AgentRole): string;
|
|
774
774
|
|
|
775
|
+
type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
776
|
+
interface SecurityFinding {
|
|
777
|
+
rule: string;
|
|
778
|
+
category: string;
|
|
779
|
+
severity: Severity;
|
|
780
|
+
file: string;
|
|
781
|
+
line: number;
|
|
782
|
+
code: string;
|
|
783
|
+
message: string;
|
|
784
|
+
fix: string;
|
|
785
|
+
cwe?: string;
|
|
786
|
+
}
|
|
787
|
+
interface SecurityReport {
|
|
788
|
+
findings: SecurityFinding[];
|
|
789
|
+
summary: {
|
|
790
|
+
critical: number;
|
|
791
|
+
high: number;
|
|
792
|
+
medium: number;
|
|
793
|
+
low: number;
|
|
794
|
+
info: number;
|
|
795
|
+
total: number;
|
|
796
|
+
filesScanned: number;
|
|
797
|
+
passed: boolean;
|
|
798
|
+
};
|
|
799
|
+
scanDate: string;
|
|
800
|
+
projectDir: string;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Scan a project for security vulnerabilities.
|
|
804
|
+
*/
|
|
805
|
+
declare function runSecurityScan(projectDir: string): SecurityReport;
|
|
806
|
+
/**
|
|
807
|
+
* Generate a markdown security report.
|
|
808
|
+
*/
|
|
809
|
+
declare function generateSecurityReport(report: SecurityReport): string;
|
|
810
|
+
/**
|
|
811
|
+
* Get the list of scan rules for display.
|
|
812
|
+
*/
|
|
813
|
+
declare function getScanRules(): {
|
|
814
|
+
id: string;
|
|
815
|
+
category: string;
|
|
816
|
+
severity: Severity;
|
|
817
|
+
message: string;
|
|
818
|
+
cwe?: string;
|
|
819
|
+
}[];
|
|
820
|
+
|
|
775
821
|
declare function getSandboxPolicyTemplate(): string;
|
|
776
822
|
|
|
777
823
|
declare function getDockerfileTemplate(): string;
|
|
778
824
|
|
|
779
825
|
declare function getDashboardHtml(): string;
|
|
780
826
|
|
|
781
|
-
export { type AgentDefinition, type AgentRole as AgentPermissionRole, type AgentPermissionSet, type AgentRole$1 as AgentRole, type AgentTemplate, type BackupManifest, type BrandGuardianIssue, type BrandGuardianReport, type BrownfieldAnalysisData, type ClaudeMdOptions, type CommandTemplate, type ComponentEntry, type ComponentRegistry, type ConflictCategory, type ConflictMap, type ConflictResolution, type CostMode, DOMAIN_INFO, type DesignTokens, type DetectionCheck, type DetectionResult, type DevServerConfig, type DomainConfig, type DynamicAgent, type DynamicAgentConfig, type ExecutionConfig, type FileConflict, type FileResolutionMap, type FishiConfig, type FishiYamlOptions, type GateConfig, type GateStatus, type GitConfig, type HookTemplate, type InitOptions, type McpConfig, type McpServerConfig, type ModelRoutingConfig, type ModelTier, type MonitorEvent, type MonitorState, type MonitorSummary, type ProjectConfig, type ProjectDomain, type ProjectType, type ProjectYamlOptions, type SandboxConfig, type SandboxMode, type SandboxPolicy, type SandboxRunResult, type ScaffoldOptions, type ScaffoldResult, type SkillTemplate, type StateConfig, type TaskStatus, type TaskboardConfig, type TemplateContext, architectAgentTemplate, backendAgentTemplate, buildSandboxEnv, createBackup, detectComponentRegistry, detectConflicts, detectDesignTokens, detectDevServer, detectDocker, devLeadTemplate, devopsAgentTemplate, docsAgentTemplate, emitEvent, frontendAgentTemplate, fullstackAgentTemplate, generateDefaultTokens, generateDesignSystemConfig, generatePermissionBlock, generateScaffold, getAdaptiveTaskGraphSkill, getAgentCompleteHook, getAgentFactoryTemplate, getAgentRegistryTemplate, getAgentSummary, getAgentsMdTemplate, getAimlArchitectTemplate, getAllPermissionSummary, getApiDesignSkill, getAutoCheckpointHook, getAvailableDomains, getBoardCommand, getBrainstormingSkill, getBrownfieldAnalysisSkill, getBrownfieldDiscoverySkill, getClaudeMdTemplate, getCodeGenSkill, getCoordinatorFactoryTemplate, getDashboardHtml, getDebuggingSkill, getDeepResearchAgentTemplate, getDeepResearchSkill, getDeploymentSkill, getDocCheckerScript, getDockerfileTemplate, getDocumentationSkill, getDomainConfigYaml, getFishiYamlTemplate, getGateCommand, getGateManagerScript, getGitignoreAdditions, getInitCommand, getLearningsManagerScript, getMarketplaceArchitectTemplate, getMasterOrchestratorTemplate, getMcpJsonTemplate, getMemoryManagerScript, getMobileArchitectTemplate, getModelRoutingReference, getMonitorEmitterScript, getPermissionsForRole, getPhaseRunnerScript, getPostEditHook, getPrdCommand, getPrdSkill, getProjectYamlTemplate, getResetCommand, getResumeCommand, getSaasArchitectTemplate, getSafetyCheckHook, getSandboxPolicyTemplate, getSessionStartHook, getSettingsJsonTemplate, getSoulMdTemplate, getSprintCommand, getStatusCommand, getTaskboardOpsSkill, getTaskboardUpdateHook, getTestingSkill, getTodoManagerScript, getValidateScaffoldScript, getVibeModeConfig, getWorktreeManagerScript, getWorktreeSetupHook, marketingAgentTemplate, mergeClaudeMd, mergeClaudeMdTop, mergeGitignore, mergeMcpJson, mergeSettingsJson, opsLeadTemplate, planningAgentTemplate, planningLeadTemplate, qualityLeadTemplate, readDomainConfig, readMonitorState, readSandboxConfig, readSandboxPolicy, researchAgentTemplate, runBrandGuardian, runInDockerSandbox, runInProcessSandbox, runInSandbox, securityAgentTemplate, startDevServer, testingAgentTemplate, uiuxAgentTemplate, writingAgentTemplate };
|
|
827
|
+
export { type AgentDefinition, type AgentRole as AgentPermissionRole, type AgentPermissionSet, type AgentRole$1 as AgentRole, type AgentTemplate, type BackupManifest, type BrandGuardianIssue, type BrandGuardianReport, type BrownfieldAnalysisData, type ClaudeMdOptions, type CommandTemplate, type ComponentEntry, type ComponentRegistry, type ConflictCategory, type ConflictMap, type ConflictResolution, type CostMode, DOMAIN_INFO, type DesignTokens, type DetectionCheck, type DetectionResult, type DevServerConfig, type DomainConfig, type DynamicAgent, type DynamicAgentConfig, type ExecutionConfig, type FileConflict, type FileResolutionMap, type FishiConfig, type FishiYamlOptions, type GateConfig, type GateStatus, type GitConfig, type HookTemplate, type InitOptions, type McpConfig, type McpServerConfig, type ModelRoutingConfig, type ModelTier, type MonitorEvent, type MonitorState, type MonitorSummary, type ProjectConfig, type ProjectDomain, type ProjectType, type ProjectYamlOptions, type SandboxConfig, type SandboxMode, type SandboxPolicy, type SandboxRunResult, type ScaffoldOptions, type ScaffoldResult, type SecurityFinding, type SecurityReport, type Severity as SecuritySeverity, type SkillTemplate, type StateConfig, type TaskStatus, type TaskboardConfig, type TemplateContext, architectAgentTemplate, backendAgentTemplate, buildSandboxEnv, createBackup, detectComponentRegistry, detectConflicts, detectDesignTokens, detectDevServer, detectDocker, devLeadTemplate, devopsAgentTemplate, docsAgentTemplate, emitEvent, frontendAgentTemplate, fullstackAgentTemplate, generateDefaultTokens, generateDesignSystemConfig, generatePermissionBlock, generateScaffold, generateSecurityReport, getAdaptiveTaskGraphSkill, getAgentCompleteHook, getAgentFactoryTemplate, getAgentRegistryTemplate, getAgentSummary, getAgentsMdTemplate, getAimlArchitectTemplate, getAllPermissionSummary, getApiDesignSkill, getAutoCheckpointHook, getAvailableDomains, getBoardCommand, getBrainstormingSkill, getBrownfieldAnalysisSkill, getBrownfieldDiscoverySkill, getClaudeMdTemplate, getCodeGenSkill, getCoordinatorFactoryTemplate, getDashboardHtml, getDebuggingSkill, getDeepResearchAgentTemplate, getDeepResearchSkill, getDeploymentSkill, getDocCheckerScript, getDockerfileTemplate, getDocumentationSkill, getDomainConfigYaml, getFishiYamlTemplate, getGateCommand, getGateManagerScript, getGitignoreAdditions, getInitCommand, getLearningsManagerScript, getMarketplaceArchitectTemplate, getMasterOrchestratorTemplate, getMcpJsonTemplate, getMemoryManagerScript, getMobileArchitectTemplate, getModelRoutingReference, getMonitorEmitterScript, getPermissionsForRole, getPhaseRunnerScript, getPostEditHook, getPrdCommand, getPrdSkill, getProjectYamlTemplate, getResetCommand, getResumeCommand, getSaasArchitectTemplate, getSafetyCheckHook, getSandboxPolicyTemplate, getScanRules, getSessionStartHook, getSettingsJsonTemplate, getSoulMdTemplate, getSprintCommand, getStatusCommand, getTaskboardOpsSkill, getTaskboardUpdateHook, getTestingSkill, getTodoManagerScript, getValidateScaffoldScript, getVibeModeConfig, getWorktreeManagerScript, getWorktreeSetupHook, marketingAgentTemplate, mergeClaudeMd, mergeClaudeMdTop, mergeGitignore, mergeMcpJson, mergeSettingsJson, opsLeadTemplate, planningAgentTemplate, planningLeadTemplate, qualityLeadTemplate, readDomainConfig, readMonitorState, readSandboxConfig, readSandboxPolicy, researchAgentTemplate, runBrandGuardian, runInDockerSandbox, runInProcessSandbox, runInSandbox, runSecurityScan, securityAgentTemplate, startDevServer, testingAgentTemplate, uiuxAgentTemplate, writingAgentTemplate };
|
package/dist/index.js
CHANGED
|
@@ -11718,7 +11718,7 @@ async function createBackup(targetDir, conflictingFiles) {
|
|
|
11718
11718
|
manifestFiles.push({ path: relPath, size: stat.size });
|
|
11719
11719
|
}
|
|
11720
11720
|
}
|
|
11721
|
-
const fishiVersion = "0.
|
|
11721
|
+
const fishiVersion = "0.12.0";
|
|
11722
11722
|
const manifest = {
|
|
11723
11723
|
timestamp: now.toISOString(),
|
|
11724
11724
|
fishi_version: fishiVersion,
|
|
@@ -12604,6 +12604,418 @@ ${allowLines}
|
|
|
12604
12604
|
${denyLines}`;
|
|
12605
12605
|
}
|
|
12606
12606
|
|
|
12607
|
+
// src/generators/security-scanner.ts
|
|
12608
|
+
import { readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
|
|
12609
|
+
import { join as join9, extname } from "path";
|
|
12610
|
+
var SCAN_RULES = [
|
|
12611
|
+
// -- OWASP A01: Broken Access Control --
|
|
12612
|
+
{
|
|
12613
|
+
id: "no-cors-wildcard",
|
|
12614
|
+
category: "OWASP-A01: Broken Access Control",
|
|
12615
|
+
severity: "high",
|
|
12616
|
+
pattern: /['"]Access-Control-Allow-Origin['"]\s*[,:]\s*['"]\*['"]/,
|
|
12617
|
+
message: "CORS wildcard (*) allows any origin \u2014 restrict to specific domains",
|
|
12618
|
+
fix: "Set Access-Control-Allow-Origin to specific trusted domains",
|
|
12619
|
+
cwe: "CWE-942",
|
|
12620
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx"]
|
|
12621
|
+
},
|
|
12622
|
+
{
|
|
12623
|
+
id: "no-open-redirect",
|
|
12624
|
+
category: "OWASP-A01: Broken Access Control",
|
|
12625
|
+
severity: "medium",
|
|
12626
|
+
pattern: /redirect\s*\(\s*req\.(query|params|body)\./,
|
|
12627
|
+
message: "Open redirect \u2014 user-controlled redirect target",
|
|
12628
|
+
fix: "Validate redirect URLs against an allowlist of trusted domains",
|
|
12629
|
+
cwe: "CWE-601",
|
|
12630
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx"]
|
|
12631
|
+
},
|
|
12632
|
+
// -- OWASP A02: Cryptographic Failures --
|
|
12633
|
+
{
|
|
12634
|
+
id: "no-hardcoded-secret",
|
|
12635
|
+
category: "OWASP-A02: Cryptographic Failures",
|
|
12636
|
+
severity: "critical",
|
|
12637
|
+
pattern: /(?:password|secret|api_?key|token|auth|private_?key)\s*[:=]\s*['"][^'"]{8,}['"]/i,
|
|
12638
|
+
message: "Possible hardcoded secret or credential",
|
|
12639
|
+
fix: "Move secrets to environment variables (.env) and never commit them",
|
|
12640
|
+
cwe: "CWE-798",
|
|
12641
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx", ".py", ".java", ".go", ".rb"],
|
|
12642
|
+
exclude: /example|placeholder|test|mock|fake|dummy|sample|TODO|FIXME|xxx|your[_-]?/i
|
|
12643
|
+
},
|
|
12644
|
+
{
|
|
12645
|
+
id: "no-md5",
|
|
12646
|
+
category: "OWASP-A02: Cryptographic Failures",
|
|
12647
|
+
severity: "high",
|
|
12648
|
+
pattern: /(?:createHash|hashlib\.md5|MD5|md5)\s*\(\s*['"]?md5['"]?\s*\)/,
|
|
12649
|
+
message: "MD5 is cryptographically broken \u2014 use SHA-256 or bcrypt",
|
|
12650
|
+
fix: "Replace MD5 with SHA-256 for hashing or bcrypt/argon2 for passwords",
|
|
12651
|
+
cwe: "CWE-328"
|
|
12652
|
+
},
|
|
12653
|
+
{
|
|
12654
|
+
id: "no-weak-crypto",
|
|
12655
|
+
category: "OWASP-A02: Cryptographic Failures",
|
|
12656
|
+
severity: "medium",
|
|
12657
|
+
pattern: /createHash\s*\(\s*['"]sha1['"]\s*\)/,
|
|
12658
|
+
message: "SHA-1 is deprecated \u2014 use SHA-256 or stronger",
|
|
12659
|
+
fix: 'Replace SHA-1 with SHA-256: createHash("sha256")',
|
|
12660
|
+
cwe: "CWE-328",
|
|
12661
|
+
fileTypes: [".ts", ".js", ".mjs"]
|
|
12662
|
+
},
|
|
12663
|
+
// -- OWASP A03: Injection --
|
|
12664
|
+
{
|
|
12665
|
+
id: "no-sql-injection",
|
|
12666
|
+
category: "OWASP-A03: Injection",
|
|
12667
|
+
severity: "critical",
|
|
12668
|
+
pattern: /(?:query|execute|raw)\s*\(\s*[`'"].*\$\{.*\}.*[`'"]/,
|
|
12669
|
+
message: "Possible SQL injection \u2014 string interpolation in query",
|
|
12670
|
+
fix: 'Use parameterized queries: db.query("SELECT * FROM users WHERE id = $1", [userId])',
|
|
12671
|
+
cwe: "CWE-89",
|
|
12672
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx"]
|
|
12673
|
+
},
|
|
12674
|
+
{
|
|
12675
|
+
id: "no-command-injection",
|
|
12676
|
+
category: "OWASP-A03: Injection",
|
|
12677
|
+
severity: "critical",
|
|
12678
|
+
pattern: /(?:exec|execSync|spawn|spawnSync)\s*\(\s*[`'"].*\$\{.*\}.*[`'"]/,
|
|
12679
|
+
message: "Possible command injection \u2014 user input in shell command",
|
|
12680
|
+
fix: "Use execFile() with argument array instead of string interpolation in exec()",
|
|
12681
|
+
cwe: "CWE-78",
|
|
12682
|
+
fileTypes: [".ts", ".js", ".mjs"],
|
|
12683
|
+
exclude: /fishi|scripts|hooks/i
|
|
12684
|
+
},
|
|
12685
|
+
{
|
|
12686
|
+
id: "no-eval",
|
|
12687
|
+
category: "OWASP-A03: Injection",
|
|
12688
|
+
severity: "high",
|
|
12689
|
+
pattern: /\beval\s*\(/,
|
|
12690
|
+
message: "eval() executes arbitrary code \u2014 never use with user input",
|
|
12691
|
+
fix: "Replace eval() with JSON.parse() for data or a safe parser for expressions",
|
|
12692
|
+
cwe: "CWE-95",
|
|
12693
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx"],
|
|
12694
|
+
exclude: /['"]use strict['"]/
|
|
12695
|
+
},
|
|
12696
|
+
{
|
|
12697
|
+
id: "no-dangerouslySetInnerHTML",
|
|
12698
|
+
category: "OWASP-A03: Injection",
|
|
12699
|
+
severity: "high",
|
|
12700
|
+
pattern: /dangerouslySetInnerHTML/,
|
|
12701
|
+
message: "dangerouslySetInnerHTML can lead to XSS if content is not sanitized",
|
|
12702
|
+
fix: "Sanitize HTML with DOMPurify before using dangerouslySetInnerHTML, or use React components instead",
|
|
12703
|
+
cwe: "CWE-79",
|
|
12704
|
+
fileTypes: [".tsx", ".jsx"]
|
|
12705
|
+
},
|
|
12706
|
+
// -- OWASP A04: Insecure Design --
|
|
12707
|
+
{
|
|
12708
|
+
id: "no-console-log-sensitive",
|
|
12709
|
+
category: "OWASP-A04: Insecure Design",
|
|
12710
|
+
severity: "medium",
|
|
12711
|
+
pattern: /console\.(log|info|debug)\s*\(.*(?:password|secret|token|key|credential|auth)/i,
|
|
12712
|
+
message: "Logging sensitive data (password, secret, token)",
|
|
12713
|
+
fix: "Remove sensitive data from log statements or use structured logging with redaction",
|
|
12714
|
+
cwe: "CWE-532",
|
|
12715
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx"],
|
|
12716
|
+
exclude: /\/\/|test|spec|mock/i
|
|
12717
|
+
},
|
|
12718
|
+
// -- OWASP A05: Security Misconfiguration --
|
|
12719
|
+
{
|
|
12720
|
+
id: "no-debug-mode",
|
|
12721
|
+
category: "OWASP-A05: Security Misconfiguration",
|
|
12722
|
+
severity: "medium",
|
|
12723
|
+
pattern: /(?:DEBUG|debug)\s*[:=]\s*(?:true|1|['"]true['"])/,
|
|
12724
|
+
message: "Debug mode enabled \u2014 disable in production",
|
|
12725
|
+
fix: 'Use environment-based config: DEBUG = process.env.NODE_ENV !== "production"',
|
|
12726
|
+
cwe: "CWE-489",
|
|
12727
|
+
exclude: /test|spec|\.env\.example|\.env\.local/i
|
|
12728
|
+
},
|
|
12729
|
+
{
|
|
12730
|
+
id: "no-http-only-false",
|
|
12731
|
+
category: "OWASP-A05: Security Misconfiguration",
|
|
12732
|
+
severity: "high",
|
|
12733
|
+
pattern: /httpOnly\s*:\s*false/,
|
|
12734
|
+
message: "Cookie httpOnly set to false \u2014 vulnerable to XSS cookie theft",
|
|
12735
|
+
fix: "Set httpOnly: true for authentication cookies",
|
|
12736
|
+
cwe: "CWE-1004",
|
|
12737
|
+
fileTypes: [".ts", ".js", ".mjs"]
|
|
12738
|
+
},
|
|
12739
|
+
{
|
|
12740
|
+
id: "no-secure-false",
|
|
12741
|
+
category: "OWASP-A05: Security Misconfiguration",
|
|
12742
|
+
severity: "medium",
|
|
12743
|
+
pattern: /secure\s*:\s*false/,
|
|
12744
|
+
message: "Cookie secure flag set to false \u2014 cookie sent over HTTP",
|
|
12745
|
+
fix: "Set secure: true for cookies in production (HTTPS only)",
|
|
12746
|
+
cwe: "CWE-614",
|
|
12747
|
+
fileTypes: [".ts", ".js", ".mjs"],
|
|
12748
|
+
exclude: /development|localhost|test/i
|
|
12749
|
+
},
|
|
12750
|
+
// -- OWASP A07: Identification and Authentication Failures --
|
|
12751
|
+
{
|
|
12752
|
+
id: "no-jwt-none-alg",
|
|
12753
|
+
category: "OWASP-A07: Auth Failures",
|
|
12754
|
+
severity: "critical",
|
|
12755
|
+
pattern: /algorithm\s*[:=]\s*['"]none['"]/i,
|
|
12756
|
+
message: 'JWT "none" algorithm \u2014 allows token forgery',
|
|
12757
|
+
fix: 'Always specify a strong algorithm: { algorithm: "HS256" } or RS256',
|
|
12758
|
+
cwe: "CWE-327",
|
|
12759
|
+
fileTypes: [".ts", ".js", ".mjs"]
|
|
12760
|
+
},
|
|
12761
|
+
{
|
|
12762
|
+
id: "no-weak-password-regex",
|
|
12763
|
+
category: "OWASP-A07: Auth Failures",
|
|
12764
|
+
severity: "low",
|
|
12765
|
+
pattern: /password.*\.length\s*[<>=]+\s*[1-5]\b/,
|
|
12766
|
+
message: "Weak password policy \u2014 minimum length too short",
|
|
12767
|
+
fix: "Require minimum 8 characters with complexity requirements",
|
|
12768
|
+
cwe: "CWE-521",
|
|
12769
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx"]
|
|
12770
|
+
},
|
|
12771
|
+
// -- OWASP A08: Software and Data Integrity --
|
|
12772
|
+
{
|
|
12773
|
+
id: "no-unsafe-deserialization",
|
|
12774
|
+
category: "OWASP-A08: Data Integrity",
|
|
12775
|
+
severity: "high",
|
|
12776
|
+
pattern: /JSON\.parse\s*\(\s*req\.(body|query|params)/,
|
|
12777
|
+
message: "Parsing untrusted input without validation",
|
|
12778
|
+
fix: "Validate and sanitize input with zod, joi, or yup before parsing",
|
|
12779
|
+
cwe: "CWE-502",
|
|
12780
|
+
fileTypes: [".ts", ".js", ".mjs"]
|
|
12781
|
+
},
|
|
12782
|
+
// -- OWASP A09: Security Logging and Monitoring Failures --
|
|
12783
|
+
{
|
|
12784
|
+
id: "no-empty-catch",
|
|
12785
|
+
category: "OWASP-A09: Logging Failures",
|
|
12786
|
+
severity: "low",
|
|
12787
|
+
pattern: /catch\s*\([^)]*\)\s*\{\s*\}/,
|
|
12788
|
+
message: "Empty catch block \u2014 errors silently swallowed",
|
|
12789
|
+
fix: 'Log the error: catch(e) { console.error("Context:", e); }',
|
|
12790
|
+
cwe: "CWE-390",
|
|
12791
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx"]
|
|
12792
|
+
},
|
|
12793
|
+
// -- OWASP A10: Server-Side Request Forgery (SSRF) --
|
|
12794
|
+
{
|
|
12795
|
+
id: "no-ssrf",
|
|
12796
|
+
category: "OWASP-A10: SSRF",
|
|
12797
|
+
severity: "high",
|
|
12798
|
+
pattern: /(?:fetch|axios|got|request|http\.get)\s*\(\s*(?:req\.|params\.|query\.|body\.)/,
|
|
12799
|
+
message: "Possible SSRF \u2014 user-controlled URL in server-side request",
|
|
12800
|
+
fix: "Validate URLs against an allowlist; block internal IPs (127.0.0.1, 10.x, 192.168.x)",
|
|
12801
|
+
cwe: "CWE-918",
|
|
12802
|
+
fileTypes: [".ts", ".js", ".mjs"]
|
|
12803
|
+
},
|
|
12804
|
+
// -- Additional SAST Rules --
|
|
12805
|
+
{
|
|
12806
|
+
id: "no-innerhtml",
|
|
12807
|
+
category: "SAST: XSS",
|
|
12808
|
+
severity: "high",
|
|
12809
|
+
pattern: /\.innerHTML\s*=/,
|
|
12810
|
+
message: "Direct innerHTML assignment \u2014 XSS vulnerability",
|
|
12811
|
+
fix: "Use textContent for text, or sanitize with DOMPurify before innerHTML",
|
|
12812
|
+
cwe: "CWE-79",
|
|
12813
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx"]
|
|
12814
|
+
},
|
|
12815
|
+
{
|
|
12816
|
+
id: "no-document-write",
|
|
12817
|
+
category: "SAST: XSS",
|
|
12818
|
+
severity: "high",
|
|
12819
|
+
pattern: /document\.write\s*\(/,
|
|
12820
|
+
message: "document.write() can inject malicious content",
|
|
12821
|
+
fix: "Use DOM manipulation methods (createElement, appendChild) instead",
|
|
12822
|
+
cwe: "CWE-79",
|
|
12823
|
+
fileTypes: [".ts", ".js", ".mjs", ".tsx", ".jsx"]
|
|
12824
|
+
},
|
|
12825
|
+
{
|
|
12826
|
+
id: "no-prototype-pollution",
|
|
12827
|
+
category: "SAST: Prototype Pollution",
|
|
12828
|
+
severity: "medium",
|
|
12829
|
+
pattern: /\[['"]__proto__['"]\]|\[['"]constructor['"]\]\s*\[['"]prototype['"]\]/,
|
|
12830
|
+
message: "Potential prototype pollution via __proto__ or constructor.prototype",
|
|
12831
|
+
fix: "Use Object.create(null) for dictionaries, validate keys against blocklist",
|
|
12832
|
+
cwe: "CWE-1321",
|
|
12833
|
+
fileTypes: [".ts", ".js", ".mjs"]
|
|
12834
|
+
},
|
|
12835
|
+
{
|
|
12836
|
+
id: "no-unvalidated-file-path",
|
|
12837
|
+
category: "SAST: Path Traversal",
|
|
12838
|
+
severity: "high",
|
|
12839
|
+
pattern: /(?:readFile|writeFile|readFileSync|writeFileSync|createReadStream)\s*\(\s*(?:req\.|params\.|query\.|body\.|`.*\$\{)/,
|
|
12840
|
+
message: "User input in file path \u2014 possible path traversal (../../etc/passwd)",
|
|
12841
|
+
fix: "Sanitize paths: use path.resolve() + verify within allowed directory",
|
|
12842
|
+
cwe: "CWE-22",
|
|
12843
|
+
fileTypes: [".ts", ".js", ".mjs"]
|
|
12844
|
+
},
|
|
12845
|
+
{
|
|
12846
|
+
id: "no-env-in-client",
|
|
12847
|
+
category: "SAST: Secret Exposure",
|
|
12848
|
+
severity: "high",
|
|
12849
|
+
pattern: /process\.env\.(?!NEXT_PUBLIC_|VITE_|NUXT_PUBLIC_)[\w]+/,
|
|
12850
|
+
message: "Server-side env var accessed \u2014 ensure this is not in client-side code",
|
|
12851
|
+
fix: "Use framework-specific public prefixes (NEXT_PUBLIC_, VITE_) for client-side env vars",
|
|
12852
|
+
cwe: "CWE-200",
|
|
12853
|
+
fileTypes: [".tsx", ".jsx"],
|
|
12854
|
+
exclude: /server|api|middleware|getServerSideProps|getStaticProps|loader|action/i
|
|
12855
|
+
},
|
|
12856
|
+
{
|
|
12857
|
+
id: "no-crypto-random",
|
|
12858
|
+
category: "SAST: Weak Randomness",
|
|
12859
|
+
severity: "medium",
|
|
12860
|
+
pattern: /Math\.random\s*\(\)/,
|
|
12861
|
+
message: "Math.random() is not cryptographically secure",
|
|
12862
|
+
fix: "Use crypto.randomUUID() or crypto.getRandomValues() for security-sensitive operations",
|
|
12863
|
+
cwe: "CWE-330",
|
|
12864
|
+
fileTypes: [".ts", ".js", ".mjs"],
|
|
12865
|
+
exclude: /test|spec|mock|animation|color|delay|jitter/i
|
|
12866
|
+
}
|
|
12867
|
+
];
|
|
12868
|
+
function runSecurityScan(projectDir) {
|
|
12869
|
+
const findings = [];
|
|
12870
|
+
let filesScanned = 0;
|
|
12871
|
+
const files = findScanFiles(projectDir);
|
|
12872
|
+
for (const file of files) {
|
|
12873
|
+
try {
|
|
12874
|
+
const content = readFileSync6(file, "utf-8");
|
|
12875
|
+
const lines = content.split("\n");
|
|
12876
|
+
const relPath = file.replace(projectDir, "").replace(/\\/g, "/").replace(/^\//, "");
|
|
12877
|
+
const ext = extname(file);
|
|
12878
|
+
filesScanned++;
|
|
12879
|
+
for (const rule of SCAN_RULES) {
|
|
12880
|
+
if (rule.fileTypes && rule.fileTypes.length > 0 && !rule.fileTypes.includes(ext)) continue;
|
|
12881
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12882
|
+
const line = lines[i];
|
|
12883
|
+
if (line.trimStart().startsWith("//") || line.trimStart().startsWith("*") || line.trimStart().startsWith("#")) continue;
|
|
12884
|
+
if (rule.exclude && rule.exclude.test(line)) continue;
|
|
12885
|
+
if (rule.pattern.test(line)) {
|
|
12886
|
+
findings.push({
|
|
12887
|
+
rule: rule.id,
|
|
12888
|
+
category: rule.category,
|
|
12889
|
+
severity: rule.severity,
|
|
12890
|
+
file: relPath,
|
|
12891
|
+
line: i + 1,
|
|
12892
|
+
code: line.trim().slice(0, 120),
|
|
12893
|
+
message: rule.message,
|
|
12894
|
+
fix: rule.fix,
|
|
12895
|
+
cwe: rule.cwe
|
|
12896
|
+
});
|
|
12897
|
+
}
|
|
12898
|
+
}
|
|
12899
|
+
}
|
|
12900
|
+
} catch {
|
|
12901
|
+
}
|
|
12902
|
+
}
|
|
12903
|
+
const critical = findings.filter((f) => f.severity === "critical").length;
|
|
12904
|
+
const high = findings.filter((f) => f.severity === "high").length;
|
|
12905
|
+
const medium = findings.filter((f) => f.severity === "medium").length;
|
|
12906
|
+
const low = findings.filter((f) => f.severity === "low").length;
|
|
12907
|
+
const info = findings.filter((f) => f.severity === "info").length;
|
|
12908
|
+
return {
|
|
12909
|
+
findings,
|
|
12910
|
+
summary: {
|
|
12911
|
+
critical,
|
|
12912
|
+
high,
|
|
12913
|
+
medium,
|
|
12914
|
+
low,
|
|
12915
|
+
info,
|
|
12916
|
+
total: findings.length,
|
|
12917
|
+
filesScanned,
|
|
12918
|
+
passed: critical === 0 && high === 0
|
|
12919
|
+
},
|
|
12920
|
+
scanDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12921
|
+
projectDir
|
|
12922
|
+
};
|
|
12923
|
+
}
|
|
12924
|
+
function generateSecurityReport(report) {
|
|
12925
|
+
const { findings, summary } = report;
|
|
12926
|
+
let md = `# Security Scan Report
|
|
12927
|
+
|
|
12928
|
+
`;
|
|
12929
|
+
md += `**Date:** ${report.scanDate}
|
|
12930
|
+
`;
|
|
12931
|
+
md += `**Files Scanned:** ${summary.filesScanned}
|
|
12932
|
+
`;
|
|
12933
|
+
md += `**Status:** ${summary.passed ? "PASSED" : "FAILED"}
|
|
12934
|
+
|
|
12935
|
+
`;
|
|
12936
|
+
md += `## Summary
|
|
12937
|
+
|
|
12938
|
+
`;
|
|
12939
|
+
md += `| Severity | Count |
|
|
12940
|
+
|----------|-------|
|
|
12941
|
+
`;
|
|
12942
|
+
md += `| Critical | ${summary.critical} |
|
|
12943
|
+
`;
|
|
12944
|
+
md += `| High | ${summary.high} |
|
|
12945
|
+
`;
|
|
12946
|
+
md += `| Medium | ${summary.medium} |
|
|
12947
|
+
`;
|
|
12948
|
+
md += `| Low | ${summary.low} |
|
|
12949
|
+
`;
|
|
12950
|
+
md += `| Info | ${summary.info} |
|
|
12951
|
+
`;
|
|
12952
|
+
md += `| **Total** | **${summary.total}** |
|
|
12953
|
+
|
|
12954
|
+
`;
|
|
12955
|
+
if (findings.length === 0) {
|
|
12956
|
+
md += `No security issues found.
|
|
12957
|
+
`;
|
|
12958
|
+
return md;
|
|
12959
|
+
}
|
|
12960
|
+
const byCategory = {};
|
|
12961
|
+
for (const f of findings) {
|
|
12962
|
+
if (!byCategory[f.category]) byCategory[f.category] = [];
|
|
12963
|
+
byCategory[f.category].push(f);
|
|
12964
|
+
}
|
|
12965
|
+
for (const [category, catFindings] of Object.entries(byCategory)) {
|
|
12966
|
+
md += `## ${category}
|
|
12967
|
+
|
|
12968
|
+
`;
|
|
12969
|
+
for (const f of catFindings) {
|
|
12970
|
+
const icon = f.severity === "critical" ? "\u{1F534}" : f.severity === "high" ? "\u{1F7E0}" : f.severity === "medium" ? "\u{1F7E1}" : "\u{1F535}";
|
|
12971
|
+
md += `### ${icon} ${f.rule} (${f.severity.toUpperCase()})
|
|
12972
|
+
`;
|
|
12973
|
+
md += `**File:** \`${f.file}:${f.line}\`
|
|
12974
|
+
`;
|
|
12975
|
+
if (f.cwe) md += `**CWE:** ${f.cwe}
|
|
12976
|
+
`;
|
|
12977
|
+
md += `**Issue:** ${f.message}
|
|
12978
|
+
`;
|
|
12979
|
+
md += `**Code:** \`${f.code}\`
|
|
12980
|
+
`;
|
|
12981
|
+
md += `**Fix:** ${f.fix}
|
|
12982
|
+
|
|
12983
|
+
`;
|
|
12984
|
+
}
|
|
12985
|
+
}
|
|
12986
|
+
return md;
|
|
12987
|
+
}
|
|
12988
|
+
function getScanRules() {
|
|
12989
|
+
return SCAN_RULES.map((r) => ({
|
|
12990
|
+
id: r.id,
|
|
12991
|
+
category: r.category,
|
|
12992
|
+
severity: r.severity,
|
|
12993
|
+
message: r.message,
|
|
12994
|
+
cwe: r.cwe
|
|
12995
|
+
}));
|
|
12996
|
+
}
|
|
12997
|
+
function findScanFiles(projectDir) {
|
|
12998
|
+
const scanExts = [".ts", ".js", ".mjs", ".tsx", ".jsx", ".py", ".java", ".go", ".rb"];
|
|
12999
|
+
const skipDirs = ["node_modules", ".next", "dist", ".git", ".fishi", ".trees", "coverage", "__pycache__", ".venv"];
|
|
13000
|
+
const files = [];
|
|
13001
|
+
function walk(dir) {
|
|
13002
|
+
try {
|
|
13003
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
13004
|
+
for (const entry of entries) {
|
|
13005
|
+
if (entry.isDirectory()) {
|
|
13006
|
+
if (skipDirs.includes(entry.name)) continue;
|
|
13007
|
+
walk(join9(dir, entry.name));
|
|
13008
|
+
} else if (scanExts.some((ext) => entry.name.endsWith(ext))) {
|
|
13009
|
+
files.push(join9(dir, entry.name));
|
|
13010
|
+
}
|
|
13011
|
+
}
|
|
13012
|
+
} catch {
|
|
13013
|
+
}
|
|
13014
|
+
}
|
|
13015
|
+
walk(projectDir);
|
|
13016
|
+
return files.slice(0, 500);
|
|
13017
|
+
}
|
|
13018
|
+
|
|
12607
13019
|
// src/templates/configs/sandbox-policy.ts
|
|
12608
13020
|
function getSandboxPolicyTemplate() {
|
|
12609
13021
|
return `# FISHI Sandbox Policy
|
|
@@ -13016,6 +13428,7 @@ export {
|
|
|
13016
13428
|
generateDesignSystemConfig,
|
|
13017
13429
|
generatePermissionBlock,
|
|
13018
13430
|
generateScaffold,
|
|
13431
|
+
generateSecurityReport,
|
|
13019
13432
|
getAdaptiveTaskGraphSkill,
|
|
13020
13433
|
getAgentCompleteHook,
|
|
13021
13434
|
getAgentFactoryTemplate,
|
|
@@ -13067,6 +13480,7 @@ export {
|
|
|
13067
13480
|
getSaasArchitectTemplate,
|
|
13068
13481
|
getSafetyCheckHook,
|
|
13069
13482
|
getSandboxPolicyTemplate,
|
|
13483
|
+
getScanRules,
|
|
13070
13484
|
getSessionStartHook,
|
|
13071
13485
|
getSettingsJsonTemplate,
|
|
13072
13486
|
getSoulMdTemplate,
|
|
@@ -13099,6 +13513,7 @@ export {
|
|
|
13099
13513
|
runInDockerSandbox,
|
|
13100
13514
|
runInProcessSandbox,
|
|
13101
13515
|
runInSandbox,
|
|
13516
|
+
runSecurityScan,
|
|
13102
13517
|
securityAgentTemplate,
|
|
13103
13518
|
startDevServer,
|
|
13104
13519
|
testingAgentTemplate,
|