@intutic/cli 2.2.0 → 2.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 +8 -8
- package/dist/cli.d.ts +0 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +481 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/apiCommand.d.ts +40 -0
- package/dist/commands/apiCommand.d.ts.map +1 -0
- package/dist/commands/apiCommand.js +88 -0
- package/dist/commands/apiCommand.js.map +1 -0
- package/dist/commands/budget.d.ts +4 -0
- package/dist/commands/budget.d.ts.map +1 -1
- package/dist/commands/budget.js +8 -0
- package/dist/commands/budget.js.map +1 -1
- package/dist/commands/compliance.d.ts +122 -0
- package/dist/commands/compliance.d.ts.map +1 -0
- package/dist/commands/compliance.js +278 -0
- package/dist/commands/compliance.js.map +1 -0
- package/dist/commands/connect.d.ts +26 -1
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +109 -51
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/credentials.d.ts +1 -1
- package/dist/commands/credentials.js +1 -1
- package/dist/commands/decision.d.ts +4 -4
- package/dist/commands/decision.js +4 -4
- package/dist/commands/disconnect.d.ts +4 -0
- package/dist/commands/disconnect.d.ts.map +1 -1
- package/dist/commands/disconnect.js +17 -3
- package/dist/commands/disconnect.js.map +1 -1
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +0 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/enforce.d.ts +1 -1
- package/dist/commands/enforce.js +1 -1
- package/dist/commands/enterprise.d.ts +3 -2
- package/dist/commands/enterprise.d.ts.map +1 -1
- package/dist/commands/enterprise.js +11 -3
- package/dist/commands/enterprise.js.map +1 -1
- package/dist/commands/env.js +1 -1
- package/dist/commands/exec.d.ts +2 -3
- package/dist/commands/exec.d.ts.map +1 -1
- package/dist/commands/exec.js +4 -5
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/gateLiveness.d.ts +15 -0
- package/dist/commands/gateLiveness.d.ts.map +1 -0
- package/dist/commands/gateLiveness.js +34 -0
- package/dist/commands/gateLiveness.js.map +1 -0
- package/dist/commands/gateway.d.ts +14 -4
- package/dist/commands/gateway.d.ts.map +1 -1
- package/dist/commands/gateway.js +34 -4
- package/dist/commands/gateway.js.map +1 -1
- package/dist/commands/github.d.ts +20 -0
- package/dist/commands/github.d.ts.map +1 -0
- package/dist/commands/github.js +41 -0
- package/dist/commands/github.js.map +1 -0
- package/dist/commands/guardrails.d.ts +28 -2
- package/dist/commands/guardrails.d.ts.map +1 -1
- package/dist/commands/guardrails.js +233 -24
- package/dist/commands/guardrails.js.map +1 -1
- package/dist/commands/init.d.ts +1 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install-daemon.d.ts +1 -1
- package/dist/commands/install-daemon.js +2 -2
- package/dist/commands/integrity.d.ts +10 -2
- package/dist/commands/integrity.d.ts.map +1 -1
- package/dist/commands/integrity.js +3 -3
- package/dist/commands/integrity.js.map +1 -1
- package/dist/commands/inventory.d.ts +60 -0
- package/dist/commands/inventory.d.ts.map +1 -0
- package/dist/commands/inventory.js +141 -0
- package/dist/commands/inventory.js.map +1 -0
- package/dist/commands/judge.d.ts +1 -1
- package/dist/commands/judge.js +2 -2
- package/dist/commands/judge.js.map +1 -1
- package/dist/commands/login.d.ts +0 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +0 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts +0 -1
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +0 -1
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/mcp.d.ts +31 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +65 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/notifications.d.ts +54 -0
- package/dist/commands/notifications.d.ts.map +1 -0
- package/dist/commands/notifications.js +151 -0
- package/dist/commands/notifications.js.map +1 -0
- package/dist/commands/org.d.ts +3 -3
- package/dist/commands/org.js +3 -3
- package/dist/commands/policy.d.ts +6 -1
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +77 -37
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/rollback.d.ts.map +1 -1
- package/dist/commands/rollback.js +3 -1
- package/dist/commands/rollback.js.map +1 -1
- package/dist/commands/routing.d.ts +1 -1
- package/dist/commands/routing.js +1 -1
- package/dist/commands/rules.d.ts +74 -0
- package/dist/commands/rules.d.ts.map +1 -0
- package/dist/commands/rules.js +283 -0
- package/dist/commands/rules.js.map +1 -0
- package/dist/commands/settings.d.ts +43 -0
- package/dist/commands/settings.d.ts.map +1 -0
- package/dist/commands/settings.js +108 -0
- package/dist/commands/settings.js.map +1 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +1 -1
- package/dist/commands/siem.d.ts +53 -0
- package/dist/commands/siem.d.ts.map +1 -0
- package/dist/commands/siem.js +132 -0
- package/dist/commands/siem.js.map +1 -0
- package/dist/commands/skill.d.ts +5 -5
- package/dist/commands/skill.js +7 -7
- package/dist/commands/skill.js.map +1 -1
- package/dist/commands/status.d.ts +0 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +2 -3
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/syncContext.d.ts +0 -1
- package/dist/commands/syncContext.d.ts.map +1 -1
- package/dist/commands/syncContext.js +0 -1
- package/dist/commands/syncContext.js.map +1 -1
- package/dist/commands/team.d.ts +2 -2
- package/dist/commands/team.js +2 -2
- package/dist/commands/traces.d.ts +0 -3
- package/dist/commands/traces.d.ts.map +1 -1
- package/dist/commands/traces.js +0 -3
- package/dist/commands/traces.js.map +1 -1
- package/dist/commands/usage.d.ts +41 -0
- package/dist/commands/usage.d.ts.map +1 -0
- package/dist/commands/usage.js +129 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/commands/whoami.d.ts +0 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +0 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/config/keychain.d.ts +0 -1
- package/dist/config/keychain.d.ts.map +1 -1
- package/dist/config/keychain.js +0 -1
- package/dist/config/keychain.js.map +1 -1
- package/dist/config/paths.d.ts +0 -1
- package/dist/config/paths.d.ts.map +1 -1
- package/dist/config/paths.js +0 -1
- package/dist/config/paths.js.map +1 -1
- package/dist/config/store.d.ts +0 -1
- package/dist/config/store.d.ts.map +1 -1
- package/dist/config/store.js +0 -1
- package/dist/config/store.js.map +1 -1
- package/dist/harness/ag2.d.ts +1 -1
- package/dist/harness/ag2.js +1 -1
- package/dist/harness/agentFramework.d.ts +1 -1
- package/dist/harness/agentFramework.js +1 -1
- package/dist/harness/agenticOrchestrator.d.ts +1 -1
- package/dist/harness/agenticOrchestrator.js +2 -2
- package/dist/harness/agenticOrchestrator.js.map +1 -1
- package/dist/harness/agentsMd.d.ts +24 -0
- package/dist/harness/agentsMd.d.ts.map +1 -0
- package/dist/harness/agentsMd.js +28 -0
- package/dist/harness/agentsMd.js.map +1 -0
- package/dist/harness/aider.d.ts +3 -1
- package/dist/harness/aider.d.ts.map +1 -1
- package/dist/harness/aider.js +9 -7
- package/dist/harness/aider.js.map +1 -1
- package/dist/harness/antigravity.d.ts +23 -5
- package/dist/harness/antigravity.d.ts.map +1 -1
- package/dist/harness/antigravity.js +45 -55
- package/dist/harness/antigravity.js.map +1 -1
- package/dist/harness/autogen.d.ts +1 -1
- package/dist/harness/autogen.js +2 -2
- package/dist/harness/autogen.js.map +1 -1
- package/dist/harness/claudeCode.d.ts +22 -1
- package/dist/harness/claudeCode.d.ts.map +1 -1
- package/dist/harness/claudeCode.js +47 -3
- package/dist/harness/claudeCode.js.map +1 -1
- package/dist/harness/cline.d.ts.map +1 -1
- package/dist/harness/cline.js +14 -23
- package/dist/harness/cline.js.map +1 -1
- package/dist/harness/codex.d.ts +6 -1
- package/dist/harness/codex.d.ts.map +1 -1
- package/dist/harness/codex.js +23 -21
- package/dist/harness/codex.js.map +1 -1
- package/dist/harness/continue.d.ts +10 -4
- package/dist/harness/continue.d.ts.map +1 -1
- package/dist/harness/continue.js +22 -22
- package/dist/harness/continue.js.map +1 -1
- package/dist/harness/cursor.d.ts +5 -2
- package/dist/harness/cursor.d.ts.map +1 -1
- package/dist/harness/cursor.js +19 -23
- package/dist/harness/cursor.js.map +1 -1
- package/dist/harness/detector.d.ts +0 -1
- package/dist/harness/detector.d.ts.map +1 -1
- package/dist/harness/detector.js +0 -1
- package/dist/harness/detector.js.map +1 -1
- package/dist/harness/dsh.d.ts +5 -3
- package/dist/harness/dsh.d.ts.map +1 -1
- package/dist/harness/dsh.js +17 -29
- package/dist/harness/dsh.js.map +1 -1
- package/dist/harness/eve.d.ts +3 -4
- package/dist/harness/eve.d.ts.map +1 -1
- package/dist/harness/eve.js +3 -4
- package/dist/harness/eve.js.map +1 -1
- package/dist/harness/githubCopilot.d.ts +4 -1
- package/dist/harness/githubCopilot.d.ts.map +1 -1
- package/dist/harness/githubCopilot.js +12 -20
- package/dist/harness/githubCopilot.js.map +1 -1
- package/dist/harness/goose.d.ts +7 -3
- package/dist/harness/goose.d.ts.map +1 -1
- package/dist/harness/goose.js +18 -21
- package/dist/harness/goose.js.map +1 -1
- package/dist/harness/grok.d.ts +2 -2
- package/dist/harness/grok.d.ts.map +1 -1
- package/dist/harness/grok.js +13 -24
- package/dist/harness/grok.js.map +1 -1
- package/dist/harness/hermes.d.ts +6 -0
- package/dist/harness/hermes.d.ts.map +1 -1
- package/dist/harness/hermes.js +17 -16
- package/dist/harness/hermes.js.map +1 -1
- package/dist/harness/muse.d.ts +3 -7
- package/dist/harness/muse.d.ts.map +1 -1
- package/dist/harness/muse.js +15 -35
- package/dist/harness/muse.js.map +1 -1
- package/dist/harness/n8n.d.ts +13 -14
- package/dist/harness/n8n.d.ts.map +1 -1
- package/dist/harness/n8n.js +21 -181
- package/dist/harness/n8n.js.map +1 -1
- package/dist/harness/openWebUI.d.ts.map +1 -1
- package/dist/harness/openWebUI.js +5 -4
- package/dist/harness/openWebUI.js.map +1 -1
- package/dist/harness/openaiAgents.d.ts +1 -1
- package/dist/harness/openaiAgents.js +2 -2
- package/dist/harness/openaiAgents.js.map +1 -1
- package/dist/harness/openclaw.d.ts +8 -0
- package/dist/harness/openclaw.d.ts.map +1 -1
- package/dist/harness/openclaw.js +27 -12
- package/dist/harness/openclaw.js.map +1 -1
- package/dist/harness/opencode.d.ts +4 -5
- package/dist/harness/opencode.d.ts.map +1 -1
- package/dist/harness/opencode.js +15 -30
- package/dist/harness/opencode.js.map +1 -1
- package/dist/harness/openhands.d.ts +9 -3
- package/dist/harness/openhands.d.ts.map +1 -1
- package/dist/harness/openhands.js +30 -24
- package/dist/harness/openhands.js.map +1 -1
- package/dist/harness/pi.d.ts +7 -2
- package/dist/harness/pi.d.ts.map +1 -1
- package/dist/harness/pi.js +19 -18
- package/dist/harness/pi.js.map +1 -1
- package/dist/harness/rooCode.d.ts +12 -5
- package/dist/harness/rooCode.d.ts.map +1 -1
- package/dist/harness/rooCode.js +23 -39
- package/dist/harness/rooCode.js.map +1 -1
- package/dist/harness/rulesFiles.d.ts +53 -0
- package/dist/harness/rulesFiles.d.ts.map +1 -0
- package/dist/harness/rulesFiles.js +106 -0
- package/dist/harness/rulesFiles.js.map +1 -0
- package/dist/harness/smolagents.d.ts +2 -2
- package/dist/harness/smolagents.js +3 -3
- package/dist/harness/smolagents.js.map +1 -1
- package/dist/harness/types.d.ts +20 -6
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js +1 -95
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/windsurf.d.ts +6 -1
- package/dist/harness/windsurf.d.ts.map +1 -1
- package/dist/harness/windsurf.js +21 -24
- package/dist/harness/windsurf.js.map +1 -1
- package/dist/harness/xirp.d.ts +1 -2
- package/dist/harness/xirp.d.ts.map +1 -1
- package/dist/harness/xirp.js +2 -3
- package/dist/harness/xirp.js.map +1 -1
- package/dist/lib/api.d.ts +27 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +55 -18
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/ciscoScanner.d.ts +1 -1
- package/dist/lib/ciscoScanner.js +1 -1
- package/dist/lib/decisionsLogRefresh.d.ts.map +1 -1
- package/dist/lib/decisionsLogRefresh.js +6 -7
- package/dist/lib/decisionsLogRefresh.js.map +1 -1
- package/dist/lib/deviceReport.d.ts +15 -0
- package/dist/lib/deviceReport.d.ts.map +1 -1
- package/dist/lib/deviceReport.js +30 -0
- package/dist/lib/deviceReport.js.map +1 -1
- package/dist/lib/gitHooks.d.ts +0 -1
- package/dist/lib/gitHooks.d.ts.map +1 -1
- package/dist/lib/gitHooks.js +8 -9
- package/dist/lib/gitHooks.js.map +1 -1
- package/dist/lib/gracefulShutdown.d.ts +1 -1
- package/dist/lib/gracefulShutdown.js +1 -1
- package/dist/lib/hash.d.ts +0 -1
- package/dist/lib/hash.d.ts.map +1 -1
- package/dist/lib/hash.js +0 -1
- package/dist/lib/hash.js.map +1 -1
- package/dist/lib/inventory.d.ts +47 -0
- package/dist/lib/inventory.d.ts.map +1 -0
- package/dist/lib/inventory.js +106 -0
- package/dist/lib/inventory.js.map +1 -0
- package/dist/lib/logger.d.ts +0 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +0 -1
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/mdmManifest.d.ts +30 -2
- package/dist/lib/mdmManifest.d.ts.map +1 -1
- package/dist/lib/mdmManifest.js +50 -2
- package/dist/lib/mdmManifest.js.map +1 -1
- package/dist/lib/onboarding.d.ts +0 -1
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +1 -2
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/policySnapshot.d.ts +3 -2
- package/dist/lib/policySnapshot.d.ts.map +1 -1
- package/dist/lib/policySnapshot.js +6 -3
- package/dist/lib/policySnapshot.js.map +1 -1
- package/dist/lib/process.d.ts +5 -1
- package/dist/lib/process.d.ts.map +1 -1
- package/dist/lib/process.js +13 -4
- package/dist/lib/process.js.map +1 -1
- package/dist/lib/providerProbe.d.ts +2 -2
- package/dist/lib/providerProbe.js +2 -2
- package/dist/lib/sandbox/firecracker.d.ts +3 -3
- package/dist/lib/sandbox/firecracker.js +3 -3
- package/dist/lib/sandbox/index.d.ts +2 -2
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/oci.d.ts +1 -1
- package/dist/lib/sandbox/oci.js +1 -1
- package/dist/lib/sandbox/types.d.ts +1 -1
- package/dist/lib/sandbox/types.js +1 -1
- package/dist/lib/sopFrontMatter.d.ts +1 -1
- package/dist/lib/sopFrontMatter.js +1 -1
- package/package.json +6 -5
- package/resources/sandbox/Dockerfile +1 -1
- package/resources/sandbox/entrypoint.sh +4 -4
- package/resources/sandbox/firecracker-launch.sh +1 -1
- package/dist/harness/base.d.ts +0 -24
- package/dist/harness/base.d.ts.map +0 -1
- package/dist/harness/base.js +0 -74
- package/dist/harness/base.js.map +0 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `intutic compliance` — framework coverage, and the evidence archive an
|
|
3
|
+
* auditor receives.
|
|
4
|
+
*
|
|
5
|
+
* Subcommands:
|
|
6
|
+
* - `intutic compliance coverage <framework_id>` — how the latest compliance
|
|
7
|
+
* probe results cover one framework (EU AI Act, ISO/IEC 42001, NIST AI
|
|
8
|
+
* RMF, MITRE ATLAS), as a summary or as the report file.
|
|
9
|
+
* - `intutic compliance collect` — run a fresh evidence collection and seal
|
|
10
|
+
* it into an archive.
|
|
11
|
+
* - `intutic compliance download <run_id>` — a stored archive.
|
|
12
|
+
* - `intutic compliance verify <file>` — check an archive offline.
|
|
13
|
+
*
|
|
14
|
+
* Server side: services/control-plane/src/routes/compliance.ts —
|
|
15
|
+
* `GET .../frameworks/:frameworkId/coverage` (`?format=` json, markdown, csv
|
|
16
|
+
* or pdf; a live report is unsigned, the signed copies are the ones sealed in
|
|
17
|
+
* the evidence pack), `POST .../soc2-collect` and `GET .../soc2-export/:runId`
|
|
18
|
+
* (OWNER/ADMIN). The archive's rules are in
|
|
19
|
+
* services/control-plane/src/services/soc2EvidenceService.ts: a sha256
|
|
20
|
+
* manifest over canonical JSON, and an Ed25519 signature over
|
|
21
|
+
* `intutic-soc2-evidence-v1\n<archiveSha256>` only when the deployment has a
|
|
22
|
+
* signing key. `verify` applies the same rules with nothing but the file and
|
|
23
|
+
* the published key set, so its answer does not depend on trusting the server
|
|
24
|
+
* that produced the archive.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
import { createHash, createPublicKey, verify as nodeVerify } from 'node:crypto';
|
|
29
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
30
|
+
import pc from 'picocolors';
|
|
31
|
+
import { resolveControlPlaneUrl } from '../config/paths.js';
|
|
32
|
+
import { log } from '../lib/logger.js';
|
|
33
|
+
import { fail, runApiCommand, writeOutput } from './apiCommand.js';
|
|
34
|
+
import { fetchSigningKeys } from './integrity.js';
|
|
35
|
+
/** The CLI's format names, and the route's for each. */
|
|
36
|
+
const FORMATS = { json: 'json', md: 'markdown', csv: 'csv', pdf: 'pdf' };
|
|
37
|
+
/** `intutic compliance coverage <framework_id>` */
|
|
38
|
+
export async function runComplianceCoverage(frameworkId, opts) {
|
|
39
|
+
const format = opts.format ?? (opts.json ? 'json' : undefined);
|
|
40
|
+
const path = `/api/v1/compliance/frameworks/${encodeURIComponent(frameworkId)}/coverage`;
|
|
41
|
+
if (format === undefined) {
|
|
42
|
+
await runApiCommand(opts, `Failed to read ${frameworkId} coverage`, (client) => client.get(path), (c) => {
|
|
43
|
+
log.header(`Intutic — ${c.name} coverage`);
|
|
44
|
+
log.field('Controls', `${c.summary.controls} (${c.summary.mapped} mapped: ${c.summary.full} full, ${c.summary.partial} partial)`);
|
|
45
|
+
for (const [state, count] of Object.entries(c.summary.states))
|
|
46
|
+
log.field(state, String(count));
|
|
47
|
+
log.field('Mapping version', c.mappingVersion);
|
|
48
|
+
log.field('Generated', c.generatedAt);
|
|
49
|
+
log.dim(' The full report: --format md|csv|pdf --out <file>.');
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!Object.keys(FORMATS).includes(format))
|
|
54
|
+
fail(`--format must be one of ${Object.keys(FORMATS).join(', ')}, got "${format}"`);
|
|
55
|
+
// A PDF written to a terminal is noise and a lost report; a pipe or a file is fine.
|
|
56
|
+
if (format === 'pdf' && !opts.out && process.stdout.isTTY) {
|
|
57
|
+
fail('A PDF report needs --out <file>, or redirect the output to a file.');
|
|
58
|
+
}
|
|
59
|
+
await runApiCommand(
|
|
60
|
+
// The output is the report itself; `--json` only chose the format.
|
|
61
|
+
{ dev: opts.dev }, `Failed to export ${frameworkId} coverage`, (client) => client.getFile(`${path}?format=${FORMATS[format]}`), (bytes) => writeOutput(bytes, opts.out, `${frameworkId} coverage (${format})`));
|
|
62
|
+
}
|
|
63
|
+
// ─── Evidence archive ────────────────────────────────────────────────
|
|
64
|
+
/** The domain the control plane signs evidence archives under (soc2EvidenceService.ts). */
|
|
65
|
+
export const EVIDENCE_SIGNING_DOMAIN = 'intutic-soc2-evidence-v1';
|
|
66
|
+
/**
|
|
67
|
+
* Object keys sorted recursively, arrays in order: the preimage the control
|
|
68
|
+
* plane hashes (`canonicalJson` in soc2EvidenceService.ts), because the
|
|
69
|
+
* archive round-trips through a JSONB column that does not keep key order.
|
|
70
|
+
*/
|
|
71
|
+
export function canonicalJson(value) {
|
|
72
|
+
if (value === null || typeof value !== 'object')
|
|
73
|
+
return JSON.stringify(value);
|
|
74
|
+
if (Array.isArray(value))
|
|
75
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
76
|
+
const entries = Object.entries(value)
|
|
77
|
+
.filter(([, v]) => v !== undefined)
|
|
78
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
79
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;
|
|
80
|
+
}
|
|
81
|
+
const sha256Hex = (data) => createHash('sha256').update(data).digest('hex');
|
|
82
|
+
/** What one manifest entry hashes, or null when the archive has nothing under that name. */
|
|
83
|
+
function sectionContent(archive, name) {
|
|
84
|
+
const category = archive.categories?.find((c) => c.category === name);
|
|
85
|
+
if (category)
|
|
86
|
+
return canonicalJson(category);
|
|
87
|
+
const m = /^framework:([^:]+)(?::(csv|pdf))?$/.exec(name);
|
|
88
|
+
const framework = m ? archive.frameworks?.find((f) => f.coverage?.frameworkId === m[1]) : undefined;
|
|
89
|
+
if (!m || !framework)
|
|
90
|
+
return null;
|
|
91
|
+
// The CSV as its UTF-8 bytes and the PDF decoded, so a file saved out of
|
|
92
|
+
// the archive checks with `sha256sum` alone.
|
|
93
|
+
if (m[2] === 'csv')
|
|
94
|
+
return framework.csv ?? null;
|
|
95
|
+
if (m[2] === 'pdf')
|
|
96
|
+
return framework.pdf === undefined ? null : Buffer.from(framework.pdf, 'base64');
|
|
97
|
+
return canonicalJson(framework);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check an evidence archive the way it was sealed: the manifest's hashes,
|
|
101
|
+
* then the signature over the archive hash against the published key its
|
|
102
|
+
* `keyId` names. A signature over a hash that does not match proves nothing
|
|
103
|
+
* about this file, so it is reported only once the hash matches.
|
|
104
|
+
*/
|
|
105
|
+
export function verifyEvidenceArchive(archive, jwks) {
|
|
106
|
+
const rest = { ...archive };
|
|
107
|
+
delete rest['manifest'];
|
|
108
|
+
delete rest['signature'];
|
|
109
|
+
const archiveSha256 = sha256Hex(canonicalJson(rest));
|
|
110
|
+
const manifest = archive.manifest ?? {};
|
|
111
|
+
const archiveHashMatches = archiveSha256 === manifest.archiveSha256;
|
|
112
|
+
const sections = Object.entries(manifest.sections ?? {});
|
|
113
|
+
const sectionMismatches = sections
|
|
114
|
+
.filter(([name, recorded]) => {
|
|
115
|
+
const content = sectionContent(archive, name);
|
|
116
|
+
return content === null || sha256Hex(content) !== recorded;
|
|
117
|
+
})
|
|
118
|
+
.map(([name]) => name);
|
|
119
|
+
const sig = archive.signature ?? null;
|
|
120
|
+
return {
|
|
121
|
+
archiveSha256,
|
|
122
|
+
archiveHashMatches,
|
|
123
|
+
sectionMismatches,
|
|
124
|
+
sectionsChecked: sections.length,
|
|
125
|
+
signature: sig ? signatureState(sig, manifest.archiveSha256 ?? '', jwks) : 'unsigned',
|
|
126
|
+
signingKeyId: sig?.keyId ?? null,
|
|
127
|
+
unsignedReason: sig ? null : (manifest.unsignedReason ?? null),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function signatureState(sig, archiveSha256, jwks) {
|
|
131
|
+
// A signature made under another domain (a human-oversight or SLA export)
|
|
132
|
+
// or algorithm is not this archive's signature, whatever its bytes.
|
|
133
|
+
if (sig.preimageDomain !== EVIDENCE_SIGNING_DOMAIN || sig.algorithm !== 'Ed25519')
|
|
134
|
+
return 'invalid';
|
|
135
|
+
if (!jwks)
|
|
136
|
+
return 'keys_unavailable';
|
|
137
|
+
const jwk = jwks.keys.find((k) => k['kid'] === sig.keyId);
|
|
138
|
+
if (!jwk)
|
|
139
|
+
return 'unverifiable';
|
|
140
|
+
try {
|
|
141
|
+
const ok = nodeVerify(null, Buffer.from(`${EVIDENCE_SIGNING_DOMAIN}\n${archiveSha256}`, 'utf8'), createPublicKey({ key: jwk, format: 'jwk' }), Buffer.from(sig.value, 'base64'));
|
|
142
|
+
return ok ? 'valid' : 'invalid';
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// A published key this build cannot load is one we do not effectively hold.
|
|
146
|
+
return 'unverifiable';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The exit code for a verification: 0 only for an archive whose hashes match
|
|
151
|
+
* and whose signature a published key accepts; 1 when the file or its
|
|
152
|
+
* signature contradicts the archive (it was changed); 2 when the hashes match
|
|
153
|
+
* but nothing establishes who produced it (unsigned, or the key could not be
|
|
154
|
+
* had), which a script must not read as a pass.
|
|
155
|
+
*/
|
|
156
|
+
export function verificationExitCode(v) {
|
|
157
|
+
if (!v.archiveHashMatches || v.sectionMismatches.length > 0 || v.signature === 'invalid')
|
|
158
|
+
return 1;
|
|
159
|
+
return v.signature === 'valid' ? 0 : 2;
|
|
160
|
+
}
|
|
161
|
+
const SIGNATURE_TEXT = {
|
|
162
|
+
valid: 'valid: the published key accepts the signature over this archive hash',
|
|
163
|
+
invalid: 'INVALID: the signature does not match this archive',
|
|
164
|
+
unsigned: 'unsigned: the deployment had no signing key when this archive was collected',
|
|
165
|
+
unverifiable: 'not verified: no published key has this key id',
|
|
166
|
+
keys_unavailable: 'not verified: the published keys could not be fetched; pass --jwks <file>',
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* `verify`'s exit status for an input it cannot read (a missing file, one
|
|
170
|
+
* that is not JSON, one that is not an archive or a JWKS): its own status, so
|
|
171
|
+
* a script never reads a wrong path as a changed archive (1) or an unsigned
|
|
172
|
+
* one (2).
|
|
173
|
+
*/
|
|
174
|
+
export const UNREADABLE_INPUT_EXIT = 3;
|
|
175
|
+
function unreadable(message) {
|
|
176
|
+
log.error(message);
|
|
177
|
+
process.exit(UNREADABLE_INPUT_EXIT);
|
|
178
|
+
}
|
|
179
|
+
function readInput(path) {
|
|
180
|
+
let text;
|
|
181
|
+
try {
|
|
182
|
+
text = readFileSync(path, 'utf8');
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
unreadable(`Cannot read ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
return JSON.parse(text);
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
unreadable(`${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/** The published key set, from a file when given, else from the control plane's unauthenticated endpoint. */
|
|
195
|
+
function loadJwks(path) {
|
|
196
|
+
const parsed = readInput(path);
|
|
197
|
+
if (!Array.isArray(parsed?.keys))
|
|
198
|
+
unreadable(`${path} is not a JWKS: it has no "keys" array`);
|
|
199
|
+
return parsed;
|
|
200
|
+
}
|
|
201
|
+
/** `intutic compliance verify <file>` */
|
|
202
|
+
export async function runComplianceVerify(file, opts) {
|
|
203
|
+
const archive = readInput(file);
|
|
204
|
+
if (!archive?.manifest?.archiveSha256)
|
|
205
|
+
unreadable(`${file} is not an evidence archive: it has no manifest.archiveSha256`);
|
|
206
|
+
const jwks = opts.jwks
|
|
207
|
+
? loadJwks(opts.jwks)
|
|
208
|
+
: archive.signature
|
|
209
|
+
? await fetchSigningKeys(resolveControlPlaneUrl(opts.dev))
|
|
210
|
+
: null;
|
|
211
|
+
const v = verifyEvidenceArchive(archive, jwks);
|
|
212
|
+
const code = verificationExitCode(v);
|
|
213
|
+
if (opts.json) {
|
|
214
|
+
console.log(JSON.stringify({ file, ...v, verified: code === 0 }, null, 2));
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
log.header('Intutic — Evidence Archive');
|
|
218
|
+
if (archive.runId)
|
|
219
|
+
log.field('Run', archive.runId);
|
|
220
|
+
if (archive.collectedAt)
|
|
221
|
+
log.field('Collected', archive.collectedAt);
|
|
222
|
+
log.field('Archive hash', v.archiveHashMatches ? pc.green(`matches (${v.archiveSha256})`) : pc.red(`DOES NOT MATCH the manifest (recomputed ${v.archiveSha256})`));
|
|
223
|
+
log.field('Sections', v.sectionMismatches.length === 0
|
|
224
|
+
? pc.green(`${v.sectionsChecked} of ${v.sectionsChecked} match`)
|
|
225
|
+
: pc.red(`${v.sectionMismatches.length} of ${v.sectionsChecked} DO NOT MATCH: ${v.sectionMismatches.join(', ')}`));
|
|
226
|
+
const text = SIGNATURE_TEXT[v.signature];
|
|
227
|
+
log.field('Signature', v.signature === 'valid' ? pc.green(text) : v.signature === 'invalid' ? pc.red(text) : pc.yellow(text));
|
|
228
|
+
if (v.signingKeyId)
|
|
229
|
+
log.field('Key id', v.signingKeyId);
|
|
230
|
+
if (v.unsignedReason)
|
|
231
|
+
log.dim(` ${v.unsignedReason}`);
|
|
232
|
+
if (code === 2)
|
|
233
|
+
log.warn('Not verified: the hashes match, but nothing shows who produced this archive.');
|
|
234
|
+
}
|
|
235
|
+
if (code !== 0)
|
|
236
|
+
process.exit(code);
|
|
237
|
+
}
|
|
238
|
+
/** `intutic compliance collect` */
|
|
239
|
+
export async function runComplianceCollect(opts) {
|
|
240
|
+
for (const [flag, value] of [['--from', opts.from], ['--to', opts.to]]) {
|
|
241
|
+
if (value !== undefined && Number.isNaN(Date.parse(value)))
|
|
242
|
+
fail(`${flag} must be an ISO 8601 date or datetime, got "${value}"`);
|
|
243
|
+
}
|
|
244
|
+
await runApiCommand(
|
|
245
|
+
// Not `opts`: with --json the response is printed, and --out still writes the archive.
|
|
246
|
+
{ dev: opts.dev }, 'Failed to collect evidence', (client) => client.post('/api/v1/compliance/soc2-collect', { periodStart: opts.from, periodEnd: opts.to }), (res) => {
|
|
247
|
+
if (opts.out)
|
|
248
|
+
writeFileSync(opts.out, JSON.stringify(res.archive, null, 2));
|
|
249
|
+
if (opts.json) {
|
|
250
|
+
console.log(JSON.stringify(res, null, 2));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
log.header('Intutic — Evidence Collected');
|
|
254
|
+
log.field('Run', res.runId);
|
|
255
|
+
log.field('Period', `${res.archive.periodStart} to ${res.archive.periodEnd}`);
|
|
256
|
+
log.field('Overall score', res.archive.overallScore === null ? '— (no scored category)' : String(res.archive.overallScore));
|
|
257
|
+
log.field('Signed', res.signed ? pc.green(`yes (key ${res.signingKeyId})`) : pc.yellow('no'));
|
|
258
|
+
if (!res.signed && res.unsignedReason)
|
|
259
|
+
log.dim(` ${res.unsignedReason}`);
|
|
260
|
+
if (res.artifactUrl)
|
|
261
|
+
log.field('Stored at', res.artifactUrl);
|
|
262
|
+
if (opts.out)
|
|
263
|
+
log.success(`Wrote the archive to ${opts.out}. Check it: intutic compliance verify ${opts.out}`);
|
|
264
|
+
else
|
|
265
|
+
log.dim(` Download it: intutic compliance download ${res.runId} --out <file>`);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/** `intutic compliance download <run_id>` */
|
|
269
|
+
export async function runComplianceDownload(runId, opts) {
|
|
270
|
+
await runApiCommand(
|
|
271
|
+
// The output is the archive itself.
|
|
272
|
+
{ dev: opts.dev }, `Failed to download evidence run ${runId}`, (client) => client.getFile(`/api/v1/compliance/soc2-export/${encodeURIComponent(runId)}`), (bytes) => {
|
|
273
|
+
writeOutput(bytes, opts.out, `evidence run ${runId}`);
|
|
274
|
+
if (opts.out)
|
|
275
|
+
log.dim(` Check it: intutic compliance verify ${opts.out}`);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=compliance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance.js","sourceRoot":"","sources":["../../src/commands/compliance.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,11 +5,36 @@
|
|
|
5
5
|
* a real-time filesystem watcher for configuration drift detection,
|
|
6
6
|
* and a 30-second HTTP polling loop as a secondary fallback.
|
|
7
7
|
*
|
|
8
|
-
* LLD #14 — connect.ts
|
|
9
8
|
* HLD §3.14 — Real-Time State Mirroring
|
|
10
9
|
*
|
|
11
10
|
* @module
|
|
12
11
|
*/
|
|
12
|
+
import type { SyncSopEntry } from '@intutic/shared-types';
|
|
13
|
+
/**
|
|
14
|
+
* Applies a synced configuration to each configured harness.
|
|
15
|
+
*
|
|
16
|
+
* The gate is installed whatever rule sets the workspace has: it enforces the
|
|
17
|
+
* built-in protections, the destructive-command tier, group rules and holds,
|
|
18
|
+
* none of which need a rule set. Gating it on rule sets left a workspace with
|
|
19
|
+
* none, or with none targeting a harness, with that harness ungoverned. The
|
|
20
|
+
* rules file is written only when a rule set targets the harness, or on a
|
|
21
|
+
* forced sync.
|
|
22
|
+
*
|
|
23
|
+
* Harnesses that read the same file (Codex, Grok Build, OpenCode, Muse Code
|
|
24
|
+
* and others all read `AGENTS.md`) each get every rule set aimed at any of
|
|
25
|
+
* them that is configured here: one section, the same for every writer, so
|
|
26
|
+
* the last writer no longer replaces the others' rules.
|
|
27
|
+
*
|
|
28
|
+
* Claude Code reads its own `.claude/rules/` file at every launch, and also
|
|
29
|
+
* the workspace's `AGENTS.md` when there is no `CLAUDE.md` on the path (or
|
|
30
|
+
* the user chose `claude-md-and-agents-md`; see claudeAgentsMd.ts). When it
|
|
31
|
+
* does, a rule set aimed at both Claude Code and an `AGENTS.md` reader
|
|
32
|
+
* reaches it through `AGENTS.md`, so its own file leaves that rule set out
|
|
33
|
+
* rather than load it twice.
|
|
34
|
+
*
|
|
35
|
+
* @returns how many rule sets were written into rules files.
|
|
36
|
+
*/
|
|
37
|
+
export declare function writeHarnessConfigs(harnesses: readonly string[], workspaceRoot: string, sops: readonly SyncSopEntry[], proxyUrl: string, force: boolean): Promise<number>;
|
|
13
38
|
/**
|
|
14
39
|
* Asset names MUST match publish.yml's build-rust-proxy matrix
|
|
15
40
|
* artifact_name — mirrors packages/proxy/bin/proxy.js's `resolveAssetName`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA8BH,OAAO,KAAK,EAAkC,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAuEzF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,MAAM,CAAC,CA0BjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,IAAI,GAAE,MAAqB,GAC1B,MAAM,GAAG,IAAI,CAWf;AA0DD,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2tChB"}
|
package/dist/commands/connect.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* a real-time filesystem watcher for configuration drift detection,
|
|
6
6
|
* and a 30-second HTTP polling loop as a secondary fallback.
|
|
7
7
|
*
|
|
8
|
-
* LLD #14 — connect.ts
|
|
9
8
|
* HLD §3.14 — Real-Time State Mirroring
|
|
10
9
|
*
|
|
11
10
|
* @module
|
|
@@ -25,10 +24,12 @@ import { getAdapter } from '../harness/detector.js';
|
|
|
25
24
|
import { printOnboardingGuide } from '../lib/onboarding.js';
|
|
26
25
|
import { writeEnforcementState } from '../lib/enforcementState.js';
|
|
27
26
|
import { reportDeviceState } from '../lib/deviceReport.js';
|
|
27
|
+
import { reportMachineInventory, shouldReportInventoryThisIteration } from '../lib/inventory.js';
|
|
28
28
|
import { parseChecksums, verifyChecksum } from '../lib/binaryChecksum.js';
|
|
29
29
|
import { newIso } from '@intutic/id';
|
|
30
|
+
import { HarnessType, rulesFileOf } from '@intutic/shared-types';
|
|
30
31
|
import pc from 'picocolors';
|
|
31
|
-
import { SyncWsClient, startWatcher, updatePreToolUseHooks, injectMcpServer, noteProxyUrl, guardSettingsFile, warnIfDshCoverageGap, writeRuntimeEnv, refreshPolicySnapshot, refreshGateCaches, localHoldTokensFor, runComplianceProbes, drainHookEvents, drainReviewRequests, REVIEW_REQUESTS_LOG, syncOfflineTraces, TrajectoryMonitor, fetchLocalProxyInstanceId, endAllOpenSessions, applySkillOptEdits, reportHarnessAgents, captureAndUpload, shouldCaptureThisIteration, refreshDecisionsDigest, writeBundledSkills, clearImmutable, setImmutable, } from '@intutic/sync-daemon';
|
|
32
|
+
import { SyncWsClient, startWatcher, updatePreToolUseHooks, injectMcpServer, noteProxyUrl, guardPolicySnapshot, guardSettingsFile, isGuardedPath, warnIfDshCoverageGap, writeRuntimeEnv, refreshPolicySnapshot, refreshGateCaches, localHoldTokensFor, runComplianceProbes, drainHookEvents, drainReviewRequests, REVIEW_REQUESTS_LOG, syncOfflineTraces, TrajectoryMonitor, fetchLocalProxyInstanceId, endAllOpenSessions, applySkillOptEdits, reportHarnessAgents, captureAndUpload, shouldCaptureThisIteration, refreshDecisionsDigest, retireClaudeMdDigest, claudeCodeReadsAgentsMd, writeBundledSkills, clearImmutable, setImmutable, } from '@intutic/sync-daemon';
|
|
32
33
|
import { watch } from 'chokidar';
|
|
33
34
|
// Named, not default: under `module: Node16` TypeScript resolves ioredis's
|
|
34
35
|
// default export to the module namespace rather than the class, so `new
|
|
@@ -61,20 +62,57 @@ function isPortInUse(port) {
|
|
|
61
62
|
});
|
|
62
63
|
}
|
|
63
64
|
/**
|
|
64
|
-
*
|
|
65
|
+
* Applies a synced configuration to each configured harness.
|
|
65
66
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
67
|
+
* The gate is installed whatever rule sets the workspace has: it enforces the
|
|
68
|
+
* built-in protections, the destructive-command tier, group rules and holds,
|
|
69
|
+
* none of which need a rule set. Gating it on rule sets left a workspace with
|
|
70
|
+
* none, or with none targeting a harness, with that harness ungoverned. The
|
|
71
|
+
* rules file is written only when a rule set targets the harness, or on a
|
|
72
|
+
* forced sync.
|
|
73
|
+
*
|
|
74
|
+
* Harnesses that read the same file (Codex, Grok Build, OpenCode, Muse Code
|
|
75
|
+
* and others all read `AGENTS.md`) each get every rule set aimed at any of
|
|
76
|
+
* them that is configured here: one section, the same for every writer, so
|
|
77
|
+
* the last writer no longer replaces the others' rules.
|
|
78
|
+
*
|
|
79
|
+
* Claude Code reads its own `.claude/rules/` file at every launch, and also
|
|
80
|
+
* the workspace's `AGENTS.md` when there is no `CLAUDE.md` on the path (or
|
|
81
|
+
* the user chose `claude-md-and-agents-md`; see claudeAgentsMd.ts). When it
|
|
82
|
+
* does, a rule set aimed at both Claude Code and an `AGENTS.md` reader
|
|
83
|
+
* reaches it through `AGENTS.md`, so its own file leaves that rule set out
|
|
84
|
+
* rather than load it twice.
|
|
85
|
+
*
|
|
86
|
+
* @returns how many rule sets were written into rules files.
|
|
68
87
|
*/
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
export async function writeHarnessConfigs(harnesses, workspaceRoot, sops, proxyUrl, force) {
|
|
89
|
+
let written = 0;
|
|
90
|
+
const writtenFiles = new Set();
|
|
91
|
+
const agentsMdReaders = harnesses.filter((h) => rulesFileOf(h) === 'AGENTS.md');
|
|
92
|
+
const claudeFromAgentsMd = harnesses.includes(HarnessType.CLAUDE_CODE) && agentsMdReaders.length > 0 && (await claudeCodeReadsAgentsMd(workspaceRoot));
|
|
93
|
+
for (const harnessType of harnesses) {
|
|
94
|
+
const adapter = getAdapter(harnessType);
|
|
95
|
+
if (!adapter)
|
|
96
|
+
continue;
|
|
97
|
+
await adapter.installGate?.(workspaceRoot, proxyUrl);
|
|
98
|
+
const file = rulesFileOf(harnessType);
|
|
99
|
+
const readers = file === null ? [harnessType] : harnesses.filter((h) => rulesFileOf(h) === file);
|
|
100
|
+
const aimed = sops.filter((sop) => sop.harnessTargets.some((t) => readers.includes(t)));
|
|
101
|
+
const viaAgentsMd = harnessType === HarnessType.CLAUDE_CODE && claudeFromAgentsMd;
|
|
102
|
+
const targetSops = viaAgentsMd ? aimed.filter((sop) => !sop.harnessTargets.some((t) => agentsMdReaders.includes(t))) : aimed;
|
|
103
|
+
// Claude Code's own file is rewritten even when every rule set aimed at
|
|
104
|
+
// it now comes through AGENTS.md, so it stops carrying them.
|
|
105
|
+
if (aimed.length === 0 && !force)
|
|
106
|
+
continue;
|
|
107
|
+
if (!(await adapter.writeConfig(workspaceRoot, targetSops, proxyUrl)))
|
|
108
|
+
continue;
|
|
109
|
+
// A shared file counts once, however many of its readers wrote it.
|
|
110
|
+
if (file === null || !writtenFiles.has(file))
|
|
111
|
+
written += targetSops.length;
|
|
112
|
+
if (file !== null)
|
|
113
|
+
writtenFiles.add(file);
|
|
114
|
+
}
|
|
115
|
+
return written;
|
|
78
116
|
}
|
|
79
117
|
/**
|
|
80
118
|
* Asset names MUST match publish.yml's build-rust-proxy matrix
|
|
@@ -262,7 +300,7 @@ export async function runConnect(opts) {
|
|
|
262
300
|
// 2. AbortController for clean shutdown
|
|
263
301
|
let proxyProc = null;
|
|
264
302
|
const ac = new AbortController();
|
|
265
|
-
//
|
|
303
|
+
// This handler used to abort the loop and hope. The process stayed
|
|
266
304
|
// alive for hours. `createShutdownHandler` adds the exit deadline and the
|
|
267
305
|
// second-signal exit; `terminateChild` escalates to SIGKILL.
|
|
268
306
|
const shutdown = createShutdownHandler({
|
|
@@ -453,12 +491,18 @@ export async function runConnect(opts) {
|
|
|
453
491
|
// when the config version moved: policy changes without the config version
|
|
454
492
|
// changing, and a guardrail promoted, a hold approved or an egress mode
|
|
455
493
|
// flipped mid-session used to reach a connected machine only on restart —
|
|
456
|
-
// this function was called once, at startup
|
|
494
|
+
// this function was called once, at startup. None of these throw.
|
|
457
495
|
//
|
|
458
496
|
// The snapshot also carries this workspace's `review_before:` tokens as hold
|
|
459
497
|
// rules (synced SOPs, settings and local `.intutic/sops`), so every gate —
|
|
460
|
-
// not only the Claude Code hook — holds on them
|
|
461
|
-
|
|
498
|
+
// not only the Claude Code hook — holds on them.
|
|
499
|
+
//
|
|
500
|
+
// First, the snapshot's self-heal: a snapshot changed while the daemon was
|
|
501
|
+
// stopped is put back to the last verified copy, and reported, before the
|
|
502
|
+
// refresh replaces it. With `checkSnapshot` false the caller is that check.
|
|
503
|
+
async function refreshGateCachesForConnect(checkSnapshot = true) {
|
|
504
|
+
if (checkSnapshot)
|
|
505
|
+
await guardPolicySnapshot(safeConfig.workspaceRoot, async () => { });
|
|
462
506
|
const localHoldTokens = await localHoldTokensFor(safeConfig.workspaceRoot, lastCachedConfig?.sops ?? [], lastCachedConfig?.settings, safeConfig.harnesses);
|
|
463
507
|
const landed = await refreshGateCaches({ controlPlaneUrl, apiKey: safeCreds.apiKey, workspaceId: safeCreds.workspaceId, localHoldTokens });
|
|
464
508
|
if (!landed.snapshot)
|
|
@@ -471,18 +515,28 @@ export async function runConnect(opts) {
|
|
|
471
515
|
.map((h) => getAdapter(h)?.configFileName)
|
|
472
516
|
.filter((f) => Boolean(f))
|
|
473
517
|
.map((f) => node_path.resolve(safeConfig.workspaceRoot, f));
|
|
518
|
+
// Whether Claude Code read the workspace's AGENTS.md at the last sync. A
|
|
519
|
+
// CLAUDE.md added or removed (or the user's Project instructions setting
|
|
520
|
+
// changed) moves rule sets between AGENTS.md and Claude Code's own file, so
|
|
521
|
+
// a change rewrites the rules files as a new config would.
|
|
522
|
+
let lastClaudeFromAgentsMd;
|
|
474
523
|
// 3. Define configuration applier function
|
|
475
524
|
async function applySyncConfig(syncConfig, force = false) {
|
|
476
525
|
let sopsWritten = 0;
|
|
477
526
|
lastCachedConfig = syncConfig;
|
|
478
527
|
await refreshGateCachesForConnect();
|
|
528
|
+
const claudeFromAgentsMd = safeConfig.harnesses.includes(HarnessType.CLAUDE_CODE)
|
|
529
|
+
? await claudeCodeReadsAgentsMd(safeConfig.workspaceRoot)
|
|
530
|
+
: false;
|
|
531
|
+
const claudeDeliveryMoved = lastClaudeFromAgentsMd !== undefined && claudeFromAgentsMd !== lastClaudeFromAgentsMd;
|
|
532
|
+
lastClaudeFromAgentsMd = claudeFromAgentsMd;
|
|
479
533
|
// Write-protect (`bypassEnforcementTier: 'immutable'`, macOS): the rules
|
|
480
534
|
// files carry the user-immutable flag between cycles, so it comes off
|
|
481
535
|
// before anything below writes them and goes back on afterwards. It also
|
|
482
536
|
// comes off whenever the config moved, so a workspace that switched away
|
|
483
537
|
// from write-protect is not left with files nothing can rewrite.
|
|
484
538
|
const writeProtect = syncConfig.settings?.bypassEnforcementTier === 'immutable';
|
|
485
|
-
const configMoved = syncConfig.configVersion > localConfigVersion || force;
|
|
539
|
+
const configMoved = syncConfig.configVersion > localConfigVersion || force || claudeDeliveryMoved;
|
|
486
540
|
if (writeProtect || configMoved) {
|
|
487
541
|
for (const file of rulesFilePaths())
|
|
488
542
|
await clearImmutable(file);
|
|
@@ -545,18 +599,7 @@ export async function runConnect(opts) {
|
|
|
545
599
|
// first: `intutic disconnect` recognises the base-URL settings it is
|
|
546
600
|
// written into by it.
|
|
547
601
|
await noteProxyUrl(syncConfig.proxyUrl);
|
|
548
|
-
|
|
549
|
-
const adapter = getAdapter(harnessType);
|
|
550
|
-
if (!adapter)
|
|
551
|
-
continue;
|
|
552
|
-
const targetSops = combinedSops.filter((sop) => sop.harnessTargets.includes(harnessType));
|
|
553
|
-
if (targetSops.length === 0 && !force)
|
|
554
|
-
continue;
|
|
555
|
-
const written = await adapter.writeConfig(safeConfig.workspaceRoot, targetSops, syncConfig.proxyUrl);
|
|
556
|
-
if (written) {
|
|
557
|
-
sopsWritten += targetSops.length;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
602
|
+
sopsWritten += await writeHarnessConfigs(safeConfig.harnesses, safeConfig.workspaceRoot, combinedSops, syncConfig.proxyUrl, force);
|
|
560
603
|
// b. Invalidate/update Claude Code hooks and settings
|
|
561
604
|
if (safeConfig.harnesses.includes('claude-code')) {
|
|
562
605
|
try {
|
|
@@ -596,7 +639,11 @@ export async function runConnect(opts) {
|
|
|
596
639
|
reapplyAll: configMoved,
|
|
597
640
|
});
|
|
598
641
|
// e. The governed decisions log, opt-in (`decisionsLogEnabled`, off by
|
|
599
|
-
// default). Also before the hashes:
|
|
642
|
+
// default), into each harness's instructions file. Also before the hashes:
|
|
643
|
+
// it writes into shared files such as AGENTS.md, whose rules section they
|
|
644
|
+
// cover. The section earlier versions put in CLAUDE.md comes out whether
|
|
645
|
+
// or not the log is on.
|
|
646
|
+
await retireClaudeMdDigest(safeConfig.workspaceRoot);
|
|
600
647
|
if (syncConfig.settings?.decisionsLogEnabled) {
|
|
601
648
|
await refreshDecisionsDigest({
|
|
602
649
|
controlPlaneUrl,
|
|
@@ -746,7 +793,7 @@ export async function runConnect(opts) {
|
|
|
746
793
|
proxyStatus = 'unhealthy';
|
|
747
794
|
if (ac.signal.aborted) {
|
|
748
795
|
// Shutting down: the proxy is gone because we stopped it. Re-spawning
|
|
749
|
-
// it here would leave an orphan holding the port
|
|
796
|
+
// it here would leave an orphan holding the port.
|
|
750
797
|
proxyStatus = 'stopped';
|
|
751
798
|
}
|
|
752
799
|
else if (proxyProc) {
|
|
@@ -813,7 +860,7 @@ export async function runConnect(opts) {
|
|
|
813
860
|
catch (err) {
|
|
814
861
|
log.warn(`Failed to send daemon heartbeat: ${err instanceof Error ? err.message : String(err)}`);
|
|
815
862
|
}
|
|
816
|
-
// g. Enforcement device visibility (post-strip gap #2,
|
|
863
|
+
// g. Enforcement device visibility (post-strip gap #2, egress
|
|
817
864
|
// hardening) — the "interval" half of "continuous compliance": this
|
|
818
865
|
// loop already runs unprivileged, as the real user, on a timer, and
|
|
819
866
|
// already just computed sslTrustStatus above for its own DR-healing
|
|
@@ -848,7 +895,7 @@ export async function runConnect(opts) {
|
|
|
848
895
|
mcpProxyFailBehavior: syncConfig.settings?.mcpProxyFailBehavior,
|
|
849
896
|
mcpProxyMode: syncConfig.settings?.mcpProxyMode,
|
|
850
897
|
bypassEnforcementTier: syncConfig.settings?.bypassEnforcementTier,
|
|
851
|
-
// Shared by the MCP proxies for their session window
|
|
898
|
+
// Shared by the MCP proxies for their session window — only when it is running.
|
|
852
899
|
valkeyUrl: valkeyResult.running ? valkeyUrl : undefined,
|
|
853
900
|
});
|
|
854
901
|
}
|
|
@@ -950,7 +997,7 @@ export async function runConnect(opts) {
|
|
|
950
997
|
controlPlaneUrl,
|
|
951
998
|
apiKey: safeCreds.apiKey,
|
|
952
999
|
workspaceId: safeCreds.workspaceId,
|
|
953
|
-
// Shared by the MCP proxies for their session window
|
|
1000
|
+
// Shared by the MCP proxies for their session window — only when it is running.
|
|
954
1001
|
valkeyUrl: valkeyResult.running ? valkeyUrl : undefined,
|
|
955
1002
|
});
|
|
956
1003
|
}
|
|
@@ -1075,7 +1122,7 @@ export async function runConnect(opts) {
|
|
|
1075
1122
|
const drainSafetyTimer = setInterval(runDrain, 60_000);
|
|
1076
1123
|
// Run initial compliance check on startup
|
|
1077
1124
|
await runProbes();
|
|
1078
|
-
// dsh coverage-gap visibility
|
|
1125
|
+
// dsh coverage-gap visibility: once at startup, not every poll
|
|
1079
1126
|
// tick — the gap only changes state on the user's first `dsh --profile
|
|
1080
1127
|
// <name>` run, which the filesystem watcher below reacts to immediately
|
|
1081
1128
|
// via settingsGuard.ts's isDshProfilesRoot `addDir` handling. Only worth
|
|
@@ -1121,22 +1168,17 @@ export async function runConnect(opts) {
|
|
|
1121
1168
|
}
|
|
1122
1169
|
// B. Tamper detection for every governed config, not just Claude Code's.
|
|
1123
1170
|
//
|
|
1124
|
-
//
|
|
1125
|
-
//
|
|
1126
|
-
//
|
|
1127
|
-
//
|
|
1128
|
-
//
|
|
1129
|
-
|
|
1130
|
-
// Windsurf/VS Code/Gemini settings file passed the basename test only to fall
|
|
1131
|
-
// through and log `settings_intact`.
|
|
1132
|
-
//
|
|
1133
|
-
// Route anything on the governed list to the guard and let it decide.
|
|
1134
|
-
if (isGovernedConfigPath(changedPath, filename)) {
|
|
1171
|
+
// Routed on the guard's own watch list. A filter on file names here, first
|
|
1172
|
+
// `settings.json` and later `settings.json`/`hooks.json`/`intutic-governance`,
|
|
1173
|
+
// kept the guard from ever seeing the paths it restores under other names:
|
|
1174
|
+
// a gate script, Cline's PreToolUse, OpenClaw's openclaw.json, Muse's
|
|
1175
|
+
// managed-hooks file and every dsh profile patch.
|
|
1176
|
+
if (isGuardedPath(changedPath, safeConfig.workspaceRoot)) {
|
|
1135
1177
|
try {
|
|
1136
1178
|
const sops = lastCachedConfig?.sops ?? [];
|
|
1137
1179
|
// The proxy URL too: the restored gate scripts carry it, and an empty
|
|
1138
1180
|
// one would be written into them.
|
|
1139
|
-
const tampered = await guardSettingsFile(changedPath, safeConfig.workspaceRoot, sops, lastCachedConfig?.proxyUrl ?? '', undefined, new Set(safeConfig.disconnectedHarnesses ?? []));
|
|
1181
|
+
const tampered = await guardSettingsFile(changedPath, safeConfig.workspaceRoot, sops, lastCachedConfig?.proxyUrl ?? '', undefined, new Set(safeConfig.disconnectedHarnesses ?? []), () => refreshGateCachesForConnect(false));
|
|
1140
1182
|
if (tampered) {
|
|
1141
1183
|
log.warn(`[Security] Governance settings tamper detected and restored: ${changedPath}`);
|
|
1142
1184
|
wsClient.send({
|
|
@@ -1153,8 +1195,9 @@ export async function runConnect(opts) {
|
|
|
1153
1195
|
}
|
|
1154
1196
|
return;
|
|
1155
1197
|
}
|
|
1156
|
-
// B. Handle governed harness file drift detection
|
|
1157
|
-
|
|
1198
|
+
// B. Handle governed harness file drift detection. By path, not
|
|
1199
|
+
// basename: rules files sit in directories (`.cursor/rules/…`).
|
|
1200
|
+
const matchingHarness = safeConfig.harnesses.find((h) => getAdapter(h)?.configFileName === relativePath.split(node_path.sep).join('/'));
|
|
1158
1201
|
if (!matchingHarness)
|
|
1159
1202
|
return;
|
|
1160
1203
|
const adapter = getAdapter(matchingHarness);
|
|
@@ -1248,6 +1291,21 @@ export async function runConnect(opts) {
|
|
|
1248
1291
|
for (const { harness, error } of failures) {
|
|
1249
1292
|
log.dim(`Agent report/session for harness '${harness}' failed: ${error}`);
|
|
1250
1293
|
}
|
|
1294
|
+
// Every few minutes, the machine's AI inventory for the org-wide view:
|
|
1295
|
+
// every harness the detection rules find, connected or not, with its
|
|
1296
|
+
// gate state, and the MCP servers and skill bundles on the machine.
|
|
1297
|
+
if (shouldReportInventoryThisIteration(pollIteration)) {
|
|
1298
|
+
const inventory = await reportMachineInventory({
|
|
1299
|
+
controlPlaneUrl,
|
|
1300
|
+
apiKey: safeCreds.apiKey,
|
|
1301
|
+
workspaceRoot: safeConfig.workspaceRoot,
|
|
1302
|
+
configured: safeConfig.harnesses,
|
|
1303
|
+
disconnected: safeConfig.disconnectedHarnesses,
|
|
1304
|
+
cliVersion: cliPkgVersion,
|
|
1305
|
+
});
|
|
1306
|
+
if (!inventory.reported)
|
|
1307
|
+
log.dim(`AI inventory report not sent (will retry): ${inventory.reason}`);
|
|
1308
|
+
}
|
|
1251
1309
|
// Every Nth poll, capture the rules files that changed for the config
|
|
1252
1310
|
// history. Content goes only when this poll's settings have
|
|
1253
1311
|
// `configBodyUpload` on; otherwise path, hash, size and time.
|