@xshieldai/aegis-guard 0.5.0 → 0.6.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/package.json +2 -2
- package/src/approval-token.ts +21 -0
- package/src/index.ts +4 -0
- package/src/signing.ts +22 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xshieldai/aegis-guard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AEGIS Guard SDK
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "AEGIS Guard SDK \u2014 Five Locks primitives (approval-token, nonce, idempotency, SENSE, quality-evidence) + opt-in Agentic Control Center event bus for AEGIS-governed services",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "Capt. Anil Sharma <capt.anil.sharma@powerpbox.org>",
|
package/src/approval-token.ts
CHANGED
|
@@ -32,6 +32,17 @@ const isText = (v: unknown): v is string => typeof v === 'string' && v.length >
|
|
|
32
32
|
const say = (v: unknown): string => (typeof v === 'string' ? v : typeof v).slice(0, 60);
|
|
33
33
|
const own = (o: object, k: string): boolean => Object.prototype.hasOwnProperty.call(o, k);
|
|
34
34
|
|
|
35
|
+
// @rule:AEG-E-016 — action-binding is the ENFORCED default (review point 4, 2026-10-07): a token
|
|
36
|
+
// that does not name the concrete act (no action_digest) is refused, so one approval cannot stand
|
|
37
|
+
// in for every instance. A caller that knowingly accepts the old label-only behaviour sets
|
|
38
|
+
// AEGIS_ALLOW_LABEL_ONLY_APPROVAL=1. (The full match — digest equals the act in hand — is still the
|
|
39
|
+
// job of verifyActionApprovalToken; this base check guarantees no loose token is accepted anywhere.)
|
|
40
|
+
export const LABEL_ONLY_ENV = 'AEGIS_ALLOW_LABEL_ONLY_APPROVAL';
|
|
41
|
+
const labelOnlyAllowed = (): boolean => {
|
|
42
|
+
const v = process.env[LABEL_ONLY_ENV];
|
|
43
|
+
return v === '1' || v === 'true' || v === 'yes';
|
|
44
|
+
};
|
|
45
|
+
|
|
35
46
|
// @rule:AEG-HG-2B-005 — SENSE stores proof reference, not proof secret.
|
|
36
47
|
// Returns first 24 hex chars of SHA-256 (96 bits) — sufficient for correlation, not reconstruction.
|
|
37
48
|
export function digestApprovalToken(token: string): string {
|
|
@@ -128,6 +139,16 @@ export function verifyApprovalToken(
|
|
|
128
139
|
if (payload.nonce !== undefined && !isText(payload.nonce)) {
|
|
129
140
|
throw new IrrNoApprovalError(cap, 'AEG-E-016: token nonce is not text');
|
|
130
141
|
}
|
|
142
|
+
// Action-binding is the enforced default (point 4): a label-only approval authorises every
|
|
143
|
+
// instance of the operation, so refuse it unless the caller opted into the old loose behaviour.
|
|
144
|
+
if (!isText(payload.action_digest) && !labelOnlyAllowed()) {
|
|
145
|
+
throw new IrrNoApprovalError(
|
|
146
|
+
cap,
|
|
147
|
+
`AEG-E-016: approval is not bound to a concrete action (no action_digest) — a label-only ` +
|
|
148
|
+
`approval authorises every instance. Mint with mintActionApprovalToken()/verify with ` +
|
|
149
|
+
`verifyActionApprovalToken(), or set ${LABEL_ONLY_ENV}=1 to accept label-only approvals.`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
131
152
|
|
|
132
153
|
emitAccReceipt({
|
|
133
154
|
receipt_id: `aegis-guard-verify-${digestApprovalToken(token)}`,
|
package/src/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ export {
|
|
|
49
49
|
actionDigest,
|
|
50
50
|
mintActionApprovalToken,
|
|
51
51
|
verifyActionApprovalToken,
|
|
52
|
+
LABEL_ONLY_ENV,
|
|
52
53
|
} from './approval-token.js';
|
|
53
54
|
|
|
54
55
|
// @rule:KGT-002 — KGT-T1.1 (v0.3.0): approval tokens are AEGIS-signed EdDSA JWTs.
|
|
@@ -62,6 +63,9 @@ export {
|
|
|
62
63
|
getPublicKeyPem,
|
|
63
64
|
signApprovalJwt,
|
|
64
65
|
verifyApprovalJwt,
|
|
66
|
+
mintAllowed,
|
|
67
|
+
MINT_AUTHORITY_ENV,
|
|
68
|
+
INSECURE_LOCAL_MINT_ENV,
|
|
65
69
|
} from './signing.js';
|
|
66
70
|
|
|
67
71
|
export {
|
package/src/signing.ts
CHANGED
|
@@ -27,6 +27,25 @@ export const APPROVAL_JWT_ALG = 'EdDSA';
|
|
|
27
27
|
const KEY_FILE = 'approval-signing.key'; // PKCS8 PEM (private) — mode 600
|
|
28
28
|
const PUB_FILE = 'approval-signing.pub'; // SPKI PEM (public) — mode 644
|
|
29
29
|
|
|
30
|
+
// @rule:KGT-002 — minting is an AUTHORITY action; separation is the default (review point 1,
|
|
31
|
+
// key leg, 2026-10-07). An ordinary agent process VERIFIES; it does not mint, and it does not
|
|
32
|
+
// make a signing key. Only the designated authority (the AEGIS dashboard, ideally under its own
|
|
33
|
+
// uid) mints — it declares itself with AEGIS_MINT_AUTHORITY=1. A single-process dev box that
|
|
34
|
+
// knowingly accepts the risk sets AEGIS_ALLOW_INSECURE_LOCAL_MINT=1 (explicit opt-out).
|
|
35
|
+
//
|
|
36
|
+
// HONEST CEILING: on one uid a determined agent can set the env itself. The real boundary is the
|
|
37
|
+
// private key living under a DIFFERENT uid the agent cannot read; this gate stops the lazy path
|
|
38
|
+
// (an agent that mints just by calling the SDK), and the OS stops the determined one.
|
|
39
|
+
export const MINT_AUTHORITY_ENV = 'AEGIS_MINT_AUTHORITY';
|
|
40
|
+
export const INSECURE_LOCAL_MINT_ENV = 'AEGIS_ALLOW_INSECURE_LOCAL_MINT';
|
|
41
|
+
const envTrue = (v?: string): boolean => v === '1' || v === 'true' || v === 'yes';
|
|
42
|
+
export const mintAllowed = (): boolean =>
|
|
43
|
+
envTrue(process.env[MINT_AUTHORITY_ENV]) || envTrue(process.env[INSECURE_LOCAL_MINT_ENV]);
|
|
44
|
+
const MINT_REFUSAL =
|
|
45
|
+
'KGT-002: minting is an authority action — this process is not the approval authority. Agents verify, ' +
|
|
46
|
+
'they do not mint. Run the authority (the AEGIS dashboard, ideally under its own uid) with ' +
|
|
47
|
+
'AEGIS_MINT_AUTHORITY=1, or set AEGIS_ALLOW_INSECURE_LOCAL_MINT=1 to accept single-process minting.';
|
|
48
|
+
|
|
30
49
|
function aegisDir(): string {
|
|
31
50
|
return process.env.AEGIS_DIR ?? join(process.env.HOME ?? homedir(), '.aegis');
|
|
32
51
|
}
|
|
@@ -102,6 +121,8 @@ function loadKeys(): Keys {
|
|
|
102
121
|
// replace the authority's key with the box's own, and the box would then accept approvals
|
|
103
122
|
// it signed itself.
|
|
104
123
|
export function ensureSigningKeypair(): { publicKeyPem: string } {
|
|
124
|
+
// Making a signing key is an authority action, not something an agent does by calling the SDK.
|
|
125
|
+
if (!mintAllowed()) throw new Error(MINT_REFUSAL);
|
|
105
126
|
const dir = aegisDir();
|
|
106
127
|
const keyPath = join(dir, KEY_FILE);
|
|
107
128
|
const pubPath = join(dir, PUB_FILE);
|
|
@@ -145,6 +166,7 @@ function b64url(buf: Buffer | string): string {
|
|
|
145
166
|
// It never makes a key: before v0.4.0 it did, on any box, at the first mint. The authority
|
|
146
167
|
// calls ensureSigningKeypair() once, at boot.
|
|
147
168
|
export function signApprovalJwt(payload: Record<string, unknown>): string {
|
|
169
|
+
if (!mintAllowed()) throw new Error(MINT_REFUSAL);
|
|
148
170
|
const { priv, conflict } = loadKeys();
|
|
149
171
|
if (!priv) {
|
|
150
172
|
throw new Error(
|