@ciphrix/cli 0.1.1 → 0.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/README.md +81 -5
- package/dist/actions.d.ts +3 -1
- package/dist/actions.js +16 -7
- package/dist/api.d.ts +3 -1
- package/dist/api.js +7 -3
- package/dist/cli.js +20 -5
- package/dist/commands/capabilities.d.ts +6 -0
- package/dist/commands/capabilities.js +311 -0
- package/dist/commands/capabilityDefinitions.d.ts +18 -0
- package/dist/commands/capabilityDefinitions.js +244 -0
- package/dist/commands/control.d.ts +2 -1
- package/dist/commands/control.js +11 -1
- package/dist/commands/framework.js +2 -1
- package/dist/commands/policyActions.js +12 -2
- package/dist/commands/test.d.ts +2 -2
- package/dist/commands/test.js +18 -3
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/manifest.d.ts +19 -0
- package/dist/manifest.js +93 -0
- package/dist/toolSurface.d.ts +0 -8
- package/dist/toolSurface.js +8 -44
- package/dist/upload.d.ts +10 -0
- package/dist/upload.js +31 -11
- package/package.json +1 -1
- package/skills/ciphrix/SKILL.md +42 -3
- package/skills/ciphrix/references/commands.md +1656 -7
- package/skills/ciphrix/references/document-workflows.md +62 -0
package/dist/upload.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { getTool, ManifestError } from './manifest.js';
|
|
2
3
|
import { constants } from 'node:fs';
|
|
3
4
|
import { lstat, open } from 'node:fs/promises';
|
|
4
5
|
import { basename } from 'node:path';
|
|
@@ -21,13 +22,15 @@ const MIME_TYPES = {
|
|
|
21
22
|
};
|
|
22
23
|
/** Maximum evidence file size read and staged by the CLI (25 MiB, matching the tool surface). */
|
|
23
24
|
export const DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
24
|
-
|
|
25
|
+
export class FileInputError extends Error {
|
|
26
|
+
}
|
|
27
|
+
export const readUploadFile = async (filePath, maxBytes) => {
|
|
25
28
|
const initialStat = await lstat(filePath);
|
|
26
29
|
if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
|
|
27
|
-
throw new
|
|
30
|
+
throw new FileInputError('Upload path must be a regular file, not a symlink or special file.');
|
|
28
31
|
}
|
|
29
32
|
if (initialStat.size > maxBytes) {
|
|
30
|
-
throw new
|
|
33
|
+
throw new FileInputError(`Upload exceeds the ${maxBytes}-byte file size limit.`);
|
|
31
34
|
}
|
|
32
35
|
const noFollow = constants.O_NOFOLLOW ?? 0;
|
|
33
36
|
const file = await open(filePath, constants.O_RDONLY | noFollow);
|
|
@@ -36,24 +39,24 @@ const readUploadFile = async (filePath, maxBytes) => {
|
|
|
36
39
|
if (!openedStat.isFile() ||
|
|
37
40
|
openedStat.dev !== initialStat.dev ||
|
|
38
41
|
openedStat.ino !== initialStat.ino) {
|
|
39
|
-
throw new
|
|
42
|
+
throw new FileInputError('Upload path changed while it was being opened. Please retry.');
|
|
40
43
|
}
|
|
41
44
|
if (openedStat.size > maxBytes) {
|
|
42
|
-
throw new
|
|
45
|
+
throw new FileInputError(`Upload exceeds the ${maxBytes}-byte file size limit.`);
|
|
43
46
|
}
|
|
44
47
|
const chunks = [];
|
|
45
48
|
let total = 0;
|
|
46
49
|
while (true) {
|
|
47
50
|
const remaining = maxBytes + 1 - total;
|
|
48
51
|
if (remaining <= 0)
|
|
49
|
-
throw new
|
|
52
|
+
throw new FileInputError(`Upload exceeds the ${maxBytes}-byte file size limit.`);
|
|
50
53
|
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, remaining));
|
|
51
54
|
const { bytesRead } = await file.read(chunk, 0, chunk.length, null);
|
|
52
55
|
if (bytesRead === 0)
|
|
53
56
|
break;
|
|
54
57
|
total += bytesRead;
|
|
55
58
|
if (total > maxBytes)
|
|
56
|
-
throw new
|
|
59
|
+
throw new FileInputError(`Upload exceeds the ${maxBytes}-byte file size limit.`);
|
|
57
60
|
chunks.push(chunk.subarray(0, bytesRead));
|
|
58
61
|
}
|
|
59
62
|
return Buffer.concat(chunks, total);
|
|
@@ -72,16 +75,26 @@ const contentTypeFor = (filename) => {
|
|
|
72
75
|
* to a target (a test run, a risk, a vendor). The file is sent as multipart to the tool surface with
|
|
73
76
|
* the bearer credential, so no cookie or CSRF token is involved.
|
|
74
77
|
*/
|
|
75
|
-
export const
|
|
78
|
+
export const stageLocalFile = async (ctx, filePath, purpose, limits = {}, descriptor, idempotencyKey = randomUUID()) => {
|
|
79
|
+
const tool = descriptor ?? (await getTool(ctx, 'upload_file'));
|
|
80
|
+
if (tool.effect !== 'write' || tool.confirmation !== 'none') {
|
|
81
|
+
throw new ManifestError('The server does not support immediate file staging.');
|
|
82
|
+
}
|
|
83
|
+
const properties = asRecord(tool.inputSchema.properties);
|
|
84
|
+
const purposes = asRecord(properties.purpose).enum;
|
|
85
|
+
if (!Array.isArray(purposes) || !purposes.includes(purpose))
|
|
86
|
+
throw new Error('Unsupported upload purpose.');
|
|
87
|
+
if (!idempotencyKey.trim() || idempotencyKey.length > 256)
|
|
88
|
+
throw new Error('The idempotency key must contain 1 to 256 characters.');
|
|
76
89
|
const filename = basename(filePath);
|
|
77
90
|
const contents = await readUploadFile(filePath, limits.maxUploadBytes ?? DEFAULT_MAX_UPLOAD_BYTES);
|
|
78
91
|
const form = new FormData();
|
|
79
92
|
form.append('purpose', purpose);
|
|
80
93
|
form.append('file', new Blob([new Uint8Array(contents)], { type: contentTypeFor(filename) }), filename);
|
|
81
94
|
return withRequestTimeout(limits.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS, async (signal) => {
|
|
82
|
-
const response = await fetchWithoutRedirects(`${ctx.baseUrl}/tools/v1
|
|
95
|
+
const response = await fetchWithoutRedirects(`${ctx.baseUrl}/tools/v1/${encodeURIComponent(tool.name)}`, {
|
|
83
96
|
method: 'POST',
|
|
84
|
-
headers: { authorization: `Bearer ${ctx.token}`, 'idempotency-key':
|
|
97
|
+
headers: { authorization: `Bearer ${ctx.token}`, 'idempotency-key': idempotencyKey },
|
|
85
98
|
body: form,
|
|
86
99
|
signal,
|
|
87
100
|
});
|
|
@@ -100,7 +113,9 @@ export const uploadLocalFile = async (ctx, filePath, purpose, limits = {}) => {
|
|
|
100
113
|
const message = typeof asRecord(envelope.error).message === 'string'
|
|
101
114
|
? asRecord(envelope.error).message
|
|
102
115
|
: `Upload failed with status ${response.status}`;
|
|
103
|
-
throw new ApiError(message, response.status)
|
|
116
|
+
throw new ApiError(message, response.status, typeof asRecord(envelope.error).code === 'string'
|
|
117
|
+
? String(asRecord(envelope.error).code)
|
|
118
|
+
: undefined, asRecord(envelope.error).details, envelope);
|
|
104
119
|
}
|
|
105
120
|
const payload = asRecord(envelope.data);
|
|
106
121
|
const id = typeof payload.uploadId === 'string' ? payload.uploadId : null;
|
|
@@ -108,8 +123,13 @@ export const uploadLocalFile = async (ctx, filePath, purpose, limits = {}) => {
|
|
|
108
123
|
throw new ApiError('Upload did not return an id', 502);
|
|
109
124
|
return {
|
|
110
125
|
id,
|
|
126
|
+
envelope,
|
|
111
127
|
filename: typeof payload.filename === 'string' ? payload.filename : filename,
|
|
112
128
|
size: typeof payload.size === 'number' ? payload.size : contents.length,
|
|
113
129
|
};
|
|
114
130
|
});
|
|
115
131
|
};
|
|
132
|
+
export const uploadLocalFile = async (ctx, filePath, purpose, limits = {}) => {
|
|
133
|
+
const { id, filename, size } = await stageLocalFile(ctx, filePath, purpose, limits);
|
|
134
|
+
return { id, filename, size };
|
|
135
|
+
};
|
package/package.json
CHANGED
package/skills/ciphrix/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ciphrix
|
|
3
|
-
description: Operate a Ciphrix compliance programme with the `ciphrix` CLI. Use
|
|
3
|
+
description: Operate a Ciphrix compliance programme with the `ciphrix` CLI. Use for onboarding and organizational context, documents, frameworks, controls, tests and evidence, access reviews and imports, agent findings, assigned tasks, risks, vendors, assets, monitoring checks, or background jobs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Ciphrix
|
|
@@ -83,8 +83,9 @@ Two consequences worth internalising:
|
|
|
83
83
|
addressed by their human name **or** their stable code (for example `ACME-DOC-A1B2`, `TSQ-001`,
|
|
84
84
|
`ACME-RSK-CQJ4`, `ACME-AST-M5SD`). Ids work but are never required, and you never invent one. Prefer the
|
|
85
85
|
code when a name is ambiguous.
|
|
86
|
-
- **Intents, not
|
|
87
|
-
|
|
86
|
+
- **Intents, not hidden decisions.** An agent may coordinate explicitly authorized platform operations
|
|
87
|
+
for one user request. Each operation remains separate: stop on errors or uncertain outcomes and never
|
|
88
|
+
infer a formal decision merely because it would let the next step proceed.
|
|
88
89
|
- **Ask only for decisions.** Only actions that change something a person should review stop for
|
|
89
90
|
confirmation — deleting, editing content, linking, or changing a control, clause, test or asset. A
|
|
90
91
|
formal decision is its own verb (`approve`, `submit`, `test run result`) and applies directly. Everything
|
|
@@ -140,6 +141,44 @@ are what a check raised. `check disable` silences a check for the tenant — rec
|
|
|
140
141
|
|
|
141
142
|
## Guardrails
|
|
142
143
|
|
|
144
|
+
**Document workflow.** Configuration, request sending, review completion, ordinary approval and
|
|
145
|
+
self-approval are separate intents. Sending for approval never authorizes completing review first.
|
|
146
|
+
When a recipient setter reports an already-sent request, explain the current stage and offer recall,
|
|
147
|
+
configure and resend, even if the requested recipient is unchanged. Proceed only with authorization;
|
|
148
|
+
recall clears the recipient, so set them again before resending rather than removing an already-cleared
|
|
149
|
+
assignment. Unsent same-recipient selection reports already set. Read again between recovery steps and
|
|
150
|
+
use a separate write key per operation. Employees may be assigned and complete decisions in the app;
|
|
151
|
+
user discovery does not grant tool access. Use exact active User IDs/emails, not People IDs or fuzzy
|
|
152
|
+
name matches. Workflow readiness is business guidance, not permission or proof of notification delivery.
|
|
153
|
+
Before coordinating a document request or recovery, read
|
|
154
|
+
[the workflow guide](references/document-workflows.md) for stage-specific sequencing and result handling;
|
|
155
|
+
use installed help or [the generated reference](references/commands.md) for exact syntax.
|
|
156
|
+
|
|
157
|
+
**Context and onboarding.** Read the server status and question schema, then save only facts agreed
|
|
158
|
+
with the user. Business and Operating Context are separate. Let the server decide readiness; initial
|
|
159
|
+
setup is optional and journey completion is explicit. A context save never authorizes starting agents,
|
|
160
|
+
initializing a framework or completing onboarding. Replayed packs are snapshots: read status again for
|
|
161
|
+
current readiness.
|
|
162
|
+
|
|
163
|
+
**Access reviews and proposals.** Inventory is not a review decision. Inspect proposal rows, matches,
|
|
164
|
+
source evidence and current revisions before selecting a merge. Never manufacture person-match or
|
|
165
|
+
visual-evidence confirmation. Keep/revoke decisions, reviewer-task completion and campaign ending are
|
|
166
|
+
distinct human intents. A revoke decision is a removal request, not provider-side revocation. Agent
|
|
167
|
+
findings/options and AI assessments are advisory; inspect the complete current proposal and exact
|
|
168
|
+
declared option before any explicit decision. Assigned-task discovery grants no completion authority.
|
|
169
|
+
|
|
170
|
+
**Writes and uncertainty.** Discover capabilities through the server, use the exact reviewed inputs,
|
|
171
|
+
and treat confirmation as explicit delegated consent, not an obstacle to automate around. Retain one
|
|
172
|
+
idempotency key per logical operation; never retry unknown outcomes with a new key. The CLI performs no
|
|
173
|
+
automatic write retries and does not promise confirmed recovery across invocations or expired tokens.
|
|
174
|
+
Inspect state/jobs when uncertain. Batch results can partially apply; preserve and report failures.
|
|
175
|
+
Advisory assessments never authorize a formal passing result.
|
|
176
|
+
|
|
177
|
+
**Files.** Stage and process only the files the user selected. General processing may update context
|
|
178
|
+
or create vendor records; staging alone does not authorize that processing. Imports are proposed work,
|
|
179
|
+
not merged inventory. Temporary evidence/export references expire; do not forward credentials to them,
|
|
180
|
+
claim bytes were downloaded when only a reference was returned, or treat a replayed URL as renewed.
|
|
181
|
+
|
|
143
182
|
- Work only in the tenant and API environment the authenticated user selected. Never discover or switch
|
|
144
183
|
tenants, endpoints or credential stores by guessing.
|
|
145
184
|
- Do not expose access tokens, stored credentials or sensitive response data in prose, logs or commands.
|