ma-agents 3.2.0 → 3.3.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/.opencode/skills/.ma-agents.json +99 -99
- package/.roo/rules/00-ma-agents.md +13 -0
- package/.roo/skills/.ma-agents.json +241 -0
- package/.roo/skills/MANIFEST.yaml +254 -0
- package/.roo/skills/ai-audit-trail/SKILL.md +23 -0
- package/.roo/skills/auto-bug-detection/SKILL.md +169 -0
- package/.roo/skills/cmake-best-practices/SKILL.md +64 -0
- package/.roo/skills/cmake-best-practices/examples/cmake.md +59 -0
- package/.roo/skills/code-documentation/SKILL.md +57 -0
- package/.roo/skills/code-documentation/examples/cpp.md +29 -0
- package/.roo/skills/code-documentation/examples/csharp.md +28 -0
- package/.roo/skills/code-documentation/examples/javascript_typescript.md +28 -0
- package/.roo/skills/code-documentation/examples/python.md +57 -0
- package/.roo/skills/code-review/SKILL.md +43 -0
- package/.roo/skills/commit-message/SKILL.md +79 -0
- package/.roo/skills/cpp-best-practices/SKILL.md +234 -0
- package/.roo/skills/cpp-best-practices/examples/modern-idioms.md +189 -0
- package/.roo/skills/cpp-best-practices/examples/naming-and-organization.md +102 -0
- package/.roo/skills/cpp-concurrency-safety/SKILL.md +60 -0
- package/.roo/skills/cpp-concurrency-safety/examples/concurrency.md +73 -0
- package/.roo/skills/cpp-const-correctness/SKILL.md +63 -0
- package/.roo/skills/cpp-const-correctness/examples/const_correctness.md +54 -0
- package/.roo/skills/cpp-memory-handling/SKILL.md +42 -0
- package/.roo/skills/cpp-memory-handling/examples/modern-cpp.md +49 -0
- package/.roo/skills/cpp-memory-handling/examples/smart-pointers.md +46 -0
- package/.roo/skills/cpp-modern-composition/SKILL.md +64 -0
- package/.roo/skills/cpp-modern-composition/examples/composition.md +51 -0
- package/.roo/skills/cpp-robust-interfaces/SKILL.md +55 -0
- package/.roo/skills/cpp-robust-interfaces/examples/interfaces.md +56 -0
- package/.roo/skills/create-hardened-docker-skill/SKILL.md +637 -0
- package/.roo/skills/create-hardened-docker-skill/scripts/create-all.sh +489 -0
- package/.roo/skills/csharp-best-practices/SKILL.md +278 -0
- package/.roo/skills/docker-hardening-verification/SKILL.md +28 -0
- package/.roo/skills/docker-hardening-verification/scripts/verify-hardening.sh +39 -0
- package/.roo/skills/docker-image-signing/SKILL.md +28 -0
- package/.roo/skills/docker-image-signing/scripts/sign-image.sh +33 -0
- package/.roo/skills/document-revision-history/SKILL.md +104 -0
- package/.roo/skills/git-workflow-skill/SKILL.md +194 -0
- package/.roo/skills/git-workflow-skill/hooks/commit-msg +61 -0
- package/.roo/skills/git-workflow-skill/hooks/pre-commit +38 -0
- package/.roo/skills/git-workflow-skill/hooks/prepare-commit-msg +56 -0
- package/.roo/skills/git-workflow-skill/scripts/finish-feature.sh +192 -0
- package/.roo/skills/git-workflow-skill/scripts/install-hooks.sh +55 -0
- package/.roo/skills/git-workflow-skill/scripts/start-feature.sh +110 -0
- package/.roo/skills/git-workflow-skill/scripts/validate-workflow.sh +229 -0
- package/.roo/skills/js-ts-dependency-mgmt/SKILL.md +49 -0
- package/.roo/skills/js-ts-dependency-mgmt/examples/dependency_mgmt.md +60 -0
- package/.roo/skills/js-ts-security-skill/SKILL.md +64 -0
- package/.roo/skills/js-ts-security-skill/scripts/verify-security.sh +136 -0
- package/.roo/skills/logging-best-practices/SKILL.md +50 -0
- package/.roo/skills/logging-best-practices/examples/cpp.md +36 -0
- package/.roo/skills/logging-best-practices/examples/csharp.md +49 -0
- package/.roo/skills/logging-best-practices/examples/javascript.md +77 -0
- package/.roo/skills/logging-best-practices/examples/python.md +57 -0
- package/.roo/skills/logging-best-practices/references/logging-standards.md +29 -0
- package/.roo/skills/open-presentation/SKILL.md +35 -0
- package/.roo/skills/opentelemetry-best-practices/SKILL.md +34 -0
- package/.roo/skills/opentelemetry-best-practices/examples/go.md +32 -0
- package/.roo/skills/opentelemetry-best-practices/examples/javascript.md +58 -0
- package/.roo/skills/opentelemetry-best-practices/examples/python.md +37 -0
- package/.roo/skills/opentelemetry-best-practices/references/otel-standards.md +37 -0
- package/.roo/skills/python-best-practices/SKILL.md +385 -0
- package/.roo/skills/python-dependency-mgmt/SKILL.md +42 -0
- package/.roo/skills/python-dependency-mgmt/examples/dependency_mgmt.md +67 -0
- package/.roo/skills/python-security-skill/SKILL.md +56 -0
- package/.roo/skills/python-security-skill/examples/security.md +56 -0
- package/.roo/skills/self-signed-cert/SKILL.md +42 -0
- package/.roo/skills/self-signed-cert/scripts/generate-cert.ps1 +45 -0
- package/.roo/skills/self-signed-cert/scripts/generate-cert.sh +43 -0
- package/.roo/skills/skill-creator/SKILL.md +196 -0
- package/.roo/skills/skill-creator/references/output-patterns.md +82 -0
- package/.roo/skills/skill-creator/references/workflows.md +28 -0
- package/.roo/skills/skill-creator/scripts/init_skill.py +208 -0
- package/.roo/skills/skill-creator/scripts/package_skill.py +99 -0
- package/.roo/skills/skill-creator/scripts/quick_validate.py +113 -0
- package/.roo/skills/story-status-lookup/SKILL.md +78 -0
- package/.roo/skills/test-accompanied-development/SKILL.md +50 -0
- package/.roo/skills/test-generator/SKILL.md +65 -0
- package/.roo/skills/vercel-react-best-practices/SKILL.md +109 -0
- package/.roo/skills/verify-hardened-docker-skill/SKILL.md +442 -0
- package/.roo/skills/verify-hardened-docker-skill/scripts/verify-docker-hardening.sh +439 -0
- package/README.md +2 -1
- package/lib/agents.js +23 -0
- package/package.json +4 -3
- package/test/roo-code-agent.test.js +166 -0
- package/test/roo-code-injection.test.js +172 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# verify-docker-hardening.sh
|
|
4
|
+
# Comprehensive Docker security verification script
|
|
5
|
+
# Checks Dockerfile, docker-compose.yml, and running containers
|
|
6
|
+
# against CIS, OWASP, and NIST standards
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
# Colors for output
|
|
12
|
+
RED='\033[0;31m'
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
BLUE='\033[0;34m'
|
|
16
|
+
NC='\033[0m' # No Color
|
|
17
|
+
|
|
18
|
+
# Configuration
|
|
19
|
+
IMAGE_NAME="${1:-contacts-app}"
|
|
20
|
+
CONTAINER_NAME="${2:-contacts-app}"
|
|
21
|
+
EXIT_CODE=0
|
|
22
|
+
|
|
23
|
+
# Counters
|
|
24
|
+
TOTAL_CHECKS=0
|
|
25
|
+
PASSED_CHECKS=0
|
|
26
|
+
FAILED_CHECKS=0
|
|
27
|
+
WARNING_CHECKS=0
|
|
28
|
+
|
|
29
|
+
# Helper functions
|
|
30
|
+
print_header() {
|
|
31
|
+
echo ""
|
|
32
|
+
echo -e "${BLUE}========================================${NC}"
|
|
33
|
+
echo -e "${BLUE}$1${NC}"
|
|
34
|
+
echo -e "${BLUE}========================================${NC}"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
print_check() {
|
|
38
|
+
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
|
|
39
|
+
echo -ne " [$TOTAL_CHECKS] $1... "
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pass() {
|
|
43
|
+
PASSED_CHECKS=$((PASSED_CHECKS + 1))
|
|
44
|
+
echo -e "${GREEN}✅ PASS${NC}"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fail() {
|
|
48
|
+
FAILED_CHECKS=$((FAILED_CHECKS + 1))
|
|
49
|
+
EXIT_CODE=2
|
|
50
|
+
echo -e "${RED}❌ FAIL${NC}"
|
|
51
|
+
[ -n "$1" ] && echo -e " ${RED}→ $1${NC}"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
warn() {
|
|
55
|
+
WARNING_CHECKS=$((WARNING_CHECKS + 1))
|
|
56
|
+
echo -e "${YELLOW}⚠️ WARN${NC}"
|
|
57
|
+
[ -n "$1" ] && echo -e " ${YELLOW}→ $1${NC}"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
critical() {
|
|
61
|
+
FAILED_CHECKS=$((FAILED_CHECKS + 1))
|
|
62
|
+
EXIT_CODE=1
|
|
63
|
+
echo -e "${RED}🚨 CRITICAL${NC}"
|
|
64
|
+
[ -n "$1" ] && echo -e " ${RED}→ $1${NC}"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Start verification
|
|
68
|
+
echo -e "${BLUE}🔍 Docker Security Verification${NC}"
|
|
69
|
+
echo -e "${BLUE}================================${NC}"
|
|
70
|
+
echo "Image: $IMAGE_NAME"
|
|
71
|
+
echo "Container: $CONTAINER_NAME"
|
|
72
|
+
|
|
73
|
+
# ============================================================================
|
|
74
|
+
# 1. Dockerfile Verification
|
|
75
|
+
# ============================================================================
|
|
76
|
+
print_header "1. Dockerfile Security"
|
|
77
|
+
|
|
78
|
+
if [ ! -f "Dockerfile" ]; then
|
|
79
|
+
print_check "Dockerfile exists"
|
|
80
|
+
critical "Dockerfile not found in current directory"
|
|
81
|
+
EXIT_CODE=5
|
|
82
|
+
exit $EXIT_CODE
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Check for specific version tags
|
|
86
|
+
print_check "Specific version tags (no :latest)"
|
|
87
|
+
if grep -qE "^FROM.*:(latest|alpine)$" Dockerfile; then
|
|
88
|
+
fail "Using :latest or unversioned :alpine tag"
|
|
89
|
+
else
|
|
90
|
+
pass
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Check for non-root user
|
|
94
|
+
print_check "Non-root user configured"
|
|
95
|
+
if grep -qE "^USER (root|[0-9]+)" Dockerfile; then
|
|
96
|
+
if grep -qE "^USER root" Dockerfile; then
|
|
97
|
+
fail "Running as root user"
|
|
98
|
+
else
|
|
99
|
+
pass
|
|
100
|
+
fi
|
|
101
|
+
elif grep -qE "^USER [a-zA-Z]" Dockerfile; then
|
|
102
|
+
pass
|
|
103
|
+
else
|
|
104
|
+
fail "No USER directive found"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Check for HEALTHCHECK
|
|
108
|
+
print_check "HEALTHCHECK instruction"
|
|
109
|
+
if grep -q "^HEALTHCHECK" Dockerfile; then
|
|
110
|
+
pass
|
|
111
|
+
else
|
|
112
|
+
warn "Missing HEALTHCHECK instruction"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Check for hardcoded secrets
|
|
116
|
+
print_check "No hardcoded secrets in ENV/ARG"
|
|
117
|
+
if grep -iE "^(ENV|ARG).*(SECRET|PASSWORD|KEY|TOKEN|CLIENT_ID)=" Dockerfile | grep -v "REACT_APP_API_BASE_URL" > /dev/null; then
|
|
118
|
+
fail "Potential hardcoded secrets found"
|
|
119
|
+
else
|
|
120
|
+
pass
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Check for multi-stage build
|
|
124
|
+
print_check "Multi-stage build pattern"
|
|
125
|
+
if [ $(grep -c "^FROM" Dockerfile) -ge 2 ]; then
|
|
126
|
+
pass
|
|
127
|
+
else
|
|
128
|
+
warn "Not using multi-stage build"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Check for Alpine base images
|
|
132
|
+
print_check "Minimal Alpine base images"
|
|
133
|
+
if grep -qE "^FROM.*alpine" Dockerfile; then
|
|
134
|
+
pass
|
|
135
|
+
else
|
|
136
|
+
warn "Not using Alpine base images"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# ============================================================================
|
|
140
|
+
# 2. docker-compose.yml Verification
|
|
141
|
+
# ============================================================================
|
|
142
|
+
print_header "2. docker-compose.yml Security"
|
|
143
|
+
|
|
144
|
+
if [ ! -f "docker-compose.yml" ]; then
|
|
145
|
+
print_check "docker-compose.yml exists"
|
|
146
|
+
warn "docker-compose.yml not found (optional)"
|
|
147
|
+
else
|
|
148
|
+
# Check for read-only filesystem
|
|
149
|
+
print_check "Read-only root filesystem"
|
|
150
|
+
if grep -q "read_only: true" docker-compose.yml; then
|
|
151
|
+
pass
|
|
152
|
+
else
|
|
153
|
+
fail "Missing read_only: true"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# Check for no-new-privileges
|
|
157
|
+
print_check "No new privileges"
|
|
158
|
+
if grep -q "no-new-privileges:true" docker-compose.yml; then
|
|
159
|
+
pass
|
|
160
|
+
else
|
|
161
|
+
fail "Missing security_opt: no-new-privileges:true"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Check for capability dropping
|
|
165
|
+
print_check "Capabilities dropped"
|
|
166
|
+
if grep -q "cap_drop:" docker-compose.yml; then
|
|
167
|
+
pass
|
|
168
|
+
else
|
|
169
|
+
fail "Missing cap_drop configuration"
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# Check for tmpfs mounts
|
|
173
|
+
print_check "Tmpfs mounts for writable dirs"
|
|
174
|
+
if grep -q "tmpfs:" docker-compose.yml; then
|
|
175
|
+
pass
|
|
176
|
+
else
|
|
177
|
+
fail "Missing tmpfs mounts"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Check for resource limits
|
|
181
|
+
print_check "Memory limits configured"
|
|
182
|
+
if grep -q "memory:" docker-compose.yml; then
|
|
183
|
+
pass
|
|
184
|
+
else
|
|
185
|
+
warn "Missing memory limits"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
print_check "CPU limits configured"
|
|
189
|
+
if grep -q "cpus:" docker-compose.yml; then
|
|
190
|
+
pass
|
|
191
|
+
else
|
|
192
|
+
warn "Missing CPU limits"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# Check for health check
|
|
196
|
+
print_check "Healthcheck configured"
|
|
197
|
+
if grep -q "healthcheck:" docker-compose.yml; then
|
|
198
|
+
pass
|
|
199
|
+
else
|
|
200
|
+
warn "Missing healthcheck configuration"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Check for privileged mode
|
|
204
|
+
print_check "Not running in privileged mode"
|
|
205
|
+
if grep -q "privileged: true" docker-compose.yml; then
|
|
206
|
+
critical "Running in privileged mode"
|
|
207
|
+
else
|
|
208
|
+
pass
|
|
209
|
+
fi
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# ============================================================================
|
|
213
|
+
# 3. .dockerignore Verification
|
|
214
|
+
# ============================================================================
|
|
215
|
+
print_header "3. .dockerignore Configuration"
|
|
216
|
+
|
|
217
|
+
if [ ! -f ".dockerignore" ]; then
|
|
218
|
+
print_check ".dockerignore exists"
|
|
219
|
+
warn ".dockerignore not found"
|
|
220
|
+
else
|
|
221
|
+
print_check ".env excluded from Docker context"
|
|
222
|
+
if grep -qE "^\.env$" .dockerignore; then
|
|
223
|
+
pass
|
|
224
|
+
else
|
|
225
|
+
critical ".env not in .dockerignore - secret leakage risk!"
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
print_check "node_modules excluded"
|
|
229
|
+
if grep -q "node_modules" .dockerignore; then
|
|
230
|
+
pass
|
|
231
|
+
else
|
|
232
|
+
warn "node_modules not excluded"
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
print_check ".git excluded"
|
|
236
|
+
if grep -qE "^\.git" .dockerignore; then
|
|
237
|
+
pass
|
|
238
|
+
else
|
|
239
|
+
warn ".git not excluded"
|
|
240
|
+
fi
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
# ============================================================================
|
|
244
|
+
# 4. Image Security Scanning
|
|
245
|
+
# ============================================================================
|
|
246
|
+
print_header "4. Image Vulnerability Scanning"
|
|
247
|
+
|
|
248
|
+
# Check if image exists
|
|
249
|
+
if ! docker images --format "{{.Repository}}" | grep -q "^${IMAGE_NAME}$"; then
|
|
250
|
+
print_check "Docker image exists"
|
|
251
|
+
warn "Image '$IMAGE_NAME' not found locally. Skipping image scans."
|
|
252
|
+
echo " Run 'docker build -t $IMAGE_NAME .' to build the image."
|
|
253
|
+
else
|
|
254
|
+
# Check if trivy is installed
|
|
255
|
+
if ! command -v trivy &> /dev/null; then
|
|
256
|
+
print_check "Trivy scanner installed"
|
|
257
|
+
warn "Trivy not installed. Skipping vulnerability scans."
|
|
258
|
+
echo " Install: brew install aquasecurity/trivy/trivy (macOS)"
|
|
259
|
+
echo " : apt-get install trivy (Linux)"
|
|
260
|
+
else
|
|
261
|
+
# Scan for CRITICAL vulnerabilities
|
|
262
|
+
print_check "No CRITICAL vulnerabilities"
|
|
263
|
+
if trivy image --quiet --severity CRITICAL --exit-code 1 "$IMAGE_NAME" > /dev/null 2>&1; then
|
|
264
|
+
pass
|
|
265
|
+
else
|
|
266
|
+
critical "CRITICAL vulnerabilities found. Run: trivy image --severity CRITICAL $IMAGE_NAME"
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
# Scan for HIGH vulnerabilities
|
|
270
|
+
print_check "No HIGH vulnerabilities"
|
|
271
|
+
if trivy image --quiet --severity HIGH --exit-code 1 "$IMAGE_NAME" > /dev/null 2>&1; then
|
|
272
|
+
pass
|
|
273
|
+
else
|
|
274
|
+
fail "HIGH vulnerabilities found. Run: trivy image --severity HIGH $IMAGE_NAME"
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Scan for leaked secrets
|
|
278
|
+
print_check "No leaked secrets in image"
|
|
279
|
+
if trivy image --quiet --scanners secret --exit-code 1 "$IMAGE_NAME" > /dev/null 2>&1; then
|
|
280
|
+
pass
|
|
281
|
+
else
|
|
282
|
+
critical "Secrets detected in image! Run: trivy image --scanners secret $IMAGE_NAME"
|
|
283
|
+
fi
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
# Check image size
|
|
287
|
+
print_check "Optimized image size (< 100MB)"
|
|
288
|
+
IMAGE_SIZE=$(docker images --format "{{.Size}}" "$IMAGE_NAME" | head -1 | sed 's/MB//' | sed 's/GB/*1024/' | bc 2>/dev/null || echo "0")
|
|
289
|
+
if [ -n "$IMAGE_SIZE" ] && [ "$IMAGE_SIZE" != "0" ]; then
|
|
290
|
+
if (( $(echo "$IMAGE_SIZE < 100" | bc -l) )); then
|
|
291
|
+
pass
|
|
292
|
+
else
|
|
293
|
+
warn "Image size is ${IMAGE_SIZE}MB (recommended < 100MB)"
|
|
294
|
+
fi
|
|
295
|
+
else
|
|
296
|
+
warn "Could not determine image size"
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# Check for .env in image
|
|
300
|
+
print_check ".env file not baked into image"
|
|
301
|
+
if docker run --rm "$IMAGE_NAME" sh -c "ls -la / 2>/dev/null | grep -q .env"; then
|
|
302
|
+
critical ".env file found in image! Secrets leaked!"
|
|
303
|
+
else
|
|
304
|
+
pass
|
|
305
|
+
fi
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
# ============================================================================
|
|
309
|
+
# 5. Runtime Security (if container is running)
|
|
310
|
+
# ============================================================================
|
|
311
|
+
print_header "5. Runtime Security Verification"
|
|
312
|
+
|
|
313
|
+
if ! docker ps --filter "name=$CONTAINER_NAME" --format "{{.Names}}" | grep -q "^$CONTAINER_NAME$"; then
|
|
314
|
+
echo -e "${YELLOW} Container '$CONTAINER_NAME' is not running.${NC}"
|
|
315
|
+
echo -e "${YELLOW} Run 'docker-compose up -d' to enable runtime checks.${NC}"
|
|
316
|
+
else
|
|
317
|
+
# Check container runs as non-root
|
|
318
|
+
print_check "Container runs as non-root"
|
|
319
|
+
CONTAINER_USER=$(docker exec "$CONTAINER_NAME" whoami 2>/dev/null || echo "root")
|
|
320
|
+
if [ "$CONTAINER_USER" != "root" ]; then
|
|
321
|
+
pass
|
|
322
|
+
else
|
|
323
|
+
critical "Container running as root user!"
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# Check user ID is not 0
|
|
327
|
+
print_check "User ID is not 0 (root)"
|
|
328
|
+
USER_ID=$(docker exec "$CONTAINER_NAME" id -u 2>/dev/null || echo "0")
|
|
329
|
+
if [ "$USER_ID" != "0" ]; then
|
|
330
|
+
pass
|
|
331
|
+
else
|
|
332
|
+
critical "Container running with UID 0 (root)!"
|
|
333
|
+
fi
|
|
334
|
+
|
|
335
|
+
# Check read-only filesystem
|
|
336
|
+
print_check "Root filesystem is read-only"
|
|
337
|
+
if docker exec "$CONTAINER_NAME" sh -c "touch /test 2>/dev/null"; then
|
|
338
|
+
fail "Root filesystem is writable"
|
|
339
|
+
docker exec "$CONTAINER_NAME" sh -c "rm /test 2>/dev/null" || true
|
|
340
|
+
else
|
|
341
|
+
pass
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
# Check tmpfs is writable
|
|
345
|
+
print_check "Tmpfs mount is writable"
|
|
346
|
+
if docker exec "$CONTAINER_NAME" sh -c "touch /tmp/test 2>/dev/null && rm /tmp/test 2>/dev/null"; then
|
|
347
|
+
pass
|
|
348
|
+
else
|
|
349
|
+
warn "Tmpfs mount /tmp is not writable"
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
# Check health status
|
|
353
|
+
print_check "Container is healthy"
|
|
354
|
+
HEALTH_STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "none")
|
|
355
|
+
if [ "$HEALTH_STATUS" = "healthy" ]; then
|
|
356
|
+
pass
|
|
357
|
+
elif [ "$HEALTH_STATUS" = "none" ]; then
|
|
358
|
+
warn "No health check configured"
|
|
359
|
+
else
|
|
360
|
+
fail "Container health status: $HEALTH_STATUS"
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
# Check capabilities
|
|
364
|
+
print_check "Capabilities dropped"
|
|
365
|
+
CAPS_DROPPED=$(docker inspect --format='{{.HostConfig.CapDrop}}' "$CONTAINER_NAME" 2>/dev/null || echo "[]")
|
|
366
|
+
if echo "$CAPS_DROPPED" | grep -q "ALL"; then
|
|
367
|
+
pass
|
|
368
|
+
else
|
|
369
|
+
warn "Not all capabilities dropped"
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
# Check memory limit
|
|
373
|
+
print_check "Memory limit enforced"
|
|
374
|
+
MEMORY_LIMIT=$(docker inspect --format='{{.HostConfig.Memory}}' "$CONTAINER_NAME" 2>/dev/null || echo "0")
|
|
375
|
+
if [ "$MEMORY_LIMIT" != "0" ]; then
|
|
376
|
+
pass
|
|
377
|
+
else
|
|
378
|
+
warn "No memory limit set"
|
|
379
|
+
fi
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
# ============================================================================
|
|
383
|
+
# 6. Git Secret Protection
|
|
384
|
+
# ============================================================================
|
|
385
|
+
print_header "6. Git Secret Protection"
|
|
386
|
+
|
|
387
|
+
if [ -d ".git" ]; then
|
|
388
|
+
# Check .env in .gitignore
|
|
389
|
+
print_check ".env in .gitignore"
|
|
390
|
+
if [ -f ".gitignore" ] && grep -qE "^\.env$" .gitignore; then
|
|
391
|
+
pass
|
|
392
|
+
else
|
|
393
|
+
critical ".env not in .gitignore! Secrets may be committed!"
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
# Check .env.example exists
|
|
397
|
+
print_check ".env.example exists (template)"
|
|
398
|
+
if [ -f ".env.example" ]; then
|
|
399
|
+
pass
|
|
400
|
+
else
|
|
401
|
+
warn ".env.example not found"
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
# Check if .env is committed
|
|
405
|
+
print_check ".env not committed to git"
|
|
406
|
+
if git ls-files --error-unmatch .env > /dev/null 2>&1; then
|
|
407
|
+
critical ".env is tracked by git! Remove immediately!"
|
|
408
|
+
else
|
|
409
|
+
pass
|
|
410
|
+
fi
|
|
411
|
+
else
|
|
412
|
+
echo -e "${YELLOW} Not a git repository. Skipping git checks.${NC}"
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
# ============================================================================
|
|
416
|
+
# Summary
|
|
417
|
+
# ============================================================================
|
|
418
|
+
print_header "Verification Summary"
|
|
419
|
+
|
|
420
|
+
echo ""
|
|
421
|
+
echo " Total checks: $TOTAL_CHECKS"
|
|
422
|
+
echo -e " ${GREEN}Passed: $PASSED_CHECKS${NC}"
|
|
423
|
+
echo -e " ${YELLOW}Warnings: $WARNING_CHECKS${NC}"
|
|
424
|
+
echo -e " ${RED}Failed: $FAILED_CHECKS${NC}"
|
|
425
|
+
echo ""
|
|
426
|
+
|
|
427
|
+
if [ $FAILED_CHECKS -eq 0 ]; then
|
|
428
|
+
echo -e "${GREEN}✅ All critical security checks passed!${NC}"
|
|
429
|
+
if [ $WARNING_CHECKS -gt 0 ]; then
|
|
430
|
+
echo -e "${YELLOW}⚠️ $WARNING_CHECKS warning(s) - consider addressing these.${NC}"
|
|
431
|
+
fi
|
|
432
|
+
else
|
|
433
|
+
echo -e "${RED}❌ $FAILED_CHECKS security check(s) failed!${NC}"
|
|
434
|
+
echo -e "${RED} Please fix the issues above before deploying.${NC}"
|
|
435
|
+
fi
|
|
436
|
+
|
|
437
|
+
echo ""
|
|
438
|
+
|
|
439
|
+
exit $EXIT_CODE
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ma-agents
|
|
2
2
|
|
|
3
|
-
A universal NPX tool to install AI coding agent skills. Write skills once, install them across Claude Code, Gemini, Copilot, Cline, Cursor, and
|
|
3
|
+
A universal NPX tool to install AI coding agent skills. Write skills once, install them across Claude Code, Gemini, Copilot, Cline, Cursor, Kilocode, and Roo Code.
|
|
4
4
|
|
|
5
5
|
## Installation & Usage
|
|
6
6
|
|
|
@@ -115,6 +115,7 @@ The file is version-controlled as part of `_bmad-output/` project knowledge. Com
|
|
|
115
115
|
| Cursor | `.cursor/skills/` | `generic` | `.cursor/cursor.md` |
|
|
116
116
|
| Kilocode | `.kilocode/skills/` | `generic` | `.kilocode/kilocode.md` |
|
|
117
117
|
| Cline | `.cline/skills/` | `cline` | `.cline/clinerules.md` |
|
|
118
|
+
| Roo Code | `.roo/skills/` | `generic` | `.roo/rules/00-ma-agents.md` |
|
|
118
119
|
| SRE Agent (Alex) | `_bmad/skills/` | `generic` | `_bmad/bmm/agents/sre.md` |
|
|
119
120
|
| DevOps Agent (Amit) | `_bmad/skills/` | `generic` | `_bmad/bmm/agents/devops.md` |
|
|
120
121
|
| Cyber Analyst (Yael) | `_bmad/skills/` | `generic` | `_bmad/bmm/agents/cyber.md` |
|
package/lib/agents.js
CHANGED
|
@@ -135,6 +135,29 @@ const agents = [
|
|
|
135
135
|
instructionFiles: ['.cline/clinerules.md', '.clinerules'],
|
|
136
136
|
injectionStrategy: { position: 'top', skipPatterns: ['---'] }
|
|
137
137
|
},
|
|
138
|
+
{
|
|
139
|
+
id: 'roo-code',
|
|
140
|
+
name: 'Roo Code',
|
|
141
|
+
version: '1.0.0',
|
|
142
|
+
category: 'ide',
|
|
143
|
+
description: 'Roo Code AI Assistant (Enhanced Cline fork)',
|
|
144
|
+
skillsDir: '.roo/skills',
|
|
145
|
+
getProjectPath: () => path.join(process.cwd(), '.roo', 'skills'),
|
|
146
|
+
getGlobalPath: () => {
|
|
147
|
+
const platform = os.platform();
|
|
148
|
+
if (platform === 'win32') {
|
|
149
|
+
return path.join(os.homedir(), 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'skills');
|
|
150
|
+
} else if (platform === 'darwin') {
|
|
151
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'skills');
|
|
152
|
+
} else {
|
|
153
|
+
return path.join(os.homedir(), '.config', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'skills');
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
fileExtension: '.md',
|
|
157
|
+
template: 'generic',
|
|
158
|
+
instructionFiles: ['.roo/rules/00-ma-agents.md'],
|
|
159
|
+
injectionStrategy: { position: 'top', skipPatterns: ['---'] }
|
|
160
|
+
},
|
|
138
161
|
{
|
|
139
162
|
id: 'cursor',
|
|
140
163
|
name: 'Cursor',
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ma-agents",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor)",
|
|
3
|
+
"version": "3.3.0",
|
|
4
|
+
"description": "NPX tool to install skills for AI coding agents (Claude Code, Gemini, Copilot, Kilocode, Cline, Cursor, Roo Code)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ma-agents": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/cli.js",
|
|
11
|
-
"test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/bmad-version-bump.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js",
|
|
11
|
+
"test": "node test/yes-flag.test.js && node test/skill-authoring.test.js && node test/skill-validation.test.js && node test/skill-mandatory.test.js && node test/skill-customize-agent.test.js && node test/create-agent.test.js && node test/generate-project-context.test.js && node test/build-bmad-args.test.js && node test/bmad-version-bump.test.js && node test/extension-module-restructure.test.js && node test/convert-agents-to-skills.test.js && node test/migration.test.js && node test/migration-validation.test.js && node test/story-15-5-workflow-skills.test.js && node test/repo-layout.test.js && node test/config-storage.test.js && node test/cross-repo-validation.test.js && node test/config-lost-on-update.test.js && node test/portable-paths.test.js && node test/cicd-remote-mode.test.js && node test/config-layout.test.js && node test/roo-code-agent.test.js && node test/roo-code-injection.test.js",
|
|
12
12
|
"build:bmad-cache": "node scripts/build-bmad-cache.js"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"kilocode",
|
|
22
22
|
"cline",
|
|
23
23
|
"cursor",
|
|
24
|
+
"roo-code",
|
|
24
25
|
"npx"
|
|
25
26
|
],
|
|
26
27
|
"author": "",
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Tests for Story 18.1: Register Roo Code Agent in Registry
|
|
4
|
+
*
|
|
5
|
+
* Validates that the roo-code agent entry conforms to the required schema
|
|
6
|
+
* and resolves correctly through the existing config-driven registry pattern.
|
|
7
|
+
*/
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const assert = require('assert');
|
|
11
|
+
|
|
12
|
+
let passed = 0;
|
|
13
|
+
let failed = 0;
|
|
14
|
+
const errors = [];
|
|
15
|
+
|
|
16
|
+
function test(name, fn) {
|
|
17
|
+
try {
|
|
18
|
+
fn();
|
|
19
|
+
console.log(` \u2713 ${name}`);
|
|
20
|
+
passed++;
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(` \u2717 ${name}: ${err.message}`);
|
|
23
|
+
failed++;
|
|
24
|
+
errors.push({ name, error: err.message });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { getAllAgents, getAgent, getAgentsByCategory } = require('../lib/agents');
|
|
29
|
+
const allAgents = getAllAgents();
|
|
30
|
+
const rooCode = getAgent('roo-code');
|
|
31
|
+
|
|
32
|
+
// --- Schema validation ---
|
|
33
|
+
|
|
34
|
+
console.log('\nRoo Code agent schema validation');
|
|
35
|
+
|
|
36
|
+
test('roo-code agent exists in registry', () => {
|
|
37
|
+
assert.ok(rooCode, 'roo-code agent should exist in the registry');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('id is "roo-code"', () => {
|
|
41
|
+
assert.strictEqual(rooCode.id, 'roo-code');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('name is "Roo Code"', () => {
|
|
45
|
+
assert.strictEqual(rooCode.name, 'Roo Code');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('category is "ide"', () => {
|
|
49
|
+
assert.strictEqual(rooCode.category, 'ide');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('getProjectPath is a function', () => {
|
|
53
|
+
assert.strictEqual(typeof rooCode.getProjectPath, 'function',
|
|
54
|
+
'getProjectPath should be a function');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('getGlobalPath is a function', () => {
|
|
58
|
+
assert.strictEqual(typeof rooCode.getGlobalPath, 'function',
|
|
59
|
+
'getGlobalPath should be a function');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('getProjectPath returns path ending in .roo/skills', () => {
|
|
63
|
+
const projectPath = rooCode.getProjectPath();
|
|
64
|
+
assert.ok(
|
|
65
|
+
projectPath.endsWith('.roo/skills') || projectPath.endsWith('.roo\\skills'),
|
|
66
|
+
`Expected path ending in .roo/skills, got: ${projectPath}`
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('getGlobalPath returns a non-empty string', () => {
|
|
71
|
+
const globalPath = rooCode.getGlobalPath();
|
|
72
|
+
assert.strictEqual(typeof globalPath, 'string');
|
|
73
|
+
assert.ok(globalPath.length > 0, 'getGlobalPath should return a non-empty string');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('getGlobalPath contains roo-cline (VS Code extension storage convention)', () => {
|
|
77
|
+
const globalPath = rooCode.getGlobalPath();
|
|
78
|
+
assert.ok(
|
|
79
|
+
globalPath.includes('roo-cline'),
|
|
80
|
+
`Expected global path to contain 'roo-cline', got: ${globalPath}`
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('fileExtension is ".md"', () => {
|
|
85
|
+
assert.strictEqual(rooCode.fileExtension, '.md');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('description is a non-empty string', () => {
|
|
89
|
+
assert.strictEqual(typeof rooCode.description, 'string',
|
|
90
|
+
'description should be a string');
|
|
91
|
+
assert.ok(rooCode.description.length > 0, 'description should not be empty');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('description mentions Cline fork lineage', () => {
|
|
95
|
+
assert.ok(rooCode.description.includes('Cline'),
|
|
96
|
+
`description should mention Cline fork lineage, got: ${rooCode.description}`);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('template is "generic"', () => {
|
|
100
|
+
assert.strictEqual(rooCode.template, 'generic');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('skillsDir is ".roo/skills"', () => {
|
|
104
|
+
assert.strictEqual(rooCode.skillsDir, '.roo/skills');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('instructionFiles is a non-empty array', () => {
|
|
108
|
+
assert.ok(Array.isArray(rooCode.instructionFiles),
|
|
109
|
+
'instructionFiles should be an array');
|
|
110
|
+
assert.ok(rooCode.instructionFiles.length > 0,
|
|
111
|
+
'instructionFiles should not be empty');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('instructionFiles targets .roo/rules/ directory', () => {
|
|
115
|
+
const hasRooRules = rooCode.instructionFiles.some(f =>
|
|
116
|
+
f.includes('.roo/rules/') || f.includes('.roo\\rules\\')
|
|
117
|
+
);
|
|
118
|
+
assert.ok(hasRooRules,
|
|
119
|
+
`instructionFiles should target .roo/rules/ directory, got: ${rooCode.instructionFiles}`);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('injectionStrategy is an object', () => {
|
|
123
|
+
assert.ok(rooCode.injectionStrategy && typeof rooCode.injectionStrategy === 'object',
|
|
124
|
+
'injectionStrategy should be an object');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('injectionStrategy.position is "top"', () => {
|
|
128
|
+
assert.strictEqual(rooCode.injectionStrategy.position, 'top');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// --- Registry resolution ---
|
|
132
|
+
|
|
133
|
+
console.log('\nRoo Code agent resolution');
|
|
134
|
+
|
|
135
|
+
test('getAgent("roo-code") returns the agent', () => {
|
|
136
|
+
const resolved = getAgent('roo-code');
|
|
137
|
+
assert.ok(resolved, 'getAgent("roo-code") should return an agent object');
|
|
138
|
+
assert.strictEqual(resolved.id, 'roo-code');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('roo-code appears in getAllAgents()', () => {
|
|
142
|
+
const ids = allAgents.map(a => a.id);
|
|
143
|
+
assert.ok(ids.includes('roo-code'), 'roo-code should appear in getAllAgents()');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('roo-code is in the ide category', () => {
|
|
147
|
+
const ideAgents = getAgentsByCategory('ide');
|
|
148
|
+
const ids = ideAgents.map(a => a.id);
|
|
149
|
+
assert.ok(ids.includes('roo-code'), 'roo-code should be in ide category');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('roo-code is distinct from cline', () => {
|
|
153
|
+
const cline = getAgent('cline');
|
|
154
|
+
assert.ok(cline, 'cline agent should still exist');
|
|
155
|
+
assert.notStrictEqual(cline.id, rooCode.id, 'roo-code and cline should be different agents');
|
|
156
|
+
assert.notStrictEqual(cline.skillsDir, rooCode.skillsDir,
|
|
157
|
+
'roo-code and cline should use different skills directories');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Print summary
|
|
161
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
162
|
+
if (errors.length > 0) {
|
|
163
|
+
console.log('\nFailed tests:');
|
|
164
|
+
errors.forEach(e => console.log(` - ${e.name}: ${e.error}`));
|
|
165
|
+
}
|
|
166
|
+
if (failed > 0) process.exit(1);
|