snow-flow 1.3.23 → 1.3.25

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.
@@ -20,6 +20,9 @@ const flow_structure_builder_1 = require("./flow-structure-builder");
20
20
  class ServiceNowClient {
21
21
  constructor() {
22
22
  this.credentials = null;
23
+ // 🔴 SNOW-003 FIX: Token refresh synchronization to prevent race conditions
24
+ this.tokenRefreshPromise = null;
25
+ this.lastTokenRefresh = 0;
23
26
  this.logger = new logger_1.Logger('ServiceNowClient');
24
27
  this.oauth = new snow_oauth_1.ServiceNowOAuth();
25
28
  this.client = axios_1.default.create({
@@ -84,26 +87,11 @@ class ServiceNowClient {
84
87
  // Add response interceptor for error handling with retry logic
85
88
  this.client.interceptors.response.use((response) => response, async (error) => {
86
89
  const originalRequest = error.config;
87
- // Check if this is a 401 error and we haven't already tried to refresh
90
+ // 🔴 SNOW-003 FIX: Prevent token refresh race conditions
88
91
  if (error.response?.status === 401 && !originalRequest._retry) {
89
92
  originalRequest._retry = true;
90
- this.logger.info('🔄 Received 401 error, attempting token refresh...');
91
- const refreshResult = await this.oauth.refreshAccessToken();
92
- if (refreshResult.success && refreshResult.accessToken) {
93
- this.logger.info('✅ Token refreshed successfully, retrying request...');
94
- // Update local credentials
95
- if (this.credentials) {
96
- this.credentials.accessToken = refreshResult.accessToken;
97
- }
98
- // Update the authorization header with new token
99
- originalRequest.headers['Authorization'] = `Bearer ${refreshResult.accessToken}`;
100
- // Retry the original request
101
- return this.client.request(originalRequest);
102
- }
103
- else {
104
- console.error('❌ Token refresh failed:', refreshResult.error);
105
- this.logger.warn('💡 Please run "snow-flow auth login" to re-authenticate');
106
- }
93
+ // 🔴 CRITICAL: Use singleton token refresh to prevent multiple concurrent refreshes
94
+ return this.handleTokenRefreshWithLock(originalRequest);
107
95
  }
108
96
  // Log other errors for debugging
109
97
  if (error.response?.status === 403) {
@@ -121,6 +109,79 @@ class ServiceNowClient {
121
109
  get credentialsInstance() {
122
110
  return this.credentials;
123
111
  }
112
+ /**
113
+ * 🔴 SNOW-003 FIX: Handle token refresh with locking to prevent concurrent refreshes
114
+ * This prevents cascade failures caused by multiple simultaneous token refresh attempts
115
+ */
116
+ async handleTokenRefreshWithLock(originalRequest) {
117
+ const now = Date.now();
118
+ // 🔴 CRITICAL: If another refresh is in progress, wait for it
119
+ if (this.tokenRefreshPromise) {
120
+ this.logger.info('🔄 Token refresh already in progress, waiting...');
121
+ try {
122
+ await this.tokenRefreshPromise;
123
+ // After waiting, check if we have valid credentials now
124
+ if (this.credentials?.accessToken) {
125
+ this.logger.info('✅ Using refreshed token from concurrent refresh');
126
+ originalRequest.headers['Authorization'] = `Bearer ${this.credentials.accessToken}`;
127
+ return this.client.request(originalRequest);
128
+ }
129
+ }
130
+ catch (error) {
131
+ this.logger.warn('Concurrent token refresh failed:', error);
132
+ }
133
+ }
134
+ // 🔴 CRITICAL: Rate limit token refresh to prevent excessive API calls
135
+ if (now - this.lastTokenRefresh < 5000) { // 5 second rate limit
136
+ this.logger.warn('⚠️ Token refresh rate limited - too many attempts');
137
+ throw new Error('Token refresh rate limited. Please wait before retrying.');
138
+ }
139
+ // 🔴 CRITICAL: Create new refresh promise with timeout and error handling
140
+ this.tokenRefreshPromise = this.performTokenRefreshWithTimeout();
141
+ this.lastTokenRefresh = now;
142
+ try {
143
+ this.logger.info('🔄 Starting token refresh process...');
144
+ const refreshResult = await this.tokenRefreshPromise;
145
+ if (refreshResult.success && refreshResult.accessToken) {
146
+ this.logger.info('✅ Token refreshed successfully, retrying original request...');
147
+ // Update local credentials
148
+ if (this.credentials) {
149
+ this.credentials.accessToken = refreshResult.accessToken;
150
+ this.credentials.expiresAt = refreshResult.expiresAt;
151
+ }
152
+ // Update the authorization header with new token
153
+ originalRequest.headers['Authorization'] = `Bearer ${refreshResult.accessToken}`;
154
+ // Clear the promise since we're done
155
+ this.tokenRefreshPromise = null;
156
+ // Retry the original request
157
+ return this.client.request(originalRequest);
158
+ }
159
+ else {
160
+ // Clear the promise on failure
161
+ this.tokenRefreshPromise = null;
162
+ const errorMsg = `Token refresh failed: ${refreshResult.error || 'Unknown error'}`;
163
+ this.logger.error('❌ ' + errorMsg);
164
+ console.error('💡 Please run "snow-flow auth login" to re-authenticate');
165
+ throw new Error(errorMsg);
166
+ }
167
+ }
168
+ catch (error) {
169
+ // Clear the promise on any error
170
+ this.tokenRefreshPromise = null;
171
+ this.logger.error('🔴 Token refresh process failed:', error);
172
+ throw error;
173
+ }
174
+ }
175
+ /**
176
+ * 🔴 SNOW-003 FIX: Token refresh with timeout to prevent hanging requests
177
+ */
178
+ async performTokenRefreshWithTimeout() {
179
+ const timeout = 15000; // 15 second timeout for token refresh
180
+ return Promise.race([
181
+ this.oauth.refreshAccessToken(),
182
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Token refresh timeout')), timeout)),
183
+ ]);
184
+ }
124
185
  /**
125
186
  * Ensure we have valid authentication with improved error handling
126
187
  */
@@ -124,26 +124,47 @@ class XMLFirstFlowGenerator {
124
124
  .replace(/\t/g, '&#9;'); // Preserve tabs
125
125
  return escaped;
126
126
  }
127
+ /**
128
+ * 🛡️ Apply security defaults to flow definition - BUG-004 FIX
129
+ */
130
+ applySecurityDefaults(flowDef) {
131
+ // 🔒 SECURITY GUARD: Never allow public access unless explicitly and safely configured
132
+ const secureFlowDef = {
133
+ ...flowDef,
134
+ // Override with secure defaults - these CANNOT be accidentally public
135
+ accessible_from: 'package_private', // FORCE secure default
136
+ run_as: flowDef.run_as || 'user' // Default to user, not system
137
+ };
138
+ // 🚨 Log security override for audit trail
139
+ if (flowDef.accessible_from && flowDef.accessible_from !== 'package_private') {
140
+ console.log('🛡️ SECURITY OVERRIDE: Flow access forced to package_private for security');
141
+ console.log(` • Original: ${flowDef.accessible_from}`);
142
+ console.log(` • Applied: package_private (secure)`);
143
+ }
144
+ return secureFlowDef;
145
+ }
127
146
  /**
128
147
  * Generate complete Update Set XML for a flow
129
148
  * Based on REAL ServiceNow XML structure research
130
149
  */
131
150
  generateFlowUpdateSetXML(flowDef) {
151
+ // 🔒 BUG-004 FIX: Apply security defaults FIRST
152
+ const secureFlowDef = this.applySecurityDefaults(flowDef);
132
153
  this.flowSysId = this.generateSysId();
133
154
  const timestamp = new Date().toISOString();
134
- const internalName = flowDef.internal_name || this.generateInternalName(flowDef.name);
155
+ const internalName = secureFlowDef.internal_name || this.generateInternalName(secureFlowDef.name);
135
156
  // Generate all component sys_ids upfront
136
157
  const updateSetSysId = this.generateSysId();
137
158
  const snapshotSysId = this.generateSysId();
138
159
  const triggerSysId = this.generateSysId();
139
- const activitySysIds = flowDef.activities.map(() => this.generateSysId());
160
+ const activitySysIds = secureFlowDef.activities.map(() => this.generateSysId());
140
161
  const logicSysIds = []; // For flow logic connections
141
162
  // Calculate logic chain sys_ids
142
163
  for (let i = 0; i < activitySysIds.length + 2; i++) {
143
164
  logicSysIds.push(this.generateSysId());
144
165
  }
145
166
  // Build the complete flow definition JSON for snapshot
146
- const flowDefinitionJson = this.buildFlowDefinitionJson(flowDef, triggerSysId, activitySysIds);
167
+ const flowDefinitionJson = this.buildFlowDefinitionJson(secureFlowDef, triggerSysId, activitySysIds);
147
168
  // Generate XML - MUST use sys_remote_update_set for imports!
148
169
  const xml = `<?xml version="1.0" encoding="UTF-8"?>
149
170
  <unload unload_date="${timestamp}">
@@ -151,7 +172,7 @@ class XMLFirstFlowGenerator {
151
172
  <sys_remote_update_set action="INSERT_OR_UPDATE">
152
173
  <sys_id>${updateSetSysId}</sys_id>
153
174
  <name>${this.escapeXml(this.updateSetName)}</name>
154
- <description>Flow import: ${this.escapeXml(flowDef.name)}</description>
175
+ <description>Flow import: ${this.escapeXml(secureFlowDef.name)}</description>
155
176
  <remote_sys_id>${updateSetSysId}</remote_sys_id>
156
177
  <state>loaded</state>
157
178
  <application>global</application>
@@ -165,7 +186,7 @@ class XMLFirstFlowGenerator {
165
186
  <action>INSERT_OR_UPDATE</action>
166
187
  <application>global</application>
167
188
  <name>sys_hub_flow_${this.flowSysId}</name>
168
- <payload><![CDATA[<?xml version="1.0" encoding="UTF-8"?><record_update table="sys_hub_flow"><sys_hub_flow action="INSERT_OR_UPDATE"><sys_id>${this.flowSysId}</sys_id><name>${this.escapeXml(flowDef.name)}</name><internal_name>${this.escapeXml(internalName)}</internal_name><description>${this.escapeXml(flowDef.description)}</description><active>true</active><type>flow</type><category>custom</category><sys_scope display_value="Global">global</sys_scope><sys_app display_value="Global">global</sys_app><sys_package display_value="Global" source="global">global</sys_package><access>${flowDef.accessible_from || 'package_private'}</access><run_as>${flowDef.run_as || 'user'}</run_as><sys_class_name>sys_hub_flow</sys_class_name><sys_created_by>admin</sys_created_by><sys_created_on>${timestamp}</sys_created_on><sys_domain>global</sys_domain><sys_domain_path>/</sys_domain_path><sys_updated_by>admin</sys_updated_by><sys_updated_on>${timestamp}</sys_updated_on><latest_snapshot>${snapshotSysId}</latest_snapshot><master_snapshot>${snapshotSysId}</master_snapshot><natlang/><copied_from/><copied_from_name/><show_draft_actions>false</show_draft_actions><show_row_wfr_actions>false</show_row_wfr_actions><show_wf_actions>false</show_wf_actions><sys_overrides/><sys_policy/></sys_hub_flow></record_update>]]></payload>
189
+ <payload><![CDATA[<?xml version="1.0" encoding="UTF-8"?><record_update table="sys_hub_flow"><sys_hub_flow action="INSERT_OR_UPDATE"><sys_id>${this.flowSysId}</sys_id><name>${this.escapeXml(secureFlowDef.name)}</name><internal_name>${this.escapeXml(internalName)}</internal_name><description>${this.escapeXml(secureFlowDef.description)}</description><active>true</active><type>flow</type><category>custom</category><sys_scope display_value="Global">global</sys_scope><sys_app display_value="Global">global</sys_app><sys_package display_value="Global" source="global">global</sys_package><access>${secureFlowDef.accessible_from}</access><run_as>${secureFlowDef.run_as}</run_as><sys_class_name>sys_hub_flow</sys_class_name><sys_created_by>admin</sys_created_by><sys_created_on>${timestamp}</sys_created_on><sys_domain>global</sys_domain><sys_domain_path>/</sys_domain_path><sys_updated_by>admin</sys_updated_by><sys_updated_on>${timestamp}</sys_updated_on><latest_snapshot>${snapshotSysId}</latest_snapshot><master_snapshot>${snapshotSysId}</master_snapshot><natlang/><copied_from/><copied_from_name/><show_draft_actions>false</show_draft_actions><show_row_wfr_actions>false</show_row_wfr_actions><show_wf_actions>false</show_wf_actions><sys_overrides/><sys_policy/></sys_hub_flow></record_update>]]></payload>
169
190
  <remote_update_set>${updateSetSysId}</remote_update_set>
170
191
  <source_table>sys_hub_flow</source_table>
171
192
  <sys_recorded_at>${timestamp}</sys_recorded_at>
@@ -179,7 +200,7 @@ class XMLFirstFlowGenerator {
179
200
  <action>INSERT_OR_UPDATE</action>
180
201
  <application>global</application>
181
202
  <name>sys_hub_flow_snapshot_${snapshotSysId}</name>
182
- <payload><![CDATA[<?xml version="1.0" encoding="UTF-8"?><record_update table="sys_hub_flow_snapshot"><sys_hub_flow_snapshot action="INSERT_OR_UPDATE"><sys_id>${snapshotSysId}</sys_id><name>${this.escapeXml(flowDef.name)}</name><flow>${this.flowSysId}</flow><note>Initial version</note><snapshot>${this.escapeForXml(JSON.stringify(flowDefinitionJson, null, 2))}</snapshot><sys_created_by>admin</sys_created_by><sys_created_on>${timestamp}</sys_created_on></sys_hub_flow_snapshot></record_update>]]></payload>
203
+ <payload><![CDATA[<?xml version="1.0" encoding="UTF-8"?><record_update table="sys_hub_flow_snapshot"><sys_hub_flow_snapshot action="INSERT_OR_UPDATE"><sys_id>${snapshotSysId}</sys_id><name>${this.escapeXml(secureFlowDef.name)}</name><flow>${this.flowSysId}</flow><note>Initial version</note><snapshot>${this.escapeForXml(JSON.stringify(flowDefinitionJson, null, 2))}</snapshot><sys_created_by>admin</sys_created_by><sys_created_on>${timestamp}</sys_created_on></sys_hub_flow_snapshot></record_update>]]></payload>
183
204
  <remote_update_set>${updateSetSysId}</remote_update_set>
184
205
  <source_table>sys_hub_flow_snapshot</source_table>
185
206
  <type>Flow Designer Snapshot</type>
@@ -191,13 +212,13 @@ class XMLFirstFlowGenerator {
191
212
  <action>INSERT_OR_UPDATE</action>
192
213
  <application>global</application>
193
214
  <name>sys_hub_trigger_instance_${triggerSysId}</name>
194
- <payload><![CDATA[<?xml version="1.0" encoding="UTF-8"?><record_update table="sys_hub_trigger_instance"><sys_hub_trigger_instance action="INSERT_OR_UPDATE"><sys_id>${triggerSysId}</sys_id><flow>${this.flowSysId}</flow><trigger_type>${this.getTriggerTypeId(flowDef.trigger_type)}</trigger_type><table>${flowDef.table || ''}</table><condition>${this.escapeXml(flowDef.trigger_condition || '')}</condition><order>100</order><active>true</active><sys_class_name>sys_hub_trigger_instance</sys_class_name><sys_created_by>admin</sys_created_by><sys_created_on>${timestamp}</sys_created_on><sys_domain>global</sys_domain><sys_domain_path>/</sys_domain_path></sys_hub_trigger_instance></record_update>]]></payload>
215
+ <payload><![CDATA[<?xml version="1.0" encoding="UTF-8"?><record_update table="sys_hub_trigger_instance"><sys_hub_trigger_instance action="INSERT_OR_UPDATE"><sys_id>${triggerSysId}</sys_id><flow>${this.flowSysId}</flow><trigger_type>${this.getTriggerTypeId(secureFlowDef.trigger_type)}</trigger_type><table>${secureFlowDef.table || ''}</table><condition>${this.escapeXml(secureFlowDef.trigger_condition || '')}</condition><order>100</order><active>true</active><sys_class_name>sys_hub_trigger_instance</sys_class_name><sys_created_by>admin</sys_created_by><sys_created_on>${timestamp}</sys_created_on><sys_domain>global</sys_domain><sys_domain_path>/</sys_domain_path></sys_hub_trigger_instance></record_update>]]></payload>
195
216
  <remote_update_set>${updateSetSysId}</remote_update_set>
196
217
  <source_table>sys_hub_trigger_instance</source_table>
197
218
  <type>Flow Designer Trigger</type>
198
219
  </sys_update_xml>
199
220
 
200
- ${this.generateActionInstancesXML(flowDef.activities, activitySysIds, timestamp, updateSetSysId)}
221
+ ${this.generateActionInstancesXML(secureFlowDef.activities, activitySysIds, timestamp, updateSetSysId)}
201
222
  ${this.generateFlowLogicXML(triggerSysId, activitySysIds, logicSysIds, timestamp, updateSetSysId)}
202
223
  </unload>`;
203
224
  return xml;
@@ -0,0 +1,35 @@
1
+ 🚀 Snow-Flow v1.3.24 - Ready for NPM Publish!
2
+
3
+ ✅ GITHUB STATUS:
4
+ - All changes committed and pushed to main branch
5
+ - Git tag v1.3.24 created and pushed
6
+ - Repository: https://github.com/groeimetai/snow-flow
7
+
8
+ ✅ FIXES COMPLETED:
9
+ - SNOW-001: Silent Deployment Failures → FIXED with mandatory verification
10
+ - SNOW-002: Search System Failures → FIXED with retry logic
11
+ - SNOW-003: High Failure Rate (19%) → FIXED with enhanced resilience
12
+ - SNOW-004: Security Compliance Module → FIXED variable shadowing
13
+
14
+ ✅ NPM PACKAGE READY:
15
+ - Version: 1.3.24
16
+ - Package size: 1.6 MB
17
+ - Unpacked size: 9.0 MB
18
+ - Files: 740 total
19
+
20
+ 📦 TO PUBLISH TO NPM:
21
+ 1. Make sure you're logged in to npm:
22
+ npm whoami
23
+
24
+ 2. If not logged in:
25
+ npm login
26
+
27
+ 3. Publish the package:
28
+ npm publish
29
+
30
+ 4. Verify publication:
31
+ npm view snow-flow version
32
+
33
+ 🎉 Snow-Flow is now production-ready with all critical beta test issues resolved!
34
+
35
+ Release notes available at: RELEASE_NOTES_1.3.24.md
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-flow",
3
- "version": "1.3.23",
3
+ "version": "1.3.25",
4
4
  "description": "ServiceNow Queen Agent - Hive-Mind Intelligence for ServiceNow Development inspired by claude-flow. Transform complex workflows into elegant one-command orchestration.",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",