Skip to content

Commit b05fe0b

Browse files
authored
fix(security): resolve all gosec, add pre-commit (#22)
1 parent 110bd64 commit b05fe0b

21 files changed

Lines changed: 283 additions & 32 deletions

File tree

.pre-commit-config.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
repos:
2+
# Secret detection — Yelp detect-secrets
3+
- repo: https://github.com/Yelp/detect-secrets
4+
rev: v1.5.0
5+
hooks:
6+
- id: detect-secrets
7+
args:
8+
- --baseline
9+
- .secrets.baseline
10+
11+
- repo: local
12+
hooks:
13+
# go vet + go build run in parallel via background jobs
14+
- id: go-checks
15+
name: go checks (parallel)
16+
language: system
17+
entry: >-
18+
bash -c '
19+
go vet ./... &
20+
pid_vet=$!
21+
22+
tmp=$(mktemp -d)
23+
go build -o "$tmp/createos" . && rm -rf "$tmp" &
24+
pid_build=$!
25+
26+
wait $pid_vet; rc_vet=$?
27+
wait $pid_build; rc_build=$?
28+
29+
[ $rc_vet -ne 0 ] && echo "go vet failed"
30+
[ $rc_build -ne 0 ] && echo "go build failed"
31+
exit $(( rc_vet | rc_build ))
32+
'
33+
pass_filenames: false
34+
types: [go]

.secrets.baseline

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
{
2+
"version": "1.5.0",
3+
"plugins_used": [
4+
{
5+
"name": "ArtifactoryDetector"
6+
},
7+
{
8+
"name": "AWSKeyDetector"
9+
},
10+
{
11+
"name": "AzureStorageKeyDetector"
12+
},
13+
{
14+
"name": "Base64HighEntropyString",
15+
"limit": 4.5
16+
},
17+
{
18+
"name": "BasicAuthDetector"
19+
},
20+
{
21+
"name": "CloudantDetector"
22+
},
23+
{
24+
"name": "DiscordBotTokenDetector"
25+
},
26+
{
27+
"name": "GitHubTokenDetector"
28+
},
29+
{
30+
"name": "GitLabTokenDetector"
31+
},
32+
{
33+
"name": "HexHighEntropyString",
34+
"limit": 3.0
35+
},
36+
{
37+
"name": "IbmCloudIamDetector"
38+
},
39+
{
40+
"name": "IbmCosHmacDetector"
41+
},
42+
{
43+
"name": "IPPublicDetector"
44+
},
45+
{
46+
"name": "JwtTokenDetector"
47+
},
48+
{
49+
"name": "KeywordDetector",
50+
"keyword_exclude": ""
51+
},
52+
{
53+
"name": "MailchimpDetector"
54+
},
55+
{
56+
"name": "NpmDetector"
57+
},
58+
{
59+
"name": "OpenAIDetector"
60+
},
61+
{
62+
"name": "PrivateKeyDetector"
63+
},
64+
{
65+
"name": "PypiTokenDetector"
66+
},
67+
{
68+
"name": "SendGridDetector"
69+
},
70+
{
71+
"name": "SlackDetector"
72+
},
73+
{
74+
"name": "SoftlayerDetector"
75+
},
76+
{
77+
"name": "SquareOAuthDetector"
78+
},
79+
{
80+
"name": "StripeDetector"
81+
},
82+
{
83+
"name": "TelegramBotTokenDetector"
84+
},
85+
{
86+
"name": "TwilioKeyDetector"
87+
}
88+
],
89+
"filters_used": [
90+
{
91+
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
92+
},
93+
{
94+
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
95+
"min_level": 2
96+
},
97+
{
98+
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
99+
},
100+
{
101+
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
102+
},
103+
{
104+
"path": "detect_secrets.filters.heuristic.is_lock_file"
105+
},
106+
{
107+
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
108+
},
109+
{
110+
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
111+
},
112+
{
113+
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
114+
},
115+
{
116+
"path": "detect_secrets.filters.heuristic.is_sequential_string"
117+
},
118+
{
119+
"path": "detect_secrets.filters.heuristic.is_swagger_file"
120+
},
121+
{
122+
"path": "detect_secrets.filters.heuristic.is_templated_secret"
123+
}
124+
],
125+
"results": {
126+
"cmd/env/set.go": [
127+
{
128+
"type": "Secret Keyword",
129+
"filename": "cmd/env/set.go",
130+
"hashed_secret": "ec417f567082612f8fd6afafe1abcab831fca840",
131+
"is_verified": false,
132+
"is_secret": false,
133+
"line_number": 24
134+
}
135+
],
136+
"cmd/oauth/helpers.go": [
137+
{
138+
"type": "Secret Keyword",
139+
"filename": "cmd/oauth/helpers.go",
140+
"hashed_secret": "a587be0a364eab71821821cfc5226eb04853224f",
141+
"is_verified": false,
142+
"is_secret": false,
143+
"line_number": 155
144+
}
145+
],
146+
"internal/api/client.go": [
147+
{
148+
"type": "Secret Keyword",
149+
"filename": "internal/api/client.go",
150+
"hashed_secret": "b19a5a3616bf8b53864ca6162b5f1f6a8c61ab94",
151+
"is_verified": false,
152+
"is_secret": false,
153+
"line_number": 97
154+
}
155+
]
156+
},
157+
"generated_at": "2026-04-02T07:26:00Z"
158+
}

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
golang 1.26.0
1+
golang 1.26.1
22
golangci-lint 2.11.3

CLAUDE.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,29 @@ fmt.Println("You don't have any projects yet.")
116116

117117
Always suggest a next action in empty states where applicable.
118118

119+
## Pre-commit Hooks
120+
121+
The repo uses [pre-commit](https://pre-commit.com) with the following hooks:
122+
123+
| Hook | What it does |
124+
|------|-------------|
125+
| `detect-secrets` | Scans for accidentally committed secrets (Yelp detect-secrets v1.5.0) |
126+
| `go-vet` | Runs `go vet ./...` on changed Go files |
127+
| `go-build-tmp` | Builds the binary to a temp dir and removes it on success |
128+
129+
### First-time setup
130+
131+
```bash
132+
pre-commit install
133+
detect-secrets scan > .secrets.baseline
134+
```
135+
136+
`.secrets.baseline` must be committed. Update it when a false positive is audited:
137+
138+
```bash
139+
detect-secrets audit .secrets.baseline
140+
```
141+
119142
## Adding a New Command Group
120143

121144
1. Create `cmd/<group>/` directory

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,40 @@ brew upgrade createos
5959

6060
### Build from source
6161

62-
Requires Go 1.21+.
62+
Requires Go 1.26+.
6363

6464
```bash
6565
git clone https://github.com/NodeOps-app/createos-cli
6666
cd createos-cli
6767
go build -o createos .
6868
```
6969

70+
## Contributing
71+
72+
### Pre-commit hooks
73+
74+
The repo ships with [pre-commit](https://pre-commit.com) hooks for secret detection and build verification. Set them up once after cloning:
75+
76+
```bash
77+
pip install pre-commit detect-secrets
78+
pre-commit install
79+
detect-secrets scan > .secrets.baseline
80+
```
81+
82+
Hooks that run on every commit:
83+
84+
| Hook | Check |
85+
|------|-------|
86+
| `detect-secrets` | Scans for accidentally committed secrets |
87+
| `go-vet` | Runs `go vet ./...` |
88+
| `go-build-tmp` | Verifies the project builds cleanly |
89+
90+
If `detect-secrets` flags a false positive, audit and update the baseline:
91+
92+
```bash
93+
detect-secrets audit .secrets.baseline
94+
```
95+
7096
## Getting started
7197

7298
**1. Sign in**

cmd/ask/ask.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func installAgent() error {
2626
}
2727

2828
agentsDir := filepath.Join(home, ".opencode", "agents")
29-
if err := os.MkdirAll(agentsDir, 0750); err != nil { //nolint:gosec // user-owned directory
29+
if err := os.MkdirAll(agentsDir, 0750); err != nil {
3030
return fmt.Errorf("could not create agents directory: %w", err)
3131
}
3232

@@ -69,7 +69,7 @@ func NewAskCommand() *cli.Command {
6969
args = []string{"--agent", agentName}
7070
}
7171

72-
cmd := exec.CommandContext(context.Background(), opencodeBin, args...) //nolint:gosec
72+
cmd := exec.CommandContext(context.Background(), opencodeBin, args...) // #nosec G204 -- opencodeBin is from exec.LookPath, args are hardcoded
7373
cmd.Stdin = os.Stdin
7474
cmd.Stdout = os.Stdout
7575
cmd.Stderr = os.Stderr

cmd/deploy/deploy.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ func streamRuntimeLogs(client *api.APIClient, projectID, deploymentID string) {
351351

352352
// loadGitignorePatterns reads .gitignore from srcDir and returns usable patterns.
353353
func loadGitignorePatterns(srcDir string) []string {
354-
data, err := os.ReadFile(filepath.Join(srcDir, ".gitignore")) //nolint:gosec
354+
data, err := os.ReadFile(filepath.Join(srcDir, ".gitignore")) // #nosec G304 -- srcDir is from filepath.Abs, filename is a constant
355355
if err != nil {
356356
return nil
357357
}
@@ -438,7 +438,7 @@ func createZip(w io.Writer, srcDir string) error {
438438
return err
439439
}
440440

441-
f, err := os.Open(path) //nolint:gosec
441+
f, err := os.Open(path) // #nosec G304,G122 -- path comes from filepath.Walk on a local directory
442442
if err != nil {
443443
return err
444444
}

cmd/env/helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func ensureEnvGitignored() {
104104
content = pattern + "\n"
105105
}
106106

107-
f, err := os.OpenFile(gitignore, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) //nolint:gosec
107+
f, err := os.OpenFile(gitignore, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) // #nosec G302 -- .gitignore must be world-readable (0644)
108108
if err != nil {
109109
return
110110
}

cmd/env/push.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func newEnvPushCommand() *cli.Command {
4343
return fmt.Errorf("--file must be a relative path without '..' (got %q)", filePath)
4444
}
4545

46-
data, err := os.ReadFile(filePath) //nolint:gosec
46+
data, err := os.ReadFile(filePath) // #nosec G304 -- filePath is validated above (relative, no ..)
4747
if err != nil {
4848
return fmt.Errorf("could not read %s: %w", filePath, err)
4949
}

cmd/templates/use.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package templates
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"net/http"
@@ -103,11 +104,15 @@ func newTemplatesUseCommand() *cli.Command {
103104
return err
104105
}
105106

106-
if err := os.MkdirAll(absDir, 0750); err != nil { //nolint:gosec
107+
if err := os.MkdirAll(absDir, 0750); err != nil {
107108
return fmt.Errorf("could not create directory %s: %w", dir, err)
108109
}
109110

110-
resp, err := http.Get(downloadURL) //nolint:gosec,noctx
111+
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, downloadURL, nil)
112+
if err != nil {
113+
return fmt.Errorf("could not create download request: %w", err)
114+
}
115+
resp, err := http.DefaultClient.Do(req)
111116
if err != nil {
112117
return fmt.Errorf("could not download template: %w", err)
113118
}
@@ -130,7 +135,7 @@ func newTemplatesUseCommand() *cli.Command {
130135
}
131136

132137
func downloadToFile(path string, src io.Reader) error {
133-
out, err := os.Create(path) //nolint:gosec
138+
out, err := os.Create(path) // #nosec G304 -- path is constructed from filepath.Join(absDir, "template.zip")
134139
if err != nil {
135140
return fmt.Errorf("could not create file: %w", err)
136141
}

0 commit comments

Comments
 (0)