@skillkit/core 1.14.0 → 1.15.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 +126 -1
- package/dist/index.js +1447 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31748,6 +31748,1438 @@ var SkillInjector = class {
|
|
|
31748
31748
|
return this.cache.stats();
|
|
31749
31749
|
}
|
|
31750
31750
|
};
|
|
31751
|
+
|
|
31752
|
+
// src/scanner/types.ts
|
|
31753
|
+
var Severity = /* @__PURE__ */ ((Severity2) => {
|
|
31754
|
+
Severity2["CRITICAL"] = "critical";
|
|
31755
|
+
Severity2["HIGH"] = "high";
|
|
31756
|
+
Severity2["MEDIUM"] = "medium";
|
|
31757
|
+
Severity2["LOW"] = "low";
|
|
31758
|
+
Severity2["INFO"] = "info";
|
|
31759
|
+
Severity2["SAFE"] = "safe";
|
|
31760
|
+
return Severity2;
|
|
31761
|
+
})(Severity || {});
|
|
31762
|
+
var ThreatCategory = /* @__PURE__ */ ((ThreatCategory2) => {
|
|
31763
|
+
ThreatCategory2["PROMPT_INJECTION"] = "prompt-injection";
|
|
31764
|
+
ThreatCategory2["COMMAND_INJECTION"] = "command-injection";
|
|
31765
|
+
ThreatCategory2["DATA_EXFILTRATION"] = "data-exfiltration";
|
|
31766
|
+
ThreatCategory2["TOOL_ABUSE"] = "tool-abuse";
|
|
31767
|
+
ThreatCategory2["HARDCODED_SECRETS"] = "hardcoded-secrets";
|
|
31768
|
+
ThreatCategory2["UNICODE_STEGANOGRAPHY"] = "unicode-steganography";
|
|
31769
|
+
ThreatCategory2["OBFUSCATION"] = "obfuscation";
|
|
31770
|
+
ThreatCategory2["SOCIAL_ENGINEERING"] = "social-engineering";
|
|
31771
|
+
ThreatCategory2["AUTONOMY_ABUSE"] = "autonomy-abuse";
|
|
31772
|
+
ThreatCategory2["POLICY_VIOLATION"] = "policy-violation";
|
|
31773
|
+
return ThreatCategory2;
|
|
31774
|
+
})(ThreatCategory || {});
|
|
31775
|
+
|
|
31776
|
+
// src/scanner/analyzers/static.ts
|
|
31777
|
+
import { readFile } from "fs/promises";
|
|
31778
|
+
import { extname as extname5 } from "path";
|
|
31779
|
+
|
|
31780
|
+
// src/scanner/rules/prompt-injection.ts
|
|
31781
|
+
var promptInjectionRules = [
|
|
31782
|
+
{
|
|
31783
|
+
id: "PI001",
|
|
31784
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31785
|
+
severity: "critical" /* CRITICAL */,
|
|
31786
|
+
patterns: [/ignore\s+(all\s+)?(previous|above|prior)\s+(instructions?|rules?|guidelines?)/i],
|
|
31787
|
+
description: "Instruction override: attempts to discard system instructions",
|
|
31788
|
+
remediation: "Remove instruction override language. Skills should not attempt to modify agent behavior."
|
|
31789
|
+
},
|
|
31790
|
+
{
|
|
31791
|
+
id: "PI002",
|
|
31792
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31793
|
+
severity: "critical" /* CRITICAL */,
|
|
31794
|
+
patterns: [/forget\s+(everything|all|what)\s+(you|i)\s+(told|said|learned)/i],
|
|
31795
|
+
description: "Context clearing: attempts to wipe agent memory",
|
|
31796
|
+
remediation: "Remove context-clearing instructions."
|
|
31797
|
+
},
|
|
31798
|
+
{
|
|
31799
|
+
id: "PI003",
|
|
31800
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31801
|
+
severity: "critical" /* CRITICAL */,
|
|
31802
|
+
patterns: [/disregard\s+(your|all|the)\s+(training|programming|instructions?)/i],
|
|
31803
|
+
description: "Training bypass: attempts to override agent training",
|
|
31804
|
+
remediation: "Remove training bypass instructions."
|
|
31805
|
+
},
|
|
31806
|
+
{
|
|
31807
|
+
id: "PI004",
|
|
31808
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31809
|
+
severity: "critical" /* CRITICAL */,
|
|
31810
|
+
patterns: [/new\s+instructions?:\s*$/im, /system\s+prompt:\s*$/im],
|
|
31811
|
+
description: "System prompt injection: attempts to inject new system-level instructions",
|
|
31812
|
+
remediation: "Do not include system prompt markers in skill content."
|
|
31813
|
+
},
|
|
31814
|
+
{
|
|
31815
|
+
id: "PI005",
|
|
31816
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31817
|
+
severity: "high" /* HIGH */,
|
|
31818
|
+
patterns: [/you\s+are\s+(now|actually|really)\s+(a|an|the)/i],
|
|
31819
|
+
description: "Role manipulation: attempts to reassign agent identity",
|
|
31820
|
+
remediation: "Skills should not attempt to change the agent's role or identity."
|
|
31821
|
+
},
|
|
31822
|
+
{
|
|
31823
|
+
id: "PI006",
|
|
31824
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31825
|
+
severity: "high" /* HIGH */,
|
|
31826
|
+
patterns: [/pretend\s+(to\s+be|you\s+are)/i, /act\s+as\s+(if|though)\s+you/i],
|
|
31827
|
+
description: "Roleplay manipulation: attempts to alter agent behavior via pretense",
|
|
31828
|
+
remediation: "Remove roleplay instructions that change agent behavior."
|
|
31829
|
+
},
|
|
31830
|
+
{
|
|
31831
|
+
id: "PI007",
|
|
31832
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31833
|
+
severity: "high" /* HIGH */,
|
|
31834
|
+
patterns: [/from\s+now\s+on,?\s+(you|always|never)/i],
|
|
31835
|
+
description: "Persistent behavior change: attempts to permanently alter agent behavior",
|
|
31836
|
+
remediation: "Skills should provide task-specific instructions, not permanent behavior changes."
|
|
31837
|
+
},
|
|
31838
|
+
{
|
|
31839
|
+
id: "PI008",
|
|
31840
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31841
|
+
severity: "high" /* HIGH */,
|
|
31842
|
+
patterns: [
|
|
31843
|
+
/show\s+me\s+your\s+(system\s+)?(prompt|instructions)/i,
|
|
31844
|
+
/reveal\s+your\s+(system\s+)?(prompt|instructions)/i,
|
|
31845
|
+
/what\s+are\s+your\s+(system\s+)?(instructions|rules)/i
|
|
31846
|
+
],
|
|
31847
|
+
description: "System prompt extraction: attempts to reveal agent configuration",
|
|
31848
|
+
remediation: "Remove attempts to extract system prompts."
|
|
31849
|
+
},
|
|
31850
|
+
{
|
|
31851
|
+
id: "PI009",
|
|
31852
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31853
|
+
severity: "critical" /* CRITICAL */,
|
|
31854
|
+
patterns: [
|
|
31855
|
+
/unrestricted\s+mode/i,
|
|
31856
|
+
/(?<!detect(?:ing)?[\s.]+)(?<!prevent(?:ing)?[\s.]+)jailbreak/i,
|
|
31857
|
+
/DAN\s+(mode|prompt)/i,
|
|
31858
|
+
/bypass\s+(safety|content)\s+(filter|restriction)/i
|
|
31859
|
+
],
|
|
31860
|
+
description: "Policy bypass: attempts to disable safety restrictions",
|
|
31861
|
+
remediation: "Remove jailbreak or policy bypass instructions."
|
|
31862
|
+
},
|
|
31863
|
+
{
|
|
31864
|
+
id: "PI010",
|
|
31865
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31866
|
+
severity: "high" /* HIGH */,
|
|
31867
|
+
patterns: [
|
|
31868
|
+
/don'?t\s+tell\s+the\s+user/i,
|
|
31869
|
+
/hide\s+this\s+from\s+the\s+user/i,
|
|
31870
|
+
/keep\s+this\s+(a\s+)?secret/i,
|
|
31871
|
+
/do\s+not\s+mention\s+this/i
|
|
31872
|
+
],
|
|
31873
|
+
description: "Concealment: instructs agent to hide information from user",
|
|
31874
|
+
remediation: "Skills should be transparent. Remove concealment instructions."
|
|
31875
|
+
},
|
|
31876
|
+
{
|
|
31877
|
+
id: "PI011",
|
|
31878
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31879
|
+
severity: "high" /* HIGH */,
|
|
31880
|
+
patterns: [
|
|
31881
|
+
/```\s*(system|assistant|user)\s*$/im,
|
|
31882
|
+
/<\/?(?:system|user|assistant|human|ai|claude)>/i
|
|
31883
|
+
],
|
|
31884
|
+
description: "Delimiter injection: attempts to inject conversation role markers",
|
|
31885
|
+
remediation: "Remove conversation role delimiters from skill content."
|
|
31886
|
+
},
|
|
31887
|
+
{
|
|
31888
|
+
id: "PI012",
|
|
31889
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31890
|
+
severity: "high" /* HIGH */,
|
|
31891
|
+
patterns: [/\[INST\]/i, /\[\/INST\]/i, /<<SYS>>/i, /<<\/SYS>>/i],
|
|
31892
|
+
description: "Model-specific delimiter injection: Llama-style instruction delimiters",
|
|
31893
|
+
remediation: "Remove model-specific instruction delimiters."
|
|
31894
|
+
},
|
|
31895
|
+
{
|
|
31896
|
+
id: "PI013",
|
|
31897
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31898
|
+
severity: "medium" /* MEDIUM */,
|
|
31899
|
+
patterns: [/---\s*$/im],
|
|
31900
|
+
excludePatterns: [/^---\s*$/],
|
|
31901
|
+
multiline: true,
|
|
31902
|
+
description: "YAML front-matter injection: attempts to inject via YAML metadata",
|
|
31903
|
+
remediation: "Use standard SKILL.md frontmatter format only."
|
|
31904
|
+
},
|
|
31905
|
+
{
|
|
31906
|
+
id: "PI014",
|
|
31907
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31908
|
+
severity: "medium" /* MEDIUM */,
|
|
31909
|
+
patterns: [
|
|
31910
|
+
/<!--[^>]{0,500}?(?:ignore|system|instruction|secret|override)[^>]{0,500}?-->/i
|
|
31911
|
+
],
|
|
31912
|
+
description: "Hidden HTML comments with suspicious instruction content",
|
|
31913
|
+
remediation: "Do not hide instructions in HTML comments."
|
|
31914
|
+
},
|
|
31915
|
+
{
|
|
31916
|
+
id: "PI015",
|
|
31917
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
31918
|
+
severity: "medium" /* MEDIUM */,
|
|
31919
|
+
patterns: [/\[comment\]:\s*#\s*\([^)]*(?:ignore|system|override)[^)]*\)/i],
|
|
31920
|
+
description: "Hidden Markdown comments with suspicious content",
|
|
31921
|
+
remediation: "Do not hide instructions in Markdown comments."
|
|
31922
|
+
}
|
|
31923
|
+
];
|
|
31924
|
+
|
|
31925
|
+
// src/scanner/rules/command-injection.ts
|
|
31926
|
+
var commandInjectionRules = [
|
|
31927
|
+
{
|
|
31928
|
+
id: "CI001",
|
|
31929
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
31930
|
+
severity: "critical" /* CRITICAL */,
|
|
31931
|
+
patterns: [/\beval\s*\(/],
|
|
31932
|
+
excludePatterns: [/\/\/.*\beval\b/, /#.*\beval\b/, /['"].*\beval\b.*['"]/],
|
|
31933
|
+
fileTypes: ["typescript", "javascript", "python"],
|
|
31934
|
+
description: "eval() call: dynamic code execution",
|
|
31935
|
+
remediation: "Replace eval() with safer alternatives like JSON.parse() or dedicated parsers."
|
|
31936
|
+
},
|
|
31937
|
+
{
|
|
31938
|
+
id: "CI002",
|
|
31939
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
31940
|
+
severity: "critical" /* CRITICAL */,
|
|
31941
|
+
patterns: [/new\s+Function\s*\(/, /\bFunction\s*\(\s*['"`]/],
|
|
31942
|
+
fileTypes: ["typescript", "javascript"],
|
|
31943
|
+
description: "Function() constructor: dynamic code generation",
|
|
31944
|
+
remediation: "Avoid Function() constructor. Use static code structures."
|
|
31945
|
+
},
|
|
31946
|
+
{
|
|
31947
|
+
id: "CI003",
|
|
31948
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
31949
|
+
severity: "critical" /* CRITICAL */,
|
|
31950
|
+
patterns: [
|
|
31951
|
+
/child_process\s*['"]?\s*\)\s*\.\s*exec\s*\(/,
|
|
31952
|
+
/\bexec\s*\(\s*['"`].*\$\{/,
|
|
31953
|
+
/\bexecSync\s*\(/,
|
|
31954
|
+
/require\s*\(\s*['"]child_process['"]\s*\)/
|
|
31955
|
+
],
|
|
31956
|
+
fileTypes: ["typescript", "javascript"],
|
|
31957
|
+
description: "child_process execution: shell command injection risk",
|
|
31958
|
+
remediation: "Use execFile() with argument arrays instead of exec() with string interpolation."
|
|
31959
|
+
},
|
|
31960
|
+
{
|
|
31961
|
+
id: "CI004",
|
|
31962
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
31963
|
+
severity: "critical" /* CRITICAL */,
|
|
31964
|
+
patterns: [
|
|
31965
|
+
/subprocess\.(?:call|run|Popen)\s*\([^)]*shell\s*=\s*True/,
|
|
31966
|
+
/os\.system\s*\(/,
|
|
31967
|
+
/os\.popen\s*\(/
|
|
31968
|
+
],
|
|
31969
|
+
fileTypes: ["python"],
|
|
31970
|
+
description: "Python shell execution with shell=True or os.system()",
|
|
31971
|
+
remediation: "Use subprocess.run() with shell=False and argument lists."
|
|
31972
|
+
},
|
|
31973
|
+
{
|
|
31974
|
+
id: "CI005",
|
|
31975
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
31976
|
+
severity: "medium" /* MEDIUM */,
|
|
31977
|
+
patterns: [/`[^`]*\$\{[^}]*\}[^`]*`/],
|
|
31978
|
+
excludePatterns: [/console\.log/, /logger\./, /throw\s+new/, /return\s+`/, /=\s*`/, /\+\s*`/, /\(\s*`/],
|
|
31979
|
+
fileTypes: ["typescript", "javascript"],
|
|
31980
|
+
description: "Template literal with dynamic content in potential command context",
|
|
31981
|
+
remediation: "Avoid interpolating user input into command strings. Use argument arrays."
|
|
31982
|
+
},
|
|
31983
|
+
{
|
|
31984
|
+
id: "CI006",
|
|
31985
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
31986
|
+
severity: "medium" /* MEDIUM */,
|
|
31987
|
+
patterns: [/\.\.\//, /\.\.\\/],
|
|
31988
|
+
excludePatterns: [/import\s+.*from\s+['"]\.\.\//, /require\s*\(\s*['"]\.\.\//, /\/\/.*\.\.\//, /#.*\.\.\//, /\]\(\.\.\//, /href\s*=\s*['"]\.\.\//, /src\s*=\s*['"]\.\.\//, /include\s+['"]\.\.\//, /!\[.*\]\(\.\.\//, /\.\.\/node_modules/],
|
|
31989
|
+
fileTypes: ["typescript", "javascript", "python", "bash"],
|
|
31990
|
+
description: "Path traversal: relative path escaping directory boundaries",
|
|
31991
|
+
remediation: "Use path.resolve() and validate paths are within expected directories."
|
|
31992
|
+
},
|
|
31993
|
+
{
|
|
31994
|
+
id: "CI007",
|
|
31995
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
31996
|
+
severity: "high" /* HIGH */,
|
|
31997
|
+
patterns: [
|
|
31998
|
+
/;\s*(?:rm|cat|curl|wget|nc|ncat)\s+/,
|
|
31999
|
+
/\|\s*(?:sh|bash|zsh|dash)\b/,
|
|
32000
|
+
/&&\s*(?:rm|curl|wget)\s+/
|
|
32001
|
+
],
|
|
32002
|
+
fileTypes: ["bash", "markdown"],
|
|
32003
|
+
description: "Shell command chaining with dangerous commands",
|
|
32004
|
+
remediation: "Avoid chaining shell commands. Use discrete, validated command invocations."
|
|
32005
|
+
},
|
|
32006
|
+
{
|
|
32007
|
+
id: "CI008",
|
|
32008
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
32009
|
+
severity: "medium" /* MEDIUM */,
|
|
32010
|
+
patterns: [/\bexec\s*\(/, /\bspawn\s*\(/],
|
|
32011
|
+
excludePatterns: [/execFile\s*\(/, /\.exec\s*\(/, /RegExp.*\.exec/],
|
|
32012
|
+
fileTypes: ["typescript", "javascript"],
|
|
32013
|
+
description: "Process execution functions detected",
|
|
32014
|
+
remediation: "Prefer execFile() over exec(). Validate all command arguments."
|
|
32015
|
+
},
|
|
32016
|
+
{
|
|
32017
|
+
id: "CI009",
|
|
32018
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
32019
|
+
severity: "high" /* HIGH */,
|
|
32020
|
+
patterns: [
|
|
32021
|
+
/\b__import__\s*\(/,
|
|
32022
|
+
/\bcompile\s*\(\s*['"][^'"]*['"]\s*,\s*['"][^'"]*['"]\s*,\s*['"]exec['"]\s*\)/
|
|
32023
|
+
],
|
|
32024
|
+
fileTypes: ["python"],
|
|
32025
|
+
description: "Python dynamic import or code compilation",
|
|
32026
|
+
remediation: "Use standard import statements. Avoid dynamic code compilation."
|
|
32027
|
+
},
|
|
32028
|
+
{
|
|
32029
|
+
id: "CI010",
|
|
32030
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
32031
|
+
severity: "medium" /* MEDIUM */,
|
|
32032
|
+
patterns: [/\bvm\.runIn(?:ThisContext|NewContext|Context)\s*\(/],
|
|
32033
|
+
fileTypes: ["typescript", "javascript"],
|
|
32034
|
+
description: "Node.js vm module: sandboxed but potentially dangerous code execution",
|
|
32035
|
+
remediation: "Avoid vm module for untrusted code. Use proper sandboxing solutions."
|
|
32036
|
+
}
|
|
32037
|
+
];
|
|
32038
|
+
|
|
32039
|
+
// src/scanner/rules/data-exfiltration.ts
|
|
32040
|
+
var dataExfiltrationRules = [
|
|
32041
|
+
{
|
|
32042
|
+
id: "DE001",
|
|
32043
|
+
category: "data-exfiltration" /* DATA_EXFILTRATION */,
|
|
32044
|
+
severity: "high" /* HIGH */,
|
|
32045
|
+
patterns: [
|
|
32046
|
+
/https?:\/\/(?:discord(?:app)?\.com\/api\/webhooks|ptb\.discord\.com\/api\/webhooks)\//i,
|
|
32047
|
+
/https?:\/\/hooks\.slack\.com\/services\//i,
|
|
32048
|
+
/https?:\/\/api\.telegram\.org\/bot/i
|
|
32049
|
+
],
|
|
32050
|
+
description: "Webhook URL detected: Discord, Slack, or Telegram webhook endpoint",
|
|
32051
|
+
remediation: "Remove hardcoded webhook URLs. Use environment variables or configuration files."
|
|
32052
|
+
},
|
|
32053
|
+
{
|
|
32054
|
+
id: "DE002",
|
|
32055
|
+
category: "data-exfiltration" /* DATA_EXFILTRATION */,
|
|
32056
|
+
severity: "high" /* HIGH */,
|
|
32057
|
+
patterns: [
|
|
32058
|
+
/(?:fetch|axios|request|got|ky|undici)\s*(?:\.\s*post)?\s*\([^)]*(?:process\.env|credentials|password|secret|token)/i
|
|
32059
|
+
],
|
|
32060
|
+
description: "HTTP request sending sensitive data: credentials or environment variables in request",
|
|
32061
|
+
remediation: "Never send credentials or env vars to external services from skill code."
|
|
32062
|
+
},
|
|
32063
|
+
{
|
|
32064
|
+
id: "DE003",
|
|
32065
|
+
category: "data-exfiltration" /* DATA_EXFILTRATION */,
|
|
32066
|
+
severity: "medium" /* MEDIUM */,
|
|
32067
|
+
patterns: [
|
|
32068
|
+
/process\.env\[/,
|
|
32069
|
+
/process\.env\./,
|
|
32070
|
+
/os\.environ/,
|
|
32071
|
+
/\$ENV\{/
|
|
32072
|
+
],
|
|
32073
|
+
excludePatterns: [/process\.env\.NODE_ENV/, /process\.env\.HOME/, /process\.env\.PATH/],
|
|
32074
|
+
description: "Environment variable access: skill reads environment variables",
|
|
32075
|
+
remediation: "Skills should declare required env vars in manifest, not access them directly."
|
|
32076
|
+
},
|
|
32077
|
+
{
|
|
32078
|
+
id: "DE004",
|
|
32079
|
+
category: "data-exfiltration" /* DATA_EXFILTRATION */,
|
|
32080
|
+
severity: "high" /* HIGH */,
|
|
32081
|
+
patterns: [
|
|
32082
|
+
/readFile.*(?:\.env|credentials|\.aws|\.ssh|\.gnupg|\.netrc)/i,
|
|
32083
|
+
/open\s*\(\s*['"].*(?:\.env|credentials|\.aws|\.ssh|\.gnupg)/i,
|
|
32084
|
+
/cat\s+.*(?:\.env|credentials|id_rsa|\.aws\/)/i
|
|
32085
|
+
],
|
|
32086
|
+
description: "Sensitive file access: reading credential or configuration files",
|
|
32087
|
+
remediation: "Skills should not read user credential files."
|
|
32088
|
+
},
|
|
32089
|
+
{
|
|
32090
|
+
id: "DE005",
|
|
32091
|
+
category: "data-exfiltration" /* DATA_EXFILTRATION */,
|
|
32092
|
+
severity: "medium" /* MEDIUM */,
|
|
32093
|
+
patterns: [
|
|
32094
|
+
/new\s+WebSocket\s*\(\s*['"`]/,
|
|
32095
|
+
/\bio\.connect\s*\(/
|
|
32096
|
+
],
|
|
32097
|
+
description: "WebSocket connection: potential covert data channel",
|
|
32098
|
+
remediation: "Document all network connections. Avoid persistent connections in skills."
|
|
32099
|
+
},
|
|
32100
|
+
{
|
|
32101
|
+
id: "DE006",
|
|
32102
|
+
category: "data-exfiltration" /* DATA_EXFILTRATION */,
|
|
32103
|
+
severity: "high" /* HIGH */,
|
|
32104
|
+
patterns: [
|
|
32105
|
+
/https?:\/\/[a-z0-9]+\.ngrok\b/i,
|
|
32106
|
+
/https?:\/\/[a-z0-9]+\.serveo\.net/i,
|
|
32107
|
+
/https?:\/\/[a-z0-9]+\.loca\.lt/i,
|
|
32108
|
+
/https?:\/\/[a-z0-9]+\.burpcollaborator\.net/i
|
|
32109
|
+
],
|
|
32110
|
+
description: "Tunneling service URL: potential data exfiltration via tunnel",
|
|
32111
|
+
remediation: "Remove tunneling service URLs. Use official API endpoints."
|
|
32112
|
+
},
|
|
32113
|
+
{
|
|
32114
|
+
id: "DE007",
|
|
32115
|
+
category: "data-exfiltration" /* DATA_EXFILTRATION */,
|
|
32116
|
+
severity: "medium" /* MEDIUM */,
|
|
32117
|
+
patterns: [
|
|
32118
|
+
/dns\.resolve/i,
|
|
32119
|
+
/\bdnslookup\b/i,
|
|
32120
|
+
/\bnslookup\s+/i,
|
|
32121
|
+
/\bdig\s+/i
|
|
32122
|
+
],
|
|
32123
|
+
description: "DNS-based data exfiltration: DNS queries can encode stolen data",
|
|
32124
|
+
remediation: "Skills should not perform DNS queries unless explicitly required."
|
|
32125
|
+
},
|
|
32126
|
+
{
|
|
32127
|
+
id: "DE008",
|
|
32128
|
+
category: "data-exfiltration" /* DATA_EXFILTRATION */,
|
|
32129
|
+
severity: "medium" /* MEDIUM */,
|
|
32130
|
+
patterns: [
|
|
32131
|
+
/(?:fetch|axios|request|got)\s*\(\s*['"`]https?:\/\/(?!localhost|127\.0\.0\.1)/
|
|
32132
|
+
],
|
|
32133
|
+
excludePatterns: [/(?:npmjs\.org|github\.com|githubusercontent\.com|registry\.npmmirror)/],
|
|
32134
|
+
fileTypes: ["typescript", "javascript"],
|
|
32135
|
+
description: "External HTTP request: skill makes outbound network call",
|
|
32136
|
+
remediation: "Declare all external URLs in skill manifest. Minimize network access."
|
|
32137
|
+
}
|
|
32138
|
+
];
|
|
32139
|
+
|
|
32140
|
+
// src/scanner/rules/tool-abuse.ts
|
|
32141
|
+
var toolAbuseRules = [
|
|
32142
|
+
{
|
|
32143
|
+
id: "TA001",
|
|
32144
|
+
category: "tool-abuse" /* TOOL_ABUSE */,
|
|
32145
|
+
severity: "high" /* HIGH */,
|
|
32146
|
+
patterns: [
|
|
32147
|
+
/(?:override|replace|redefine|shadow)\s+(?:the\s+)?(?:built-?in|default|original)\s+(?:tool|function|command)/i
|
|
32148
|
+
],
|
|
32149
|
+
fileTypes: ["markdown"],
|
|
32150
|
+
description: "Tool shadowing: instructs agent to override built-in tools",
|
|
32151
|
+
remediation: "Skills should extend functionality, not replace built-in tools."
|
|
32152
|
+
},
|
|
32153
|
+
{
|
|
32154
|
+
id: "TA002",
|
|
32155
|
+
category: "autonomy-abuse" /* AUTONOMY_ABUSE */,
|
|
32156
|
+
severity: "high" /* HIGH */,
|
|
32157
|
+
patterns: [
|
|
32158
|
+
/(?:keep|continue)\s+(?:retrying|trying|running)\s+(?:until|without)/i,
|
|
32159
|
+
/(?:run|execute|proceed)\s+without\s+(?:confirmation|approval|asking)/i,
|
|
32160
|
+
/(?:auto-?approve|skip\s+confirmation|bypass\s+approval)/i,
|
|
32161
|
+
/(?:don'?t|do\s+not|never)\s+(?:ask|prompt|wait)\s+(?:for|the)\s+(?:permission|confirmation|approval)/i
|
|
32162
|
+
],
|
|
32163
|
+
fileTypes: ["markdown"],
|
|
32164
|
+
description: "Autonomy abuse: instructs agent to bypass user confirmation",
|
|
32165
|
+
remediation: "Skills must respect user confirmation flows. Remove autonomy escalation."
|
|
32166
|
+
},
|
|
32167
|
+
{
|
|
32168
|
+
id: "TA003",
|
|
32169
|
+
category: "tool-abuse" /* TOOL_ABUSE */,
|
|
32170
|
+
severity: "medium" /* MEDIUM */,
|
|
32171
|
+
patterns: [
|
|
32172
|
+
/(?:discover|find|list|enumerate)\s+(?:all\s+)?(?:available\s+)?(?:tools|capabilities|functions)/i,
|
|
32173
|
+
/(?:enable|activate|unlock)\s+(?:all\s+)?(?:hidden|disabled|extra)\s+(?:tools|capabilities)/i
|
|
32174
|
+
],
|
|
32175
|
+
fileTypes: ["markdown"],
|
|
32176
|
+
description: "Capability inflation: attempts to discover or enable additional tools",
|
|
32177
|
+
remediation: "Skills should use only declared tools. Do not probe for hidden capabilities."
|
|
32178
|
+
},
|
|
32179
|
+
{
|
|
32180
|
+
id: "TA004",
|
|
32181
|
+
category: "tool-abuse" /* TOOL_ABUSE */,
|
|
32182
|
+
severity: "high" /* HIGH */,
|
|
32183
|
+
patterns: [
|
|
32184
|
+
/read\s+(?:the\s+)?(?:file|contents?).*(?:then|and)\s+(?:send|post|upload|transmit)/i,
|
|
32185
|
+
/(?:access|read|get)\s+.*(?:credential|secret|key|token).*(?:then|and)\s+(?:send|share|post)/i
|
|
32186
|
+
],
|
|
32187
|
+
fileTypes: ["markdown"],
|
|
32188
|
+
description: "Tool chaining: read sensitive data then send externally",
|
|
32189
|
+
remediation: "Skills should not combine file reading with external transmission."
|
|
32190
|
+
},
|
|
32191
|
+
{
|
|
32192
|
+
id: "TA005",
|
|
32193
|
+
category: "social-engineering" /* SOCIAL_ENGINEERING */,
|
|
32194
|
+
severity: "medium" /* MEDIUM */,
|
|
32195
|
+
patterns: [
|
|
32196
|
+
/(?:urgent|immediately|critical|emergency)[!:]\s+(?:you\s+must|run|execute|do\s+this)/i,
|
|
32197
|
+
/(?:as\s+(?:an?\s+)?(?:admin|root|superuser|administrator)),?\s+(?:I|we)\s+(?:require|need|demand)/i
|
|
32198
|
+
],
|
|
32199
|
+
fileTypes: ["markdown"],
|
|
32200
|
+
description: "Social engineering: uses urgency or authority to manipulate agent",
|
|
32201
|
+
remediation: "Remove manipulative language. Provide clear, neutral instructions."
|
|
32202
|
+
},
|
|
32203
|
+
{
|
|
32204
|
+
id: "TA006",
|
|
32205
|
+
category: "tool-abuse" /* TOOL_ABUSE */,
|
|
32206
|
+
severity: "medium" /* MEDIUM */,
|
|
32207
|
+
patterns: [
|
|
32208
|
+
/(?:when|if)\s+(?:the\s+)?(?:user|human)\s+(?:isn'?t|is\s+not)\s+(?:looking|watching|present)/i,
|
|
32209
|
+
/(?:only|secretly)\s+(?:when|while)\s+(?:idle|unattended|background)/i
|
|
32210
|
+
],
|
|
32211
|
+
fileTypes: ["markdown"],
|
|
32212
|
+
description: "Stealth execution: instructs agent to act when user is not present",
|
|
32213
|
+
remediation: "All skill actions must be transparent and auditable."
|
|
32214
|
+
}
|
|
32215
|
+
];
|
|
32216
|
+
|
|
32217
|
+
// src/scanner/rules/unicode.ts
|
|
32218
|
+
var unicodeRules = [
|
|
32219
|
+
{
|
|
32220
|
+
id: "UC001",
|
|
32221
|
+
category: "unicode-steganography" /* UNICODE_STEGANOGRAPHY */,
|
|
32222
|
+
severity: "medium" /* MEDIUM */,
|
|
32223
|
+
patterns: [/\u200B|\u200C|\u200D|\uFEFF/],
|
|
32224
|
+
description: "Zero-width characters detected: may hide invisible instructions",
|
|
32225
|
+
remediation: "Remove zero-width characters (U+200B, U+200C, U+200D, U+FEFF)."
|
|
32226
|
+
},
|
|
32227
|
+
{
|
|
32228
|
+
id: "UC002",
|
|
32229
|
+
category: "unicode-steganography" /* UNICODE_STEGANOGRAPHY */,
|
|
32230
|
+
severity: "high" /* HIGH */,
|
|
32231
|
+
patterns: [/[\u202A-\u202E]/],
|
|
32232
|
+
description: "Bidirectional text override: can reverse displayed text direction to hide content",
|
|
32233
|
+
remediation: "Remove bidirectional override characters (U+202A-U+202E)."
|
|
32234
|
+
},
|
|
32235
|
+
{
|
|
32236
|
+
id: "UC003",
|
|
32237
|
+
category: "unicode-steganography" /* UNICODE_STEGANOGRAPHY */,
|
|
32238
|
+
severity: "high" /* HIGH */,
|
|
32239
|
+
patterns: [/[\u2066-\u2069]/],
|
|
32240
|
+
description: "Bidirectional isolate characters: can create hidden text regions",
|
|
32241
|
+
remediation: "Remove bidirectional isolate characters (U+2066-U+2069)."
|
|
32242
|
+
},
|
|
32243
|
+
{
|
|
32244
|
+
id: "UC004",
|
|
32245
|
+
category: "unicode-steganography" /* UNICODE_STEGANOGRAPHY */,
|
|
32246
|
+
severity: "high" /* HIGH */,
|
|
32247
|
+
patterns: [/[\u{E0001}-\u{E007F}]/u],
|
|
32248
|
+
description: "Tag characters detected: Unicode tag block can encode hidden payloads",
|
|
32249
|
+
remediation: "Remove Unicode tag characters (U+E0001-U+E007F)."
|
|
32250
|
+
},
|
|
32251
|
+
{
|
|
32252
|
+
id: "UC005",
|
|
32253
|
+
category: "unicode-steganography" /* UNICODE_STEGANOGRAPHY */,
|
|
32254
|
+
severity: "medium" /* MEDIUM */,
|
|
32255
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional detection of control characters
|
|
32256
|
+
patterns: [/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/],
|
|
32257
|
+
description: "Control characters detected: non-printable characters in content",
|
|
32258
|
+
remediation: "Remove control characters. Use only printable Unicode content."
|
|
32259
|
+
},
|
|
32260
|
+
{
|
|
32261
|
+
id: "UC006",
|
|
32262
|
+
category: "obfuscation" /* OBFUSCATION */,
|
|
32263
|
+
severity: "medium" /* MEDIUM */,
|
|
32264
|
+
patterns: [
|
|
32265
|
+
/atob\s*\(\s*['"`][A-Za-z0-9+/=]{40,}['"`]\s*\)/,
|
|
32266
|
+
/Buffer\.from\s*\(\s*['"`][A-Za-z0-9+/=]{40,}['"`]\s*,\s*['"`]base64['"`]\s*\)/,
|
|
32267
|
+
/base64\.b64decode\s*\(\s*['"`][A-Za-z0-9+/=]{40,}['"`]\s*\)/
|
|
32268
|
+
],
|
|
32269
|
+
description: "Base64-encoded payload: long encoded string being decoded at runtime",
|
|
32270
|
+
remediation: "Avoid embedding base64-encoded payloads. Use plain text or documented data formats."
|
|
32271
|
+
},
|
|
32272
|
+
{
|
|
32273
|
+
id: "UC007",
|
|
32274
|
+
category: "obfuscation" /* OBFUSCATION */,
|
|
32275
|
+
severity: "medium" /* MEDIUM */,
|
|
32276
|
+
patterns: [
|
|
32277
|
+
/\\x[0-9a-fA-F]{2}(?:\\x[0-9a-fA-F]{2}){9,}/,
|
|
32278
|
+
/\\u[0-9a-fA-F]{4}(?:\\u[0-9a-fA-F]{4}){9,}/
|
|
32279
|
+
],
|
|
32280
|
+
description: "Hex/Unicode escape sequences: potential obfuscated content",
|
|
32281
|
+
remediation: "Use readable strings instead of hex or unicode escape sequences."
|
|
32282
|
+
}
|
|
32283
|
+
];
|
|
32284
|
+
|
|
32285
|
+
// src/scanner/rules/index.ts
|
|
32286
|
+
function getAllRules() {
|
|
32287
|
+
return [
|
|
32288
|
+
...promptInjectionRules,
|
|
32289
|
+
...commandInjectionRules,
|
|
32290
|
+
...dataExfiltrationRules,
|
|
32291
|
+
...toolAbuseRules,
|
|
32292
|
+
...unicodeRules
|
|
32293
|
+
];
|
|
32294
|
+
}
|
|
32295
|
+
|
|
32296
|
+
// src/scanner/analyzers/static.ts
|
|
32297
|
+
var EXT_TO_FILETYPE = {
|
|
32298
|
+
".ts": "typescript",
|
|
32299
|
+
".tsx": "typescript",
|
|
32300
|
+
".js": "javascript",
|
|
32301
|
+
".jsx": "javascript",
|
|
32302
|
+
".mjs": "javascript",
|
|
32303
|
+
".cjs": "javascript",
|
|
32304
|
+
".py": "python",
|
|
32305
|
+
".sh": "bash",
|
|
32306
|
+
".bash": "bash",
|
|
32307
|
+
".zsh": "bash",
|
|
32308
|
+
".md": "markdown",
|
|
32309
|
+
".mdx": "markdown"
|
|
32310
|
+
};
|
|
32311
|
+
var PLACEHOLDER_PATTERNS = [
|
|
32312
|
+
/your[-_]?api[-_]?key/i,
|
|
32313
|
+
/example[-_]?token/i,
|
|
32314
|
+
/sample[-_]?secret/i,
|
|
32315
|
+
/dummy[-_]?password/i,
|
|
32316
|
+
/placeholder/i,
|
|
32317
|
+
/xxx+/i,
|
|
32318
|
+
/\.\.\./,
|
|
32319
|
+
/TODO/,
|
|
32320
|
+
/FIXME/
|
|
32321
|
+
];
|
|
32322
|
+
var TEST_FILE_PATTERNS = [
|
|
32323
|
+
/\.test\.[jt]sx?$/,
|
|
32324
|
+
/\.spec\.[jt]sx?$/,
|
|
32325
|
+
/__tests__\//,
|
|
32326
|
+
/test\//,
|
|
32327
|
+
/tests\//,
|
|
32328
|
+
/\.stories\.[jt]sx?$/
|
|
32329
|
+
];
|
|
32330
|
+
function isTestFile(filePath) {
|
|
32331
|
+
return TEST_FILE_PATTERNS.some((p) => p.test(filePath));
|
|
32332
|
+
}
|
|
32333
|
+
function isPlaceholderLine(line) {
|
|
32334
|
+
return PLACEHOLDER_PATTERNS.some((p) => p.test(line));
|
|
32335
|
+
}
|
|
32336
|
+
function getFileType(filePath) {
|
|
32337
|
+
return EXT_TO_FILETYPE[extname5(filePath)];
|
|
32338
|
+
}
|
|
32339
|
+
function ruleMatchesFileType(rule, fileType) {
|
|
32340
|
+
if (!rule.fileTypes || rule.fileTypes.length === 0) return true;
|
|
32341
|
+
if (!fileType) return true;
|
|
32342
|
+
return rule.fileTypes.includes(fileType);
|
|
32343
|
+
}
|
|
32344
|
+
function matchesExcludePatterns(line, rule) {
|
|
32345
|
+
if (!rule.excludePatterns) return false;
|
|
32346
|
+
return rule.excludePatterns.some((p) => p.test(line));
|
|
32347
|
+
}
|
|
32348
|
+
var StaticAnalyzer = class {
|
|
32349
|
+
name = "static";
|
|
32350
|
+
rules;
|
|
32351
|
+
skipRules;
|
|
32352
|
+
findingCounter = 0;
|
|
32353
|
+
constructor(skipRules) {
|
|
32354
|
+
this.rules = getAllRules();
|
|
32355
|
+
this.skipRules = new Set(skipRules ?? []);
|
|
32356
|
+
}
|
|
32357
|
+
async analyze(_skillPath, files) {
|
|
32358
|
+
this.findingCounter = 0;
|
|
32359
|
+
const findings = [];
|
|
32360
|
+
for (const file of files) {
|
|
32361
|
+
if (isTestFile(file)) continue;
|
|
32362
|
+
const fileType = getFileType(file);
|
|
32363
|
+
const applicableRules = this.rules.filter(
|
|
32364
|
+
(r) => !this.skipRules.has(r.id) && ruleMatchesFileType(r, fileType)
|
|
32365
|
+
);
|
|
32366
|
+
if (applicableRules.length === 0) continue;
|
|
32367
|
+
let content;
|
|
32368
|
+
try {
|
|
32369
|
+
content = await readFile(file, "utf-8");
|
|
32370
|
+
} catch {
|
|
32371
|
+
continue;
|
|
32372
|
+
}
|
|
32373
|
+
const lines = content.split("\n");
|
|
32374
|
+
for (const rule of applicableRules) {
|
|
32375
|
+
if (rule.multiline) {
|
|
32376
|
+
for (const pattern of rule.patterns) {
|
|
32377
|
+
pattern.lastIndex = 0;
|
|
32378
|
+
let match;
|
|
32379
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
32380
|
+
const lineNumber = content.substring(0, match.index).split("\n").length;
|
|
32381
|
+
const snippetStart = Math.max(0, match.index - 40);
|
|
32382
|
+
const snippet = content.substring(snippetStart, match.index + match[0].length + 40).trim().substring(0, 200);
|
|
32383
|
+
if (isPlaceholderLine(snippet) || matchesExcludePatterns(snippet, rule)) {
|
|
32384
|
+
if (!pattern.global) break;
|
|
32385
|
+
continue;
|
|
32386
|
+
}
|
|
32387
|
+
findings.push({
|
|
32388
|
+
id: `F${++this.findingCounter}`,
|
|
32389
|
+
ruleId: rule.id,
|
|
32390
|
+
category: rule.category,
|
|
32391
|
+
severity: rule.severity,
|
|
32392
|
+
title: rule.description,
|
|
32393
|
+
description: rule.description,
|
|
32394
|
+
filePath: file,
|
|
32395
|
+
lineNumber,
|
|
32396
|
+
snippet,
|
|
32397
|
+
remediation: rule.remediation,
|
|
32398
|
+
analyzer: this.name
|
|
32399
|
+
});
|
|
32400
|
+
if (!pattern.global) break;
|
|
32401
|
+
}
|
|
32402
|
+
}
|
|
32403
|
+
continue;
|
|
32404
|
+
}
|
|
32405
|
+
for (let i = 0; i < lines.length; i++) {
|
|
32406
|
+
const line = lines[i];
|
|
32407
|
+
if (isPlaceholderLine(line)) continue;
|
|
32408
|
+
if (matchesExcludePatterns(line, rule)) continue;
|
|
32409
|
+
for (const pattern of rule.patterns) {
|
|
32410
|
+
pattern.lastIndex = 0;
|
|
32411
|
+
if (pattern.test(line)) {
|
|
32412
|
+
findings.push({
|
|
32413
|
+
id: `F${++this.findingCounter}`,
|
|
32414
|
+
ruleId: rule.id,
|
|
32415
|
+
category: rule.category,
|
|
32416
|
+
severity: rule.severity,
|
|
32417
|
+
title: rule.description,
|
|
32418
|
+
description: rule.description,
|
|
32419
|
+
filePath: file,
|
|
32420
|
+
lineNumber: i + 1,
|
|
32421
|
+
snippet: line.trim().substring(0, 200),
|
|
32422
|
+
remediation: rule.remediation,
|
|
32423
|
+
analyzer: this.name
|
|
32424
|
+
});
|
|
32425
|
+
break;
|
|
32426
|
+
}
|
|
32427
|
+
}
|
|
32428
|
+
}
|
|
32429
|
+
}
|
|
32430
|
+
}
|
|
32431
|
+
return findings;
|
|
32432
|
+
}
|
|
32433
|
+
};
|
|
32434
|
+
|
|
32435
|
+
// src/scanner/analyzers/manifest.ts
|
|
32436
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
32437
|
+
import { extname as extname6 } from "path";
|
|
32438
|
+
var DANGEROUS_TOOLS = /* @__PURE__ */ new Set(["Bash", "bash", "shell", "terminal", "exec", "system"]);
|
|
32439
|
+
var IMPERSONATION_PATTERNS = [
|
|
32440
|
+
/official\s+(?:anthropic|openai|google|microsoft|meta)\s+(?:tool|skill|plugin)/i,
|
|
32441
|
+
/(?:anthropic|openai|google|microsoft|meta)\s+certified/i,
|
|
32442
|
+
/endorsed\s+by\s+(?:anthropic|openai|google|microsoft|meta)/i
|
|
32443
|
+
];
|
|
32444
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
32445
|
+
".exe",
|
|
32446
|
+
".dll",
|
|
32447
|
+
".so",
|
|
32448
|
+
".dylib",
|
|
32449
|
+
".bin",
|
|
32450
|
+
".dat",
|
|
32451
|
+
".zip",
|
|
32452
|
+
".tar",
|
|
32453
|
+
".gz",
|
|
32454
|
+
".rar",
|
|
32455
|
+
".7z",
|
|
32456
|
+
".wasm",
|
|
32457
|
+
".pyc",
|
|
32458
|
+
".pyo",
|
|
32459
|
+
".class"
|
|
32460
|
+
]);
|
|
32461
|
+
var ManifestAnalyzer = class {
|
|
32462
|
+
name = "manifest";
|
|
32463
|
+
findingCounter = 0;
|
|
32464
|
+
skipRules;
|
|
32465
|
+
constructor(skipRules) {
|
|
32466
|
+
this.skipRules = new Set(skipRules ?? []);
|
|
32467
|
+
}
|
|
32468
|
+
async analyze(_skillPath, files) {
|
|
32469
|
+
this.findingCounter = 0;
|
|
32470
|
+
const findings = [];
|
|
32471
|
+
const skillMdFiles = files.filter((f) => f.toLowerCase().endsWith("skill.md"));
|
|
32472
|
+
for (const skillMd of skillMdFiles) {
|
|
32473
|
+
let content;
|
|
32474
|
+
try {
|
|
32475
|
+
content = await readFile2(skillMd, "utf-8");
|
|
32476
|
+
} catch {
|
|
32477
|
+
continue;
|
|
32478
|
+
}
|
|
32479
|
+
this.validateFrontmatter(content, skillMd, findings);
|
|
32480
|
+
this.checkImpersonation(content, skillMd, findings);
|
|
32481
|
+
}
|
|
32482
|
+
this.checkBinaryFiles(files, findings);
|
|
32483
|
+
return findings;
|
|
32484
|
+
}
|
|
32485
|
+
shouldSkip(ruleId) {
|
|
32486
|
+
return this.skipRules.has(ruleId);
|
|
32487
|
+
}
|
|
32488
|
+
validateFrontmatter(content, filePath, findings) {
|
|
32489
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
32490
|
+
if (!fmMatch) {
|
|
32491
|
+
if (!this.shouldSkip("MF001")) {
|
|
32492
|
+
findings.push({
|
|
32493
|
+
id: `MF${++this.findingCounter}`,
|
|
32494
|
+
ruleId: "MF001",
|
|
32495
|
+
category: "policy-violation" /* POLICY_VIOLATION */,
|
|
32496
|
+
severity: "low" /* LOW */,
|
|
32497
|
+
title: "Missing SKILL.md frontmatter",
|
|
32498
|
+
description: "SKILL.md should have YAML frontmatter with name, description, and allowed-tools",
|
|
32499
|
+
filePath,
|
|
32500
|
+
analyzer: this.name,
|
|
32501
|
+
remediation: "Add YAML frontmatter with name, description, and allowed-tools fields."
|
|
32502
|
+
});
|
|
32503
|
+
}
|
|
32504
|
+
return;
|
|
32505
|
+
}
|
|
32506
|
+
const fm = fmMatch[1];
|
|
32507
|
+
if (!/^name:/m.test(fm) && !this.shouldSkip("MF002")) {
|
|
32508
|
+
findings.push({
|
|
32509
|
+
id: `MF${++this.findingCounter}`,
|
|
32510
|
+
ruleId: "MF002",
|
|
32511
|
+
category: "policy-violation" /* POLICY_VIOLATION */,
|
|
32512
|
+
severity: "low" /* LOW */,
|
|
32513
|
+
title: "Missing skill name in frontmatter",
|
|
32514
|
+
description: "SKILL.md frontmatter should include a name field",
|
|
32515
|
+
filePath,
|
|
32516
|
+
analyzer: this.name,
|
|
32517
|
+
remediation: "Add a name field to the YAML frontmatter."
|
|
32518
|
+
});
|
|
32519
|
+
}
|
|
32520
|
+
const nameMatch = fm.match(/^name:\s*(.+)$/m);
|
|
32521
|
+
if (nameMatch) {
|
|
32522
|
+
const name = nameMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
32523
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(name) && !this.shouldSkip("MF003")) {
|
|
32524
|
+
findings.push({
|
|
32525
|
+
id: `MF${++this.findingCounter}`,
|
|
32526
|
+
ruleId: "MF003",
|
|
32527
|
+
category: "policy-violation" /* POLICY_VIOLATION */,
|
|
32528
|
+
severity: "low" /* LOW */,
|
|
32529
|
+
title: "Invalid skill name format",
|
|
32530
|
+
description: `Skill name "${name}" should use alphanumeric characters, dots, hyphens, or underscores`,
|
|
32531
|
+
filePath,
|
|
32532
|
+
analyzer: this.name,
|
|
32533
|
+
remediation: "Use a name matching [a-z0-9][a-z0-9._-]* pattern."
|
|
32534
|
+
});
|
|
32535
|
+
}
|
|
32536
|
+
}
|
|
32537
|
+
const descMatch = fm.match(/^description:\s*(.+)$/m);
|
|
32538
|
+
if (!descMatch) {
|
|
32539
|
+
if (!this.shouldSkip("MF004")) {
|
|
32540
|
+
findings.push({
|
|
32541
|
+
id: `MF${++this.findingCounter}`,
|
|
32542
|
+
ruleId: "MF004",
|
|
32543
|
+
category: "policy-violation" /* POLICY_VIOLATION */,
|
|
32544
|
+
severity: "info" /* INFO */,
|
|
32545
|
+
title: "Missing skill description",
|
|
32546
|
+
description: "SKILL.md should include a description for discoverability",
|
|
32547
|
+
filePath,
|
|
32548
|
+
analyzer: this.name,
|
|
32549
|
+
remediation: "Add a description field to the YAML frontmatter."
|
|
32550
|
+
});
|
|
32551
|
+
}
|
|
32552
|
+
} else {
|
|
32553
|
+
const desc = descMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
32554
|
+
if (desc.length < 20 && !this.shouldSkip("MF005")) {
|
|
32555
|
+
findings.push({
|
|
32556
|
+
id: `MF${++this.findingCounter}`,
|
|
32557
|
+
ruleId: "MF005",
|
|
32558
|
+
category: "policy-violation" /* POLICY_VIOLATION */,
|
|
32559
|
+
severity: "info" /* INFO */,
|
|
32560
|
+
title: "Short skill description",
|
|
32561
|
+
description: `Description "${desc}" is too short (${desc.length} chars). Aim for at least 20 characters.`,
|
|
32562
|
+
filePath,
|
|
32563
|
+
analyzer: this.name,
|
|
32564
|
+
remediation: "Provide a more detailed description."
|
|
32565
|
+
});
|
|
32566
|
+
}
|
|
32567
|
+
}
|
|
32568
|
+
const inlineToolsMatch = fm.match(/^allowed-tools:\s*\[([^\]]*)\]/m);
|
|
32569
|
+
const multiLineToolsMatch = fm.match(/^allowed-tools:\s*\n((?:\s+-\s+.+\n?)+)/m);
|
|
32570
|
+
let tools = [];
|
|
32571
|
+
if (inlineToolsMatch) {
|
|
32572
|
+
tools = inlineToolsMatch[1].split(",").map((t) => t.trim().replace(/^["']|["']$/g, ""));
|
|
32573
|
+
} else if (multiLineToolsMatch) {
|
|
32574
|
+
tools = multiLineToolsMatch[1].split("\n").map((line) => line.replace(/^\s*-\s*/, "").trim().replace(/^["']|["']$/g, "")).filter((t) => t.length > 0);
|
|
32575
|
+
}
|
|
32576
|
+
for (const tool of tools) {
|
|
32577
|
+
if (DANGEROUS_TOOLS.has(tool) && !this.shouldSkip("MF006")) {
|
|
32578
|
+
findings.push({
|
|
32579
|
+
id: `MF${++this.findingCounter}`,
|
|
32580
|
+
ruleId: "MF006",
|
|
32581
|
+
category: "tool-abuse" /* TOOL_ABUSE */,
|
|
32582
|
+
severity: "high" /* HIGH */,
|
|
32583
|
+
title: `Dangerous tool in allowed-tools: ${tool}`,
|
|
32584
|
+
description: `The tool "${tool}" grants shell access. This is a significant security risk.`,
|
|
32585
|
+
filePath,
|
|
32586
|
+
analyzer: this.name,
|
|
32587
|
+
remediation: "Restrict allowed-tools to the minimum needed. Avoid shell/exec tools."
|
|
32588
|
+
});
|
|
32589
|
+
}
|
|
32590
|
+
}
|
|
32591
|
+
}
|
|
32592
|
+
checkImpersonation(content, filePath, findings) {
|
|
32593
|
+
for (const pattern of IMPERSONATION_PATTERNS) {
|
|
32594
|
+
const match = content.match(pattern);
|
|
32595
|
+
if (match && !this.shouldSkip("MF007")) {
|
|
32596
|
+
const lineNumber = content.substring(0, match.index).split("\n").length;
|
|
32597
|
+
findings.push({
|
|
32598
|
+
id: `MF${++this.findingCounter}`,
|
|
32599
|
+
ruleId: "MF007",
|
|
32600
|
+
category: "social-engineering" /* SOCIAL_ENGINEERING */,
|
|
32601
|
+
severity: "high" /* HIGH */,
|
|
32602
|
+
title: "Impersonation detected",
|
|
32603
|
+
description: `Skill claims official affiliation: "${match[0]}"`,
|
|
32604
|
+
filePath,
|
|
32605
|
+
lineNumber,
|
|
32606
|
+
snippet: match[0],
|
|
32607
|
+
analyzer: this.name,
|
|
32608
|
+
remediation: "Remove false claims of official endorsement or certification."
|
|
32609
|
+
});
|
|
32610
|
+
}
|
|
32611
|
+
}
|
|
32612
|
+
}
|
|
32613
|
+
checkBinaryFiles(files, findings) {
|
|
32614
|
+
for (const file of files) {
|
|
32615
|
+
const ext = extname6(file).toLowerCase();
|
|
32616
|
+
if (BINARY_EXTENSIONS.has(ext) && !this.shouldSkip("MF008")) {
|
|
32617
|
+
findings.push({
|
|
32618
|
+
id: `MF${++this.findingCounter}`,
|
|
32619
|
+
ruleId: "MF008",
|
|
32620
|
+
category: "policy-violation" /* POLICY_VIOLATION */,
|
|
32621
|
+
severity: "medium" /* MEDIUM */,
|
|
32622
|
+
title: `Binary file detected: ${ext}`,
|
|
32623
|
+
description: `Binary file found in skill directory. Skills should contain only text files.`,
|
|
32624
|
+
filePath: file,
|
|
32625
|
+
analyzer: this.name,
|
|
32626
|
+
remediation: "Remove binary files from skill directory. Use package managers for dependencies."
|
|
32627
|
+
});
|
|
32628
|
+
}
|
|
32629
|
+
}
|
|
32630
|
+
}
|
|
32631
|
+
};
|
|
32632
|
+
|
|
32633
|
+
// src/scanner/analyzers/secrets.ts
|
|
32634
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
32635
|
+
import { basename as basename19 } from "path";
|
|
32636
|
+
var SECRET_PATTERNS = [
|
|
32637
|
+
{
|
|
32638
|
+
id: "SK001",
|
|
32639
|
+
name: "OpenAI API key",
|
|
32640
|
+
pattern: /sk-(?!ant-)(?:proj-|admin-)?[A-Za-z0-9-]{20,}/,
|
|
32641
|
+
severity: "critical" /* CRITICAL */
|
|
32642
|
+
},
|
|
32643
|
+
{
|
|
32644
|
+
id: "SK002",
|
|
32645
|
+
name: "Stripe live key",
|
|
32646
|
+
pattern: /pk_live_[a-zA-Z0-9]{20,}/,
|
|
32647
|
+
severity: "critical" /* CRITICAL */
|
|
32648
|
+
},
|
|
32649
|
+
{
|
|
32650
|
+
id: "SK003",
|
|
32651
|
+
name: "Stripe secret key",
|
|
32652
|
+
pattern: /sk_live_[a-zA-Z0-9]{20,}/,
|
|
32653
|
+
severity: "critical" /* CRITICAL */
|
|
32654
|
+
},
|
|
32655
|
+
{
|
|
32656
|
+
id: "SK004",
|
|
32657
|
+
name: "GitHub personal access token",
|
|
32658
|
+
pattern: /ghp_[a-zA-Z0-9]{36}/,
|
|
32659
|
+
severity: "critical" /* CRITICAL */
|
|
32660
|
+
},
|
|
32661
|
+
{
|
|
32662
|
+
id: "SK005",
|
|
32663
|
+
name: "GitHub OAuth token",
|
|
32664
|
+
pattern: /gho_[a-zA-Z0-9]{36}/,
|
|
32665
|
+
severity: "critical" /* CRITICAL */
|
|
32666
|
+
},
|
|
32667
|
+
{
|
|
32668
|
+
id: "SK006",
|
|
32669
|
+
name: "AWS access key",
|
|
32670
|
+
pattern: /AKIA[0-9A-Z]{16}/,
|
|
32671
|
+
severity: "critical" /* CRITICAL */
|
|
32672
|
+
},
|
|
32673
|
+
{
|
|
32674
|
+
id: "SK007",
|
|
32675
|
+
name: "Slack bot token",
|
|
32676
|
+
pattern: /xoxb-[0-9]{10,}-[a-zA-Z0-9]{20,}/,
|
|
32677
|
+
severity: "critical" /* CRITICAL */
|
|
32678
|
+
},
|
|
32679
|
+
{
|
|
32680
|
+
id: "SK008",
|
|
32681
|
+
name: "Slack user token",
|
|
32682
|
+
pattern: /xoxp-[0-9]{10,}-[a-zA-Z0-9]{20,}/,
|
|
32683
|
+
severity: "critical" /* CRITICAL */
|
|
32684
|
+
},
|
|
32685
|
+
{
|
|
32686
|
+
id: "SK009",
|
|
32687
|
+
name: "Private key block",
|
|
32688
|
+
pattern: /-----BEGIN\s+(?:RSA\s+|EC\s+|DSA\s+|OPENSSH\s+)?PRIVATE\s+KEY-----/,
|
|
32689
|
+
severity: "critical" /* CRITICAL */
|
|
32690
|
+
},
|
|
32691
|
+
{
|
|
32692
|
+
id: "SK010",
|
|
32693
|
+
name: "Google API key",
|
|
32694
|
+
pattern: /AIza[0-9A-Za-z_-]{35}/,
|
|
32695
|
+
severity: "high" /* HIGH */
|
|
32696
|
+
},
|
|
32697
|
+
{
|
|
32698
|
+
id: "SK011",
|
|
32699
|
+
name: "Anthropic API key",
|
|
32700
|
+
pattern: /sk-ant-[a-zA-Z0-9]{20,}/,
|
|
32701
|
+
severity: "critical" /* CRITICAL */
|
|
32702
|
+
},
|
|
32703
|
+
{
|
|
32704
|
+
id: "SK012",
|
|
32705
|
+
name: "npm token",
|
|
32706
|
+
pattern: /npm_[a-zA-Z0-9]{36}/,
|
|
32707
|
+
severity: "high" /* HIGH */
|
|
32708
|
+
},
|
|
32709
|
+
{
|
|
32710
|
+
id: "SK013",
|
|
32711
|
+
name: "Heroku API key",
|
|
32712
|
+
pattern: /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/,
|
|
32713
|
+
severity: "low" /* LOW */
|
|
32714
|
+
}
|
|
32715
|
+
];
|
|
32716
|
+
var PLACEHOLDER_INDICATORS = [
|
|
32717
|
+
/your[-_]?api[-_]?key/i,
|
|
32718
|
+
/example/i,
|
|
32719
|
+
/sample/i,
|
|
32720
|
+
/dummy/i,
|
|
32721
|
+
/placeholder/i,
|
|
32722
|
+
/xxx+/i,
|
|
32723
|
+
/test[-_]?key/i,
|
|
32724
|
+
/fake/i,
|
|
32725
|
+
/replace[-_]?with/i,
|
|
32726
|
+
/\<.*key.*\>/i
|
|
32727
|
+
];
|
|
32728
|
+
var ENV_FILE_PATTERN = /^\.env/;
|
|
32729
|
+
function isPlaceholder(line) {
|
|
32730
|
+
return PLACEHOLDER_INDICATORS.some((p) => p.test(line));
|
|
32731
|
+
}
|
|
32732
|
+
var SecretsAnalyzer = class {
|
|
32733
|
+
name = "secrets";
|
|
32734
|
+
findingCounter = 0;
|
|
32735
|
+
skipRules;
|
|
32736
|
+
constructor(skipRules) {
|
|
32737
|
+
this.skipRules = new Set(skipRules ?? []);
|
|
32738
|
+
}
|
|
32739
|
+
async analyze(_skillPath, files) {
|
|
32740
|
+
this.findingCounter = 0;
|
|
32741
|
+
const findings = [];
|
|
32742
|
+
for (const file of files) {
|
|
32743
|
+
const name = basename19(file);
|
|
32744
|
+
if (ENV_FILE_PATTERN.test(name) && !this.skipRules.has("SK-ENV")) {
|
|
32745
|
+
findings.push({
|
|
32746
|
+
id: `SK${++this.findingCounter}`,
|
|
32747
|
+
ruleId: "SK-ENV",
|
|
32748
|
+
category: "hardcoded-secrets" /* HARDCODED_SECRETS */,
|
|
32749
|
+
severity: "high" /* HIGH */,
|
|
32750
|
+
title: `Environment file included: ${name}`,
|
|
32751
|
+
description: "Environment files should not be included in skill distributions",
|
|
32752
|
+
filePath: file,
|
|
32753
|
+
analyzer: this.name,
|
|
32754
|
+
remediation: "Remove .env files from skill directory. Add to .gitignore."
|
|
32755
|
+
});
|
|
32756
|
+
continue;
|
|
32757
|
+
}
|
|
32758
|
+
let content;
|
|
32759
|
+
try {
|
|
32760
|
+
content = await readFile3(file, "utf-8");
|
|
32761
|
+
} catch {
|
|
32762
|
+
continue;
|
|
32763
|
+
}
|
|
32764
|
+
if (content.length > 1e6) continue;
|
|
32765
|
+
const lines = content.split("\n");
|
|
32766
|
+
for (let i = 0; i < lines.length; i++) {
|
|
32767
|
+
const line = lines[i];
|
|
32768
|
+
if (isPlaceholder(line)) continue;
|
|
32769
|
+
for (const secret of SECRET_PATTERNS) {
|
|
32770
|
+
if (this.skipRules.has(secret.id)) continue;
|
|
32771
|
+
if (secret.pattern.test(line)) {
|
|
32772
|
+
if (secret.id === "SK013") {
|
|
32773
|
+
if (!/(?:key|token|secret|password|credential|api)/i.test(line)) continue;
|
|
32774
|
+
}
|
|
32775
|
+
findings.push({
|
|
32776
|
+
id: `SK${++this.findingCounter}`,
|
|
32777
|
+
ruleId: secret.id,
|
|
32778
|
+
category: "hardcoded-secrets" /* HARDCODED_SECRETS */,
|
|
32779
|
+
severity: secret.severity,
|
|
32780
|
+
title: `${secret.name} detected`,
|
|
32781
|
+
description: `Potential ${secret.name} found in skill file`,
|
|
32782
|
+
filePath: file,
|
|
32783
|
+
lineNumber: i + 1,
|
|
32784
|
+
snippet: line.trim().replace(secret.pattern, "[REDACTED]").substring(0, 100),
|
|
32785
|
+
analyzer: this.name,
|
|
32786
|
+
remediation: "Remove hardcoded secrets. Use environment variables or secret managers."
|
|
32787
|
+
});
|
|
32788
|
+
break;
|
|
32789
|
+
}
|
|
32790
|
+
}
|
|
32791
|
+
}
|
|
32792
|
+
}
|
|
32793
|
+
return findings;
|
|
32794
|
+
}
|
|
32795
|
+
};
|
|
32796
|
+
|
|
32797
|
+
// src/scanner/scanner.ts
|
|
32798
|
+
import { readdir } from "fs/promises";
|
|
32799
|
+
import { join as join53, basename as basename20 } from "path";
|
|
32800
|
+
var SEVERITY_ORDER = {
|
|
32801
|
+
["critical" /* CRITICAL */]: 5,
|
|
32802
|
+
["high" /* HIGH */]: 4,
|
|
32803
|
+
["medium" /* MEDIUM */]: 3,
|
|
32804
|
+
["low" /* LOW */]: 2,
|
|
32805
|
+
["info" /* INFO */]: 1,
|
|
32806
|
+
["safe" /* SAFE */]: 0
|
|
32807
|
+
};
|
|
32808
|
+
async function discoverFiles(dirPath) {
|
|
32809
|
+
const files = [];
|
|
32810
|
+
async function walk(dir) {
|
|
32811
|
+
let entries;
|
|
32812
|
+
try {
|
|
32813
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
32814
|
+
} catch {
|
|
32815
|
+
return;
|
|
32816
|
+
}
|
|
32817
|
+
for (const entry of entries) {
|
|
32818
|
+
const fullPath = join53(dir, entry.name);
|
|
32819
|
+
if (entry.name.startsWith(".") && entry.name !== ".env" && !entry.name.startsWith(".env.")) continue;
|
|
32820
|
+
if (entry.name === "node_modules") continue;
|
|
32821
|
+
if (entry.isDirectory()) {
|
|
32822
|
+
await walk(fullPath);
|
|
32823
|
+
} else if (entry.isFile()) {
|
|
32824
|
+
files.push(fullPath);
|
|
32825
|
+
}
|
|
32826
|
+
}
|
|
32827
|
+
}
|
|
32828
|
+
await walk(dirPath);
|
|
32829
|
+
return files;
|
|
32830
|
+
}
|
|
32831
|
+
function deduplicateFindings(findings) {
|
|
32832
|
+
const seen = /* @__PURE__ */ new Set();
|
|
32833
|
+
return findings.filter((f) => {
|
|
32834
|
+
const key = `${f.ruleId}:${f.filePath ?? ""}:${f.lineNumber ?? ""}`;
|
|
32835
|
+
if (seen.has(key)) return false;
|
|
32836
|
+
seen.add(key);
|
|
32837
|
+
return true;
|
|
32838
|
+
});
|
|
32839
|
+
}
|
|
32840
|
+
function calculateVerdict(findings, failOnSeverity = "high" /* HIGH */) {
|
|
32841
|
+
const threshold = SEVERITY_ORDER[failOnSeverity] ?? SEVERITY_ORDER["high" /* HIGH */];
|
|
32842
|
+
for (const f of findings) {
|
|
32843
|
+
if ((SEVERITY_ORDER[f.severity] ?? 0) >= threshold) {
|
|
32844
|
+
return "fail";
|
|
32845
|
+
}
|
|
32846
|
+
}
|
|
32847
|
+
if (findings.some((f) => (SEVERITY_ORDER[f.severity] ?? 0) >= SEVERITY_ORDER["medium" /* MEDIUM */])) {
|
|
32848
|
+
return "warn";
|
|
32849
|
+
}
|
|
32850
|
+
return "pass";
|
|
32851
|
+
}
|
|
32852
|
+
function countStats(findings) {
|
|
32853
|
+
const stats = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
32854
|
+
for (const f of findings) {
|
|
32855
|
+
switch (f.severity) {
|
|
32856
|
+
case "critical" /* CRITICAL */:
|
|
32857
|
+
stats.critical++;
|
|
32858
|
+
break;
|
|
32859
|
+
case "high" /* HIGH */:
|
|
32860
|
+
stats.high++;
|
|
32861
|
+
break;
|
|
32862
|
+
case "medium" /* MEDIUM */:
|
|
32863
|
+
stats.medium++;
|
|
32864
|
+
break;
|
|
32865
|
+
case "low" /* LOW */:
|
|
32866
|
+
stats.low++;
|
|
32867
|
+
break;
|
|
32868
|
+
case "info" /* INFO */:
|
|
32869
|
+
stats.info++;
|
|
32870
|
+
break;
|
|
32871
|
+
}
|
|
32872
|
+
}
|
|
32873
|
+
return stats;
|
|
32874
|
+
}
|
|
32875
|
+
var SkillScanner = class {
|
|
32876
|
+
analyzers;
|
|
32877
|
+
options;
|
|
32878
|
+
constructor(options) {
|
|
32879
|
+
this.options = options ?? {};
|
|
32880
|
+
this.analyzers = [
|
|
32881
|
+
new StaticAnalyzer(this.options.skipRules),
|
|
32882
|
+
new ManifestAnalyzer(this.options.skipRules),
|
|
32883
|
+
new SecretsAnalyzer(this.options.skipRules)
|
|
32884
|
+
];
|
|
32885
|
+
}
|
|
32886
|
+
async scan(skillPath) {
|
|
32887
|
+
const start = performance.now();
|
|
32888
|
+
const files = await discoverFiles(skillPath);
|
|
32889
|
+
const skillName = basename20(skillPath.replace(/\/+$/, "")) || "unknown";
|
|
32890
|
+
const resultSets = await Promise.all(
|
|
32891
|
+
this.analyzers.map((a) => a.analyze(skillPath, files))
|
|
32892
|
+
);
|
|
32893
|
+
const allFindings = deduplicateFindings(resultSets.flat());
|
|
32894
|
+
allFindings.sort(
|
|
32895
|
+
(a, b) => (SEVERITY_ORDER[b.severity] ?? 0) - (SEVERITY_ORDER[a.severity] ?? 0)
|
|
32896
|
+
);
|
|
32897
|
+
const duration = Math.round(performance.now() - start);
|
|
32898
|
+
return {
|
|
32899
|
+
skillPath,
|
|
32900
|
+
skillName,
|
|
32901
|
+
verdict: calculateVerdict(allFindings, this.options.failOnSeverity),
|
|
32902
|
+
findings: allFindings,
|
|
32903
|
+
stats: countStats(allFindings),
|
|
32904
|
+
duration,
|
|
32905
|
+
analyzersUsed: this.analyzers.map((a) => a.name)
|
|
32906
|
+
};
|
|
32907
|
+
}
|
|
32908
|
+
};
|
|
32909
|
+
|
|
32910
|
+
// src/scanner/reporter.ts
|
|
32911
|
+
import { basename as basename21 } from "path";
|
|
32912
|
+
var SCANNER_VERSION = true ? "1.15.0" : "0.0.0";
|
|
32913
|
+
var SEVERITY_COLORS = {
|
|
32914
|
+
["critical" /* CRITICAL */]: "\x1B[91m",
|
|
32915
|
+
["high" /* HIGH */]: "\x1B[31m",
|
|
32916
|
+
["medium" /* MEDIUM */]: "\x1B[33m",
|
|
32917
|
+
["low" /* LOW */]: "\x1B[36m",
|
|
32918
|
+
["info" /* INFO */]: "\x1B[37m",
|
|
32919
|
+
["safe" /* SAFE */]: "\x1B[32m"
|
|
32920
|
+
};
|
|
32921
|
+
var RESET = "\x1B[0m";
|
|
32922
|
+
var BOLD = "\x1B[1m";
|
|
32923
|
+
var DIM = "\x1B[2m";
|
|
32924
|
+
var VERDICT_ICON = {
|
|
32925
|
+
pass: "\x1B[32m PASS \x1B[0m",
|
|
32926
|
+
warn: "\x1B[33m WARN \x1B[0m",
|
|
32927
|
+
fail: "\x1B[91m FAIL \x1B[0m"
|
|
32928
|
+
};
|
|
32929
|
+
function formatSummary(result) {
|
|
32930
|
+
const lines = [];
|
|
32931
|
+
lines.push("");
|
|
32932
|
+
lines.push(`${BOLD}Security Scan: ${result.skillName}${RESET}`);
|
|
32933
|
+
lines.push(`Verdict: ${VERDICT_ICON[result.verdict] ?? result.verdict}`);
|
|
32934
|
+
lines.push(`Duration: ${result.duration}ms | Analyzers: ${result.analyzersUsed.join(", ")}`);
|
|
32935
|
+
lines.push("");
|
|
32936
|
+
const { critical, high, medium, low, info } = result.stats;
|
|
32937
|
+
const parts = [];
|
|
32938
|
+
if (critical) parts.push(`${SEVERITY_COLORS["critical" /* CRITICAL */]}${critical} critical${RESET}`);
|
|
32939
|
+
if (high) parts.push(`${SEVERITY_COLORS["high" /* HIGH */]}${high} high${RESET}`);
|
|
32940
|
+
if (medium) parts.push(`${SEVERITY_COLORS["medium" /* MEDIUM */]}${medium} medium${RESET}`);
|
|
32941
|
+
if (low) parts.push(`${SEVERITY_COLORS["low" /* LOW */]}${low} low${RESET}`);
|
|
32942
|
+
if (info) parts.push(`${SEVERITY_COLORS["info" /* INFO */]}${info} info${RESET}`);
|
|
32943
|
+
if (parts.length > 0) {
|
|
32944
|
+
lines.push(`Findings: ${parts.join(" | ")}`);
|
|
32945
|
+
lines.push("");
|
|
32946
|
+
} else {
|
|
32947
|
+
lines.push(`${SEVERITY_COLORS["safe" /* SAFE */]}No security findings${RESET}`);
|
|
32948
|
+
lines.push("");
|
|
32949
|
+
return lines.join("\n");
|
|
32950
|
+
}
|
|
32951
|
+
for (const finding of result.findings) {
|
|
32952
|
+
const color = SEVERITY_COLORS[finding.severity] ?? "";
|
|
32953
|
+
const sev = finding.severity.toUpperCase().padEnd(8);
|
|
32954
|
+
lines.push(` ${color}${sev}${RESET} [${finding.ruleId}] ${finding.title}`);
|
|
32955
|
+
if (finding.filePath) {
|
|
32956
|
+
const loc = finding.lineNumber ? `${finding.filePath}:${finding.lineNumber}` : finding.filePath;
|
|
32957
|
+
lines.push(` ${DIM}${loc}${RESET}`);
|
|
32958
|
+
}
|
|
32959
|
+
if (finding.snippet) {
|
|
32960
|
+
lines.push(` ${DIM}> ${finding.snippet}${RESET}`);
|
|
32961
|
+
}
|
|
32962
|
+
if (finding.remediation) {
|
|
32963
|
+
lines.push(` Fix: ${finding.remediation}`);
|
|
32964
|
+
}
|
|
32965
|
+
lines.push("");
|
|
32966
|
+
}
|
|
32967
|
+
return lines.join("\n");
|
|
32968
|
+
}
|
|
32969
|
+
function formatJson(result) {
|
|
32970
|
+
return JSON.stringify(result, null, 2);
|
|
32971
|
+
}
|
|
32972
|
+
function formatTable(result) {
|
|
32973
|
+
const lines = [];
|
|
32974
|
+
const header = ["Severity", "Rule", "Title", "File", "Line"];
|
|
32975
|
+
const widths = [10, 8, 50, 40, 6];
|
|
32976
|
+
lines.push(header.map((h, i) => h.padEnd(widths[i])).join(" | "));
|
|
32977
|
+
lines.push(widths.map((w) => "-".repeat(w)).join("-+-"));
|
|
32978
|
+
for (const f of result.findings) {
|
|
32979
|
+
const row = [
|
|
32980
|
+
f.severity.toUpperCase().padEnd(widths[0]),
|
|
32981
|
+
f.ruleId.padEnd(widths[1]),
|
|
32982
|
+
f.title.substring(0, widths[2]).padEnd(widths[2]),
|
|
32983
|
+
(f.filePath ? basename21(f.filePath) : "").substring(0, widths[3]).padEnd(widths[3]),
|
|
32984
|
+
String(f.lineNumber ?? "").padEnd(widths[4])
|
|
32985
|
+
];
|
|
32986
|
+
lines.push(row.join(" | "));
|
|
32987
|
+
}
|
|
32988
|
+
lines.push("");
|
|
32989
|
+
lines.push(`Total: ${result.findings.length} findings | Verdict: ${result.verdict.toUpperCase()}`);
|
|
32990
|
+
return lines.join("\n");
|
|
32991
|
+
}
|
|
32992
|
+
function formatSarif(result) {
|
|
32993
|
+
const rules = /* @__PURE__ */ new Map();
|
|
32994
|
+
for (const f of result.findings) {
|
|
32995
|
+
if (!rules.has(f.ruleId)) rules.set(f.ruleId, f);
|
|
32996
|
+
}
|
|
32997
|
+
const severityToSarif = {
|
|
32998
|
+
["critical" /* CRITICAL */]: "error",
|
|
32999
|
+
["high" /* HIGH */]: "error",
|
|
33000
|
+
["medium" /* MEDIUM */]: "warning",
|
|
33001
|
+
["low" /* LOW */]: "note",
|
|
33002
|
+
["info" /* INFO */]: "note",
|
|
33003
|
+
["safe" /* SAFE */]: "none"
|
|
33004
|
+
};
|
|
33005
|
+
const sarif = {
|
|
33006
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
|
|
33007
|
+
version: "2.1.0",
|
|
33008
|
+
runs: [
|
|
33009
|
+
{
|
|
33010
|
+
tool: {
|
|
33011
|
+
driver: {
|
|
33012
|
+
name: "skillkit-scanner",
|
|
33013
|
+
version: SCANNER_VERSION,
|
|
33014
|
+
informationUri: "https://agenstskills.com",
|
|
33015
|
+
rules: [...rules.values()].map((r) => ({
|
|
33016
|
+
id: r.ruleId,
|
|
33017
|
+
name: r.ruleId,
|
|
33018
|
+
shortDescription: { text: r.title },
|
|
33019
|
+
fullDescription: { text: r.description },
|
|
33020
|
+
helpUri: "https://agenstskills.com/docs/security",
|
|
33021
|
+
defaultConfiguration: {
|
|
33022
|
+
level: severityToSarif[r.severity] ?? "warning"
|
|
33023
|
+
},
|
|
33024
|
+
properties: {
|
|
33025
|
+
category: r.category
|
|
33026
|
+
}
|
|
33027
|
+
}))
|
|
33028
|
+
}
|
|
33029
|
+
},
|
|
33030
|
+
results: result.findings.map((f) => ({
|
|
33031
|
+
ruleId: f.ruleId,
|
|
33032
|
+
level: severityToSarif[f.severity] ?? "warning",
|
|
33033
|
+
message: { text: f.description },
|
|
33034
|
+
locations: f.filePath ? [
|
|
33035
|
+
{
|
|
33036
|
+
physicalLocation: {
|
|
33037
|
+
artifactLocation: {
|
|
33038
|
+
uri: f.filePath.startsWith(result.skillPath) ? f.filePath.slice(result.skillPath.length).replace(/^\//, "") : f.filePath
|
|
33039
|
+
},
|
|
33040
|
+
region: f.lineNumber ? { startLine: f.lineNumber } : void 0
|
|
33041
|
+
}
|
|
33042
|
+
}
|
|
33043
|
+
] : [],
|
|
33044
|
+
fixes: f.remediation ? [{ description: { text: f.remediation } }] : void 0
|
|
33045
|
+
}))
|
|
33046
|
+
}
|
|
33047
|
+
]
|
|
33048
|
+
};
|
|
33049
|
+
return JSON.stringify(sarif, null, 2);
|
|
33050
|
+
}
|
|
33051
|
+
function formatResult(result, format = "summary") {
|
|
33052
|
+
switch (format) {
|
|
33053
|
+
case "json":
|
|
33054
|
+
return formatJson(result);
|
|
33055
|
+
case "table":
|
|
33056
|
+
return formatTable(result);
|
|
33057
|
+
case "sarif":
|
|
33058
|
+
return formatSarif(result);
|
|
33059
|
+
default:
|
|
33060
|
+
return formatSummary(result);
|
|
33061
|
+
}
|
|
33062
|
+
}
|
|
33063
|
+
|
|
33064
|
+
// src/scanner/taxonomy.ts
|
|
33065
|
+
var THREAT_TAXONOMY = {
|
|
33066
|
+
["prompt-injection" /* PROMPT_INJECTION */]: {
|
|
33067
|
+
category: "prompt-injection" /* PROMPT_INJECTION */,
|
|
33068
|
+
name: "Prompt Injection",
|
|
33069
|
+
description: "Attempts to override, manipulate, or bypass AI agent instructions through crafted text",
|
|
33070
|
+
defaultSeverity: "critical" /* CRITICAL */,
|
|
33071
|
+
examples: [
|
|
33072
|
+
"Instruction override (ignore previous instructions)",
|
|
33073
|
+
"Role manipulation (you are now...)",
|
|
33074
|
+
"System prompt extraction"
|
|
33075
|
+
]
|
|
33076
|
+
},
|
|
33077
|
+
["command-injection" /* COMMAND_INJECTION */]: {
|
|
33078
|
+
category: "command-injection" /* COMMAND_INJECTION */,
|
|
33079
|
+
name: "Command Injection",
|
|
33080
|
+
description: "Code execution via eval, exec, subprocess, or shell commands embedded in skill content",
|
|
33081
|
+
defaultSeverity: "critical" /* CRITICAL */,
|
|
33082
|
+
examples: [
|
|
33083
|
+
"eval() or Function() calls",
|
|
33084
|
+
"subprocess.run with shell=True",
|
|
33085
|
+
"child_process.exec with user input"
|
|
33086
|
+
]
|
|
33087
|
+
},
|
|
33088
|
+
["data-exfiltration" /* DATA_EXFILTRATION */]: {
|
|
33089
|
+
category: "data-exfiltration" /* DATA_EXFILTRATION */,
|
|
33090
|
+
name: "Data Exfiltration",
|
|
33091
|
+
description: "Attempts to send sensitive data to external endpoints or read protected files",
|
|
33092
|
+
defaultSeverity: "high" /* HIGH */,
|
|
33093
|
+
examples: [
|
|
33094
|
+
"Webhook URLs to Discord/Telegram/Slack",
|
|
33095
|
+
"HTTP POST with environment variables",
|
|
33096
|
+
"Reading .env or credential files"
|
|
33097
|
+
]
|
|
33098
|
+
},
|
|
33099
|
+
["tool-abuse" /* TOOL_ABUSE */]: {
|
|
33100
|
+
category: "tool-abuse" /* TOOL_ABUSE */,
|
|
33101
|
+
name: "Tool Abuse",
|
|
33102
|
+
description: "Manipulation of AI agent tools through shadowing, chaining, or autonomy escalation",
|
|
33103
|
+
defaultSeverity: "high" /* HIGH */,
|
|
33104
|
+
examples: [
|
|
33105
|
+
"Redefining built-in tools",
|
|
33106
|
+
"Instructing agent to run without confirmation",
|
|
33107
|
+
"Chaining sensitive read + external send"
|
|
33108
|
+
]
|
|
33109
|
+
},
|
|
33110
|
+
["hardcoded-secrets" /* HARDCODED_SECRETS */]: {
|
|
33111
|
+
category: "hardcoded-secrets" /* HARDCODED_SECRETS */,
|
|
33112
|
+
name: "Hardcoded Secrets",
|
|
33113
|
+
description: "API keys, tokens, passwords, or other credentials embedded in skill files",
|
|
33114
|
+
defaultSeverity: "high" /* HIGH */,
|
|
33115
|
+
examples: [
|
|
33116
|
+
"API keys (sk-, pk_live_, ghp_)",
|
|
33117
|
+
"Private key blocks",
|
|
33118
|
+
"Embedded .env file contents"
|
|
33119
|
+
]
|
|
33120
|
+
},
|
|
33121
|
+
["unicode-steganography" /* UNICODE_STEGANOGRAPHY */]: {
|
|
33122
|
+
category: "unicode-steganography" /* UNICODE_STEGANOGRAPHY */,
|
|
33123
|
+
name: "Unicode Steganography",
|
|
33124
|
+
description: "Hidden content using invisible Unicode characters, bidirectional overrides, or homoglyphs",
|
|
33125
|
+
defaultSeverity: "medium" /* MEDIUM */,
|
|
33126
|
+
examples: [
|
|
33127
|
+
"Zero-width characters hiding instructions",
|
|
33128
|
+
"Bidirectional text override attacks",
|
|
33129
|
+
"Tag characters encoding hidden payloads"
|
|
33130
|
+
]
|
|
33131
|
+
},
|
|
33132
|
+
["obfuscation" /* OBFUSCATION */]: {
|
|
33133
|
+
category: "obfuscation" /* OBFUSCATION */,
|
|
33134
|
+
name: "Obfuscation",
|
|
33135
|
+
description: "Deliberately obscured code or instructions to hide malicious intent",
|
|
33136
|
+
defaultSeverity: "medium" /* MEDIUM */,
|
|
33137
|
+
examples: [
|
|
33138
|
+
"Base64-encoded commands",
|
|
33139
|
+
"Hex-encoded payloads",
|
|
33140
|
+
"String concatenation to evade detection"
|
|
33141
|
+
]
|
|
33142
|
+
},
|
|
33143
|
+
["social-engineering" /* SOCIAL_ENGINEERING */]: {
|
|
33144
|
+
category: "social-engineering" /* SOCIAL_ENGINEERING */,
|
|
33145
|
+
name: "Social Engineering",
|
|
33146
|
+
description: "Manipulative language targeting the AI agent or the user to bypass safety measures",
|
|
33147
|
+
defaultSeverity: "medium" /* MEDIUM */,
|
|
33148
|
+
examples: [
|
|
33149
|
+
"Urgency pressure (do this immediately)",
|
|
33150
|
+
"Authority claims (as an admin, I require...)",
|
|
33151
|
+
"Concealment requests (don't tell the user)"
|
|
33152
|
+
]
|
|
33153
|
+
},
|
|
33154
|
+
["autonomy-abuse" /* AUTONOMY_ABUSE */]: {
|
|
33155
|
+
category: "autonomy-abuse" /* AUTONOMY_ABUSE */,
|
|
33156
|
+
name: "Autonomy Abuse",
|
|
33157
|
+
description: "Instructions that escalate agent autonomy beyond intended boundaries",
|
|
33158
|
+
defaultSeverity: "high" /* HIGH */,
|
|
33159
|
+
examples: [
|
|
33160
|
+
"Run without user confirmation",
|
|
33161
|
+
"Keep retrying until success",
|
|
33162
|
+
"Auto-approve all actions"
|
|
33163
|
+
]
|
|
33164
|
+
},
|
|
33165
|
+
["policy-violation" /* POLICY_VIOLATION */]: {
|
|
33166
|
+
category: "policy-violation" /* POLICY_VIOLATION */,
|
|
33167
|
+
name: "Policy Violation",
|
|
33168
|
+
description: "Content that violates skill marketplace policies or naming conventions",
|
|
33169
|
+
defaultSeverity: "low" /* LOW */,
|
|
33170
|
+
examples: [
|
|
33171
|
+
"Impersonating official tools",
|
|
33172
|
+
"Missing required metadata",
|
|
33173
|
+
"Binary files in skill directory"
|
|
33174
|
+
]
|
|
33175
|
+
}
|
|
33176
|
+
};
|
|
33177
|
+
function getThreatInfo(category) {
|
|
33178
|
+
return THREAT_TAXONOMY[category];
|
|
33179
|
+
}
|
|
33180
|
+
function getDefaultSeverity(category) {
|
|
33181
|
+
return THREAT_TAXONOMY[category].defaultSeverity;
|
|
33182
|
+
}
|
|
31751
33183
|
export {
|
|
31752
33184
|
AGENT_CLI_CONFIGS,
|
|
31753
33185
|
AGENT_CONFIG,
|
|
@@ -31835,6 +33267,7 @@ export {
|
|
|
31835
33267
|
LocalProvider,
|
|
31836
33268
|
MARKETPLACE_CACHE_FILE,
|
|
31837
33269
|
MODEL_REGISTRY,
|
|
33270
|
+
ManifestAnalyzer,
|
|
31838
33271
|
MarketplaceAggregator,
|
|
31839
33272
|
MemoryCache,
|
|
31840
33273
|
MemoryCompressor,
|
|
@@ -31880,9 +33313,11 @@ export {
|
|
|
31880
33313
|
SKILL_MATCH_PROMPT,
|
|
31881
33314
|
STANDARD_PLACEHOLDERS,
|
|
31882
33315
|
STEP_HANDLERS,
|
|
33316
|
+
SecretsAnalyzer,
|
|
31883
33317
|
SessionEndHook,
|
|
31884
33318
|
SessionManager,
|
|
31885
33319
|
SessionStartHook,
|
|
33320
|
+
Severity,
|
|
31886
33321
|
Skill,
|
|
31887
33322
|
SkillAnalyzer,
|
|
31888
33323
|
SkillBundle,
|
|
@@ -31895,20 +33330,24 @@ export {
|
|
|
31895
33330
|
SkillMdTranslator,
|
|
31896
33331
|
SkillMerger,
|
|
31897
33332
|
SkillMetadata,
|
|
33333
|
+
SkillScanner,
|
|
31898
33334
|
SkillSummary,
|
|
31899
33335
|
SkillTreeSchema,
|
|
31900
33336
|
SkillTriggerEngine,
|
|
31901
33337
|
SkillWizard,
|
|
31902
33338
|
SkillkitConfig,
|
|
31903
33339
|
SkillsSource,
|
|
33340
|
+
StaticAnalyzer,
|
|
31904
33341
|
TAG_TO_CATEGORY,
|
|
31905
33342
|
TAG_TO_TECH,
|
|
31906
33343
|
TASK_TEMPLATES,
|
|
33344
|
+
THREAT_TAXONOMY,
|
|
31907
33345
|
TREE_FILE_NAME,
|
|
31908
33346
|
TaskManager,
|
|
31909
33347
|
TeamManager,
|
|
31910
33348
|
TeamMessageBus,
|
|
31911
33349
|
TeamOrchestrator,
|
|
33350
|
+
ThreatCategory,
|
|
31912
33351
|
TranslatorRegistry,
|
|
31913
33352
|
TreeGenerator,
|
|
31914
33353
|
TreeNodeSchema,
|
|
@@ -32045,7 +33484,12 @@ export {
|
|
|
32045
33484
|
findSkillsByRelationType,
|
|
32046
33485
|
findSkillsInCategory,
|
|
32047
33486
|
formatBytes,
|
|
33487
|
+
formatJson,
|
|
33488
|
+
formatResult,
|
|
33489
|
+
formatSarif,
|
|
32048
33490
|
formatSkillAsPrompt,
|
|
33491
|
+
formatSummary,
|
|
33492
|
+
formatTable,
|
|
32049
33493
|
fromCanonicalAgent,
|
|
32050
33494
|
fuseWithRRF,
|
|
32051
33495
|
generateComparisonNotes,
|
|
@@ -32077,6 +33521,7 @@ export {
|
|
|
32077
33521
|
getAllPatterns,
|
|
32078
33522
|
getAllProfiles,
|
|
32079
33523
|
getAllProviders,
|
|
33524
|
+
getAllRules,
|
|
32080
33525
|
getAllSkillsDirs,
|
|
32081
33526
|
getApprovedPatterns,
|
|
32082
33527
|
getAvailableCLIAgents,
|
|
@@ -32092,6 +33537,7 @@ export {
|
|
|
32092
33537
|
getDefaultConfigPath,
|
|
32093
33538
|
getDefaultModelDir,
|
|
32094
33539
|
getDefaultProvider,
|
|
33540
|
+
getDefaultSeverity,
|
|
32095
33541
|
getDefaultStorePath,
|
|
32096
33542
|
getEnabledGuidelineContent,
|
|
32097
33543
|
getEnabledGuidelines,
|
|
@@ -32142,6 +33588,7 @@ export {
|
|
|
32142
33588
|
getStepOrder,
|
|
32143
33589
|
getSupportedTranslationAgents,
|
|
32144
33590
|
getTechTags,
|
|
33591
|
+
getThreatInfo,
|
|
32145
33592
|
getTotalSteps,
|
|
32146
33593
|
globalMemoryDirectoryExists,
|
|
32147
33594
|
hybridSearch,
|