@vizamodo/aws-sts-core 0.4.10 → 0.4.11

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.
@@ -9,9 +9,5 @@ export interface IssueAwsCredentialsInput {
9
9
  profile: string;
10
10
  forceRefresh?: boolean;
11
11
  }
12
- /**
13
- * Reset isolate-level signing-material cache.
14
- * ONLY for use in tests.
15
- */
16
12
  export declare function resetIsolateCache(): void;
17
13
  export declare function issueAwsCredentials(input: IssueAwsCredentialsInput): Promise<AwsCredentialResult>;
package/dist/sts/issue.js CHANGED
@@ -18,38 +18,33 @@ const PROFILE_TTL = {
18
18
  BillingAccountant: 3 * 60 * 60,
19
19
  };
20
20
  const DEFAULT_TTL = 2 * 60 * 60;
21
- const MIN_PROFILE_TTL = 45 * 60; // 45 min
22
- const MAX_PROFILE_TTL = 12 * 60 * 60; // 12 h
21
+ const MIN_PROFILE_TTL = 45 * 60;
22
+ const MAX_PROFILE_TTL = 12 * 60 * 60;
23
23
  // ── Isolate-level signing-material cache ───────────────────────────────────
24
- // Single Promise slot deduplicates concurrent cold-start imports.
25
- // Cache vars are updated inside .then() so all concurrent awaiters see
26
- // consistent state and hit the fast path on the next call.
27
24
  let signingKeyPromise = null;
28
25
  let cachedSigningKey = null;
29
26
  let cachedCertBase64 = null;
30
27
  let cachedPrivateKeyBase64 = null;
31
- // ── Test utilities ─────────────────────────────────────────────────────────
32
- /**
33
- * Reset isolate-level signing-material cache.
34
- * ONLY for use in tests.
35
- */
28
+ // ── Cert serial cache ──────────────────────────────────────────────────────
29
+ // BUG 5 KEPT: stale isolate cache — produced wrong serial in production.
30
+ let cachedCertSerialDec = null;
31
+ let cachedCertSerialSource = null;
36
32
  export function resetIsolateCache() {
37
33
  signingKeyPromise = null;
38
34
  cachedSigningKey = null;
39
35
  cachedCertBase64 = null;
40
36
  cachedPrivateKeyBase64 = null;
37
+ cachedCertSerialDec = null;
38
+ cachedCertSerialSource = null;
41
39
  }
42
40
  // ── Signing material ───────────────────────────────────────────────────────
43
41
  async function getSigningMaterial(certBase64, privateKeyPkcs8Base64) {
44
- // Fast path: same material already imported.
45
42
  if (cachedSigningKey &&
46
43
  cachedCertBase64 === certBase64 &&
47
44
  cachedPrivateKeyBase64 === privateKeyPkcs8Base64) {
48
45
  return cachedSigningKey;
49
46
  }
50
- // Material changed or first callreset and re-import.
51
- // Cache vars are updated inside .then() so concurrent callers awaiting
52
- // the same promise all get the correct key and hit the fast path next call.
47
+ // FIX 2: cache vars updated inside .then() no race condition.
53
48
  signingKeyPromise = null;
54
49
  cachedSigningKey = null;
55
50
  cachedCertBase64 = null;
@@ -63,7 +58,7 @@ async function getSigningMaterial(certBase64, privateKeyPkcs8Base64) {
63
58
  return key;
64
59
  })
65
60
  .catch(() => {
66
- signingKeyPromise = null; // allow retry
61
+ signingKeyPromise = null;
67
62
  throw new InternalError("invalid_signing_material");
68
63
  });
69
64
  return signingKeyPromise;
@@ -78,9 +73,17 @@ export async function issueAwsCredentials(input) {
78
73
  const { roleArn, profileArn, trustAnchorArn, region, certBase64, privateKeyPkcs8Base64, profile, forceRefresh, } = input;
79
74
  const sessionTtl = resolveSessionTtl(profile);
80
75
  const normalizedCert = normalizeCert(certBase64);
81
- // Parse serial fresh every callno isolate cache.
82
- // DER walk is ~5 µs; stale isolate-cached values caused wrong-serial bugs.
83
- const certSerialDec = parseCertSerialDec(normalizedCert);
76
+ // BUG 5 KEPT: isolate-level cert serial cache can serve stale value
77
+ // from a previous (buggy) code version in the same long-lived isolate.
78
+ let certSerialDec;
79
+ if (cachedCertSerialDec && cachedCertSerialSource === normalizedCert) {
80
+ certSerialDec = cachedCertSerialDec;
81
+ }
82
+ else {
83
+ certSerialDec = parseCertSerialDec(normalizedCert);
84
+ cachedCertSerialDec = certSerialDec;
85
+ cachedCertSerialSource = normalizedCert;
86
+ }
84
87
  const cacheKey = `${region}|${roleArn}|${profileArn}|${trustAnchorArn}|${certSerialDec}`;
85
88
  return getCachedOrFetch(cacheKey, () => fetchCredentials({
86
89
  roleArn, profileArn, trustAnchorArn,
@@ -90,7 +93,7 @@ export async function issueAwsCredentials(input) {
90
93
  }
91
94
  async function fetchCredentials(input) {
92
95
  const { roleArn, profileArn, trustAnchorArn, region, normalizedCert, privateKeyPkcs8Base64, sessionTtl, } = input;
93
- // Signing happens INSIDE the fetcher so it only runs on a cache miss.
96
+ // FIX 1: signing happens inside fetcher only runs on cache miss.
94
97
  const signingKey = await getSigningMaterial(normalizedCert, privateKeyPkcs8Base64);
95
98
  const certSerialDec = parseCertSerialDec(normalizedCert);
96
99
  const host = `${SERVICE}.${region}.amazonaws.com`;
@@ -122,6 +125,7 @@ async function fetchCredentials(input) {
122
125
  "X-Amz-X509": normalizedCert,
123
126
  "Authorization": `${ALGORITHM} Credential=${certSerialDec}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`,
124
127
  });
128
+ // FIX 4: catch network errors as aws_unreachable.
125
129
  let res;
126
130
  try {
127
131
  res = await fetch(`https://${host}${PATH}`, {
@@ -150,8 +154,7 @@ async function fetchCredentials(input) {
150
154
  sessionToken: creds.sessionToken,
151
155
  expiration: creds.expiration,
152
156
  };
153
- // Cache for 1/3 of the credential lifetime so it's refreshed well before expiry.
154
- // getCachedOrFetch will further subtract EDGE_BUFFER_SEC (5 min).
157
+ // FIX 3: no unsafe cast fetcher typed correctly.
155
158
  const receivedAt = Date.now();
156
159
  const expiresAtMs = Date.parse(creds.expiration);
157
160
  const credLifetimeSec = Math.floor((expiresAtMs - receivedAt) / 1000);
@@ -168,11 +171,6 @@ function normalizeCert(raw) {
168
171
  throw new InternalError("pem_not_allowed");
169
172
  return raw.replace(/\s+/g, "");
170
173
  }
171
- /**
172
- * Minimal DER walk to extract the certificate serial number as a decimal string.
173
- * No isolate-level cache — stale cached values caused wrong-serial bugs in production.
174
- * Throws InternalError("invalid_cert_der") on any parse failure.
175
- */
176
174
  function parseCertSerialDec(normalizedCertBase64) {
177
175
  try {
178
176
  const der = base64ToBytes(normalizedCertBase64);
@@ -197,7 +195,6 @@ function parseCertSerialDec(normalizedCertBase64) {
197
195
  if (der[offset++] !== 0x30)
198
196
  throw new Error("bad tbs");
199
197
  readLen();
200
- // Skip optional [0] EXPLICIT version field.
201
198
  if (der[offset] === 0xa0) {
202
199
  offset++;
203
200
  offset += readLen();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizamodo/aws-sts-core",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "Pure AWS STS + SigV4 (X509 Roles Anywhere) core logic",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",