Skip to content

Commit 7c0bfa5

Browse files
committed
feat: release v1.0.0 with security check
1 parent 3698a94 commit 7c0bfa5

File tree

16 files changed

+868
-3
lines changed

16 files changed

+868
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ ask.yaml
3232
homebrew-tap/
3333
dist/
3434
.agent/
35+
.ask/

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.0] - 2026-01-25
9+
10+
### Added
11+
- **Security Checks**: New `ask check` command to scan skills for secrets, dangerous commands, and suspicious files.
12+
- **Security Reports**: Generate detailed security reports in Markdown or HTML with `ask check --report <file>`.
13+
- **Entropy Analysis**: Smart secret detection using Shannon entropy to reduce false positives.
14+
815
## [1.0.0-rc2] - 2026-01-24
916

1017
### Added

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ ask install mcp-builder@v1.0.0
9999

100100
# Install for specific agent
101101
ask install mcp-builder --agent claude
102+
103+
# Security Check
104+
ask check .
105+
ask check anthropics/mcp-builder --report report.html
102106
```
103107

104108
## 📋 Commands
@@ -112,6 +116,7 @@ ask install mcp-builder --agent claude
112116
| `ask skill uninstall <name>` | Remove a skill |
113117
| `ask skill update` | Update skills to latest version |
114118
| `ask skill outdated` | Check for newer versions |
119+
| `ask skill check <path>` | Security scan (Secrets, Dangerous Commands) |
115120

116121
### Repository Management
117122
| Command | Description |

README_zh.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ ask install mcp-builder@v1.0.0
9999

100100
# 为指定 Agent 安装
101101
ask install mcp-builder --agent claude
102+
103+
# 安全检查
104+
ask check .
105+
ask check anthropics/mcp-builder --report report.html
102106
```
103107

104108
## 📋 命令参考
@@ -112,6 +116,7 @@ ask install mcp-builder --agent claude
112116
| `ask uninstall <name>` | 卸载 Skill |
113117
| `ask update` | 更新 Skill 到最新版本 |
114118
| `ask outdated` | 检查可用更新 |
119+
| `ask check <path>` | 安全扫描 (密钥泄漏, 危险命令等) |
115120

116121
### 仓库管理
117122
| 命令 | 说明 |

cmd/check.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/fatih/color"
9+
"github.com/spf13/cobra"
10+
"github.com/yeasy/ask/internal/skill"
11+
)
12+
13+
// checkCmd represents the check command
14+
var checkCmd = &cobra.Command{
15+
Use: "check [skill-path]",
16+
Short: "Check a skill for security issues",
17+
Long: `Analyze a skill directory for potential security risks,
18+
including hardcoded secrets, dangerous commands, and network activity.
19+
20+
If skill-path is not provided, the current directory is checked.`,
21+
Args: cobra.MaximumNArgs(1),
22+
Run: runCheck,
23+
}
24+
25+
var reportFile string
26+
27+
func init() {
28+
checkCmd.Flags().StringVar(&reportFile, "report", "", "Save report to file (supports .md, .html)")
29+
}
30+
31+
func runCheck(cmd *cobra.Command, args []string) {
32+
skillPath := "."
33+
if len(args) > 0 {
34+
skillPath = args[0]
35+
}
36+
37+
absPath, err := filepath.Abs(skillPath)
38+
if err != nil {
39+
fmt.Printf("Error resolving path: %v\n", err)
40+
os.Exit(1)
41+
}
42+
43+
if !skill.FindSkillMD(absPath) {
44+
fmt.Printf("Error: No SKILL.md found in %s. Is this a valid skill directory?\n", absPath)
45+
os.Exit(1)
46+
}
47+
48+
fmt.Printf("Checking skill at %s...\n", absPath)
49+
result, err := skill.CheckSafety(absPath)
50+
if err != nil {
51+
fmt.Printf("Error checking skill: %v\n", err)
52+
os.Exit(1)
53+
}
54+
55+
if reportFile != "" {
56+
handleReport(result, reportFile)
57+
} else {
58+
printReport(result)
59+
}
60+
}
61+
62+
func handleReport(result *skill.CheckResult, filename string) {
63+
ext := filepath.Ext(filename)
64+
format := "md"
65+
if ext == ".html" || ext == ".htm" {
66+
format = "html"
67+
}
68+
69+
content, err := skill.GenerateReport(result, format)
70+
if err != nil {
71+
fmt.Printf("Error generating report: %v\n", err)
72+
os.Exit(1)
73+
}
74+
75+
if err := os.WriteFile(filename, []byte(content), 0644); err != nil {
76+
fmt.Printf("Error writing report to %s: %v\n", filename, err)
77+
os.Exit(1)
78+
}
79+
80+
fmt.Printf("\n%s Security report saved to %s\n", color.GreenString("✓"), filename)
81+
82+
// Print a brief summary even when saving to file
83+
criticals := 0
84+
for _, f := range result.Findings {
85+
if f.Severity == skill.SeverityCritical {
86+
criticals++
87+
}
88+
}
89+
if criticals > 0 {
90+
fmt.Printf("%s Found %d critical issues. Please review the report.\n", color.RedString("!"), criticals)
91+
os.Exit(1)
92+
}
93+
}
94+
95+
func printReport(result *skill.CheckResult) {
96+
fmt.Printf("\nSecurity Report for: %s\n", color.CyanString(result.SkillName))
97+
fmt.Println("----------------------------------------")
98+
99+
if len(result.Findings) == 0 {
100+
color.Green("✓ No issues found. Skill appears safe.\n")
101+
return
102+
}
103+
104+
criticals := 0
105+
warnings := 0
106+
infos := 0
107+
108+
for _, finding := range result.Findings {
109+
switch finding.Severity {
110+
case skill.SeverityCritical:
111+
criticals++
112+
printFinding(color.RedString("[CRITICAL]"), finding)
113+
case skill.SeverityWarning:
114+
warnings++
115+
printFinding(color.YellowString("[WARNING] "), finding)
116+
case skill.SeverityInfo:
117+
infos++
118+
printFinding(color.BlueString("[INFO] "), finding)
119+
}
120+
}
121+
122+
fmt.Println("----------------------------------------")
123+
fmt.Printf("Summary: %d Critical, %d Warning, %d Info\n", criticals, warnings, infos)
124+
125+
if criticals > 0 {
126+
os.Exit(1) // Exit with error if critical issues found
127+
}
128+
}
129+
130+
func printFinding(prefix string, finding skill.Finding) {
131+
fmt.Printf("%s %s\n", prefix, finding.Description)
132+
fmt.Printf(" File: %s:%d\n", finding.File, finding.Line)
133+
fmt.Printf(" Match: %s\n\n", finding.Match)
134+
}

cmd/root.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Skill Commands (ask skill <command>):
2424
info Show detailed skill information
2525
update Update skills to latest versions
2626
outdated Check for available updates
27+
check Check a skill for security issues
2728
create Create a new skill template
2829
2930
Repository Commands (ask repo <command>):
@@ -46,7 +47,7 @@ Codex, etc.) with a familiar CLI experience, just like Homebrew or npm.`,
4647
// Uncomment the following line if your bare application
4748
// has an action associated with it:
4849
// Run: func(cmd *cobra.Command, args []string) { },
49-
Version: "1.0.0-rc2",
50+
Version: "1.0.0",
5051
}
5152

5253
// Top-level aliases (Docker-style)
@@ -84,6 +85,13 @@ var uninstallRootCmd = &cobra.Command{
8485
Run: uninstallCmd.Run,
8586
}
8687

88+
var checkRootCmd = &cobra.Command{
89+
Use: "check [skill-path]",
90+
Short: "Check a skill for security issues (alias for 'skill check')",
91+
Long: "Analyze a skill directory for potential security risks.\nThis is a shortcut for 'ask skill check'.",
92+
Run: runCheck,
93+
}
94+
8795
// Execute adds all child commands to the root command and sets flags appropriately.
8896
// This is called by main.main(). It only needs to happen once to the rootCmd.
8997
func Execute() {
@@ -118,13 +126,15 @@ func init() {
118126
registerListFlags(listRootCmd)
119127

120128
rootCmd.AddCommand(uninstallRootCmd)
129+
rootCmd.AddCommand(checkRootCmd)
121130
// No specific flags to register for uninstall root shim as it uses uninstallCmd.Run directly?
122131
// Actually uninstallCmd.Run uses flags so we should share flags definition or re-register.
123132
// Since uninstallCmd is in another file, we can't easily reuse 'registerUninstallFlags' unless we export it.
124133
// But uninstallCmd is exported. Let's see how registerListFlags works.
125134
// It's likely defined in list.go.
126135
// We should probably just copy flags setup here.
127136
uninstallRootCmd.Flags().AddFlagSet(uninstallCmd.Flags())
137+
checkRootCmd.Flags().StringVar(&reportFile, "report", "", "Save report to file (supports .md, .html)")
128138
}
129139

130140
// initConfig reads in config file and ENV variables if set.

cmd/skill.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ Examples:
2020

2121
func init() {
2222
rootCmd.AddCommand(skillCmd)
23+
skillCmd.AddCommand(checkCmd)
2324
}

docs/commands.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,28 @@ Generate shell completion scripts.
244244
ask completion [bash|zsh|fish|powershell]
245245
```
246246

247+
248+
---
249+
250+
### ask skill check
251+
252+
Check a skill for security issues.
253+
254+
```bash
255+
ask skill check <skill-path> # Check local skill
256+
ask skill check . # Check current directory
257+
ask skill check --report out.html # Generate HTML report
258+
```
259+
260+
**Flags:**
261+
- `--report`: Save detailed findings to a file (`.md` or `.html`).
262+
263+
**What it does:**
264+
- Scans for hardcoded secrets (API keys, tokens)
265+
- specific dangerous commands (`rm -rf`, `sudo`, reverse shells)
266+
- Flags suspicious file extensions (`.exe`, `.dll`, etc.)
267+
- Calculates entropy to reduce false positives
268+
247269
---
248270

249271
## Global Flags

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/yeasy/ask
33
go 1.24.2
44

55
require (
6+
github.com/fatih/color v1.18.0
67
github.com/schollz/progressbar/v3 v3.19.0
78
github.com/spf13/cobra v1.10.2
89
github.com/spf13/viper v1.21.0
@@ -13,6 +14,8 @@ require (
1314
github.com/fsnotify/fsnotify v1.9.0 // indirect
1415
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
1516
github.com/inconshreveable/mousetrap v1.1.0 // indirect
17+
github.com/mattn/go-colorable v0.1.13 // indirect
18+
github.com/mattn/go-isatty v0.0.20 // indirect
1619
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
1720
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
1821
github.com/rivo/uniseg v0.4.7 // indirect

go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0
33
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
44
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
7+
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
68
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
79
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
810
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
@@ -17,6 +19,11 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
1719
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
1820
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
1921
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
22+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
23+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
24+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
25+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
26+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
2027
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
2128
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
2229
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
@@ -53,6 +60,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
5360
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
5461
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
5562
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
63+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
64+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5665
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
5766
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
5867
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=

0 commit comments

Comments
 (0)