gemstack-ai 1.4.0 → 2.0.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/.agents/rules/03-gemstack-security.md +2 -2
- package/.gemstack/state.json +10 -11
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +47 -13
- package/RELEASE_NOTES.md +40 -1
- package/handoff.md +33 -19
- package/package.json +4 -3
- package/scripts/ci/check-package-contents.js +1 -1
- package/scripts/ci/check-secrets.js +84 -0
- package/specs/011-gemstack-2.0-hardening/.gemstack.json +5 -0
- package/specs/011-gemstack-2.0-hardening/closure.json +58 -0
- package/specs/011-gemstack-2.0-hardening/plan.md +210 -0
- package/specs/011-gemstack-2.0-hardening/spec.md +277 -0
- package/specs/011-gemstack-2.0-hardening/tasks.md +59 -0
- package/specs/012-gemstack-2.0-honest-evidence/.gemstack.json +5 -0
- package/specs/012-gemstack-2.0-honest-evidence/closure.json +58 -0
- package/specs/012-gemstack-2.0-honest-evidence/plan.md +202 -0
- package/specs/012-gemstack-2.0-honest-evidence/spec.md +222 -0
- package/specs/012-gemstack-2.0-honest-evidence/tasks.md +99 -0
- package/specs/013-gemstack-2.0-adaptable-sdd/.gemstack.json +9 -0
- package/specs/013-gemstack-2.0-adaptable-sdd/closure.json +58 -0
- package/specs/013-gemstack-2.0-adaptable-sdd/context-capsule.json +227 -0
- package/specs/013-gemstack-2.0-adaptable-sdd/plan.md +179 -0
- package/specs/013-gemstack-2.0-adaptable-sdd/spec.md +212 -0
- package/specs/013-gemstack-2.0-adaptable-sdd/tasks.md +90 -0
- package/specs/014-gemstack-2.0-context-memory/.gemstack.json +9 -0
- package/specs/014-gemstack-2.0-context-memory/closure.json +58 -0
- package/specs/014-gemstack-2.0-context-memory/plan.md +161 -0
- package/specs/014-gemstack-2.0-context-memory/spec.md +163 -0
- package/specs/014-gemstack-2.0-context-memory/tasks.md +79 -0
- package/src/cli.js +3 -0
- package/src/commands/doctor.js +18 -0
- package/src/commands/hooks.js +98 -14
- package/src/commands/init.js +1 -1
- package/src/commands/install.js +174 -49
- package/src/commands/spec.js +105 -0
- package/src/commands/update.js +1 -1
- package/src/commands/verify.js +10 -0
- package/src/lib/backup.js +3 -3
- package/src/lib/context-fatigue.js +165 -0
- package/src/lib/contract-amendments.js +109 -0
- package/src/lib/dependency-audit.js +202 -0
- package/src/lib/filesystem-safe.js +85 -15
- package/src/lib/memory-audit.js +121 -0
- package/src/lib/provider-boundary.js +5 -1
- package/src/lib/provider-registry.js +6 -4
- package/src/lib/safety-gates.js +176 -8
- package/src/lib/sdd-rigor.js +181 -0
- package/src/lib/spec-delta.js +194 -0
- package/src/lib/spec-merge.js +168 -0
- package/src/lib/swarm.js +2 -2
- package/src/lib/visual-qa.js +162 -9
- package/template/.agents/rules/03-gemstack-security.md +2 -2
- package/.github/workflows/main-ci.yml +0 -32
- package/.github/workflows/pr-ci.yml +0 -31
- package/.github/workflows/publish.yml +0 -52
- package/.github/workflows/release-readiness.yml +0 -43
- package/gemstack-ai-1.4.0.tgz +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Plan Técnico de Implementación: Gemstack 2.0 Hardening (Sprint A)
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `011-gemstack-2.0-hardening`
|
|
4
|
+
**Feature Directory**: `specs/011-gemstack-2.0-hardening/`
|
|
5
|
+
**Lifecycle Status**: `PLAN_COMPLETE`
|
|
6
|
+
**Stop Reason**: `PLAN_COMPLETE_AWAITING_TASKS`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Arquitectura Técnica y Módulos Afectados
|
|
11
|
+
|
|
12
|
+
Sprint A implementa el endurecimiento de fronteras de confianza sobre los siguientes subsistemas:
|
|
13
|
+
|
|
14
|
+
1. **`src/lib/filesystem-safe.js`**:
|
|
15
|
+
- Incorpora función `resolveSafeStrict(rootDir, targetPath, options = { allowCreate: false })`.
|
|
16
|
+
- Resuelve el camino físico real de `rootDir` y de los componentes existentes de `targetPath` usando `fs.realpathSync`.
|
|
17
|
+
- Si cualquier componente intermedio existente o el destino resuelve fuera del límite canónico de `rootDir`, lanza un error con código `SYMLINK_ESCAPE_DETECTED`.
|
|
18
|
+
- Incorpora `withConfinedAtomicWrite(rootDir, targetPath, writeFn)`:
|
|
19
|
+
- Asegura que el archivo temporal se aloje dentro de `path.join(rootDir, '.gemstack', 'tmp')`.
|
|
20
|
+
- Ejecuta la escritura y renombra atómicamente al destino verificado (`fs.renameSync`).
|
|
21
|
+
|
|
22
|
+
2. **`src/commands/install.js`**:
|
|
23
|
+
- Valida el esquema URL: rechaza todo lo que no sea `https:` (`INSECURE_HTTP_BLOCKED`).
|
|
24
|
+
- Resuelve hostname e IP antes de conectar o rechaza hostnames literales/IPs privadas (`127.0.0.1`, `localhost`, `::1`, `10.0.0.0/8`, `192.168.0.0/16`, `172.16.0.0/12`, `169.254.0.0/16`) con `PRIVATE_IP_BLOCKED`.
|
|
25
|
+
- Aplica timeout estricto de 8000ms mediante `AbortController`.
|
|
26
|
+
- Controla el tamaño en streaming: acumula hasta 262,144 bytes; si se excede, aborta inmediatamente con `SKILL_PAYLOAD_TOO_LARGE`.
|
|
27
|
+
- Valida el slug del skill mediante regex `^[a-z0-9][a-z0-9-_]{1,63}$` (`INVALID_SKILL_SLUG`).
|
|
28
|
+
- Extrae frontmatter y calcula digest SHA-256 en memoria antes de persistir.
|
|
29
|
+
|
|
30
|
+
3. **`src/commands/hooks.js`**:
|
|
31
|
+
- Inspecciona `.git/hooks/pre-commit`.
|
|
32
|
+
- Si ya existe y no tiene la firma de Gemstack, renombra el hook existente a `.git/hooks/pre-commit.gemstack-wrapped` y crea un dispatcher que ejecuta secuencialmente el hook del usuario y la validación de Gemstack.
|
|
33
|
+
- Si falla el hook del usuario, el commit se detiene; si pasa, se ejecuta el escáner de Gemstack.
|
|
34
|
+
- Expande los patrones de detección de secretos a:
|
|
35
|
+
- Google Gemini: `AIza[0-9A-Za-z-_]{35}`
|
|
36
|
+
- OpenAI: `sk-[a-zA-Z0-9]{20,}` y `sk-proj-[a-zA-Z0-9_-]{20,}`
|
|
37
|
+
- Anthropic: `sk-ant-[a-zA-Z0-9_-]{20,}`
|
|
38
|
+
- GitHub PAT: `ghp_[0-9a-zA-Z]{36}`, `github_pat_[0-9a-zA-Z_]{22,}`
|
|
39
|
+
- Slack: `xox[baprs]-[0-9a-zA-Z-]{10,}`
|
|
40
|
+
- Claves privadas: `-----BEGIN [A-Z ]*PRIVATE KEY-----`
|
|
41
|
+
|
|
42
|
+
4. **`scripts/ci/check-secrets.js`**:
|
|
43
|
+
- Nuevo script CLI de CI que escanea archivos rastreados por git (o recursivo si no es git) omitiendo `node_modules`, `.git`, fixtures de prueba controlados (`tests/fixtures/secrets/`), y reporta con código de salida 1 si encuentra secretos activos.
|
|
44
|
+
- Añadido a `npm run ci:all` en `package.json`.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 2. Contratos Heredados de Arquitectura
|
|
49
|
+
|
|
50
|
+
```gemstack-contracts
|
|
51
|
+
[
|
|
52
|
+
{
|
|
53
|
+
"id": "zero-dependency-core",
|
|
54
|
+
"type": "BOOLEAN_INVARIANT",
|
|
55
|
+
"value": true
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "https-only-skills",
|
|
59
|
+
"type": "BOOLEAN_INVARIANT",
|
|
60
|
+
"value": true
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "allowed-skill-schemes",
|
|
64
|
+
"type": "ENUM_SET",
|
|
65
|
+
"values": [
|
|
66
|
+
"https"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "skill-max-payload-bytes",
|
|
71
|
+
"type": "ROADMAP_LIMIT",
|
|
72
|
+
"value": 262144
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "skill-slug-identity",
|
|
76
|
+
"type": "IDENTITY_TUPLE",
|
|
77
|
+
"values": [
|
|
78
|
+
"namespace",
|
|
79
|
+
"slug"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "symlink-out-of-bounds",
|
|
84
|
+
"type": "BOUNDARY",
|
|
85
|
+
"value": "FORBIDDEN"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "existing-hook-destruction",
|
|
89
|
+
"type": "BOUNDARY",
|
|
90
|
+
"value": "FORBIDDEN"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "secret-scanning-ci",
|
|
94
|
+
"type": "BOOLEAN_INVARIANT",
|
|
95
|
+
"value": true
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "atomic-writes-confined",
|
|
99
|
+
"type": "BOOLEAN_INVARIANT",
|
|
100
|
+
"value": true
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 3. Mapeo Canónico de Pruebas (Test Bindings)
|
|
108
|
+
|
|
109
|
+
```gemstack-test-bindings
|
|
110
|
+
[
|
|
111
|
+
{
|
|
112
|
+
"id": "TEST-HARDEN-A01",
|
|
113
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
114
|
+
"symbol": "TEST-HARDEN-A01"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "TEST-HARDEN-A02",
|
|
118
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
119
|
+
"symbol": "TEST-HARDEN-A02"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "TEST-HARDEN-A03",
|
|
123
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
124
|
+
"symbol": "TEST-HARDEN-A03"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "TEST-HARDEN-A04",
|
|
128
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
129
|
+
"symbol": "TEST-HARDEN-A04"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"id": "TEST-HARDEN-A05",
|
|
133
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
134
|
+
"symbol": "TEST-HARDEN-A05"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "TEST-HARDEN-B01",
|
|
138
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
139
|
+
"symbol": "TEST-HARDEN-B01"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"id": "TEST-HARDEN-B02",
|
|
143
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
144
|
+
"symbol": "TEST-HARDEN-B02"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "TEST-HARDEN-B03",
|
|
148
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
149
|
+
"symbol": "TEST-HARDEN-B03"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"id": "TEST-HARDEN-B04",
|
|
153
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
154
|
+
"symbol": "TEST-HARDEN-B04"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"id": "TEST-HARDEN-C01",
|
|
158
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
159
|
+
"symbol": "TEST-HARDEN-C01"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"id": "TEST-HARDEN-C02",
|
|
163
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
164
|
+
"symbol": "TEST-HARDEN-C02"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"id": "TEST-HARDEN-C03",
|
|
168
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
169
|
+
"symbol": "TEST-HARDEN-C03"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"id": "TEST-HARDEN-C04",
|
|
173
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
174
|
+
"symbol": "TEST-HARDEN-C04"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"id": "TEST-HARDEN-C05",
|
|
178
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
179
|
+
"symbol": "TEST-HARDEN-C05"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"id": "TEST-HARDEN-C06",
|
|
183
|
+
"file": "tests/security-p0-hardening.test.js",
|
|
184
|
+
"symbol": "TEST-HARDEN-C06"
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 4. Portones de Cierre (Closure Gates)
|
|
192
|
+
|
|
193
|
+
```gemstack-closure-gates
|
|
194
|
+
[
|
|
195
|
+
{
|
|
196
|
+
"id": "gate-test",
|
|
197
|
+
"type": "PACKAGE_SCRIPT",
|
|
198
|
+
"script": "test",
|
|
199
|
+
"requirement": "REQUIRED",
|
|
200
|
+
"waivable": false
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"id": "gate-ci-all",
|
|
204
|
+
"type": "PACKAGE_SCRIPT",
|
|
205
|
+
"script": "ci:all",
|
|
206
|
+
"requirement": "REQUIRED",
|
|
207
|
+
"waivable": false
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
```
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Especificación de Funcionalidad: Gemstack 2.0 Hardening (Sprint A)
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `011-gemstack-2.0-hardening`
|
|
4
|
+
**Feature Directory**: `specs/011-gemstack-2.0-hardening/`
|
|
5
|
+
**Lifecycle Status**: `SPEC_COMPLETE`
|
|
6
|
+
**Stop Reason**: `SPEC_COMPLETE_AWAITING_PLAN`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Problem Statement
|
|
11
|
+
|
|
12
|
+
Gemstack v1.4.0 establishes declarative Spec-Driven Development, phase contracts, closure evidence, provider safety gates, context capsules, and visual QA manifests. However, a security and reliability audit of the v1.4.0 codebase reveals three critical trust boundary vulnerabilities:
|
|
13
|
+
|
|
14
|
+
1. **Unbounded Remote Skill Fetching (`src/commands/install.js`)**:
|
|
15
|
+
- `fetch(url)` accepts arbitrary origins including cleartext HTTP, localhost, and private intranet IPs (SSRF).
|
|
16
|
+
- No network timeouts or byte limits exist, creating vulnerability to unbounded streaming or memory exhaustion.
|
|
17
|
+
- Skill names are not strictly validated as canonical slugs, creating path traversal risks.
|
|
18
|
+
- Remote code is downloaded and written without content pre-inspection or cryptographic fingerprint display.
|
|
19
|
+
|
|
20
|
+
2. **Filesystem Traversal via Symlink Resolution (`src/lib/filesystem-safe.js`)**:
|
|
21
|
+
- Current path safety checks rely solely on lexical path containment (`path.relative`).
|
|
22
|
+
- If an existing or intermediate directory component is a symbolic link pointing outside the repository tree, writes or reads escape project boundaries.
|
|
23
|
+
- Atomic writes using OS temporary directories can cross filesystem mount points and risk symlink attacks.
|
|
24
|
+
|
|
25
|
+
3. **Destructive Git Hook Replacement & Narrow Secret Detection (`src/commands/hooks.js`)**:
|
|
26
|
+
- `gemstack hooks` unconditionally overwrites `.git/hooks/pre-commit`, silently destroying existing user hooks (Husky, linters, formatters).
|
|
27
|
+
- Secret scanning covers only 3 basic patterns, completely ignoring modern AI API keys (Google Gemini, OpenAI, Anthropic), GitHub PATs, Slack tokens, and private SSH/RSA keys.
|
|
28
|
+
- CI does not enforce zero-secrets validation on the codebase.
|
|
29
|
+
|
|
30
|
+
Gemstack 2.0 Sprint A implements mechanical, fail-closed trust boundaries for remote skill installation, filesystem containment with realpath resolution, safe git hook chaining, and comprehensive multi-provider secret detection.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. Goals & Non-Goals
|
|
35
|
+
|
|
36
|
+
### Goals
|
|
37
|
+
- **Strict HTTPS & SSRF Defense**: Enforce HTTPS for remote skill installation. Block all loopback, link-local, and RFC 1918 private IP addresses.
|
|
38
|
+
- **Bounded Remote Downloads**: Enforce maximum payload size (256 KB) and strict network timeout (8000ms).
|
|
39
|
+
- **Canonical Skill Slugs**: Enforce strict alphanumeric regex on skill names (`^[a-z0-9][a-z0-9-_]{1,63}$`).
|
|
40
|
+
- **Cryptographic Preview**: Compute SHA-256 fingerprint and validate frontmatter before disk persistence.
|
|
41
|
+
- **Physical Filesystem Containment**: Implement `resolveSafeStrict` using canonical `realpathSync` to reject symlinks escaping repository root.
|
|
42
|
+
- **Confined Atomic Writes**: Stage temporary writes within `.gemstack/tmp/` inside project boundaries.
|
|
43
|
+
- **Git Hook Preservation & Chaining**: Detect existing `.git/hooks/pre-commit` scripts and chain execution without clobbering.
|
|
44
|
+
- **Comprehensive Secret Detection**: Detect Google Gemini, OpenAI, Anthropic, GitHub PAT, Slack tokens, and private keys in both git hooks and CI.
|
|
45
|
+
- **Zero Runtime Dependencies**: Implement all hardening using pure Node.js standard library.
|
|
46
|
+
|
|
47
|
+
### Non-Goals
|
|
48
|
+
- Gemstack does NOT implement an arbitrary remote package manager or NPM registry substitute.
|
|
49
|
+
- Gemstack does NOT resolve external symlinks outside the repository root.
|
|
50
|
+
- Gemstack does NOT replace dedicated enterprise SAST scanning tools; it enforces local-first pre-commit and CI hygiene.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 3. Core Architectural Principles & Invariants
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
remote input ≠ trusted code
|
|
58
|
+
lexical containment ≠ physical containment
|
|
59
|
+
symlink escape = security violation
|
|
60
|
+
install skill ≠ execute code
|
|
61
|
+
preserve user hooks > overwrite user hooks
|
|
62
|
+
credentials in repo = immediate blocker
|
|
63
|
+
zero runtime dependencies = permanent invariant
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 4. Base Architectural Contracts
|
|
69
|
+
|
|
70
|
+
```gemstack-contracts
|
|
71
|
+
[
|
|
72
|
+
{
|
|
73
|
+
"id": "zero-dependency-core",
|
|
74
|
+
"type": "BOOLEAN_INVARIANT",
|
|
75
|
+
"value": true
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "https-only-skills",
|
|
79
|
+
"type": "BOOLEAN_INVARIANT",
|
|
80
|
+
"value": true
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "allowed-skill-schemes",
|
|
84
|
+
"type": "ENUM_SET",
|
|
85
|
+
"values": [
|
|
86
|
+
"https"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "skill-max-payload-bytes",
|
|
91
|
+
"type": "ROADMAP_LIMIT",
|
|
92
|
+
"value": 262144
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "skill-slug-identity",
|
|
96
|
+
"type": "IDENTITY_TUPLE",
|
|
97
|
+
"values": [
|
|
98
|
+
"namespace",
|
|
99
|
+
"slug"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "symlink-out-of-bounds",
|
|
104
|
+
"type": "BOUNDARY",
|
|
105
|
+
"value": "FORBIDDEN"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "existing-hook-destruction",
|
|
109
|
+
"type": "BOUNDARY",
|
|
110
|
+
"value": "FORBIDDEN"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "secret-scanning-ci",
|
|
114
|
+
"type": "BOOLEAN_INVARIANT",
|
|
115
|
+
"value": true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"id": "atomic-writes-confined",
|
|
119
|
+
"type": "BOOLEAN_INVARIANT",
|
|
120
|
+
"value": true
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 5. Canonical Acceptance Requirements
|
|
128
|
+
|
|
129
|
+
### Group A: Remote Skills Installation Hardening
|
|
130
|
+
- **TEST-HARDEN-A01**: Rejects non-HTTPS remote URLs by default with `INSECURE_HTTP_BLOCKED`.
|
|
131
|
+
- **TEST-HARDEN-A02**: Rejects private and loopback IP addresses (127.0.0.1, localhost, 10.x, 192.168.x, 172.16-31.x, 169.254.x) with `PRIVATE_IP_BLOCKED`.
|
|
132
|
+
- **TEST-HARDEN-A03**: Rejects payload exceeding 256 KB with `SKILL_PAYLOAD_TOO_LARGE`.
|
|
133
|
+
- **TEST-HARDEN-A04**: Rejects invalid skill names (path traversal, separators, symbols) with `INVALID_SKILL_SLUG`.
|
|
134
|
+
- **TEST-HARDEN-A05**: Computes SHA-256 fingerprint before writing and validates frontmatter integrity.
|
|
135
|
+
|
|
136
|
+
### Group B: Filesystem Containment & Realpath Hardening
|
|
137
|
+
- **TEST-HARDEN-B01**: `resolveSafeStrict` detects and blocks symlinks pointing outside the repository root (`SYMLINK_ESCAPE_DETECTED`).
|
|
138
|
+
- **TEST-HARDEN-B02**: `resolveSafeStrict` detects and blocks intermediate symlink path traversal escaping repository root.
|
|
139
|
+
- **TEST-HARDEN-B03**: Confines atomic file staging writes within `.gemstack/tmp/` inside project boundaries.
|
|
140
|
+
- **TEST-HARDEN-B04**: Verifies all critical CLI call sites (init, update, install, backup, vqa) enforce containment with zero out-of-boundary mutations.
|
|
141
|
+
|
|
142
|
+
### Group C: Git Hooks Preservation & Secret Detection
|
|
143
|
+
- **TEST-HARDEN-C01**: `gemstack hooks` detects pre-existing user hooks and preserves them via executable delegation/chaining without data loss.
|
|
144
|
+
- **TEST-HARDEN-C02**: Detects Google AI / Gemini API keys (`AIza[0-9A-Za-z-_]{35}`).
|
|
145
|
+
- **TEST-HARDEN-C03**: Detects OpenAI and Anthropic API keys (`sk-[a-zA-Z0-9]{20,}`, `sk-ant-`).
|
|
146
|
+
- **TEST-HARDEN-C04**: Detects GitHub PATs and Slack tokens (`ghp_`, `github_pat_`, `xox[baprs]-`).
|
|
147
|
+
- **TEST-HARDEN-C05**: Detects private RSA/EC/OPENSSH keys (`-----BEGIN [A-Z ]*PRIVATE KEY-----`).
|
|
148
|
+
- **TEST-HARDEN-C06**: Integrates zero-dependency secret scanner script `scripts/ci/check-secrets.js` into `ci:all` to verify repository cleanliness.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 6. Canonical Test Matrix
|
|
153
|
+
|
|
154
|
+
```gemstack-test-matrix
|
|
155
|
+
[
|
|
156
|
+
{
|
|
157
|
+
"id": "TEST-HARDEN-A01",
|
|
158
|
+
"category": "REMOTE_SKILLS",
|
|
159
|
+
"layer": "UNIT",
|
|
160
|
+
"description": "Rejects non-HTTPS remote URLs by default with INSECURE_HTTP_BLOCKED",
|
|
161
|
+
"pass_criteria": "Throws or returns error with code INSECURE_HTTP_BLOCKED when URL protocol is http:",
|
|
162
|
+
"gate": "REQUIRED"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"id": "TEST-HARDEN-A02",
|
|
166
|
+
"category": "REMOTE_SKILLS",
|
|
167
|
+
"layer": "UNIT",
|
|
168
|
+
"description": "Rejects private and loopback IP addresses (SSRF defense) with PRIVATE_IP_BLOCKED",
|
|
169
|
+
"pass_criteria": "Blocks connections to loopback (127.0.0.1, ::1, localhost), link-local (169.254.x), and private RFC1918 subnets",
|
|
170
|
+
"gate": "REQUIRED"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"id": "TEST-HARDEN-A03",
|
|
174
|
+
"category": "REMOTE_SKILLS",
|
|
175
|
+
"layer": "UNIT",
|
|
176
|
+
"description": "Rejects payload exceeding 256 KB with SKILL_PAYLOAD_TOO_LARGE",
|
|
177
|
+
"pass_criteria": "Aborts stream download and throws SKILL_PAYLOAD_TOO_LARGE when response body exceeds 262144 bytes",
|
|
178
|
+
"gate": "REQUIRED"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"id": "TEST-HARDEN-A04",
|
|
182
|
+
"category": "REMOTE_SKILLS",
|
|
183
|
+
"layer": "UNIT",
|
|
184
|
+
"description": "Rejects invalid skill names with INVALID_SKILL_SLUG",
|
|
185
|
+
"pass_criteria": "Fails when skill name contains directory separators, path traversal (..), or forbidden characters",
|
|
186
|
+
"gate": "REQUIRED"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"id": "TEST-HARDEN-A05",
|
|
190
|
+
"category": "REMOTE_SKILLS",
|
|
191
|
+
"layer": "INTEGRATION",
|
|
192
|
+
"description": "Computes SHA-256 fingerprint before writing and validates frontmatter integrity",
|
|
193
|
+
"pass_criteria": "Calculates content digest in memory and verifies valid SKILL.md frontmatter prior to filesystem persistence",
|
|
194
|
+
"gate": "REQUIRED"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "TEST-HARDEN-B01",
|
|
198
|
+
"category": "FILESYSTEM",
|
|
199
|
+
"layer": "UNIT",
|
|
200
|
+
"description": "Detects and blocks symlinks pointing outside repository root with SYMLINK_ESCAPE_DETECTED",
|
|
201
|
+
"pass_criteria": "Resolves real physical path and throws SYMLINK_ESCAPE_DETECTED when symlink destination escapes root",
|
|
202
|
+
"gate": "REQUIRED"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"id": "TEST-HARDEN-B02",
|
|
206
|
+
"category": "FILESYSTEM",
|
|
207
|
+
"layer": "UNIT",
|
|
208
|
+
"description": "Detects and blocks intermediate symlink path traversal escaping repository root",
|
|
209
|
+
"pass_criteria": "Throws SYMLINK_ESCAPE_DETECTED when any intermediate path component resolves outside repository root",
|
|
210
|
+
"gate": "REQUIRED"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"id": "TEST-HARDEN-B03",
|
|
214
|
+
"category": "FILESYSTEM",
|
|
215
|
+
"layer": "UNIT",
|
|
216
|
+
"description": "Confines atomic staging writes within .gemstack/tmp/ inside project boundaries",
|
|
217
|
+
"pass_criteria": "Staging file is written inside repository .gemstack/tmp/ and atomically renamed to final target",
|
|
218
|
+
"gate": "REQUIRED"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"id": "TEST-HARDEN-B04",
|
|
222
|
+
"category": "FILESYSTEM",
|
|
223
|
+
"layer": "INTEGRATION",
|
|
224
|
+
"description": "Verifies all CLI call sites enforce containment with zero out-of-boundary mutations",
|
|
225
|
+
"pass_criteria": "Audits init, update, install, backup, and vqa call sites verifying no file is read or written outside target root",
|
|
226
|
+
"gate": "REQUIRED"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"id": "TEST-HARDEN-C01",
|
|
230
|
+
"category": "GIT_HOOKS",
|
|
231
|
+
"layer": "INTEGRATION",
|
|
232
|
+
"description": "Preserves pre-existing user hooks via executable delegation without data loss",
|
|
233
|
+
"pass_criteria": "Detects non-Gemstack pre-commit hook and preserves execution of both user hook and Gemstack validator",
|
|
234
|
+
"gate": "REQUIRED"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"id": "TEST-HARDEN-C02",
|
|
238
|
+
"category": "SECRET_SCAN",
|
|
239
|
+
"layer": "UNIT",
|
|
240
|
+
"description": "Detects Google AI / Gemini API keys",
|
|
241
|
+
"pass_criteria": "Regex pattern matches AIza[0-9A-Za-z-_]{35} and blocks commit or CI execution",
|
|
242
|
+
"gate": "REQUIRED"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"id": "TEST-HARDEN-C03",
|
|
246
|
+
"category": "SECRET_SCAN",
|
|
247
|
+
"layer": "UNIT",
|
|
248
|
+
"description": "Detects OpenAI and Anthropic API keys",
|
|
249
|
+
"pass_criteria": "Regex patterns match sk-[a-zA-Z0-9]{20,} and sk-ant- and block commit or CI execution",
|
|
250
|
+
"gate": "REQUIRED"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"id": "TEST-HARDEN-C04",
|
|
254
|
+
"category": "SECRET_SCAN",
|
|
255
|
+
"layer": "UNIT",
|
|
256
|
+
"description": "Detects GitHub PATs and Slack tokens",
|
|
257
|
+
"pass_criteria": "Regex patterns match ghp_, github_pat_, and xox[baprs]- and block commit or CI execution",
|
|
258
|
+
"gate": "REQUIRED"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"id": "TEST-HARDEN-C05",
|
|
262
|
+
"category": "SECRET_SCAN",
|
|
263
|
+
"layer": "UNIT",
|
|
264
|
+
"description": "Detects private RSA/EC/OPENSSH keys",
|
|
265
|
+
"pass_criteria": "Regex pattern matches -----BEGIN [A-Z ]*PRIVATE KEY----- and blocks commit or CI execution",
|
|
266
|
+
"gate": "REQUIRED"
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"id": "TEST-HARDEN-C06",
|
|
270
|
+
"category": "CI_INTEGRATION",
|
|
271
|
+
"layer": "CLI",
|
|
272
|
+
"description": "Integrates zero-dependency secret scanner script into ci:all",
|
|
273
|
+
"pass_criteria": "scripts/ci/check-secrets.js executes in ci:all and returns exit code 1 on detected secrets",
|
|
274
|
+
"gate": "REQUIRED"
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Tareas de Implementación: Gemstack 2.0 Hardening (Sprint A)
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `011-gemstack-2.0-hardening`
|
|
4
|
+
**Feature Directory**: `specs/011-gemstack-2.0-hardening/`
|
|
5
|
+
**Lifecycle Status**: `TASKS_COMPLETE`
|
|
6
|
+
**Stop Reason**: `TASKS_COMPLETE_AWAITING_IMPLEMENTATION`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Inventario de Tareas
|
|
11
|
+
|
|
12
|
+
- [ ] **TASK-001: Implementar suite adversarial P0 reproduciendo vulnerabilidades de base 1.4.0**
|
|
13
|
+
<!-- gemstack:validation_required=true -->
|
|
14
|
+
<!-- gemstack:tests=TEST-HARDEN-A01, TEST-HARDEN-A02, TEST-HARDEN-A03, TEST-HARDEN-A04, TEST-HARDEN-A05, TEST-HARDEN-B01, TEST-HARDEN-B02, TEST-HARDEN-B03, TEST-HARDEN-B04, TEST-HARDEN-C01, TEST-HARDEN-C02, TEST-HARDEN-C03, TEST-HARDEN-C04, TEST-HARDEN-C05, TEST-HARDEN-C06 -->
|
|
15
|
+
<!-- gemstack:files=tests/security-p0-hardening.test.js -->
|
|
16
|
+
<!-- gemstack:depends= -->
|
|
17
|
+
Crear la suite de pruebas `tests/security-p0-hardening.test.js` que ejercita ataques adversariales contra skills remotos (HTTP, SSRF, payload gigante, slugs maliciosos), symlinks saliendo de raíz, destrucción de pre-commit existente y evasión de escaneo de secretos.
|
|
18
|
+
|
|
19
|
+
- [ ] **TASK-002: Contención estricta de filesystem y resolución realpath en filesystem-safe.js**
|
|
20
|
+
<!-- gemstack:validation_required=true -->
|
|
21
|
+
<!-- gemstack:tests=TEST-HARDEN-B01, TEST-HARDEN-B02, TEST-HARDEN-B03, TEST-HARDEN-B04 -->
|
|
22
|
+
<!-- gemstack:files=src/lib/filesystem-safe.js -->
|
|
23
|
+
<!-- gemstack:depends=TASK-001 -->
|
|
24
|
+
Implementar `resolveSafeStrict` usando `fs.realpathSync` para verificar que ningún enlace simbólico existente ni componente intermedio escape de la raíz del proyecto. Confinar escrituras temporales atómicas a `.gemstack/tmp/`.
|
|
25
|
+
|
|
26
|
+
- [ ] **TASK-003: Blindar descarga e instalación de skills remotos en install.js**
|
|
27
|
+
<!-- gemstack:validation_required=true -->
|
|
28
|
+
<!-- gemstack:tests=TEST-HARDEN-A01, TEST-HARDEN-A02, TEST-HARDEN-A03, TEST-HARDEN-A04, TEST-HARDEN-A05 -->
|
|
29
|
+
<!-- gemstack:files=src/commands/install.js -->
|
|
30
|
+
<!-- gemstack:depends=TASK-001, TASK-002 -->
|
|
31
|
+
Implementar política HTTPS obligatoria, bloqueo de IPs privadas/loopback (SSRF), timeout de 8000ms, límite de 256 KB en streaming, validación estricta de slug (`^[a-z0-9][a-z0-9-_]{1,63}$`) y pre-inspección de fingerprint SHA-256 antes de persistir en disco.
|
|
32
|
+
|
|
33
|
+
- [ ] **TASK-004: Preservación de git hooks existentes y escaneo ampliado de secretos en hooks.js**
|
|
34
|
+
<!-- gemstack:validation_required=true -->
|
|
35
|
+
<!-- gemstack:tests=TEST-HARDEN-C01, TEST-HARDEN-C02, TEST-HARDEN-C03, TEST-HARDEN-C04, TEST-HARDEN-C05 -->
|
|
36
|
+
<!-- gemstack:files=src/commands/hooks.js -->
|
|
37
|
+
<!-- gemstack:depends=TASK-001 -->
|
|
38
|
+
Modificar el instalador de hooks para preservar y encadenar hooks preexistentes del usuario. Ampliar el escáner a Google Gemini, OpenAI, Anthropic, GitHub PAT, Slack y claves privadas RSA/SSH.
|
|
39
|
+
|
|
40
|
+
- [ ] **TASK-005: Script de verificación de secretos en CI e integración con ci:all**
|
|
41
|
+
<!-- gemstack:validation_required=true -->
|
|
42
|
+
<!-- gemstack:tests=TEST-HARDEN-C06 -->
|
|
43
|
+
<!-- gemstack:files=scripts/ci/check-secrets.js, package.json -->
|
|
44
|
+
<!-- gemstack:depends=TASK-004 -->
|
|
45
|
+
Crear `scripts/ci/check-secrets.js` con cero dependencias externas y conectarlo en el script `ci:all` de `package.json`.
|
|
46
|
+
|
|
47
|
+
- [ ] **TASK-006: Auditoría de contención en call sites CLI (init, update, install, backup, vqa)**
|
|
48
|
+
<!-- gemstack:validation_required=true -->
|
|
49
|
+
<!-- gemstack:tests=TEST-HARDEN-B04 -->
|
|
50
|
+
<!-- gemstack:files=src/commands/init.js, src/commands/update.js, src/commands/install.js, src/lib/backup.js, src/commands/visual.js -->
|
|
51
|
+
<!-- gemstack:depends=TASK-002, TASK-003 -->
|
|
52
|
+
Auditar y conectar `resolveSafeStrict` en todos los comandos para garantizar que ninguna operación pueda crear, modificar o leer archivos fuera de los límites del repositorio.
|
|
53
|
+
|
|
54
|
+
- [ ] **TASK-007: Cierre mecánico, ejecución de matriz completa y generación de closure.json**
|
|
55
|
+
<!-- gemstack:validation_required=true -->
|
|
56
|
+
<!-- gemstack:tests=TEST-HARDEN-A01, TEST-HARDEN-A02, TEST-HARDEN-A03, TEST-HARDEN-A04, TEST-HARDEN-A05, TEST-HARDEN-B01, TEST-HARDEN-B02, TEST-HARDEN-B03, TEST-HARDEN-B04, TEST-HARDEN-C01, TEST-HARDEN-C02, TEST-HARDEN-C03, TEST-HARDEN-C04, TEST-HARDEN-C05, TEST-HARDEN-C06 -->
|
|
57
|
+
<!-- gemstack:files=specs/011-gemstack-2.0-hardening/closure.json -->
|
|
58
|
+
<!-- gemstack:depends=TASK-001, TASK-002, TASK-003, TASK-004, TASK-005, TASK-006 -->
|
|
59
|
+
Ejecutar `gemstack collect` para correr toda la matriz de pruebas unitarias, de integración y CI, verificar status VERIFIED y dejar la evidencia de cierre lista.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "gemstack-closure",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"feature": "specs/012-gemstack-2.0-honest-evidence",
|
|
5
|
+
"generated_at": "2026-09-23T01:58:35.740Z",
|
|
6
|
+
"status": "VERIFIED",
|
|
7
|
+
"closure_context": {
|
|
8
|
+
"closure_context_hash": "a05e1759c3818e35cf9dee0edaa676bbfbe33ecdc39e08d265fc0c4b37476fb3",
|
|
9
|
+
"repository_type": "git",
|
|
10
|
+
"git_commit": "ba58055ea00a8a8cd6ee4b9b7f96deccbbf46dea",
|
|
11
|
+
"working_tree_clean": false,
|
|
12
|
+
"relevant_files_digest": "9adf80e140cd87942d5ff8db9b52a9c63c5fdadeb8a4c6f197e9319a90db4bb4"
|
|
13
|
+
},
|
|
14
|
+
"acceptance_signature": "3fe0b2f8d7083da811ecd33402dd5dbc627c64759a2532ee8eaf65e281e49af2",
|
|
15
|
+
"canonical_summary": {
|
|
16
|
+
"required_total": 14,
|
|
17
|
+
"required_passed": 14,
|
|
18
|
+
"supplemental_total": 0,
|
|
19
|
+
"supplemental_passed": 0
|
|
20
|
+
},
|
|
21
|
+
"physical_summary": {
|
|
22
|
+
"supporting_total": 0,
|
|
23
|
+
"supporting_passed": 0,
|
|
24
|
+
"total_executed": 14,
|
|
25
|
+
"total_passed": 14,
|
|
26
|
+
"total_failed": 0,
|
|
27
|
+
"total_skipped": 0
|
|
28
|
+
},
|
|
29
|
+
"reconciliation": {
|
|
30
|
+
"math_valid": true,
|
|
31
|
+
"phantoms_detected": 0,
|
|
32
|
+
"orphans_detected": 0,
|
|
33
|
+
"missing_canonical_ids": []
|
|
34
|
+
},
|
|
35
|
+
"task_traceability_summary": {
|
|
36
|
+
"tasks_total": 6,
|
|
37
|
+
"tasks_with_validation": 6,
|
|
38
|
+
"tasks_documentation_only": 0,
|
|
39
|
+
"unmapped_canonical_tests": []
|
|
40
|
+
},
|
|
41
|
+
"required_gates": {
|
|
42
|
+
"gate-test": "PASS",
|
|
43
|
+
"gate-ci-all": "PASS"
|
|
44
|
+
},
|
|
45
|
+
"supplemental_gates": {},
|
|
46
|
+
"exceptions": [],
|
|
47
|
+
"evidence_sources": [
|
|
48
|
+
{
|
|
49
|
+
"type": "PACKAGE_SCRIPT",
|
|
50
|
+
"script": "test",
|
|
51
|
+
"runner": "node:test",
|
|
52
|
+
"exit_code": 0,
|
|
53
|
+
"duration_ms": 406
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"blockers": [],
|
|
57
|
+
"warnings": []
|
|
58
|
+
}
|