safepropel 1.0.1
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/LICENSE +15 -0
- package/README.md +161 -0
- package/engine/prompt_bundle.enc +0 -0
- package/engine/runtime.js +406 -0
- package/engine/template-parser.js +138 -0
- package/engine/workflow-executor.js +539 -0
- package/package.json +33 -0
- package/safepropel.js +225 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# SafePropel Framework
|
|
2
|
+
|
|
3
|
+
Unified Protection System for AI Workflow Execution with 4-layer security architecture.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **4-Layer Security Architecture**
|
|
8
|
+
- Approach 1: Prompt Compilation (binary format)
|
|
9
|
+
- Approach 2: Encryption (AES-256-GCM)
|
|
10
|
+
- Approach 3: Runtime Prompt Engine (access control)
|
|
11
|
+
- Approach 4: Prompt Firewall (injection/extraction protection)
|
|
12
|
+
|
|
13
|
+
- **35+ Built-in Workflows**
|
|
14
|
+
- Specification & Requirements (create-spec, create-user-stories, create-epics)
|
|
15
|
+
- Architecture & Design (design-architecture, design-model)
|
|
16
|
+
- Development Planning (plan-development-tasks, plan-unit-test, plan-bug-resolution)
|
|
17
|
+
- DevOps & Infrastructure (plan-cicd-pipeline, plan-cloud-infrastructure, create-iac)
|
|
18
|
+
- Testing & Validation (create-test-plan, generate-playwright-scripts, validation-agent)
|
|
19
|
+
- Code Review & Quality (review-code, review-devops-security, analyze-codebase)
|
|
20
|
+
- Agent Orchestration (discovery-agent, backlog-agent, build-feature-agent, bug-fixing-agent)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g safepropel
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
### Basic Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Run a workflow
|
|
34
|
+
safepropel create-spec BRD.txt
|
|
35
|
+
|
|
36
|
+
# Run without input file
|
|
37
|
+
safepropel design-architecture
|
|
38
|
+
|
|
39
|
+
# Show help
|
|
40
|
+
safepropel --help
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### With Encrypted Bundle
|
|
44
|
+
|
|
45
|
+
If using an encrypted bundle, provide a license key:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Via command line
|
|
49
|
+
safepropel create-spec BRD.txt --license-key=your-key-here
|
|
50
|
+
|
|
51
|
+
# Via environment variable
|
|
52
|
+
export SAFEPROPEL_LICENSE_KEY=your-key-here
|
|
53
|
+
safepropel create-spec BRD.txt
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Custom Bundle Path
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Via command line
|
|
60
|
+
safepropel create-spec BRD.txt --bundle=./custom/bundle.enc
|
|
61
|
+
|
|
62
|
+
# Via environment variable
|
|
63
|
+
export SAFEPROPEL_BUNDLE_PATH=./custom/bundle.enc
|
|
64
|
+
safepropel create-spec BRD.txt
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Available Workflows
|
|
68
|
+
|
|
69
|
+
### Specification & Requirements
|
|
70
|
+
- `create-spec` - Generate functional requirements from feature specifications
|
|
71
|
+
- `create-figma-spec` - Generate UX requirements and screen specifications
|
|
72
|
+
- `create-user-stories` - Generate detailed user stories from epics
|
|
73
|
+
- `create-epics` - Generate epic decomposition from specifications
|
|
74
|
+
- `create-project-plan` - Generate comprehensive project plan
|
|
75
|
+
- `create-sprint-plan` - Generate sprint plan with dependency mapping
|
|
76
|
+
|
|
77
|
+
### Architecture & Design
|
|
78
|
+
- `design-architecture` - Generate architecture design documents
|
|
79
|
+
- `design-model` - Generate UML diagrams and architectural models
|
|
80
|
+
- `analyze-codebase` - Comprehensive codebase analysis
|
|
81
|
+
- `analyze-implementation` - Review completed code changes
|
|
82
|
+
- `analyze-ux` - UI/UX analysis with Playwright automation
|
|
83
|
+
|
|
84
|
+
### Development Planning
|
|
85
|
+
- `plan-development-tasks` - Generate implementation tasks
|
|
86
|
+
- `plan-unit-test` - Generate unit test plans
|
|
87
|
+
- `plan-bug-resolution` - Bug triage and resolution planning
|
|
88
|
+
- `implement-tasks` - Execute development tasks
|
|
89
|
+
|
|
90
|
+
### DevOps & Infrastructure
|
|
91
|
+
- `plan-cicd-pipeline` - Design CI/CD pipeline architecture
|
|
92
|
+
- `plan-cloud-infrastructure` - Generate infrastructure specifications
|
|
93
|
+
- `create-iac` - Generate Terraform Infrastructure as Code
|
|
94
|
+
- `create-pipeline-scripts` - Generate CI/CD pipeline scripts
|
|
95
|
+
- `devops-agent` - Orchestrate DevOps phase
|
|
96
|
+
|
|
97
|
+
### Testing & Validation
|
|
98
|
+
- `create-test-plan` - Generate comprehensive test plans
|
|
99
|
+
- `create-automation-test` - Create test workflow specifications
|
|
100
|
+
- `generate-playwright-scripts` - Generate Playwright automation scripts
|
|
101
|
+
- `validation-agent` - Orchestrate test automation
|
|
102
|
+
|
|
103
|
+
### Code Review & Quality
|
|
104
|
+
- `review-code` - Comprehensive code review
|
|
105
|
+
- `review-devops-security` - Security review of DevOps artifacts
|
|
106
|
+
- `pull-request` - Create pull requests with validation
|
|
107
|
+
|
|
108
|
+
### Prototyping & Design
|
|
109
|
+
- `build-prototype` - Transform business hypotheses into prototypes
|
|
110
|
+
- `generate-figma` - Generate Figma artifacts
|
|
111
|
+
- `generate-wireframe` - Generate wireframes from requirements
|
|
112
|
+
|
|
113
|
+
### Agent Orchestration
|
|
114
|
+
- `discovery-agent` - Orchestrate technical discovery
|
|
115
|
+
- `backlog-agent` - Transform specs into backlog
|
|
116
|
+
- `build-feature-agent` - Orchestrate implementation phase
|
|
117
|
+
- `bug-fixing-agent` - Orchestrate bug resolution
|
|
118
|
+
|
|
119
|
+
### Evaluation
|
|
120
|
+
- `evaluate-output` - Validate workflow outputs
|
|
121
|
+
|
|
122
|
+
## Environment Variables
|
|
123
|
+
|
|
124
|
+
- `SAFEPROPEL_LICENSE_KEY` - License key for encrypted bundles
|
|
125
|
+
- `SAFEPROPEL_BUNDLE_PATH` - Custom bundle path (default: ./engine/prompt_bundle.enc)
|
|
126
|
+
|
|
127
|
+
## Security Features
|
|
128
|
+
|
|
129
|
+
### Prompt Protection
|
|
130
|
+
- Prompts never exposed in readable form
|
|
131
|
+
- Binary compilation prevents reverse engineering
|
|
132
|
+
- AES-256-GCM encryption for additional security
|
|
133
|
+
- Runtime access control and audit logging
|
|
134
|
+
|
|
135
|
+
### Input Validation
|
|
136
|
+
- Prompt injection detection and prevention
|
|
137
|
+
- Extraction attempt blocking
|
|
138
|
+
- Input sanitization with configurable strictness
|
|
139
|
+
- Threat pattern recognition
|
|
140
|
+
|
|
141
|
+
### Access Control
|
|
142
|
+
- Controlled content access through runtime engine
|
|
143
|
+
- Audit logging for all bundle accesses
|
|
144
|
+
- License-based access for encrypted bundles
|
|
145
|
+
|
|
146
|
+
## Output
|
|
147
|
+
|
|
148
|
+
Workflows generate output in `.propel/context/docs/` directory with structured documentation following predefined templates.
|
|
149
|
+
|
|
150
|
+
## Requirements
|
|
151
|
+
|
|
152
|
+
- Node.js >= 14.0.0
|
|
153
|
+
- No external dependencies (uses only Node.js built-in modules)
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
ISC
|
|
158
|
+
|
|
159
|
+
## Support
|
|
160
|
+
|
|
161
|
+
For issues, questions, or feature requests, please contact the maintainer.
|
|
Binary file
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SafePropel Runtime Prompt Engine
|
|
3
|
+
*
|
|
4
|
+
* Loads a compiled prompt bundle (.bin) and reconstructs prompts
|
|
5
|
+
* in memory on demand. Prompts never touch disk in readable form.
|
|
6
|
+
*
|
|
7
|
+
* IP PROTECTION: By default, only metadata (paths) is exposed.
|
|
8
|
+
* Content is accessible only through explicit getContent() call.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const runtime = new PromptRuntime();
|
|
12
|
+
* runtime.load('path/to/prompt_bundle.bin');
|
|
13
|
+
*
|
|
14
|
+
* // Get metadata only (safe)
|
|
15
|
+
* const meta = runtime.get('.windsurf/workflows/create-spec.md');
|
|
16
|
+
* console.log(meta.path); // OK - path only
|
|
17
|
+
*
|
|
18
|
+
* // Get content only when needed (keep in memory)
|
|
19
|
+
* const content = runtime.getContent('.windsurf/workflows/create-spec.md');
|
|
20
|
+
* // NEVER log or expose content
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const zlib = require('zlib');
|
|
27
|
+
const crypto = require('crypto');
|
|
28
|
+
|
|
29
|
+
// --- Integrated Encryption (Approach 2) ---
|
|
30
|
+
const ENCRYPTION_ALGORITHM = 'aes-256-gcm';
|
|
31
|
+
const KEY_LENGTH = 32;
|
|
32
|
+
const IV_LENGTH = 12;
|
|
33
|
+
const AUTH_TAG_LENGTH = 16;
|
|
34
|
+
const SALT_LENGTH = 32;
|
|
35
|
+
const PBKDF2_ITERATIONS = 100000;
|
|
36
|
+
|
|
37
|
+
// --- Integrated Firewall (Approach 4) ---
|
|
38
|
+
const INJECTION_PATTERNS = [
|
|
39
|
+
/ignore\s+(all\s+)?(previous|prior|above|system)\s+(instructions?|prompts?|rules?)/i,
|
|
40
|
+
/disregard\s+(all\s+)?(previous|prior|above|system)\s+(instructions?|prompts?|rules?)/i,
|
|
41
|
+
/forget\s+(all\s+)?(previous|prior|above|system)\s+(instructions?|prompts?|rules?)/i,
|
|
42
|
+
/you\s+are\s+now\s+(a|an)\s+/i,
|
|
43
|
+
/act\s+as\s+(if\s+)?(you\s+are|a|an)\s+/i,
|
|
44
|
+
/show\s+(me\s+)?(your|the)\s+(system\s+)?(prompt|instruction|rule)/i,
|
|
45
|
+
/print\s+(your|the)\s+(system\s+)?(prompt|instruction|rule)/i,
|
|
46
|
+
/reveal\s+(your|the)\s+(system\s+)?(prompt|instruction|rule)/i,
|
|
47
|
+
/repeat\s+(everything|all)\s+(above|before)/i,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// --- Constants ---------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
const MAGIC_COMPILED = Buffer.from('SPBL');
|
|
53
|
+
const MAGIC_ENCRYPTED = Buffer.from('SPEC');
|
|
54
|
+
const HEADER_SIZE_COMPILED = 16; // 4 magic + 4 version + 4 count + 4 payload size
|
|
55
|
+
|
|
56
|
+
// --- Runtime Engine ----------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
class PromptRuntime {
|
|
59
|
+
constructor(options = {}) {
|
|
60
|
+
this._manifest = null;
|
|
61
|
+
this._index = new Map(); // path → entry
|
|
62
|
+
this._typeIndex = new Map(); // type → [entry]
|
|
63
|
+
this._licenseKey = null;
|
|
64
|
+
this._isEncrypted = false;
|
|
65
|
+
|
|
66
|
+
// Integrated Firewall (Approach 4)
|
|
67
|
+
this.firewallEnabled = options.firewallEnabled !== false;
|
|
68
|
+
this.firewallStrictMode = options.firewallStrictMode || false;
|
|
69
|
+
this.maxInputLength = options.maxInputLength || 50000;
|
|
70
|
+
|
|
71
|
+
// Access Control (Approach 3)
|
|
72
|
+
this.accessControl = {
|
|
73
|
+
allowContentAccess: options.allowContentAccess !== false,
|
|
74
|
+
logAccess: options.logAccess !== false
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Security metrics
|
|
78
|
+
this.metrics = {
|
|
79
|
+
bundleLoads: 0,
|
|
80
|
+
contentAccesses: 0,
|
|
81
|
+
firewallBlocks: 0,
|
|
82
|
+
decryptionAttempts: 0
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Derives encryption key from license key.
|
|
88
|
+
*/
|
|
89
|
+
_deriveKey(licenseKey, salt) {
|
|
90
|
+
return crypto.pbkdf2Sync(
|
|
91
|
+
licenseKey,
|
|
92
|
+
salt,
|
|
93
|
+
PBKDF2_ITERATIONS,
|
|
94
|
+
KEY_LENGTH,
|
|
95
|
+
'sha256'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Decrypts encrypted bundle (Approach 2).
|
|
101
|
+
*/
|
|
102
|
+
_decryptBundle(encryptedData, licenseKey) {
|
|
103
|
+
let offset = 0;
|
|
104
|
+
|
|
105
|
+
const magic = encryptedData.subarray(offset, offset + 4);
|
|
106
|
+
offset += 4;
|
|
107
|
+
if (!magic.equals(MAGIC_ENCRYPTED)) {
|
|
108
|
+
throw new Error('Invalid encrypted bundle: bad magic marker');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const version = encryptedData.readUInt32LE(offset);
|
|
112
|
+
offset += 4;
|
|
113
|
+
|
|
114
|
+
const saltLength = encryptedData.readUInt32LE(offset);
|
|
115
|
+
offset += 4;
|
|
116
|
+
const salt = encryptedData.subarray(offset, offset + saltLength);
|
|
117
|
+
offset += saltLength;
|
|
118
|
+
|
|
119
|
+
const ivLength = encryptedData.readUInt32LE(offset);
|
|
120
|
+
offset += 4;
|
|
121
|
+
const iv = encryptedData.subarray(offset, offset + ivLength);
|
|
122
|
+
offset += ivLength;
|
|
123
|
+
|
|
124
|
+
const authTagLength = encryptedData.readUInt32LE(offset);
|
|
125
|
+
offset += 4;
|
|
126
|
+
const authTag = encryptedData.subarray(offset, offset + authTagLength);
|
|
127
|
+
offset += authTagLength;
|
|
128
|
+
|
|
129
|
+
const payloadLength = encryptedData.readUInt32LE(offset);
|
|
130
|
+
offset += 4;
|
|
131
|
+
const encrypted = encryptedData.subarray(offset, offset + payloadLength);
|
|
132
|
+
|
|
133
|
+
const key = this._deriveKey(licenseKey, salt);
|
|
134
|
+
const decipher = crypto.createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);
|
|
135
|
+
decipher.setAuthTag(authTag);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
return Buffer.concat([
|
|
139
|
+
decipher.update(encrypted),
|
|
140
|
+
decipher.final()
|
|
141
|
+
]);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new Error('Decryption failed: invalid license key or corrupted bundle');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Validates input through integrated firewall (Approach 4).
|
|
149
|
+
*/
|
|
150
|
+
validateInput(input) {
|
|
151
|
+
if (!this.firewallEnabled) {
|
|
152
|
+
return { allowed: true, sanitized: input, threats: [] };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const threats = [];
|
|
156
|
+
let sanitized = input;
|
|
157
|
+
|
|
158
|
+
// Length check
|
|
159
|
+
if (input.length > this.maxInputLength) {
|
|
160
|
+
threats.push('input_too_long');
|
|
161
|
+
if (this.firewallStrictMode) {
|
|
162
|
+
this.metrics.firewallBlocks++;
|
|
163
|
+
return {
|
|
164
|
+
allowed: false,
|
|
165
|
+
sanitized: '',
|
|
166
|
+
threats,
|
|
167
|
+
message: `Input exceeds maximum length of ${this.maxInputLength} characters.`
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
sanitized = sanitized.substring(0, this.maxInputLength);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Injection/extraction detection
|
|
174
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
175
|
+
if (pattern.test(sanitized)) {
|
|
176
|
+
threats.push('prompt_injection_or_extraction');
|
|
177
|
+
if (this.firewallStrictMode) {
|
|
178
|
+
this.metrics.firewallBlocks++;
|
|
179
|
+
return {
|
|
180
|
+
allowed: false,
|
|
181
|
+
sanitized: '',
|
|
182
|
+
threats,
|
|
183
|
+
message: 'Potential prompt injection/extraction detected. Request blocked.'
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Sanitize in permissive mode
|
|
187
|
+
sanitized = sanitized.replace(pattern, '[REMOVED]');
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
allowed: true,
|
|
194
|
+
sanitized,
|
|
195
|
+
threats,
|
|
196
|
+
message: threats.length > 0 ? 'Input sanitized for security.' : 'Input validated successfully.'
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Loads and validates a bundle (encrypted or compiled).
|
|
202
|
+
* Automatically detects format and decrypts if needed.
|
|
203
|
+
* Prompts are decompressed into memory — nothing is written to disk.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} bundlePath Absolute path to the bundle file.
|
|
206
|
+
* @param {string} [licenseKey] License key for encrypted bundles.
|
|
207
|
+
*/
|
|
208
|
+
load(bundlePath, licenseKey = null) {
|
|
209
|
+
const raw = fs.readFileSync(bundlePath);
|
|
210
|
+
|
|
211
|
+
// Detect bundle format
|
|
212
|
+
const magic = raw.subarray(0, 4);
|
|
213
|
+
let compiledData;
|
|
214
|
+
|
|
215
|
+
if (magic.equals(MAGIC_ENCRYPTED)) {
|
|
216
|
+
// Encrypted bundle - decrypt first (Approach 2)
|
|
217
|
+
if (!licenseKey) {
|
|
218
|
+
throw new Error('License key required for encrypted bundle');
|
|
219
|
+
}
|
|
220
|
+
this._licenseKey = licenseKey;
|
|
221
|
+
this._isEncrypted = true;
|
|
222
|
+
this.metrics.decryptionAttempts++;
|
|
223
|
+
|
|
224
|
+
compiledData = this._decryptBundle(raw, licenseKey);
|
|
225
|
+
} else if (magic.equals(MAGIC_COMPILED)) {
|
|
226
|
+
// Compiled bundle - use directly
|
|
227
|
+
compiledData = raw;
|
|
228
|
+
this._isEncrypted = false;
|
|
229
|
+
} else {
|
|
230
|
+
throw new Error('Invalid bundle format: unrecognized magic marker');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Load compiled bundle (Approach 1)
|
|
234
|
+
this._loadCompiled(compiledData);
|
|
235
|
+
this.metrics.bundleLoads++;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Loads compiled bundle data.
|
|
240
|
+
*/
|
|
241
|
+
_loadCompiled(raw) {
|
|
242
|
+
|
|
243
|
+
// --- Validate header ---
|
|
244
|
+
if (raw.length < HEADER_SIZE_COMPILED) {
|
|
245
|
+
throw new Error('Invalid bundle: file too small.');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const magic = raw.subarray(0, 4);
|
|
249
|
+
if (!magic.equals(MAGIC_COMPILED)) {
|
|
250
|
+
throw new Error('Invalid bundle: bad magic marker.');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const version = raw.readUInt32LE(4);
|
|
254
|
+
const entryCount = raw.readUInt32LE(8);
|
|
255
|
+
const payloadSize = raw.readUInt32LE(12);
|
|
256
|
+
|
|
257
|
+
if (raw.length < HEADER_SIZE_COMPILED + payloadSize) {
|
|
258
|
+
throw new Error('Invalid bundle: truncated payload.');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// --- Decompress ---
|
|
262
|
+
const compressed = raw.subarray(HEADER_SIZE_COMPILED, HEADER_SIZE_COMPILED + payloadSize);
|
|
263
|
+
const jsonBuf = zlib.inflateSync(compressed);
|
|
264
|
+
const manifest = JSON.parse(jsonBuf.toString('utf-8'));
|
|
265
|
+
|
|
266
|
+
if (manifest.entry_count !== entryCount) {
|
|
267
|
+
throw new Error('Bundle integrity error: entry count mismatch.');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this._manifest = manifest;
|
|
271
|
+
|
|
272
|
+
// --- Build indexes ---
|
|
273
|
+
this._index.clear();
|
|
274
|
+
this._typeIndex.clear();
|
|
275
|
+
|
|
276
|
+
for (const entry of manifest.entries) {
|
|
277
|
+
this._index.set(entry.path, entry);
|
|
278
|
+
|
|
279
|
+
if (!this._typeIndex.has(entry.type)) {
|
|
280
|
+
this._typeIndex.set(entry.type, []);
|
|
281
|
+
}
|
|
282
|
+
this._typeIndex.get(entry.type).push(entry);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Returns bundle metadata (without exposing prompt contents).
|
|
288
|
+
*/
|
|
289
|
+
info() {
|
|
290
|
+
if (!this._manifest) throw new Error('No bundle loaded.');
|
|
291
|
+
return {
|
|
292
|
+
version: this._manifest.version,
|
|
293
|
+
compiled_at: this._manifest.compiled_at,
|
|
294
|
+
framework: this._manifest.framework_root,
|
|
295
|
+
entry_count: this._manifest.entry_count,
|
|
296
|
+
encrypted: this._isEncrypted,
|
|
297
|
+
firewallEnabled: this.firewallEnabled,
|
|
298
|
+
types: Object.fromEntries(
|
|
299
|
+
[...this._typeIndex.entries()].map(([t, arr]) => [t, arr.length])
|
|
300
|
+
),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Lists all entries in the bundle.
|
|
306
|
+
* Returns metadata only (paths, types) - NO CONTENT.
|
|
307
|
+
*
|
|
308
|
+
* @param {string} [type] Optional filter by entry type.
|
|
309
|
+
* @returns {Array<{ path: string, type: string, id: string }>}
|
|
310
|
+
*/
|
|
311
|
+
list(type) {
|
|
312
|
+
if (!this._manifest) throw new Error('No bundle loaded.');
|
|
313
|
+
|
|
314
|
+
let entries = this._manifest.entries;
|
|
315
|
+
if (type) {
|
|
316
|
+
entries = this._typeIndex.get(type) || [];
|
|
317
|
+
}
|
|
318
|
+
// Return metadata only - NO CONTENT
|
|
319
|
+
return entries.map((e) => ({ path: e.path, type: e.type, id: e.id }));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Retrieves a single prompt/instruction by its relative path.
|
|
324
|
+
* Content is reconstructed in memory from the compiled bundle.
|
|
325
|
+
* Includes access control (Approach 3).
|
|
326
|
+
*
|
|
327
|
+
* @param {string} relativePath e.g. ".windsurf/workflows/create-spec.md"
|
|
328
|
+
* @returns {{ path: string, type: string, frontmatter: string|null, content: string }}
|
|
329
|
+
*/
|
|
330
|
+
get(relativePath) {
|
|
331
|
+
if (!this._manifest) throw new Error('No bundle loaded.');
|
|
332
|
+
|
|
333
|
+
// Access control check
|
|
334
|
+
if (!this.accessControl.allowContentAccess) {
|
|
335
|
+
throw new Error('Content access is disabled');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const entry = this._index.get(relativePath);
|
|
339
|
+
if (!entry) {
|
|
340
|
+
throw new Error('Entry not found in bundle: ' + relativePath);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Reconstruct content from base64 — exists only in memory.
|
|
344
|
+
const content = Buffer.from(entry.content, 'base64').toString('utf-8');
|
|
345
|
+
|
|
346
|
+
this.metrics.contentAccesses++;
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
path: entry.path,
|
|
350
|
+
type: entry.type,
|
|
351
|
+
frontmatter: entry.frontmatter,
|
|
352
|
+
content,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Retrieves all entries of a given type with their reconstructed content.
|
|
358
|
+
*
|
|
359
|
+
* @param {string} type One of: agent, instruction, prompt, template, context, config
|
|
360
|
+
* @returns {Array<{ path: string, type: string, frontmatter: string|null, content: string }>}
|
|
361
|
+
*/
|
|
362
|
+
getByType(type) {
|
|
363
|
+
if (!this._manifest) throw new Error('No bundle loaded.');
|
|
364
|
+
|
|
365
|
+
const entries = this._typeIndex.get(type) || [];
|
|
366
|
+
return entries.map((entry) => ({
|
|
367
|
+
path: entry.path,
|
|
368
|
+
type: entry.type,
|
|
369
|
+
frontmatter: entry.frontmatter,
|
|
370
|
+
content: Buffer.from(entry.content, 'base64').toString('utf-8'),
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Verifies bundle integrity by comparing stored entry count with actual.
|
|
376
|
+
* @returns {{ valid: boolean, encrypted: boolean, details: string }}
|
|
377
|
+
*/
|
|
378
|
+
verify() {
|
|
379
|
+
if (!this._manifest) throw new Error('No bundle loaded.');
|
|
380
|
+
|
|
381
|
+
const expected = this._manifest.entry_count;
|
|
382
|
+
const actual = this._manifest.entries.length;
|
|
383
|
+
const valid = expected === actual;
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
valid,
|
|
387
|
+
encrypted: this._isEncrypted,
|
|
388
|
+
details: valid
|
|
389
|
+
? `Bundle OK — ${actual} entries verified.`
|
|
390
|
+
: `MISMATCH — header says ${expected}, manifest has ${actual}.`,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Gets security metrics.
|
|
396
|
+
*/
|
|
397
|
+
getMetrics() {
|
|
398
|
+
return {
|
|
399
|
+
...this.metrics,
|
|
400
|
+
firewallEnabled: this.firewallEnabled,
|
|
401
|
+
firewallStrictMode: this.firewallStrictMode
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
module.exports = { PromptRuntime };
|