@datacules/agent-identity 0.10.0 → 0.11.1
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/LICENSE +109 -0
- package/dist/cjs/decision.js +66 -17
- package/dist/cjs/decision.js.map +1 -1
- package/dist/cjs/rotation.js +59 -18
- package/dist/cjs/rotation.js.map +1 -1
- package/dist/esm/decision.js +65 -17
- package/dist/esm/decision.js.map +1 -1
- package/dist/esm/rotation.js +59 -18
- package/dist/esm/rotation.js.map +1 -1
- package/dist/types/decision.d.ts +30 -0
- package/dist/types/decision.d.ts.map +1 -1
- package/dist/types/rotation.d.ts +37 -19
- package/dist/types/rotation.d.ts.map +1 -1
- package/dist/types/schemas.d.ts +12 -12
- package/package.json +15 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Datacules Agent Identity License — Version 1.0
|
|
2
|
+
Copyright (c) 2026 Datacules LLC. All rights reserved.
|
|
3
|
+
|
|
4
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
PREAMBLE
|
|
6
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
This software — Agent Identity & Auth Patterns — is developed and owned by
|
|
9
|
+
Datacules LLC. It is made available to the public as open-source software
|
|
10
|
+
under the permissive terms below.
|
|
11
|
+
|
|
12
|
+
Datacules LLC retains ownership and authorship of this software while
|
|
13
|
+
granting broad, royalty-free rights for anyone to use, copy, modify, and
|
|
14
|
+
distribute it — in commercial or non-commercial contexts — without requiring
|
|
15
|
+
that derivative works also become open source.
|
|
16
|
+
|
|
17
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
TERMS AND CONDITIONS
|
|
19
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
1. PERMISSION TO USE
|
|
22
|
+
|
|
23
|
+
Permission is hereby granted, free of charge, to any person or
|
|
24
|
+
organization obtaining a copy of this software and associated
|
|
25
|
+
documentation files (the "Software"), to use, copy, modify, merge,
|
|
26
|
+
publish, distribute, sublicense, and/or sell copies of the Software,
|
|
27
|
+
and to permit persons to whom the Software is furnished to do so,
|
|
28
|
+
subject to the conditions below.
|
|
29
|
+
|
|
30
|
+
2. ATTRIBUTION
|
|
31
|
+
|
|
32
|
+
a. Redistributions of source code must retain this copyright notice,
|
|
33
|
+
this list of conditions, and the disclaimer below.
|
|
34
|
+
|
|
35
|
+
b. Redistributions in binary form or as a product must reproduce this
|
|
36
|
+
copyright notice, this list of conditions, and the disclaimer in the
|
|
37
|
+
documentation and/or other materials provided with the distribution.
|
|
38
|
+
|
|
39
|
+
c. Neither the name "Datacules LLC" nor the names of its contributors
|
|
40
|
+
may be used to endorse or promote products derived from this Software
|
|
41
|
+
without prior written permission from Datacules LLC.
|
|
42
|
+
|
|
43
|
+
3. COMMERCIAL USE
|
|
44
|
+
|
|
45
|
+
Use of this Software in commercial products, SaaS platforms, internal
|
|
46
|
+
enterprise tools, or any revenue-generating context is explicitly
|
|
47
|
+
permitted without royalty, fee, or additional licensing agreement,
|
|
48
|
+
provided that the conditions in Section 2 (Attribution) are met.
|
|
49
|
+
|
|
50
|
+
4. NO COPYLEFT / NO VIRAL REQUIREMENT
|
|
51
|
+
|
|
52
|
+
This license does NOT require that derivative works, modifications,
|
|
53
|
+
or software that uses or embeds this Software be made open source.
|
|
54
|
+
You may incorporate this Software into proprietary or closed-source
|
|
55
|
+
products under your own license terms.
|
|
56
|
+
|
|
57
|
+
5. MODIFICATIONS
|
|
58
|
+
|
|
59
|
+
Modified versions of the Software may be distributed under the same
|
|
60
|
+
terms as this license or under any other permissive open-source
|
|
61
|
+
license (e.g. MIT, Apache 2.0, BSD), provided that:
|
|
62
|
+
|
|
63
|
+
a. The original copyright notice of Datacules LLC is preserved.
|
|
64
|
+
b. Modifications are clearly documented and distinguished from the
|
|
65
|
+
original work.
|
|
66
|
+
|
|
67
|
+
6. COMPATIBILITY
|
|
68
|
+
|
|
69
|
+
This license is compatible with other permissive open-source licenses
|
|
70
|
+
such as MIT, BSD 2-Clause, BSD 3-Clause, and Apache License 2.0. It
|
|
71
|
+
is also GPL-compatible — this Software may coexist with GPL-licensed
|
|
72
|
+
code, though this Software itself is not distributed under the GPL.
|
|
73
|
+
|
|
74
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
75
|
+
DISCLAIMER
|
|
76
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
THIS SOFTWARE IS PROVIDED BY DATACULES LLC AND CONTRIBUTORS "AS IS" AND
|
|
79
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
80
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
|
|
81
|
+
AND NON-INFRINGEMENT ARE DISCLAIMED.
|
|
82
|
+
|
|
83
|
+
IN NO EVENT SHALL DATACULES LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
84
|
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
85
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
86
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
87
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
88
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
89
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
90
|
+
|
|
91
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
92
|
+
SUMMARY (non-binding)
|
|
93
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
✔ Use freely — commercial, proprietary, or open-source projects
|
|
96
|
+
✔ Modify and distribute with or without changes
|
|
97
|
+
✔ Sell products built on this Software
|
|
98
|
+
✔ No royalties or fees
|
|
99
|
+
✔ No requirement to open-source your own code
|
|
100
|
+
✔ Attribution to Datacules LLC required in source and binary distributions
|
|
101
|
+
✗ Do not use "Datacules LLC" to endorse derived products without permission
|
|
102
|
+
|
|
103
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
104
|
+
CONTACT
|
|
105
|
+
─────────────────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
Datacules LLC
|
|
108
|
+
For licensing enquiries: legal@datacules.com
|
|
109
|
+
Product: https://github.com/hvrcharon1/agent-identity
|
package/dist/cjs/decision.js
CHANGED
|
@@ -1,30 +1,79 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DECISION_QUESTIONS = void 0;
|
|
3
4
|
exports.computeDecision = computeDecision;
|
|
5
|
+
exports.DECISION_QUESTIONS = [
|
|
6
|
+
{
|
|
7
|
+
key: 'variableAccess',
|
|
8
|
+
text: 'Do different users need different levels of access to the same resource?',
|
|
9
|
+
yes: 'Yes — user A can see more than user B',
|
|
10
|
+
no: 'No — all users have identical access',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
key: 'mixedResources',
|
|
14
|
+
text: 'Does the agent access both shared (all-user) and personal (per-user) resources?',
|
|
15
|
+
yes: 'Yes — both kinds in the same agent',
|
|
16
|
+
no: 'No — only one kind',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: 'auditRequired',
|
|
20
|
+
text: 'Do you need a per-user audit trail?',
|
|
21
|
+
yes: 'Yes — we need to know which user caused each action',
|
|
22
|
+
no: 'No — agent-level logging is enough',
|
|
23
|
+
// Q3 only affects fixed-access paths. Individual-user-auth and
|
|
24
|
+
// token-exchange inherently carry user identity, so asking about audit
|
|
25
|
+
// trail for those paths would imply an effect that does not exist.
|
|
26
|
+
showIf: (a) => a.variableAccess === false,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: 'longTermTokenStorage',
|
|
30
|
+
text: 'Can you store per-user tokens long-term (e.g. in an encrypted DB)?',
|
|
31
|
+
yes: 'Yes — we can persist user tokens securely',
|
|
32
|
+
no: 'No — tokens must be obtained at request time only',
|
|
33
|
+
// Q4 is only relevant when access is variable AND resources are not mixed.
|
|
34
|
+
// For context-switched (variable + mixed) the result is known from Q1+Q2.
|
|
35
|
+
showIf: (a) => a.variableAccess === true && a.mixedResources === false,
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
// ─── Decision engine ──────────────────────────────────────────────────────────
|
|
39
|
+
function make(pattern, label, explanation) {
|
|
40
|
+
return { pattern, label, explanation };
|
|
41
|
+
}
|
|
4
42
|
function computeDecision(answers) {
|
|
5
43
|
const { variableAccess, mixedResources, auditRequired, longTermTokenStorage } = answers;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (variableAccess && longTermTokenStorage === null)
|
|
44
|
+
// Require Q1 and Q2 before proceeding
|
|
45
|
+
if (variableAccess === null || mixedResources === null)
|
|
9
46
|
return null;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
47
|
+
// ── Variable-access paths ─────────────────────────────────────────────────
|
|
48
|
+
if (variableAccess) {
|
|
49
|
+
if (mixedResources) {
|
|
50
|
+
// Hybrid routing: Q3 and Q4 have no bearing on this pattern.
|
|
51
|
+
// Result is determined by Q1 + Q2 alone.
|
|
52
|
+
return make('context-switched', 'Hybrid (context-switched)', 'Your agent needs both fixed credentials for shared resources and user-delegated tokens for personal data. Set explicit routing rules so the agent always knows which to use.');
|
|
53
|
+
}
|
|
54
|
+
// Variable access, single resource type — Q4 determines token strategy.
|
|
55
|
+
// Gate: Q4 not yet answered.
|
|
56
|
+
if (longTermTokenStorage === null)
|
|
57
|
+
return null;
|
|
14
58
|
if (!longTermTokenStorage) {
|
|
15
|
-
return
|
|
59
|
+
return make('token-exchange', 'Token exchange / impersonation', 'You need per-user access but cannot store tokens long-term. Use OAuth token exchange or STS assume-role to get scoped user tokens at request time, without persisting them.');
|
|
16
60
|
}
|
|
17
|
-
return
|
|
61
|
+
return make('individual-user-auth', 'Individual user auth', "Users have different access levels, so each request must carry that user's own token. The agent passes it through; the downstream resource enforces the ACL.");
|
|
18
62
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
63
|
+
// ── Fixed-access paths (variableAccess = false) ───────────────────────────
|
|
64
|
+
// Q3 (auditRequired) is relevant for every branch below.
|
|
65
|
+
if (auditRequired === null)
|
|
66
|
+
return null;
|
|
67
|
+
if (mixedResources) {
|
|
68
|
+
if (auditRequired) {
|
|
69
|
+
return make('fixed-credential', 'Fixed credential — resource-type awareness + request tagging', "A single service account can access both resource types. Tag each request with the calling user's ID in your own audit log so you have a per-user trace even though the credential is shared.");
|
|
70
|
+
}
|
|
71
|
+
return make('fixed-credential', 'Fixed credential with resource-type awareness', 'All users are equal but the agent accesses different resource types. A single fixed credential works — ensure its scope covers both resource types your agent needs.');
|
|
24
72
|
}
|
|
25
|
-
|
|
26
|
-
|
|
73
|
+
// Single resource type, fixed access
|
|
74
|
+
if (auditRequired) {
|
|
75
|
+
return make('fixed-credential', 'Fixed credential + request tagging', "Use a shared service account, but tag each request with the calling user's ID in your own logs. Gives you the simplicity of a fixed credential with an audit trail layer above it.");
|
|
27
76
|
}
|
|
28
|
-
return
|
|
77
|
+
return make('fixed-credential', 'Fixed credential', "All users are equal and audit trail isn't critical. A single service account keeps setup simple. Store the key encrypted; never pass it to the model layer.");
|
|
29
78
|
}
|
|
30
79
|
//# sourceMappingURL=decision.js.map
|
package/dist/cjs/decision.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision.js","sourceRoot":"","sources":["../../src/decision.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"decision.js","sourceRoot":"","sources":["../../src/decision.ts"],"names":[],"mappings":";;;AA0EA,0CAqEC;AA7GY,QAAA,kBAAkB,GAAuB;IACpD;QACE,GAAG,EAAE,gBAAgB;QACrB,IAAI,EAAE,0EAA0E;QAChF,GAAG,EAAE,uCAAuC;QAC5C,EAAE,EAAE,sCAAsC;KAC3C;IACD;QACE,GAAG,EAAE,gBAAgB;QACrB,IAAI,EAAE,iFAAiF;QACvF,GAAG,EAAE,oCAAoC;QACzC,EAAE,EAAE,oBAAoB;KACzB;IACD;QACE,GAAG,EAAE,eAAe;QACpB,IAAI,EAAE,qCAAqC;QAC3C,GAAG,EAAE,qDAAqD;QAC1D,EAAE,EAAE,oCAAoC;QACxC,+DAA+D;QAC/D,uEAAuE;QACvE,mEAAmE;QACnE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,KAAK;KAC1C;IACD;QACE,GAAG,EAAE,sBAAsB;QAC3B,IAAI,EAAE,oEAAoE;QAC1E,GAAG,EAAE,2CAA2C;QAChD,EAAE,EAAE,mDAAmD;QACvD,2EAA2E;QAC3E,0EAA0E;QAC1E,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,IAAI,IAAI,CAAC,CAAC,cAAc,KAAK,KAAK;KACvE;CACF,CAAC;AAEF,iFAAiF;AAEjF,SAAS,IAAI,CAAC,OAAwB,EAAE,KAAa,EAAE,WAAmB;IACxE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AACzC,CAAC;AAED,SAAgB,eAAe,CAAC,OAAwB;IACtD,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC;IAExF,sCAAsC;IACtC,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEpE,6EAA6E;IAE7E,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,cAAc,EAAE,CAAC;YACnB,6DAA6D;YAC7D,yCAAyC;YACzC,OAAO,IAAI,CACT,kBAAkB,EAClB,2BAA2B,EAC3B,8KAA8K,CAC/K,CAAC;QACJ,CAAC;QAED,wEAAwE;QACxE,6BAA6B;QAC7B,IAAI,oBAAoB,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAE/C,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC1B,OAAO,IAAI,CACT,gBAAgB,EAChB,gCAAgC,EAChC,6KAA6K,CAC9K,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CACT,sBAAsB,EACtB,sBAAsB,EACtB,8JAA8J,CAC/J,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,yDAAyD;IACzD,IAAI,aAAa,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAExC,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,IAAI,CACT,kBAAkB,EAClB,8DAA8D,EAC9D,+LAA+L,CAChM,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CACT,kBAAkB,EAClB,+CAA+C,EAC/C,sKAAsK,CACvK,CAAC;IACJ,CAAC;IAED,qCAAqC;IACrC,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,IAAI,CACT,kBAAkB,EAClB,oCAAoC,EACpC,oLAAoL,CACrL,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CACT,kBAAkB,EAClB,kBAAkB,EAClB,6JAA6J,CAC9J,CAAC;AACJ,CAAC"}
|
package/dist/cjs/rotation.js
CHANGED
|
@@ -1,33 +1,62 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CredentialRotationScheduler = void 0;
|
|
4
|
-
// ─── CredentialRotationScheduler
|
|
4
|
+
// ─── CredentialRotationScheduler ──────────────────────────────────────────────
|
|
5
5
|
class CredentialRotationScheduler {
|
|
6
|
-
constructor(repository, auditLogger) {
|
|
6
|
+
constructor(repository, auditLogger, getUsageCount) {
|
|
7
7
|
this.repository = repository;
|
|
8
8
|
this.auditLogger = auditLogger;
|
|
9
|
+
this.getUsageCount = getUsageCount;
|
|
9
10
|
this.providers = new Map();
|
|
10
11
|
this.intervalHandle = null;
|
|
12
|
+
/**
|
|
13
|
+
* Tracks credentials that are inside their post-rotation grace window.
|
|
14
|
+
* During the window, the old ref is still valid for in-flight requests
|
|
15
|
+
* (resolved externally via getGraceRef()), and no new rotation is triggered.
|
|
16
|
+
*/
|
|
17
|
+
this.graceWindows = new Map();
|
|
18
|
+
}
|
|
19
|
+
/** Alternate constructor from an options object. */
|
|
20
|
+
static fromOptions(opts) {
|
|
21
|
+
return new CredentialRotationScheduler(opts.repository, opts.auditLogger, opts.getUsageCount);
|
|
11
22
|
}
|
|
12
23
|
registerProvider(provider) {
|
|
13
24
|
this.providers.set(provider.id, provider);
|
|
14
25
|
}
|
|
15
26
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
27
|
+
* Returns true when a credential is inside its post-rotation grace window.
|
|
28
|
+
* Expired entries are pruned on read.
|
|
18
29
|
*/
|
|
30
|
+
inGracePeriod(credentialId) {
|
|
31
|
+
const grace = this.graceWindows.get(credentialId);
|
|
32
|
+
if (!grace)
|
|
33
|
+
return false;
|
|
34
|
+
if (grace.endsAt > Date.now())
|
|
35
|
+
return true;
|
|
36
|
+
this.graceWindows.delete(credentialId);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns the OLD credential ref while the grace window is active, or null.
|
|
41
|
+
* Use this in routers that need to accept both the old and new ref during
|
|
42
|
+
* the post-rotation handover period (e.g. for in-flight HTTP requests).
|
|
43
|
+
*/
|
|
44
|
+
getGraceRef(credentialId) {
|
|
45
|
+
const grace = this.graceWindows.get(credentialId);
|
|
46
|
+
return grace && grace.endsAt > Date.now() ? grace.oldRef : null;
|
|
47
|
+
}
|
|
19
48
|
async runOnce() {
|
|
20
49
|
const credentials = await this.repository.listActive();
|
|
21
50
|
const now = new Date();
|
|
22
51
|
for (const cred of credentials) {
|
|
23
|
-
// Skip credentials with no rotation policy
|
|
24
52
|
if (!cred.rotation)
|
|
25
53
|
continue;
|
|
26
|
-
// Skip unclaimed auth.md credentials — they cannot be rotated until
|
|
27
|
-
// the claim ceremony is complete and status flips to 'active'
|
|
28
54
|
if (cred.status === 'unclaimed')
|
|
29
55
|
continue;
|
|
30
|
-
|
|
56
|
+
// Skip when inside the grace window from a recent rotation
|
|
57
|
+
if (this.inGracePeriod(cred.id))
|
|
58
|
+
continue;
|
|
59
|
+
const due = await this.isRotationDue(cred, cred.rotation, now);
|
|
31
60
|
if (!due) {
|
|
32
61
|
await this.maybeEmitWarning(cred, cred.rotation, now);
|
|
33
62
|
continue;
|
|
@@ -41,6 +70,14 @@ class CredentialRotationScheduler {
|
|
|
41
70
|
}
|
|
42
71
|
try {
|
|
43
72
|
const { newRef, rotatedAt } = await provider.rotate(cred);
|
|
73
|
+
// Record the grace window BEFORE updating the store so any in-flight
|
|
74
|
+
// resolution holding the old ref can still resolve cleanly.
|
|
75
|
+
if (cred.rotation.gracePeriodSeconds && cred.rotation.gracePeriodSeconds > 0) {
|
|
76
|
+
this.graceWindows.set(cred.id, {
|
|
77
|
+
oldRef: cred.ref,
|
|
78
|
+
endsAt: Date.now() + cred.rotation.gracePeriodSeconds * 1000,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
44
81
|
await this.repository.update(cred.id, { ref: newRef, lastRotated: rotatedAt });
|
|
45
82
|
if (this.auditLogger) {
|
|
46
83
|
await this.auditLogger.log({
|
|
@@ -78,10 +115,6 @@ class CredentialRotationScheduler {
|
|
|
78
115
|
}
|
|
79
116
|
}
|
|
80
117
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Start a background rotation loop at the given interval.
|
|
83
|
-
* @param intervalMs Check frequency in milliseconds (default: 3600000 = 1 hour)
|
|
84
|
-
*/
|
|
85
118
|
start(intervalMs = 3600000) {
|
|
86
119
|
if (this.intervalHandle !== null)
|
|
87
120
|
return;
|
|
@@ -95,21 +128,29 @@ class CredentialRotationScheduler {
|
|
|
95
128
|
this.intervalHandle = null;
|
|
96
129
|
}
|
|
97
130
|
}
|
|
98
|
-
isRotationDue(cred, policy, now) {
|
|
131
|
+
async isRotationDue(cred, policy, now) {
|
|
132
|
+
// Days-based rotation
|
|
99
133
|
if (policy.rotateAfterDays !== undefined && cred.lastRotated) {
|
|
100
|
-
const
|
|
101
|
-
const daysSince = (now.getTime() - lastRotated.getTime()) / 86400000;
|
|
134
|
+
const daysSince = (now.getTime() - new Date(cred.lastRotated).getTime()) / 86400000;
|
|
102
135
|
if (daysSince >= policy.rotateAfterDays)
|
|
103
136
|
return true;
|
|
104
137
|
}
|
|
138
|
+
// Uses-based rotation — requires a getUsageCount callback
|
|
139
|
+
if (policy.rotateAfterUses !== undefined && this.getUsageCount) {
|
|
140
|
+
const count = await this.getUsageCount(cred.id);
|
|
141
|
+
if (count >= policy.rotateAfterUses)
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
105
144
|
return false;
|
|
106
145
|
}
|
|
107
146
|
async maybeEmitWarning(cred, policy, now) {
|
|
108
147
|
if (!this.auditLogger)
|
|
109
148
|
return;
|
|
110
|
-
if (policy.notifyBeforeDays !== undefined &&
|
|
111
|
-
|
|
112
|
-
|
|
149
|
+
if (policy.notifyBeforeDays !== undefined &&
|
|
150
|
+
policy.rotateAfterDays !== undefined &&
|
|
151
|
+
cred.lastRotated) {
|
|
152
|
+
const daysUntilDue = policy.rotateAfterDays -
|
|
153
|
+
(now.getTime() - new Date(cred.lastRotated).getTime()) / 86400000;
|
|
113
154
|
if (daysUntilDue > 0 && daysUntilDue <= policy.notifyBeforeDays) {
|
|
114
155
|
await this.auditLogger.log({
|
|
115
156
|
timestamp: new Date().toISOString(),
|
package/dist/cjs/rotation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rotation.js","sourceRoot":"","sources":["../../src/rotation.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"rotation.js","sourceRoot":"","sources":["../../src/rotation.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/decision.js
CHANGED
|
@@ -1,27 +1,75 @@
|
|
|
1
|
+
export const DECISION_QUESTIONS = [
|
|
2
|
+
{
|
|
3
|
+
key: 'variableAccess',
|
|
4
|
+
text: 'Do different users need different levels of access to the same resource?',
|
|
5
|
+
yes: 'Yes — user A can see more than user B',
|
|
6
|
+
no: 'No — all users have identical access',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
key: 'mixedResources',
|
|
10
|
+
text: 'Does the agent access both shared (all-user) and personal (per-user) resources?',
|
|
11
|
+
yes: 'Yes — both kinds in the same agent',
|
|
12
|
+
no: 'No — only one kind',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
key: 'auditRequired',
|
|
16
|
+
text: 'Do you need a per-user audit trail?',
|
|
17
|
+
yes: 'Yes — we need to know which user caused each action',
|
|
18
|
+
no: 'No — agent-level logging is enough',
|
|
19
|
+
// Q3 only affects fixed-access paths. Individual-user-auth and
|
|
20
|
+
// token-exchange inherently carry user identity, so asking about audit
|
|
21
|
+
// trail for those paths would imply an effect that does not exist.
|
|
22
|
+
showIf: (a) => a.variableAccess === false,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: 'longTermTokenStorage',
|
|
26
|
+
text: 'Can you store per-user tokens long-term (e.g. in an encrypted DB)?',
|
|
27
|
+
yes: 'Yes — we can persist user tokens securely',
|
|
28
|
+
no: 'No — tokens must be obtained at request time only',
|
|
29
|
+
// Q4 is only relevant when access is variable AND resources are not mixed.
|
|
30
|
+
// For context-switched (variable + mixed) the result is known from Q1+Q2.
|
|
31
|
+
showIf: (a) => a.variableAccess === true && a.mixedResources === false,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
// ─── Decision engine ──────────────────────────────────────────────────────────
|
|
35
|
+
function make(pattern, label, explanation) {
|
|
36
|
+
return { pattern, label, explanation };
|
|
37
|
+
}
|
|
1
38
|
export function computeDecision(answers) {
|
|
2
39
|
const { variableAccess, mixedResources, auditRequired, longTermTokenStorage } = answers;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
if (variableAccess && longTermTokenStorage === null)
|
|
40
|
+
// Require Q1 and Q2 before proceeding
|
|
41
|
+
if (variableAccess === null || mixedResources === null)
|
|
6
42
|
return null;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
43
|
+
// ── Variable-access paths ─────────────────────────────────────────────────
|
|
44
|
+
if (variableAccess) {
|
|
45
|
+
if (mixedResources) {
|
|
46
|
+
// Hybrid routing: Q3 and Q4 have no bearing on this pattern.
|
|
47
|
+
// Result is determined by Q1 + Q2 alone.
|
|
48
|
+
return make('context-switched', 'Hybrid (context-switched)', 'Your agent needs both fixed credentials for shared resources and user-delegated tokens for personal data. Set explicit routing rules so the agent always knows which to use.');
|
|
49
|
+
}
|
|
50
|
+
// Variable access, single resource type — Q4 determines token strategy.
|
|
51
|
+
// Gate: Q4 not yet answered.
|
|
52
|
+
if (longTermTokenStorage === null)
|
|
53
|
+
return null;
|
|
11
54
|
if (!longTermTokenStorage) {
|
|
12
|
-
return
|
|
55
|
+
return make('token-exchange', 'Token exchange / impersonation', 'You need per-user access but cannot store tokens long-term. Use OAuth token exchange or STS assume-role to get scoped user tokens at request time, without persisting them.');
|
|
13
56
|
}
|
|
14
|
-
return
|
|
57
|
+
return make('individual-user-auth', 'Individual user auth', "Users have different access levels, so each request must carry that user's own token. The agent passes it through; the downstream resource enforces the ACL.");
|
|
15
58
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
59
|
+
// ── Fixed-access paths (variableAccess = false) ───────────────────────────
|
|
60
|
+
// Q3 (auditRequired) is relevant for every branch below.
|
|
61
|
+
if (auditRequired === null)
|
|
62
|
+
return null;
|
|
63
|
+
if (mixedResources) {
|
|
64
|
+
if (auditRequired) {
|
|
65
|
+
return make('fixed-credential', 'Fixed credential — resource-type awareness + request tagging', "A single service account can access both resource types. Tag each request with the calling user's ID in your own audit log so you have a per-user trace even though the credential is shared.");
|
|
66
|
+
}
|
|
67
|
+
return make('fixed-credential', 'Fixed credential with resource-type awareness', 'All users are equal but the agent accesses different resource types. A single fixed credential works — ensure its scope covers both resource types your agent needs.');
|
|
21
68
|
}
|
|
22
|
-
|
|
23
|
-
|
|
69
|
+
// Single resource type, fixed access
|
|
70
|
+
if (auditRequired) {
|
|
71
|
+
return make('fixed-credential', 'Fixed credential + request tagging', "Use a shared service account, but tag each request with the calling user's ID in your own logs. Gives you the simplicity of a fixed credential with an audit trail layer above it.");
|
|
24
72
|
}
|
|
25
|
-
return
|
|
73
|
+
return make('fixed-credential', 'Fixed credential', "All users are equal and audit trail isn't critical. A single service account keeps setup simple. Store the key encrypted; never pass it to the model layer.");
|
|
26
74
|
}
|
|
27
75
|
//# sourceMappingURL=decision.js.map
|
package/dist/esm/decision.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision.js","sourceRoot":"","sources":["../../src/decision.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"decision.js","sourceRoot":"","sources":["../../src/decision.ts"],"names":[],"mappings":"AAkCA,MAAM,CAAC,MAAM,kBAAkB,GAAuB;IACpD;QACE,GAAG,EAAE,gBAAgB;QACrB,IAAI,EAAE,0EAA0E;QAChF,GAAG,EAAE,uCAAuC;QAC5C,EAAE,EAAE,sCAAsC;KAC3C;IACD;QACE,GAAG,EAAE,gBAAgB;QACrB,IAAI,EAAE,iFAAiF;QACvF,GAAG,EAAE,oCAAoC;QACzC,EAAE,EAAE,oBAAoB;KACzB;IACD;QACE,GAAG,EAAE,eAAe;QACpB,IAAI,EAAE,qCAAqC;QAC3C,GAAG,EAAE,qDAAqD;QAC1D,EAAE,EAAE,oCAAoC;QACxC,+DAA+D;QAC/D,uEAAuE;QACvE,mEAAmE;QACnE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,KAAK;KAC1C;IACD;QACE,GAAG,EAAE,sBAAsB;QAC3B,IAAI,EAAE,oEAAoE;QAC1E,GAAG,EAAE,2CAA2C;QAChD,EAAE,EAAE,mDAAmD;QACvD,2EAA2E;QAC3E,0EAA0E;QAC1E,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,IAAI,IAAI,CAAC,CAAC,cAAc,KAAK,KAAK;KACvE;CACF,CAAC;AAEF,iFAAiF;AAEjF,SAAS,IAAI,CAAC,OAAwB,EAAE,KAAa,EAAE,WAAmB;IACxE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAwB;IACtD,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC;IAExF,sCAAsC;IACtC,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEpE,6EAA6E;IAE7E,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,cAAc,EAAE,CAAC;YACnB,6DAA6D;YAC7D,yCAAyC;YACzC,OAAO,IAAI,CACT,kBAAkB,EAClB,2BAA2B,EAC3B,8KAA8K,CAC/K,CAAC;QACJ,CAAC;QAED,wEAAwE;QACxE,6BAA6B;QAC7B,IAAI,oBAAoB,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAE/C,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC1B,OAAO,IAAI,CACT,gBAAgB,EAChB,gCAAgC,EAChC,6KAA6K,CAC9K,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CACT,sBAAsB,EACtB,sBAAsB,EACtB,8JAA8J,CAC/J,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,yDAAyD;IACzD,IAAI,aAAa,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAExC,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,IAAI,CACT,kBAAkB,EAClB,8DAA8D,EAC9D,+LAA+L,CAChM,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CACT,kBAAkB,EAClB,+CAA+C,EAC/C,sKAAsK,CACvK,CAAC;IACJ,CAAC;IAED,qCAAqC;IACrC,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,IAAI,CACT,kBAAkB,EAClB,oCAAoC,EACpC,oLAAoL,CACrL,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CACT,kBAAkB,EAClB,kBAAkB,EAClB,6JAA6J,CAC9J,CAAC;AACJ,CAAC"}
|
package/dist/esm/rotation.js
CHANGED
|
@@ -1,30 +1,59 @@
|
|
|
1
|
-
// ─── CredentialRotationScheduler
|
|
1
|
+
// ─── CredentialRotationScheduler ──────────────────────────────────────────────
|
|
2
2
|
export class CredentialRotationScheduler {
|
|
3
|
-
constructor(repository, auditLogger) {
|
|
3
|
+
constructor(repository, auditLogger, getUsageCount) {
|
|
4
4
|
this.repository = repository;
|
|
5
5
|
this.auditLogger = auditLogger;
|
|
6
|
+
this.getUsageCount = getUsageCount;
|
|
6
7
|
this.providers = new Map();
|
|
7
8
|
this.intervalHandle = null;
|
|
9
|
+
/**
|
|
10
|
+
* Tracks credentials that are inside their post-rotation grace window.
|
|
11
|
+
* During the window, the old ref is still valid for in-flight requests
|
|
12
|
+
* (resolved externally via getGraceRef()), and no new rotation is triggered.
|
|
13
|
+
*/
|
|
14
|
+
this.graceWindows = new Map();
|
|
15
|
+
}
|
|
16
|
+
/** Alternate constructor from an options object. */
|
|
17
|
+
static fromOptions(opts) {
|
|
18
|
+
return new CredentialRotationScheduler(opts.repository, opts.auditLogger, opts.getUsageCount);
|
|
8
19
|
}
|
|
9
20
|
registerProvider(provider) {
|
|
10
21
|
this.providers.set(provider.id, provider);
|
|
11
22
|
}
|
|
12
23
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
24
|
+
* Returns true when a credential is inside its post-rotation grace window.
|
|
25
|
+
* Expired entries are pruned on read.
|
|
15
26
|
*/
|
|
27
|
+
inGracePeriod(credentialId) {
|
|
28
|
+
const grace = this.graceWindows.get(credentialId);
|
|
29
|
+
if (!grace)
|
|
30
|
+
return false;
|
|
31
|
+
if (grace.endsAt > Date.now())
|
|
32
|
+
return true;
|
|
33
|
+
this.graceWindows.delete(credentialId);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the OLD credential ref while the grace window is active, or null.
|
|
38
|
+
* Use this in routers that need to accept both the old and new ref during
|
|
39
|
+
* the post-rotation handover period (e.g. for in-flight HTTP requests).
|
|
40
|
+
*/
|
|
41
|
+
getGraceRef(credentialId) {
|
|
42
|
+
const grace = this.graceWindows.get(credentialId);
|
|
43
|
+
return grace && grace.endsAt > Date.now() ? grace.oldRef : null;
|
|
44
|
+
}
|
|
16
45
|
async runOnce() {
|
|
17
46
|
const credentials = await this.repository.listActive();
|
|
18
47
|
const now = new Date();
|
|
19
48
|
for (const cred of credentials) {
|
|
20
|
-
// Skip credentials with no rotation policy
|
|
21
49
|
if (!cred.rotation)
|
|
22
50
|
continue;
|
|
23
|
-
// Skip unclaimed auth.md credentials — they cannot be rotated until
|
|
24
|
-
// the claim ceremony is complete and status flips to 'active'
|
|
25
51
|
if (cred.status === 'unclaimed')
|
|
26
52
|
continue;
|
|
27
|
-
|
|
53
|
+
// Skip when inside the grace window from a recent rotation
|
|
54
|
+
if (this.inGracePeriod(cred.id))
|
|
55
|
+
continue;
|
|
56
|
+
const due = await this.isRotationDue(cred, cred.rotation, now);
|
|
28
57
|
if (!due) {
|
|
29
58
|
await this.maybeEmitWarning(cred, cred.rotation, now);
|
|
30
59
|
continue;
|
|
@@ -38,6 +67,14 @@ export class CredentialRotationScheduler {
|
|
|
38
67
|
}
|
|
39
68
|
try {
|
|
40
69
|
const { newRef, rotatedAt } = await provider.rotate(cred);
|
|
70
|
+
// Record the grace window BEFORE updating the store so any in-flight
|
|
71
|
+
// resolution holding the old ref can still resolve cleanly.
|
|
72
|
+
if (cred.rotation.gracePeriodSeconds && cred.rotation.gracePeriodSeconds > 0) {
|
|
73
|
+
this.graceWindows.set(cred.id, {
|
|
74
|
+
oldRef: cred.ref,
|
|
75
|
+
endsAt: Date.now() + cred.rotation.gracePeriodSeconds * 1000,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
41
78
|
await this.repository.update(cred.id, { ref: newRef, lastRotated: rotatedAt });
|
|
42
79
|
if (this.auditLogger) {
|
|
43
80
|
await this.auditLogger.log({
|
|
@@ -75,10 +112,6 @@ export class CredentialRotationScheduler {
|
|
|
75
112
|
}
|
|
76
113
|
}
|
|
77
114
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Start a background rotation loop at the given interval.
|
|
80
|
-
* @param intervalMs Check frequency in milliseconds (default: 3600000 = 1 hour)
|
|
81
|
-
*/
|
|
82
115
|
start(intervalMs = 3600000) {
|
|
83
116
|
if (this.intervalHandle !== null)
|
|
84
117
|
return;
|
|
@@ -92,21 +125,29 @@ export class CredentialRotationScheduler {
|
|
|
92
125
|
this.intervalHandle = null;
|
|
93
126
|
}
|
|
94
127
|
}
|
|
95
|
-
isRotationDue(cred, policy, now) {
|
|
128
|
+
async isRotationDue(cred, policy, now) {
|
|
129
|
+
// Days-based rotation
|
|
96
130
|
if (policy.rotateAfterDays !== undefined && cred.lastRotated) {
|
|
97
|
-
const
|
|
98
|
-
const daysSince = (now.getTime() - lastRotated.getTime()) / 86400000;
|
|
131
|
+
const daysSince = (now.getTime() - new Date(cred.lastRotated).getTime()) / 86400000;
|
|
99
132
|
if (daysSince >= policy.rotateAfterDays)
|
|
100
133
|
return true;
|
|
101
134
|
}
|
|
135
|
+
// Uses-based rotation — requires a getUsageCount callback
|
|
136
|
+
if (policy.rotateAfterUses !== undefined && this.getUsageCount) {
|
|
137
|
+
const count = await this.getUsageCount(cred.id);
|
|
138
|
+
if (count >= policy.rotateAfterUses)
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
102
141
|
return false;
|
|
103
142
|
}
|
|
104
143
|
async maybeEmitWarning(cred, policy, now) {
|
|
105
144
|
if (!this.auditLogger)
|
|
106
145
|
return;
|
|
107
|
-
if (policy.notifyBeforeDays !== undefined &&
|
|
108
|
-
|
|
109
|
-
|
|
146
|
+
if (policy.notifyBeforeDays !== undefined &&
|
|
147
|
+
policy.rotateAfterDays !== undefined &&
|
|
148
|
+
cred.lastRotated) {
|
|
149
|
+
const daysUntilDue = policy.rotateAfterDays -
|
|
150
|
+
(now.getTime() - new Date(cred.lastRotated).getTime()) / 86400000;
|
|
110
151
|
if (daysUntilDue > 0 && daysUntilDue <= policy.notifyBeforeDays) {
|
|
111
152
|
await this.auditLogger.log({
|
|
112
153
|
timestamp: new Date().toISOString(),
|
package/dist/esm/rotation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rotation.js","sourceRoot":"","sources":["../../src/rotation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rotation.js","sourceRoot":"","sources":["../../src/rotation.ts"],"names":[],"mappings":"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"}
|
package/dist/types/decision.d.ts
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision helper — recommends an auth pattern based on user answers.
|
|
3
|
+
*
|
|
4
|
+
* Fixes applied in this version:
|
|
5
|
+
* 1. Q4 null-guard moved inside the variableAccess && !mixedResources branch —
|
|
6
|
+
* context-switched no longer gates on an unused Q4 answer.
|
|
7
|
+
* 2. Q3 (auditRequired) is now consulted on ALL fixed-access paths, including
|
|
8
|
+
* the !variableAccess && mixedResources case. Previously Q3 was silently
|
|
9
|
+
* ignored there, producing the same result regardless of audit requirement.
|
|
10
|
+
* 3. New distinct result label: 'resource-type awareness + request tagging'
|
|
11
|
+
* for !variableAccess && mixedResources && auditRequired.
|
|
12
|
+
* 4. DECISION_QUESTIONS exported here (moved from DecisionTab.tsx) so the
|
|
13
|
+
* question set can be tested independently of React and imported by the UI.
|
|
14
|
+
* 5. Q3 showIf: variableAccess === false — question is hidden and never asked
|
|
15
|
+
* on variable-access paths where it has no effect.
|
|
16
|
+
* 6. Q4 showIf: variableAccess === true && mixedResources === false —
|
|
17
|
+
* context-switched path (variable + mixed) no longer surfaces Q4.
|
|
18
|
+
*/
|
|
1
19
|
import type { DecisionAnswers, DecisionResult } from './types';
|
|
20
|
+
export interface DecisionQuestion {
|
|
21
|
+
key: keyof DecisionAnswers;
|
|
22
|
+
text: string;
|
|
23
|
+
yes: string;
|
|
24
|
+
no: string;
|
|
25
|
+
/**
|
|
26
|
+
* When present, the question is only shown (and therefore only required)
|
|
27
|
+
* when the predicate returns true. Hidden questions do NOT gate the result.
|
|
28
|
+
*/
|
|
29
|
+
showIf?: (a: DecisionAnswers) => boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare const DECISION_QUESTIONS: DecisionQuestion[];
|
|
2
32
|
export declare function computeDecision(answers: DecisionAnswers): DecisionResult | null;
|
|
3
33
|
//# sourceMappingURL=decision.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../src/decision.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../src/decision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAmB,MAAM,SAAS,CAAC;AAIhF,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,eAAe,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC;CAC1C;AAED,eAAO,MAAM,kBAAkB,EAAE,gBAAgB,EAgChD,CAAC;AAQF,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,cAAc,GAAG,IAAI,CAqE/E"}
|
package/dist/types/rotation.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Automated Credential Rotation — Feature #4 from FEATURE_SUGGESTIONS.md
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Additions in this version:
|
|
5
|
+
* - rotateAfterUses: optional getUsageCount callback; isRotationDue() is now
|
|
6
|
+
* async so it can query usage counts alongside the days check.
|
|
7
|
+
* - Grace period: graceWindows Map tracks old ref for gracePeriodSeconds after
|
|
8
|
+
* a successful rotation. runOnce() skips re-rotation while in the window.
|
|
9
|
+
* inGracePeriod() and getGraceRef() are public so routers can accept both
|
|
10
|
+
* the old and new ref during the handover window.
|
|
11
|
+
* - RotationSchedulerOptions interface + static fromOptions() factory.
|
|
12
|
+
* - Constructor is backwards-compatible: getUsageCount is an optional third param.
|
|
7
13
|
*/
|
|
8
14
|
import type { Credential, AuditLogger } from './types';
|
|
9
|
-
/**
|
|
10
|
-
* A RotationProvider mints a new secret for a credential and updates the
|
|
11
|
-
* store. Built-in providers: VaultRotationProvider, AwsRotationProvider.
|
|
12
|
-
* Custom providers implement this interface.
|
|
13
|
-
*/
|
|
14
15
|
export interface RotationProvider {
|
|
15
16
|
id: string;
|
|
16
17
|
rotate(credential: Credential): Promise<{
|
|
@@ -18,31 +19,48 @@ export interface RotationProvider {
|
|
|
18
19
|
rotatedAt: string;
|
|
19
20
|
}>;
|
|
20
21
|
}
|
|
21
|
-
/**
|
|
22
|
-
* CredentialRepository is a minimal interface over any CredentialStore that
|
|
23
|
-
* supports mutation — listing and updating credentials. The core store
|
|
24
|
-
* interface is read-only for callers; rotation needs write access.
|
|
25
|
-
*/
|
|
26
22
|
export interface RotationRepository {
|
|
27
23
|
listActive(): Promise<Credential[]>;
|
|
28
24
|
update(id: string, patch: Partial<Credential>): Promise<void>;
|
|
29
25
|
}
|
|
26
|
+
export interface RotationSchedulerOptions {
|
|
27
|
+
repository: RotationRepository;
|
|
28
|
+
auditLogger?: AuditLogger;
|
|
29
|
+
/**
|
|
30
|
+
* Returns how many times a credential has been used since its last rotation.
|
|
31
|
+
* Required for rotateAfterUses enforcement. Typically delegates to a usage
|
|
32
|
+
* counter maintained alongside audit events or budget tracking.
|
|
33
|
+
*/
|
|
34
|
+
getUsageCount?: (credentialId: string) => Promise<number>;
|
|
35
|
+
}
|
|
30
36
|
export declare class CredentialRotationScheduler {
|
|
31
37
|
private readonly repository;
|
|
32
38
|
private readonly auditLogger?;
|
|
39
|
+
private readonly getUsageCount?;
|
|
33
40
|
private readonly providers;
|
|
34
41
|
private intervalHandle;
|
|
35
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Tracks credentials that are inside their post-rotation grace window.
|
|
44
|
+
* During the window, the old ref is still valid for in-flight requests
|
|
45
|
+
* (resolved externally via getGraceRef()), and no new rotation is triggered.
|
|
46
|
+
*/
|
|
47
|
+
private readonly graceWindows;
|
|
48
|
+
constructor(repository: RotationRepository, auditLogger?: AuditLogger | undefined, getUsageCount?: ((credentialId: string) => Promise<number>) | undefined);
|
|
49
|
+
/** Alternate constructor from an options object. */
|
|
50
|
+
static fromOptions(opts: RotationSchedulerOptions): CredentialRotationScheduler;
|
|
36
51
|
registerProvider(provider: RotationProvider): void;
|
|
37
52
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
53
|
+
* Returns true when a credential is inside its post-rotation grace window.
|
|
54
|
+
* Expired entries are pruned on read.
|
|
40
55
|
*/
|
|
41
|
-
|
|
56
|
+
inGracePeriod(credentialId: string): boolean;
|
|
42
57
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
58
|
+
* Returns the OLD credential ref while the grace window is active, or null.
|
|
59
|
+
* Use this in routers that need to accept both the old and new ref during
|
|
60
|
+
* the post-rotation handover period (e.g. for in-flight HTTP requests).
|
|
45
61
|
*/
|
|
62
|
+
getGraceRef(credentialId: string): string | null;
|
|
63
|
+
runOnce(): Promise<void>;
|
|
46
64
|
start(intervalMs?: number): void;
|
|
47
65
|
stop(): void;
|
|
48
66
|
private isRotationDue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../../src/rotation.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../../src/rotation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAkB,MAAM,SAAS,CAAC;AAIvE,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,kBAAkB,CAAC;IAC/B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3D;AAID,qBAAa,2BAA2B;IAYpC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAbjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;IACjE,OAAO,CAAC,cAAc,CAA+C;IAErE;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyD;gBAGnE,UAAU,EAAE,kBAAkB,EAC9B,WAAW,CAAC,EAAE,WAAW,YAAA,EACzB,aAAa,CAAC,GAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,aAAA;IAG5E,oDAAoD;IACpD,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,wBAAwB,GAAG,2BAA2B;IAI/E,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAIlD;;;OAGG;IACH,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAQ5C;;;;OAIG;IACH,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAK1C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8E9B,KAAK,CAAC,UAAU,SAAY,GAAG,IAAI;IAOnC,IAAI,IAAI,IAAI;YAOE,aAAa;YAgBb,gBAAgB;CA2B/B"}
|
package/dist/types/schemas.d.ts
CHANGED
|
@@ -27,15 +27,15 @@ export declare const RotationPolicySchema: z.ZodObject<{
|
|
|
27
27
|
provisioner: z.ZodOptional<z.ZodString>;
|
|
28
28
|
}, "strip", z.ZodTypeAny, {
|
|
29
29
|
provisioner?: string | undefined;
|
|
30
|
+
gracePeriodSeconds?: number | undefined;
|
|
30
31
|
rotateAfterDays?: number | undefined;
|
|
31
32
|
rotateAfterUses?: number | undefined;
|
|
32
|
-
gracePeriodSeconds?: number | undefined;
|
|
33
33
|
notifyBeforeDays?: number | undefined;
|
|
34
34
|
}, {
|
|
35
35
|
provisioner?: string | undefined;
|
|
36
|
+
gracePeriodSeconds?: number | undefined;
|
|
36
37
|
rotateAfterDays?: number | undefined;
|
|
37
38
|
rotateAfterUses?: number | undefined;
|
|
38
|
-
gracePeriodSeconds?: number | undefined;
|
|
39
39
|
notifyBeforeDays?: number | undefined;
|
|
40
40
|
}>;
|
|
41
41
|
export declare const BudgetPolicySchema: z.ZodObject<{
|
|
@@ -133,15 +133,15 @@ export declare const CredentialSchema: z.ZodObject<{
|
|
|
133
133
|
provisioner: z.ZodOptional<z.ZodString>;
|
|
134
134
|
}, "strip", z.ZodTypeAny, {
|
|
135
135
|
provisioner?: string | undefined;
|
|
136
|
+
gracePeriodSeconds?: number | undefined;
|
|
136
137
|
rotateAfterDays?: number | undefined;
|
|
137
138
|
rotateAfterUses?: number | undefined;
|
|
138
|
-
gracePeriodSeconds?: number | undefined;
|
|
139
139
|
notifyBeforeDays?: number | undefined;
|
|
140
140
|
}, {
|
|
141
141
|
provisioner?: string | undefined;
|
|
142
|
+
gracePeriodSeconds?: number | undefined;
|
|
142
143
|
rotateAfterDays?: number | undefined;
|
|
143
144
|
rotateAfterUses?: number | undefined;
|
|
144
|
-
gracePeriodSeconds?: number | undefined;
|
|
145
145
|
notifyBeforeDays?: number | undefined;
|
|
146
146
|
}>>;
|
|
147
147
|
budget: z.ZodOptional<z.ZodObject<{
|
|
@@ -181,9 +181,9 @@ export declare const CredentialSchema: z.ZodObject<{
|
|
|
181
181
|
rotationIntervalDays?: number | undefined;
|
|
182
182
|
rotation?: {
|
|
183
183
|
provisioner?: string | undefined;
|
|
184
|
+
gracePeriodSeconds?: number | undefined;
|
|
184
185
|
rotateAfterDays?: number | undefined;
|
|
185
186
|
rotateAfterUses?: number | undefined;
|
|
186
|
-
gracePeriodSeconds?: number | undefined;
|
|
187
187
|
notifyBeforeDays?: number | undefined;
|
|
188
188
|
} | undefined;
|
|
189
189
|
budget?: {
|
|
@@ -211,9 +211,9 @@ export declare const CredentialSchema: z.ZodObject<{
|
|
|
211
211
|
rotationIntervalDays?: number | undefined;
|
|
212
212
|
rotation?: {
|
|
213
213
|
provisioner?: string | undefined;
|
|
214
|
+
gracePeriodSeconds?: number | undefined;
|
|
214
215
|
rotateAfterDays?: number | undefined;
|
|
215
216
|
rotateAfterUses?: number | undefined;
|
|
216
|
-
gracePeriodSeconds?: number | undefined;
|
|
217
217
|
notifyBeforeDays?: number | undefined;
|
|
218
218
|
} | undefined;
|
|
219
219
|
budget?: {
|
|
@@ -448,15 +448,15 @@ export declare const TrustedIdentityProviderSchema: z.ZodObject<{
|
|
|
448
448
|
requiredAmr: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
449
449
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
450
450
|
}, "strip", z.ZodTypeAny, {
|
|
451
|
-
label: string;
|
|
452
451
|
issuerUrl: string;
|
|
452
|
+
label: string;
|
|
453
453
|
enabled?: boolean | undefined;
|
|
454
454
|
requiredAmr?: string[] | undefined;
|
|
455
455
|
jwksUri?: string | undefined;
|
|
456
456
|
cimdUri?: string | undefined;
|
|
457
457
|
}, {
|
|
458
|
-
label: string;
|
|
459
458
|
issuerUrl: string;
|
|
459
|
+
label: string;
|
|
460
460
|
enabled?: boolean | undefined;
|
|
461
461
|
requiredAmr?: string[] | undefined;
|
|
462
462
|
jwksUri?: string | undefined;
|
|
@@ -471,15 +471,15 @@ export declare const TrustedProviderRegistrySchema: z.ZodObject<{
|
|
|
471
471
|
requiredAmr: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
472
472
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
473
473
|
}, "strip", z.ZodTypeAny, {
|
|
474
|
-
label: string;
|
|
475
474
|
issuerUrl: string;
|
|
475
|
+
label: string;
|
|
476
476
|
enabled?: boolean | undefined;
|
|
477
477
|
requiredAmr?: string[] | undefined;
|
|
478
478
|
jwksUri?: string | undefined;
|
|
479
479
|
cimdUri?: string | undefined;
|
|
480
480
|
}, {
|
|
481
|
-
label: string;
|
|
482
481
|
issuerUrl: string;
|
|
482
|
+
label: string;
|
|
483
483
|
enabled?: boolean | undefined;
|
|
484
484
|
requiredAmr?: string[] | undefined;
|
|
485
485
|
jwksUri?: string | undefined;
|
|
@@ -489,8 +489,8 @@ export declare const TrustedProviderRegistrySchema: z.ZodObject<{
|
|
|
489
489
|
jwksCacheFloorMs: z.ZodOptional<z.ZodNumber>;
|
|
490
490
|
}, "strip", z.ZodTypeAny, {
|
|
491
491
|
providers: {
|
|
492
|
-
label: string;
|
|
493
492
|
issuerUrl: string;
|
|
493
|
+
label: string;
|
|
494
494
|
enabled?: boolean | undefined;
|
|
495
495
|
requiredAmr?: string[] | undefined;
|
|
496
496
|
jwksUri?: string | undefined;
|
|
@@ -500,8 +500,8 @@ export declare const TrustedProviderRegistrySchema: z.ZodObject<{
|
|
|
500
500
|
jwksCacheFloorMs?: number | undefined;
|
|
501
501
|
}, {
|
|
502
502
|
providers: {
|
|
503
|
-
label: string;
|
|
504
503
|
issuerUrl: string;
|
|
504
|
+
label: string;
|
|
505
505
|
enabled?: boolean | undefined;
|
|
506
506
|
requiredAmr?: string[] | undefined;
|
|
507
507
|
jwksUri?: string | undefined;
|
package/package.json
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datacules/agent-identity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Provider-agnostic credential routing and identity management for AI agents",
|
|
6
6
|
"author": "Datacules LLC",
|
|
7
|
-
"license": "
|
|
7
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"agent-identity",
|
|
10
|
+
"credential-routing",
|
|
11
|
+
"ai-agents",
|
|
12
|
+
"identity",
|
|
13
|
+
"oauth",
|
|
14
|
+
"zero-trust",
|
|
15
|
+
"audit",
|
|
16
|
+
"provider-agnostic",
|
|
17
|
+
"datacules"
|
|
18
|
+
],
|
|
8
19
|
"repository": {
|
|
9
20
|
"type": "git",
|
|
10
21
|
"url": "https://github.com/hvrcharon1/agent-identity.git",
|
|
@@ -31,7 +42,8 @@
|
|
|
31
42
|
},
|
|
32
43
|
"files": [
|
|
33
44
|
"dist",
|
|
34
|
-
"README.md"
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE"
|
|
35
47
|
],
|
|
36
48
|
"scripts": {
|
|
37
49
|
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.cjs.json",
|