@cogenta/fleet 0.1.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/dist/agent/assert.d.ts +9 -0
- package/dist/agent/assert.d.ts.map +1 -0
- package/dist/agent/assert.js +64 -0
- package/dist/agent/assert.js.map +1 -0
- package/dist/agent/build.d.ts +21 -0
- package/dist/agent/build.d.ts.map +1 -0
- package/dist/agent/build.js +43 -0
- package/dist/agent/build.js.map +1 -0
- package/dist/agent/commands.d.ts +39 -0
- package/dist/agent/commands.d.ts.map +1 -0
- package/dist/agent/commands.js +43 -0
- package/dist/agent/commands.js.map +1 -0
- package/dist/agent/rollback-handler.d.ts +28 -0
- package/dist/agent/rollback-handler.d.ts.map +1 -0
- package/dist/agent/rollback-handler.js +52 -0
- package/dist/agent/rollback-handler.js.map +1 -0
- package/dist/agent/sign.d.ts +18 -0
- package/dist/agent/sign.d.ts.map +1 -0
- package/dist/agent/sign.js +19 -0
- package/dist/agent/sign.js.map +1 -0
- package/dist/agent/types.d.ts +86 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +2 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/alerts/alerts.d.ts +42 -0
- package/dist/alerts/alerts.d.ts.map +1 -0
- package/dist/alerts/alerts.js +98 -0
- package/dist/alerts/alerts.js.map +1 -0
- package/dist/alerts/conditions.d.ts +25 -0
- package/dist/alerts/conditions.d.ts.map +1 -0
- package/dist/alerts/conditions.js +28 -0
- package/dist/alerts/conditions.js.map +1 -0
- package/dist/alerts/tables.d.ts +13 -0
- package/dist/alerts/tables.d.ts.map +1 -0
- package/dist/alerts/tables.js +27 -0
- package/dist/alerts/tables.js.map +1 -0
- package/dist/control/commands.d.ts +38 -0
- package/dist/control/commands.d.ts.map +1 -0
- package/dist/control/commands.js +51 -0
- package/dist/control/commands.js.map +1 -0
- package/dist/control/identity.d.ts +13 -0
- package/dist/control/identity.d.ts.map +1 -0
- package/dist/control/identity.js +5 -0
- package/dist/control/identity.js.map +1 -0
- package/dist/control/ingest.d.ts +46 -0
- package/dist/control/ingest.d.ts.map +1 -0
- package/dist/control/ingest.js +56 -0
- package/dist/control/ingest.js.map +1 -0
- package/dist/control/risk.d.ts +66 -0
- package/dist/control/risk.d.ts.map +1 -0
- package/dist/control/risk.js +130 -0
- package/dist/control/risk.js.map +1 -0
- package/dist/control/state.d.ts +19 -0
- package/dist/control/state.d.ts.map +1 -0
- package/dist/control/state.js +67 -0
- package/dist/control/state.js.map +1 -0
- package/dist/control/tables.d.ts +18 -0
- package/dist/control/tables.d.ts.map +1 -0
- package/dist/control/tables.js +58 -0
- package/dist/control/tables.js.map +1 -0
- package/dist/enrollment/store.d.ts +84 -0
- package/dist/enrollment/store.d.ts.map +1 -0
- package/dist/enrollment/store.js +81 -0
- package/dist/enrollment/store.js.map +1 -0
- package/dist/enrollment/tables.d.ts +20 -0
- package/dist/enrollment/tables.d.ts.map +1 -0
- package/dist/enrollment/tables.js +55 -0
- package/dist/enrollment/tables.js.map +1 -0
- package/dist/enrollment/tokens.d.ts +4 -0
- package/dist/enrollment/tokens.d.ts.map +1 -0
- package/dist/enrollment/tokens.js +20 -0
- package/dist/enrollment/tokens.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/inventory/drift.d.ts +73 -0
- package/dist/inventory/drift.d.ts.map +1 -0
- package/dist/inventory/drift.js +91 -0
- package/dist/inventory/drift.js.map +1 -0
- package/dist/reporting/report.d.ts +65 -0
- package/dist/reporting/report.d.ts.map +1 -0
- package/dist/reporting/report.js +137 -0
- package/dist/reporting/report.js.map +1 -0
- package/dist/reporting/schedule.d.ts +10 -0
- package/dist/reporting/schedule.d.ts.map +1 -0
- package/dist/reporting/schedule.js +34 -0
- package/dist/reporting/schedule.js.map +1 -0
- package/dist/reporting/tables.d.ts +11 -0
- package/dist/reporting/tables.d.ts.map +1 -0
- package/dist/reporting/tables.js +22 -0
- package/dist/reporting/tables.js.map +1 -0
- package/dist/rollout/campaign.d.ts +103 -0
- package/dist/rollout/campaign.d.ts.map +1 -0
- package/dist/rollout/campaign.js +258 -0
- package/dist/rollout/campaign.js.map +1 -0
- package/dist/rollout/rollback.d.ts +51 -0
- package/dist/rollout/rollback.d.ts.map +1 -0
- package/dist/rollout/rollback.js +60 -0
- package/dist/rollout/rollback.js.map +1 -0
- package/dist/rollout/tables.d.ts +19 -0
- package/dist/rollout/tables.d.ts.map +1 -0
- package/dist/rollout/tables.js +65 -0
- package/dist/rollout/tables.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws if `payload` — already serialized to a plain object, exactly as it
|
|
3
|
+
* would be before signing and sending — contains any key from the forbidden
|
|
4
|
+
* list at any depth. Never silently drops the offending field: a payload
|
|
5
|
+
* that could only be made "safe" by quietly deleting data is not a payload
|
|
6
|
+
* this function considers safe to send at all.
|
|
7
|
+
*/
|
|
8
|
+
export declare function assertNoForbiddenFields(payload: unknown): void;
|
|
9
|
+
//# sourceMappingURL=assert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../src/agent/assert.ts"],"names":[],"mappings":"AAgDA;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAW9D"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CogentaError } from '@cogenta/core';
|
|
2
|
+
/**
|
|
3
|
+
* "Vérification exhaustive de ce qui sort d'un site" — the security test
|
|
4
|
+
* `docs/lots/L8-flotte.md` names explicitly. `TelemetryPayload`'s type
|
|
5
|
+
* already makes the forbidden fields unrepresentable for any caller that
|
|
6
|
+
* goes through TypeScript's own checking — this function is the
|
|
7
|
+
* defense-in-depth half: a real, runtime inspection of the object about to
|
|
8
|
+
* be signed and sent, catching a forbidden key smuggled in past a loosely
|
|
9
|
+
* typed call site (an unchecked cast, an object spread, a JSON round-trip
|
|
10
|
+
* from an untyped source) that the type system alone would not stop.
|
|
11
|
+
*/
|
|
12
|
+
const FORBIDDEN_KEYS = [
|
|
13
|
+
'content',
|
|
14
|
+
'media',
|
|
15
|
+
'visitorpii',
|
|
16
|
+
'visitordata',
|
|
17
|
+
'apikey',
|
|
18
|
+
'apikeys',
|
|
19
|
+
'secret',
|
|
20
|
+
'secrets',
|
|
21
|
+
'agentmemory',
|
|
22
|
+
'memory',
|
|
23
|
+
'rawlogs',
|
|
24
|
+
'logs',
|
|
25
|
+
'log',
|
|
26
|
+
];
|
|
27
|
+
/** Recurses into plain objects and arrays only — a class instance or a function is not what a JSON-serializable telemetry payload should ever contain in the first place. */
|
|
28
|
+
function walk(value, path, hits) {
|
|
29
|
+
if (value === null || typeof value !== 'object')
|
|
30
|
+
return;
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
value.forEach((entry, index) => {
|
|
33
|
+
walk(entry, `${path}[${index}]`, hits);
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
38
|
+
const keyPath = path === '' ? key : `${path}.${key}`;
|
|
39
|
+
if (FORBIDDEN_KEYS.includes(key.toLowerCase())) {
|
|
40
|
+
hits.push(keyPath);
|
|
41
|
+
}
|
|
42
|
+
walk(entry, keyPath, hits);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Throws if `payload` — already serialized to a plain object, exactly as it
|
|
47
|
+
* would be before signing and sending — contains any key from the forbidden
|
|
48
|
+
* list at any depth. Never silently drops the offending field: a payload
|
|
49
|
+
* that could only be made "safe" by quietly deleting data is not a payload
|
|
50
|
+
* this function considers safe to send at all.
|
|
51
|
+
*/
|
|
52
|
+
export function assertNoForbiddenFields(payload) {
|
|
53
|
+
const hits = [];
|
|
54
|
+
walk(payload, '', hits);
|
|
55
|
+
if (hits.length > 0) {
|
|
56
|
+
throw new CogentaError({
|
|
57
|
+
code: 'FLEET_TELEMETRY_FORBIDDEN_FIELD',
|
|
58
|
+
message: `Telemetry payload contains forbidden field(s): ${hits.join(', ')}.`,
|
|
59
|
+
hint: 'Only the fields named in "## Ce qui remonte" (docs/lots/L8-flotte.md) may leave a site. Remove the offending field before sending.',
|
|
60
|
+
details: { fields: hits },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=assert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert.js","sourceRoot":"","sources":["../../src/agent/assert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C;;;;;;;;;GASG;AACH,MAAM,cAAc,GAAG;IACrB,SAAS;IACT,OAAO;IACP,YAAY;IACZ,aAAa;IACb,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,SAAS;IACT,aAAa;IACb,QAAQ;IACR,SAAS;IACT,MAAM;IACN,KAAK;CACG,CAAA;AAEV,6KAA6K;AAC7K,SAAS,IAAI,CAAC,KAAc,EAAE,IAAY,EAAE,IAAc;IACxD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAM;IAEvD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC7B,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,IAAI,CAAC,CAAA;QACxC,CAAC,CAAC,CAAA;QACF,OAAM;IACR,CAAC;IAED,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,OAAO,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,CAAA;QACpD,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAqC,CAAC,EAAE,CAAC;YAClF,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACpB,CAAC;QACD,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IAC5B,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAgB;IACtD,MAAM,IAAI,GAAa,EAAE,CAAA;IACzB,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;IACvB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,iCAAiC;YACvC,OAAO,EAAE,kDAAkD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC7E,IAAI,EAAE,oIAAoI;YAC1I,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;SAC1B,CAAC,CAAA;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SbomEntry } from '@cogenta/agents-builtin';
|
|
2
|
+
import type { CredentialStore, UserStore } from '@cogenta/auth';
|
|
3
|
+
import type { AdminAccountsSummary } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* A SHA-256 hex digest of the site's real SBOM (`buildSbom`,
|
|
6
|
+
* `@cogenta/agents-builtin`) — reuses the same deterministic, sorted-key
|
|
7
|
+
* canonicalization `@cogenta/plugins`' signing primitive already relies on
|
|
8
|
+
* (`canonicalizeContent`), so two sites with the identical dependency set
|
|
9
|
+
* always fingerprint identically regardless of `package.json` insertion
|
|
10
|
+
* order.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fingerprintSbom(entries: readonly SbomEntry[]): string;
|
|
13
|
+
/**
|
|
14
|
+
* Real counts, never identities — `docs/lots/L8-flotte.md`'s own words:
|
|
15
|
+
* "comptes administrateurs (nombre et état MFA, pas les identités)". Reads
|
|
16
|
+
* every admin-role user's real credential kinds (`CredentialStore.kinds`)
|
|
17
|
+
* and counts one as MFA-enabled the moment it has any kind beyond a bare
|
|
18
|
+
* password — `password` alone is not a second factor.
|
|
19
|
+
*/
|
|
20
|
+
export declare function summarizeAdminAccounts(users: UserStore, credentials: CredentialStore, adminRole?: string): Promise<AdminAccountsSummary>;
|
|
21
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/agent/build.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEtD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAarE;AAKD;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,eAAe,EAC5B,SAAS,SAAU,GAClB,OAAO,CAAC,oBAAoB,CAAC,CAW/B"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalizeContent } from '@cogenta/plugins';
|
|
3
|
+
/**
|
|
4
|
+
* A SHA-256 hex digest of the site's real SBOM (`buildSbom`,
|
|
5
|
+
* `@cogenta/agents-builtin`) — reuses the same deterministic, sorted-key
|
|
6
|
+
* canonicalization `@cogenta/plugins`' signing primitive already relies on
|
|
7
|
+
* (`canonicalizeContent`), so two sites with the identical dependency set
|
|
8
|
+
* always fingerprint identically regardless of `package.json` insertion
|
|
9
|
+
* order.
|
|
10
|
+
*/
|
|
11
|
+
export function fingerprintSbom(entries) {
|
|
12
|
+
// `canonicalizeContent` sorts each object's own keys, but an array's
|
|
13
|
+
// ELEMENT order is exactly what it was given — `buildSbom`'s own order
|
|
14
|
+
// follows `Object.entries()` on a plain `dependencies` record, which is
|
|
15
|
+
// insertion order, not a stable sort. Two sites with the identical
|
|
16
|
+
// dependency set built from a `package.json`/lockfile whose keys simply
|
|
17
|
+
// appear in a different order must fingerprint identically, so the
|
|
18
|
+
// entries are sorted here, by name then ecosystem, before canonicalizing.
|
|
19
|
+
const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name) || a.ecosystem.localeCompare(b.ecosystem));
|
|
20
|
+
const canonical = canonicalizeContent(sorted);
|
|
21
|
+
return createHash('sha256').update(canonical, 'utf8').digest('hex');
|
|
22
|
+
}
|
|
23
|
+
/** `CredentialStore.kinds()`'s real return values beyond a bare password — `@cogenta/auth`'s own `CREDENTIAL_KINDS` (`'password' | 'totp' | 'webauthn'`), not re-exported publicly, so named here as the literal strings its real API returns. */
|
|
24
|
+
const MFA_KINDS = ['totp', 'webauthn'];
|
|
25
|
+
/**
|
|
26
|
+
* Real counts, never identities — `docs/lots/L8-flotte.md`'s own words:
|
|
27
|
+
* "comptes administrateurs (nombre et état MFA, pas les identités)". Reads
|
|
28
|
+
* every admin-role user's real credential kinds (`CredentialStore.kinds`)
|
|
29
|
+
* and counts one as MFA-enabled the moment it has any kind beyond a bare
|
|
30
|
+
* password — `password` alone is not a second factor.
|
|
31
|
+
*/
|
|
32
|
+
export async function summarizeAdminAccounts(users, credentials, adminRole = 'admin') {
|
|
33
|
+
const all = await users.list();
|
|
34
|
+
const admins = all.filter((user) => user.roles.includes(adminRole));
|
|
35
|
+
let mfaEnabledCount = 0;
|
|
36
|
+
for (const admin of admins) {
|
|
37
|
+
const kinds = await credentials.kinds(admin.id);
|
|
38
|
+
if (kinds.some((kind) => MFA_KINDS.includes(kind)))
|
|
39
|
+
mfaEnabledCount += 1;
|
|
40
|
+
}
|
|
41
|
+
return { count: admins.length, mfaEnabledCount };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/agent/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAGtD;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,OAA6B;IAC3D,qEAAqE;IACrE,uEAAuE;IACvE,wEAAwE;IACxE,mEAAmE;IACnE,wEAAwE;IACxE,mEAAmE;IACnE,0EAA0E;IAC1E,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CACjF,CAAA;IACD,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAA;IAC7C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACrE,CAAC;AAED,kPAAkP;AAClP,MAAM,SAAS,GAAsB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;AAEzD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAgB,EAChB,WAA4B,EAC5B,SAAS,GAAG,OAAO;IAEnB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAA;IAC9B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;IAEnE,IAAI,eAAe,GAAG,CAAC,CAAA;IACvB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAC/C,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAAE,eAAe,IAAI,CAAC,CAAA;IAC1E,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAA;AAClD,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type FleetCommand, type FleetCommandAction, type SignedFleetCommand } from '../control/commands.js';
|
|
2
|
+
export type FleetCommandVerification = {
|
|
3
|
+
readonly ok: true;
|
|
4
|
+
readonly command: FleetCommand;
|
|
5
|
+
} | {
|
|
6
|
+
readonly ok: false;
|
|
7
|
+
readonly reason: 'action_not_whitelisted' | 'invalid_signature';
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The real security boundary "## Appairage" describes: "exécutables
|
|
11
|
+
* uniquement dans une liste blanche d'actions." Whitelist checked first — a
|
|
12
|
+
* command naming an action outside `FLEET_COMMAND_ACTIONS` is refused before
|
|
13
|
+
* its signature is even inspected, since signature validity alone must never
|
|
14
|
+
* be sufficient to authorize an action this site doesn't recognise at all.
|
|
15
|
+
* Verifies against the CONTROL PLANE'S public key (`../control/identity.js`,
|
|
16
|
+
* learned once at pairing — `../enrollment/store.js`'s
|
|
17
|
+
* `PairingToken.controlPlanePublicKey`), the reverse direction from
|
|
18
|
+
* `../agent/sign.js`'s telemetry signing, which uses the site's own key.
|
|
19
|
+
*/
|
|
20
|
+
export declare function verifyFleetCommand(signed: SignedFleetCommand, controlPlanePublicKeyBase64: string): FleetCommandVerification;
|
|
21
|
+
export type FleetCommandHandlers = {
|
|
22
|
+
readonly [K in FleetCommandAction]?: (payload: unknown) => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
export type FleetCommandDispatchResult = {
|
|
25
|
+
readonly ok: true;
|
|
26
|
+
readonly executed: true;
|
|
27
|
+
} | {
|
|
28
|
+
readonly ok: false;
|
|
29
|
+
readonly reason: 'action_not_whitelisted' | 'invalid_signature' | 'no_handler_registered';
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Verifies, then — and only then — looks up a real handler for the
|
|
33
|
+
* command's action. A rejected command never reaches a handler: this is the
|
|
34
|
+
* one real call site a site deployment should ever use to act on a fleet
|
|
35
|
+
* command, so "verify, then dispatch" is not something a caller can
|
|
36
|
+
* accidentally skip by calling a handler directly.
|
|
37
|
+
*/
|
|
38
|
+
export declare function dispatchFleetCommand(signed: SignedFleetCommand, controlPlanePublicKeyBase64: string, handlers: FleetCommandHandlers): Promise<FleetCommandDispatchResult>;
|
|
39
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/agent/commands.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,wBAAwB,CAAA;AAE/B,MAAM,MAAM,wBAAwB,GAChC;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,GAAG,mBAAmB,CAAA;CAAE,CAAA;AAM3F;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,kBAAkB,EAC1B,2BAA2B,EAAE,MAAM,GAClC,wBAAwB,CAW1B;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,CAAC,IAAI,kBAAkB,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC;CACzE,CAAA;AAED,MAAM,MAAM,0BAA0B,GAClC;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,GAC9C;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAA;IAClB,QAAQ,CAAC,MAAM,EAAE,wBAAwB,GAAG,mBAAmB,GAAG,uBAAuB,CAAA;CAC1F,CAAA;AAEL;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,kBAAkB,EAC1B,2BAA2B,EAAE,MAAM,EACnC,QAAQ,EAAE,oBAAoB,GAC7B,OAAO,CAAC,0BAA0B,CAAC,CASrC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { verifyContentSignature } from '@cogenta/plugins';
|
|
2
|
+
import { FLEET_COMMAND_ACTIONS, } from '../control/commands.js';
|
|
3
|
+
function isWhitelistedAction(action) {
|
|
4
|
+
return FLEET_COMMAND_ACTIONS.includes(action);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The real security boundary "## Appairage" describes: "exécutables
|
|
8
|
+
* uniquement dans une liste blanche d'actions." Whitelist checked first — a
|
|
9
|
+
* command naming an action outside `FLEET_COMMAND_ACTIONS` is refused before
|
|
10
|
+
* its signature is even inspected, since signature validity alone must never
|
|
11
|
+
* be sufficient to authorize an action this site doesn't recognise at all.
|
|
12
|
+
* Verifies against the CONTROL PLANE'S public key (`../control/identity.js`,
|
|
13
|
+
* learned once at pairing — `../enrollment/store.js`'s
|
|
14
|
+
* `PairingToken.controlPlanePublicKey`), the reverse direction from
|
|
15
|
+
* `../agent/sign.js`'s telemetry signing, which uses the site's own key.
|
|
16
|
+
*/
|
|
17
|
+
export function verifyFleetCommand(signed, controlPlanePublicKeyBase64) {
|
|
18
|
+
if (!isWhitelistedAction(signed.command.action)) {
|
|
19
|
+
return { ok: false, reason: 'action_not_whitelisted' };
|
|
20
|
+
}
|
|
21
|
+
const valid = verifyContentSignature(signed.command, signed.signatureBase64, controlPlanePublicKeyBase64);
|
|
22
|
+
if (!valid)
|
|
23
|
+
return { ok: false, reason: 'invalid_signature' };
|
|
24
|
+
return { ok: true, command: signed.command };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Verifies, then — and only then — looks up a real handler for the
|
|
28
|
+
* command's action. A rejected command never reaches a handler: this is the
|
|
29
|
+
* one real call site a site deployment should ever use to act on a fleet
|
|
30
|
+
* command, so "verify, then dispatch" is not something a caller can
|
|
31
|
+
* accidentally skip by calling a handler directly.
|
|
32
|
+
*/
|
|
33
|
+
export async function dispatchFleetCommand(signed, controlPlanePublicKeyBase64, handlers) {
|
|
34
|
+
const verification = verifyFleetCommand(signed, controlPlanePublicKeyBase64);
|
|
35
|
+
if (!verification.ok)
|
|
36
|
+
return verification;
|
|
37
|
+
const handler = handlers[verification.command.action];
|
|
38
|
+
if (handler === undefined)
|
|
39
|
+
return { ok: false, reason: 'no_handler_registered' };
|
|
40
|
+
await handler(verification.command.payload);
|
|
41
|
+
return { ok: true, executed: true };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/agent/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EACL,qBAAqB,GAItB,MAAM,wBAAwB,CAAA;AAM/B,SAAS,mBAAmB,CAAC,MAAc;IACzC,OAAQ,qBAA2C,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AACtE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAA0B,EAC1B,2BAAmC;IAEnC,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAA;IACxD,CAAC;IACD,MAAM,KAAK,GAAG,sBAAsB,CAClC,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,eAAe,EACtB,2BAA2B,CAC5B,CAAA;IACD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAA;IAC7D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;AAC9C,CAAC;AAaD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAA0B,EAC1B,2BAAmC,EACnC,QAA8B;IAE9B,MAAM,YAAY,GAAG,kBAAkB,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAA;IAC5E,IAAI,CAAC,YAAY,CAAC,EAAE;QAAE,OAAO,YAAY,CAAA;IAEzC,MAAM,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAA;IAEhF,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AACrC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { InventoryComponentKind } from '../inventory/drift.js';
|
|
2
|
+
/**
|
|
3
|
+
* The real shape of a `rollback` command's payload, per
|
|
4
|
+
* `../rollout/rollback.js`'s `triggerRollback` — validated here rather than
|
|
5
|
+
* trusted, since a command's `payload` field is `unknown` at the type level
|
|
6
|
+
* until a handler actually inspects it (`../agent/commands.js`'s
|
|
7
|
+
* `FleetCommandHandlers`).
|
|
8
|
+
*/
|
|
9
|
+
export interface RollbackIntent {
|
|
10
|
+
readonly componentKind: InventoryComponentKind;
|
|
11
|
+
readonly componentName: string;
|
|
12
|
+
readonly targetVersion: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The real, honest scope of this task's site-side rollback handling:
|
|
16
|
+
* validate the intent a signed, whitelisted `rollback` command carries, then
|
|
17
|
+
* hand it to a caller-supplied callback — never a claim that this package
|
|
18
|
+
* itself reverts installed code. No mechanism exists anywhere in this
|
|
19
|
+
* codebase today to revert a site's installed plugin/theme/CMS code to a
|
|
20
|
+
* prior version (no package-manager integration, no `@cogenta/plugins`
|
|
21
|
+
* uninstall/downgrade path) — that is a real, documented gap, not something
|
|
22
|
+
* papered over with a handler that pretends to act. `recordIntent` is where
|
|
23
|
+
* a real deployment wires whatever it actually has (an operator alert, a
|
|
24
|
+
* queued manual task, or — once real reversion infrastructure exists — the
|
|
25
|
+
* genuine mechanism) without this package inventing one it cannot back.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createRollbackIntentHandler(recordIntent: (intent: RollbackIntent) => Promise<void>): (payload: unknown) => Promise<void>;
|
|
28
|
+
//# sourceMappingURL=rollback-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollback-handler.d.ts","sourceRoot":"","sources":["../../src/agent/rollback-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAEnE;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,aAAa,EAAE,sBAAsB,CAAA;IAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAC/B;AAqCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GACtD,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAKrC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CogentaError } from '@cogenta/core';
|
|
2
|
+
const COMPONENT_KINDS = ['cms', 'plugin', 'theme'];
|
|
3
|
+
function parseRollbackPayload(payload) {
|
|
4
|
+
if (payload === null || typeof payload !== 'object') {
|
|
5
|
+
throw new CogentaError({
|
|
6
|
+
code: 'FLEET_ROLLBACK_NO_PRIOR_VERSION',
|
|
7
|
+
message: 'Rollback command payload is not an object.',
|
|
8
|
+
hint: 'This indicates a control-plane bug — triggerRollback always sends a real object payload.',
|
|
9
|
+
details: { payload },
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
const record = payload;
|
|
13
|
+
const { componentKind, componentName, targetVersion } = record;
|
|
14
|
+
if (typeof componentKind !== 'string' ||
|
|
15
|
+
!COMPONENT_KINDS.includes(componentKind) ||
|
|
16
|
+
typeof componentName !== 'string' ||
|
|
17
|
+
componentName.trim() === '' ||
|
|
18
|
+
typeof targetVersion !== 'string' ||
|
|
19
|
+
targetVersion.trim() === '') {
|
|
20
|
+
throw new CogentaError({
|
|
21
|
+
code: 'FLEET_ROLLBACK_NO_PRIOR_VERSION',
|
|
22
|
+
message: 'Rollback command payload is missing or has malformed fields.',
|
|
23
|
+
hint: 'Expected { componentKind: "cms"|"plugin"|"theme", componentName: string, targetVersion: string }.',
|
|
24
|
+
details: { payload },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
componentKind: componentKind,
|
|
29
|
+
componentName,
|
|
30
|
+
targetVersion,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The real, honest scope of this task's site-side rollback handling:
|
|
35
|
+
* validate the intent a signed, whitelisted `rollback` command carries, then
|
|
36
|
+
* hand it to a caller-supplied callback — never a claim that this package
|
|
37
|
+
* itself reverts installed code. No mechanism exists anywhere in this
|
|
38
|
+
* codebase today to revert a site's installed plugin/theme/CMS code to a
|
|
39
|
+
* prior version (no package-manager integration, no `@cogenta/plugins`
|
|
40
|
+
* uninstall/downgrade path) — that is a real, documented gap, not something
|
|
41
|
+
* papered over with a handler that pretends to act. `recordIntent` is where
|
|
42
|
+
* a real deployment wires whatever it actually has (an operator alert, a
|
|
43
|
+
* queued manual task, or — once real reversion infrastructure exists — the
|
|
44
|
+
* genuine mechanism) without this package inventing one it cannot back.
|
|
45
|
+
*/
|
|
46
|
+
export function createRollbackIntentHandler(recordIntent) {
|
|
47
|
+
return async (payload) => {
|
|
48
|
+
const intent = parseRollbackPayload(payload);
|
|
49
|
+
await recordIntent(intent);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=rollback-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollback-handler.js","sourceRoot":"","sources":["../../src/agent/rollback-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAgB5C,MAAM,eAAe,GAAsC,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;AAErF,SAAS,oBAAoB,CAAC,OAAgB;IAC5C,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QACpD,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,iCAAiC;YACvC,OAAO,EAAE,4CAA4C;YACrD,IAAI,EAAE,0FAA0F;YAChG,OAAO,EAAE,EAAE,OAAO,EAAE;SACrB,CAAC,CAAA;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,OAAkC,CAAA;IACjD,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,MAAM,CAAA;IAC9D,IACE,OAAO,aAAa,KAAK,QAAQ;QACjC,CAAC,eAAe,CAAC,QAAQ,CAAC,aAAuC,CAAC;QAClE,OAAO,aAAa,KAAK,QAAQ;QACjC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE;QAC3B,OAAO,aAAa,KAAK,QAAQ;QACjC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAC3B,CAAC;QACD,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,iCAAiC;YACvC,OAAO,EAAE,8DAA8D;YACvE,IAAI,EAAE,mGAAmG;YACzG,OAAO,EAAE,EAAE,OAAO,EAAE;SACrB,CAAC,CAAA;IACJ,CAAC;IACD,OAAO;QACL,aAAa,EAAE,aAAuC;QACtD,aAAa;QACb,aAAa;KACd,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,2BAA2B,CACzC,YAAuD;IAEvD,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAA;QAC5C,MAAM,YAAY,CAAC,MAAM,CAAC,CAAA;IAC5B,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TelemetryPayload } from './types.js';
|
|
2
|
+
export interface SignedTelemetry {
|
|
3
|
+
readonly payload: TelemetryPayload;
|
|
4
|
+
/** Base64 Ed25519 signature over the payload's canonical content — `@cogenta/plugins`' generalized signing primitive (task 9/12), the same one this project's control-plane pairing already uses (L8 task 1). */
|
|
5
|
+
readonly signatureBase64: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Signs a telemetry payload with the site's own paired private key
|
|
9
|
+
* (`@cogenta/fleet`'s enrollment, task 1) before it ever leaves the
|
|
10
|
+
* process. Runs the real, defense-in-depth forbidden-field check first —
|
|
11
|
+
* signing a payload does not make an accidentally-forbidden field safe to
|
|
12
|
+
* send, so this refuses to sign one at all rather than signing something
|
|
13
|
+
* that then has to be caught later, closer to the network boundary.
|
|
14
|
+
*/
|
|
15
|
+
export declare function signTelemetryPayload(payload: TelemetryPayload, sitePrivateKeyBase64: string): SignedTelemetry;
|
|
16
|
+
/** Verifies a signed telemetry payload against the site's registered public key — the control plane's real half of this primitive (ingestion is task 3's job, not this one's). */
|
|
17
|
+
export declare function verifyTelemetrySignature(signed: SignedTelemetry, sitePublicKeyBase64: string): boolean;
|
|
18
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../src/agent/sign.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;IAClC,iNAAiN;IACjN,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CACjC;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,gBAAgB,EACzB,oBAAoB,EAAE,MAAM,GAC3B,eAAe,CAGjB;AAED,kLAAkL;AAClL,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,eAAe,EACvB,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAET"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { signContent, verifyContentSignature } from '@cogenta/plugins';
|
|
2
|
+
import { assertNoForbiddenFields } from './assert.js';
|
|
3
|
+
/**
|
|
4
|
+
* Signs a telemetry payload with the site's own paired private key
|
|
5
|
+
* (`@cogenta/fleet`'s enrollment, task 1) before it ever leaves the
|
|
6
|
+
* process. Runs the real, defense-in-depth forbidden-field check first —
|
|
7
|
+
* signing a payload does not make an accidentally-forbidden field safe to
|
|
8
|
+
* send, so this refuses to sign one at all rather than signing something
|
|
9
|
+
* that then has to be caught later, closer to the network boundary.
|
|
10
|
+
*/
|
|
11
|
+
export function signTelemetryPayload(payload, sitePrivateKeyBase64) {
|
|
12
|
+
assertNoForbiddenFields(payload);
|
|
13
|
+
return { payload, signatureBase64: signContent(payload, sitePrivateKeyBase64) };
|
|
14
|
+
}
|
|
15
|
+
/** Verifies a signed telemetry payload against the site's registered public key — the control plane's real half of this primitive (ingestion is task 3's job, not this one's). */
|
|
16
|
+
export function verifyTelemetrySignature(signed, sitePublicKeyBase64) {
|
|
17
|
+
return verifyContentSignature(signed.payload, signed.signatureBase64, sitePublicKeyBase64);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=sign.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.js","sourceRoot":"","sources":["../../src/agent/sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AASrD;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAyB,EACzB,oBAA4B;IAE5B,uBAAuB,CAAC,OAAO,CAAC,CAAA;IAChC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,CAAC,OAAO,EAAE,oBAAoB,CAAC,EAAE,CAAA;AACjF,CAAC;AAED,kLAAkL;AAClL,MAAM,UAAU,wBAAwB,CACtC,MAAuB,EACvB,mBAA2B;IAE3B,OAAO,sBAAsB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAAA;AAC5F,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { CruxMetrics, Urgency } from '@cogenta/agents-builtin';
|
|
2
|
+
/**
|
|
3
|
+
* What a site is allowed to send to the control plane — the closed
|
|
4
|
+
* enumeration of "## Ce qui remonte, et ce qui ne remonte pas"
|
|
5
|
+
* (`docs/lots/L8-flotte.md`), and nothing else. No field on this type, or
|
|
6
|
+
* any type it references, can hold content, media, visitor PII, an API key,
|
|
7
|
+
* agent memory, or a raw log line — that is a property of the SHAPE, not a
|
|
8
|
+
* filter applied after the fact: there is no `content`/`media`/`raw`/`logs`
|
|
9
|
+
* key anywhere on this type for a caller to even attempt to fill in.
|
|
10
|
+
*
|
|
11
|
+
* Some fields are wired to a real, existing data source in this codebase
|
|
12
|
+
* today (see each field's own doc comment); others are a real, honest SHAPE
|
|
13
|
+
* with no data source built yet — `docs/lots/L8-flotte.md`'s own "remonte"
|
|
14
|
+
* list names them regardless, and building the shape now means a later
|
|
15
|
+
* task's real data source slots in without a breaking payload change.
|
|
16
|
+
*/
|
|
17
|
+
export interface TelemetryPayload {
|
|
18
|
+
readonly siteId: string;
|
|
19
|
+
readonly collectedAt: string;
|
|
20
|
+
/** Shape only — no real CMS/plugin/theme version inventory exists anywhere in this codebase yet (`cogenta doctor` reports only the Node runtime version). */
|
|
21
|
+
readonly installedVersions: InstalledVersions;
|
|
22
|
+
/**
|
|
23
|
+
* A SHA-256 hex digest of the site's real SBOM (`buildSbom`,
|
|
24
|
+
* `@cogenta/agents-builtin`'s security agent) — the fingerprint travels,
|
|
25
|
+
* never the SBOM's actual dependency list, which would itself be more
|
|
26
|
+
* detail than "## Ce qui remonte" asks for.
|
|
27
|
+
*/
|
|
28
|
+
readonly sbomFingerprint: string;
|
|
29
|
+
/** Real — one entry per open CVE the security agent's real OSV/EPSS pipeline (`assessExploitability`) has assessed for this site's dependencies. */
|
|
30
|
+
readonly openCves: readonly OpenCveSummary[];
|
|
31
|
+
/** Real — the performance agent's real, aggregated CrUX field-data metrics (`CruxMetrics`, `@cogenta/agents-builtin`). `null` when no CrUX data exists for this site (not enough real-user traffic — not an error). */
|
|
32
|
+
readonly coreWebVitalsAggregate: CruxMetrics | null;
|
|
33
|
+
/** Shape only — no uptime/availability monitoring exists anywhere in this codebase yet. */
|
|
34
|
+
readonly availability: AvailabilitySummary;
|
|
35
|
+
/** Shape only — no real backup dump/restore mechanism exists anywhere in this codebase yet (confirmed absent, L9 task 9's own honest CLI scoping). */
|
|
36
|
+
readonly backups: BackupSummary;
|
|
37
|
+
/** Shape only, and nullable — no TLS certificate expiry check exists anywhere in this codebase yet. */
|
|
38
|
+
readonly certificateExpiry: CertificateExpirySummary | null;
|
|
39
|
+
/**
|
|
40
|
+
* Real — `count` from `@cogenta/auth`'s real `UserStore.list()` filtered
|
|
41
|
+
* to admin-role users; `mfaEnabledCount` from `CredentialStore.kinds()`
|
|
42
|
+
* reporting a real second factor (`totp` or `webauthn`, never `password`
|
|
43
|
+
* alone) for each. Never an identity — no email, no user id, only counts.
|
|
44
|
+
*/
|
|
45
|
+
readonly adminAccounts: AdminAccountsSummary;
|
|
46
|
+
/** Shape only — no structured error-aggregation sink exists anywhere in this codebase yet (the logger is real and structured, but nothing aggregates its output for fleet reporting). */
|
|
47
|
+
readonly aggregatedErrors: AggregatedErrorsSummary;
|
|
48
|
+
}
|
|
49
|
+
export interface InstalledVersions {
|
|
50
|
+
readonly cms: string | null;
|
|
51
|
+
readonly plugins: readonly {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly version: string;
|
|
54
|
+
}[];
|
|
55
|
+
readonly themes: readonly {
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly version: string;
|
|
58
|
+
}[];
|
|
59
|
+
}
|
|
60
|
+
export interface OpenCveSummary {
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly urgency: Urgency;
|
|
63
|
+
readonly status: 'open' | 'patched' | 'ignored';
|
|
64
|
+
}
|
|
65
|
+
export interface AvailabilitySummary {
|
|
66
|
+
/** 0..1 over the reporting window. `null` when no measurement exists yet. */
|
|
67
|
+
readonly uptimeRatio: number | null;
|
|
68
|
+
}
|
|
69
|
+
export interface BackupSummary {
|
|
70
|
+
readonly lastBackupAt: string | null;
|
|
71
|
+
readonly lastResult: 'success' | 'failure' | 'unknown';
|
|
72
|
+
}
|
|
73
|
+
export interface CertificateExpirySummary {
|
|
74
|
+
readonly domain: string;
|
|
75
|
+
readonly expiresAt: string;
|
|
76
|
+
}
|
|
77
|
+
export interface AdminAccountsSummary {
|
|
78
|
+
readonly count: number;
|
|
79
|
+
readonly mfaEnabledCount: number;
|
|
80
|
+
}
|
|
81
|
+
export interface AggregatedErrorsSummary {
|
|
82
|
+
readonly count: number;
|
|
83
|
+
readonly windowStart: string;
|
|
84
|
+
readonly windowEnd: string;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AAEnE;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAE5B,6JAA6J;IAC7J,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAA;IAE7C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAEhC,oJAAoJ;IACpJ,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAA;IAE5C,uNAAuN;IACvN,QAAQ,CAAC,sBAAsB,EAAE,WAAW,GAAG,IAAI,CAAA;IAEnD,2FAA2F;IAC3F,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAA;IAE1C,sJAAsJ;IACtJ,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;IAE/B,uGAAuG;IACvG,QAAQ,CAAC,iBAAiB,EAAE,wBAAwB,GAAG,IAAI,CAAA;IAE3D;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAA;IAE5C,yLAAyL;IACzL,QAAQ,CAAC,gBAAgB,EAAE,uBAAuB,CAAA;CACnD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAChF,QAAQ,CAAC,MAAM,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAChF;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAA;CAChD;AAED,MAAM,WAAW,mBAAmB;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,QAAQ,CAAC,UAAU,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;CACvD;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agent/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type AlertChannelMessage } from '@cogenta/channels';
|
|
2
|
+
import type { SiteRisk } from '../control/risk.js';
|
|
3
|
+
import type { TelemetrySnapshot } from '../control/state.js';
|
|
4
|
+
import type { SiteRegistration } from '../enrollment/store.js';
|
|
5
|
+
import type { CampaignRecord } from '../rollout/campaign.js';
|
|
6
|
+
import type { AlertConditionStore } from './conditions.js';
|
|
7
|
+
/**
|
|
8
|
+
* A site crossing into `critical` risk (task 5's real `computeSiteRisk`) —
|
|
9
|
+
* "un site avec une CVE critique passe devant" on the dashboard is the same
|
|
10
|
+
* real signal an operator needs pushed to them immediately, not only seen
|
|
11
|
+
* next time they open it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectCriticalRiskAlert(risk: SiteRisk): AlertChannelMessage | null;
|
|
14
|
+
/**
|
|
15
|
+
* A rollout campaign transitioning to `halted` (task 7's real
|
|
16
|
+
* `CampaignRecord`) — "un échec arrête toute la campagne" is exactly the
|
|
17
|
+
* kind of event an operator needs to know about the moment it happens, not
|
|
18
|
+
* discover by noticing an update never finished.
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectCampaignHaltedAlert(campaign: CampaignRecord, siteNameById: ReadonlyMap<string, string>): AlertChannelMessage | null;
|
|
21
|
+
export declare function detectSiteSilentAlert(site: SiteRegistration, latestSnapshot: TelemetrySnapshot | null, now?: () => number): AlertChannelMessage | null;
|
|
22
|
+
/**
|
|
23
|
+
* Wraps any detection function with the real de-duplication layer
|
|
24
|
+
* (`./conditions.js`): the message is only returned — and thus only ever
|
|
25
|
+
* sent — on the check where the condition genuinely transitions from
|
|
26
|
+
* inactive to active. Every subsequent check while it remains active
|
|
27
|
+
* returns `null`, even though the underlying condition still holds.
|
|
28
|
+
*/
|
|
29
|
+
export declare function raiseIfNew(conditionStore: AlertConditionStore, siteId: string, conditionType: 'critical-risk' | 'campaign-halted' | 'site-silent', message: AlertChannelMessage | null, now?: () => number): Promise<AlertChannelMessage | null>;
|
|
30
|
+
/**
|
|
31
|
+
* The real dispatch integration point — a structural interface, never a
|
|
32
|
+
* hard dependency on any specific `@cogenta/channels` adapter instance
|
|
33
|
+
* (matches `../reporting/report.js`'s own boundary: depend on the real
|
|
34
|
+
* message-CONSTRUCTOR types, never on a live adapter). No live control-plane
|
|
35
|
+
* deployment exists anywhere in this lot; a real deployment plugs a real
|
|
36
|
+
* `ChannelAdapter.send` (or a registry's dispatch function) in here.
|
|
37
|
+
*/
|
|
38
|
+
export interface AlertSender {
|
|
39
|
+
send(message: AlertChannelMessage): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export declare function dispatchAlert(sender: AlertSender, message: AlertChannelMessage): Promise<void>;
|
|
42
|
+
//# sourceMappingURL=alerts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alerts.d.ts","sourceRoot":"","sources":["../../src/alerts/alerts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAc,MAAM,mBAAmB,CAAA;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAa1D;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ,GAAG,mBAAmB,GAAG,IAAI,CAUlF;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,cAAc,EACxB,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACxC,mBAAmB,GAAG,IAAI,CAY5B;AAmBD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,gBAAgB,EACtB,cAAc,EAAE,iBAAiB,GAAG,IAAI,EACxC,GAAG,GAAE,MAAM,MAAiB,GAC3B,mBAAmB,GAAG,IAAI,CAgB5B;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,cAAc,EAAE,mBAAmB,EACnC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,eAAe,GAAG,iBAAiB,GAAG,aAAa,EAClE,OAAO,EAAE,mBAAmB,GAAG,IAAI,EACnC,GAAG,CAAC,EAAE,MAAM,MAAM,GACjB,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAOrC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClD;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAEf"}
|