@panguard-ai/panguard-guard 1.7.2 → 1.7.3
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.
|
@@ -1521,9 +1521,9 @@
|
|
|
1521
1521
|
<div class="welcome-step-h">Join collective defense</div>
|
|
1522
1522
|
<div class="welcome-step-b">
|
|
1523
1523
|
Optionally let the attacks Guard blocks here help draft new open rules for everyone —
|
|
1524
|
-
and get the community's new rules back sooner. Shares only
|
|
1525
|
-
|
|
1526
|
-
until you turn it on.
|
|
1524
|
+
and get the community's new rules back sooner. Shares only the matched rule ID(s),
|
|
1525
|
+
attack category, a coarse region, and a truncated IP; <strong>never your prompts,
|
|
1526
|
+
code, files, secrets, paths, or hostname</strong>. Off until you turn it on.
|
|
1527
1527
|
</div>
|
|
1528
1528
|
<button type="button" class="btn btn-secondary btn-sm" data-goto="tcloud">
|
|
1529
1529
|
Open Threat Cloud →
|
|
@@ -2104,9 +2104,9 @@
|
|
|
2104
2104
|
<div class="cd-box">
|
|
2105
2105
|
<div class="cd-box-title">What is shared (and what is never shared)</div>
|
|
2106
2106
|
<ul class="cd-list">
|
|
2107
|
-
<li>Shared: the matched rule ID,
|
|
2108
|
-
<li>Never shared: your prompts, code, file contents,
|
|
2109
|
-
<li>A random install ID
|
|
2107
|
+
<li>Shared: the matched rule ID(s), attack category + MITRE technique, a coarse country region, and a truncated IP (last two octets zeroed).</li>
|
|
2108
|
+
<li>Never shared: your prompts, code, file contents, secrets, file paths, hostname, or username.</li>
|
|
2109
|
+
<li>A stable random install ID links your own submissions (pseudonymous) — raw samples never leave this machine.</li>
|
|
2110
2110
|
</ul>
|
|
2111
2111
|
</div>
|
|
2112
2112
|
</div>
|
|
@@ -3790,10 +3790,10 @@
|
|
|
3790
3790
|
if (!isOn) {
|
|
3791
3791
|
var ok = window.confirm(
|
|
3792
3792
|
'Turn on collective defense?\n\n' +
|
|
3793
|
-
'When Guard blocks an attack on this machine, it will share
|
|
3794
|
-
'(the matched rule ID +
|
|
3795
|
-
'network.\n\nNever shared: your prompts, code, file contents,
|
|
3796
|
-
'hostname.\n\nYou can turn this off anytime.'
|
|
3793
|
+
'When Guard blocks an attack on this machine, it will share a minimal signature ' +
|
|
3794
|
+
'(the matched rule ID + attack category + a coarse region + a truncated IP) with the ATR ' +
|
|
3795
|
+
'network.\n\nNever shared: your prompts, code, file contents, secrets, file paths, ' +
|
|
3796
|
+
'hostname, or username.\n\nYou can turn this off anytime.'
|
|
3797
3797
|
);
|
|
3798
3798
|
if (!ok) return;
|
|
3799
3799
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sealed Threat-Cloud contributions — endpoint upload encryption (P1).
|
|
3
|
+
*
|
|
4
|
+
* Encrypts an outbound contribution to PanGuard's ingest PUBLIC key so ONLY the
|
|
5
|
+
* ingest private-key holder (kept in KMS, never in the client) can read it — not
|
|
6
|
+
* PanGuard's relay, not a TLS-termination middlebox, not a self-hosted relay
|
|
7
|
+
* operator who lacks the key. This is end-to-end / client-side encryption on TOP
|
|
8
|
+
* of TLS, closing the gap that the upload was previously plaintext-to-our-server.
|
|
9
|
+
*
|
|
10
|
+
* Construction: JWE (RFC 7516) with ECDH-ES key agreement over X25519 (RFC 8037)
|
|
11
|
+
* + A256GCM content encryption — a standard, audited primitive available via the
|
|
12
|
+
* already-bundled `jose` dependency (no new crypto dependency, no homemade crypto).
|
|
13
|
+
* ECDH-ES derives the content key from a FRESH ephemeral key per message, so the
|
|
14
|
+
* envelope carries NO sender identity (anonymous sender) — supporting the
|
|
15
|
+
* unlinkable-by-default goal.
|
|
16
|
+
*
|
|
17
|
+
* Trust root = the ingest keypair. The PUBLIC half is bundled + pinned here (safe
|
|
18
|
+
* to ship in the open-source client). The PRIVATE half lives in KMS and is NEVER
|
|
19
|
+
* present in the client, so a client provably cannot decrypt its own contribution
|
|
20
|
+
* — a property a customer can verify (see `pga tc --show-sample-upload`).
|
|
21
|
+
*
|
|
22
|
+
* @module @panguard-ai/panguard-guard/threat-cloud/seal
|
|
23
|
+
*/
|
|
24
|
+
import { type JWK } from 'jose';
|
|
25
|
+
export interface IngestKey {
|
|
26
|
+
/** Key id carried in the JWE header so the server selects the matching private key. */
|
|
27
|
+
readonly kid: string;
|
|
28
|
+
/** X25519 public key (OKP/X25519 JWK). Public — safe to bundle. NEVER a private key. */
|
|
29
|
+
readonly publicJwk: JWK;
|
|
30
|
+
/** Rotation window (ISO 8601). A key is active when notBefore <= now < notAfter. */
|
|
31
|
+
readonly notBefore: string;
|
|
32
|
+
readonly notAfter: string;
|
|
33
|
+
}
|
|
34
|
+
export interface SealedEnvelope {
|
|
35
|
+
readonly v: 1;
|
|
36
|
+
readonly alg: 'ECDH-ES+A256GCM';
|
|
37
|
+
readonly kid: string;
|
|
38
|
+
/** Compact JWE — decryptable only by the ingest private key (in KMS). */
|
|
39
|
+
readonly jwe: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Bundled + pinned ingest public keys. POPULATE from the KMS-provisioned public key
|
|
43
|
+
* before enabling sealed upload wiring (P1b). Multiple entries allow flag-day-free
|
|
44
|
+
* rotation: publish the next key's validity window ahead of cutover so old + new
|
|
45
|
+
* clients both seal to a key the server can still open during the overlap.
|
|
46
|
+
*
|
|
47
|
+
* SECURITY: only X25519 PUBLIC keys belong here. A private key must NEVER appear in
|
|
48
|
+
* this file or anywhere in the open-source client.
|
|
49
|
+
*/
|
|
50
|
+
export declare const INGEST_KEYS: readonly IngestKey[];
|
|
51
|
+
/**
|
|
52
|
+
* Pick the ingest key valid at `now` (newest eligible wins, so a freshly-rotated
|
|
53
|
+
* key is preferred during an overlap window). Returns null if none configured/valid.
|
|
54
|
+
*/
|
|
55
|
+
export declare function activeIngestKey(now?: Date, keys?: readonly IngestKey[]): IngestKey | null;
|
|
56
|
+
/** Seal a payload to a specific ingest public key. Exported for tests + explicit use. */
|
|
57
|
+
export declare function sealToIngestKey(payload: unknown, key: IngestKey): Promise<SealedEnvelope>;
|
|
58
|
+
/**
|
|
59
|
+
* Seal a payload to the currently-active bundled ingest key. Returns null when NO
|
|
60
|
+
* ingest key is configured/valid. Callers MUST treat null as "do not upload" — never
|
|
61
|
+
* fall back to plaintext — so a key misconfiguration can never leak cleartext.
|
|
62
|
+
*/
|
|
63
|
+
export declare function sealForIngest(payload: unknown, now?: Date): Promise<SealedEnvelope | null>;
|
|
64
|
+
/** True when a usable ingest key is bundled, so the upload path can require sealing. */
|
|
65
|
+
export declare function sealingAvailable(now?: Date): boolean;
|
|
66
|
+
//# sourceMappingURL=seal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../../src/threat-cloud/seal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAA6B,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AAE3D,MAAM,WAAW,SAAS;IACxB,uFAAuF;IACvF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC;IACxB,oFAAoF;IACpF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC;IACd,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAQ3C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,GAAE,IAAiB,EACtB,IAAI,GAAE,SAAS,SAAS,EAAgB,GACvC,SAAS,GAAG,IAAI,CAKlB;AAED,yFAAyF;AACzF,wBAAsB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAM/F;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,OAAO,EAChB,GAAG,GAAE,IAAiB,GACrB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAIhC;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,IAAiB,GAAG,OAAO,CAEhE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sealed Threat-Cloud contributions — endpoint upload encryption (P1).
|
|
3
|
+
*
|
|
4
|
+
* Encrypts an outbound contribution to PanGuard's ingest PUBLIC key so ONLY the
|
|
5
|
+
* ingest private-key holder (kept in KMS, never in the client) can read it — not
|
|
6
|
+
* PanGuard's relay, not a TLS-termination middlebox, not a self-hosted relay
|
|
7
|
+
* operator who lacks the key. This is end-to-end / client-side encryption on TOP
|
|
8
|
+
* of TLS, closing the gap that the upload was previously plaintext-to-our-server.
|
|
9
|
+
*
|
|
10
|
+
* Construction: JWE (RFC 7516) with ECDH-ES key agreement over X25519 (RFC 8037)
|
|
11
|
+
* + A256GCM content encryption — a standard, audited primitive available via the
|
|
12
|
+
* already-bundled `jose` dependency (no new crypto dependency, no homemade crypto).
|
|
13
|
+
* ECDH-ES derives the content key from a FRESH ephemeral key per message, so the
|
|
14
|
+
* envelope carries NO sender identity (anonymous sender) — supporting the
|
|
15
|
+
* unlinkable-by-default goal.
|
|
16
|
+
*
|
|
17
|
+
* Trust root = the ingest keypair. The PUBLIC half is bundled + pinned here (safe
|
|
18
|
+
* to ship in the open-source client). The PRIVATE half lives in KMS and is NEVER
|
|
19
|
+
* present in the client, so a client provably cannot decrypt its own contribution
|
|
20
|
+
* — a property a customer can verify (see `pga tc --show-sample-upload`).
|
|
21
|
+
*
|
|
22
|
+
* @module @panguard-ai/panguard-guard/threat-cloud/seal
|
|
23
|
+
*/
|
|
24
|
+
import { CompactEncrypt, importJWK } from 'jose';
|
|
25
|
+
/**
|
|
26
|
+
* Bundled + pinned ingest public keys. POPULATE from the KMS-provisioned public key
|
|
27
|
+
* before enabling sealed upload wiring (P1b). Multiple entries allow flag-day-free
|
|
28
|
+
* rotation: publish the next key's validity window ahead of cutover so old + new
|
|
29
|
+
* clients both seal to a key the server can still open during the overlap.
|
|
30
|
+
*
|
|
31
|
+
* SECURITY: only X25519 PUBLIC keys belong here. A private key must NEVER appear in
|
|
32
|
+
* this file or anywhere in the open-source client.
|
|
33
|
+
*/
|
|
34
|
+
export const INGEST_KEYS = [
|
|
35
|
+
// Example (replace `x` with the real KMS-provisioned public key, then ship):
|
|
36
|
+
// {
|
|
37
|
+
// kid: 'ingest-2026-07',
|
|
38
|
+
// publicJwk: { kty: 'OKP', crv: 'X25519', x: '<base64url-public>' },
|
|
39
|
+
// notBefore: '2026-07-01T00:00:00Z',
|
|
40
|
+
// notAfter: '2027-07-01T00:00:00Z',
|
|
41
|
+
// },
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Pick the ingest key valid at `now` (newest eligible wins, so a freshly-rotated
|
|
45
|
+
* key is preferred during an overlap window). Returns null if none configured/valid.
|
|
46
|
+
*/
|
|
47
|
+
export function activeIngestKey(now = new Date(), keys = INGEST_KEYS) {
|
|
48
|
+
const t = now.getTime();
|
|
49
|
+
const eligible = keys.filter((k) => Date.parse(k.notBefore) <= t && t < Date.parse(k.notAfter));
|
|
50
|
+
if (eligible.length === 0)
|
|
51
|
+
return null;
|
|
52
|
+
return eligible.reduce((a, b) => (Date.parse(b.notBefore) > Date.parse(a.notBefore) ? b : a));
|
|
53
|
+
}
|
|
54
|
+
/** Seal a payload to a specific ingest public key. Exported for tests + explicit use. */
|
|
55
|
+
export async function sealToIngestKey(payload, key) {
|
|
56
|
+
const pub = await importJWK(key.publicJwk, 'ECDH-ES');
|
|
57
|
+
const jwe = await new CompactEncrypt(new TextEncoder().encode(JSON.stringify(payload)))
|
|
58
|
+
.setProtectedHeader({ alg: 'ECDH-ES', enc: 'A256GCM', kid: key.kid })
|
|
59
|
+
.encrypt(pub);
|
|
60
|
+
return { v: 1, alg: 'ECDH-ES+A256GCM', kid: key.kid, jwe };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Seal a payload to the currently-active bundled ingest key. Returns null when NO
|
|
64
|
+
* ingest key is configured/valid. Callers MUST treat null as "do not upload" — never
|
|
65
|
+
* fall back to plaintext — so a key misconfiguration can never leak cleartext.
|
|
66
|
+
*/
|
|
67
|
+
export async function sealForIngest(payload, now = new Date()) {
|
|
68
|
+
const key = activeIngestKey(now);
|
|
69
|
+
if (!key)
|
|
70
|
+
return null;
|
|
71
|
+
return sealToIngestKey(payload, key);
|
|
72
|
+
}
|
|
73
|
+
/** True when a usable ingest key is bundled, so the upload path can require sealing. */
|
|
74
|
+
export function sealingAvailable(now = new Date()) {
|
|
75
|
+
return activeIngestKey(now) !== null;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=seal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seal.js","sourceRoot":"","sources":["../../src/threat-cloud/seal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAY,MAAM,MAAM,CAAC;AAoB3D;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,WAAW,GAAyB;AAC/C,6EAA6E;AAC7E,IAAI;AACJ,2BAA2B;AAC3B,uEAAuE;AACvE,uCAAuC;AACvC,sCAAsC;AACtC,KAAK;CACN,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAY,IAAI,IAAI,EAAE,EACtB,OAA6B,WAAW;IAExC,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;IACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChG,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChG,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAgB,EAAE,GAAc;IACpE,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,MAAM,IAAI,cAAc,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;SACpF,kBAAkB,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;SACpE,OAAO,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,iBAAiB,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;AAC7D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAgB,EAChB,MAAY,IAAI,IAAI,EAAE;IAEtB,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO,eAAe,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,gBAAgB,CAAC,MAAY,IAAI,IAAI,EAAE;IACrD,OAAO,eAAe,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;AACvC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panguard-ai/panguard-guard",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
"js-yaml": "^4.1.0",
|
|
43
43
|
"ws": "^8.21.0",
|
|
44
44
|
"zod": "^3.24.0",
|
|
45
|
-
"@panguard-ai/atr": "1.7.
|
|
46
|
-
"@panguard-ai/panguard-
|
|
47
|
-
"@panguard-ai/
|
|
48
|
-
"@panguard-ai/
|
|
49
|
-
"@panguard-ai/
|
|
50
|
-
"@panguard-ai/
|
|
51
|
-
"@panguard-ai/
|
|
45
|
+
"@panguard-ai/atr": "1.7.3",
|
|
46
|
+
"@panguard-ai/panguard-skill-auditor": "1.7.3",
|
|
47
|
+
"@panguard-ai/panguard-mcp": "1.7.3",
|
|
48
|
+
"@panguard-ai/panguard-trap": "1.7.3",
|
|
49
|
+
"@panguard-ai/security-hardening": "1.7.3",
|
|
50
|
+
"@panguard-ai/scan-core": "1.7.3",
|
|
51
|
+
"@panguard-ai/core": "1.7.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/js-yaml": "^4.0.9",
|