@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xshieldai/aegis-guard",
3
- "version": "0.5.0",
4
- "description": "AEGIS Guard SDK — Five Locks primitives (approval-token, nonce, idempotency, SENSE, quality-evidence) + opt-in Agentic Control Center event bus for AEGIS-governed services",
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>",
@@ -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(