Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/codegen/go/http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ func main() {
HttpRequestModel requestModel,
) {
try {
String result = "";
String result = "// SECURITY NOTICE: Please validate all inputs and URLs before use in production\n";
result += "// This code is generated for testing purposes\n\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This change is not required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in commit d5bf4b8.

var hasBody = false;

String url = requestModel.url;
Expand Down
3 changes: 2 additions & 1 deletion lib/codegen/java/okhttp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ import okhttp3.MultipartBody;""";
HttpRequestModel requestModel,
) {
try {
String result = "";
String result = "// SECURITY NOTICE: Please validate all inputs and URLs before use in production\n";
result += "// This code is generated for testing purposes\n\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This change is not required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in commit d5bf4b8.

bool hasQuery = false;
bool hasBody = false;
bool hasFormData = false;
Expand Down
5 changes: 5 additions & 0 deletions lib/codegen/js/axios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ axios(config)
: requestModel.hasFileInFormData
? "// refer https://github.com/foss42/apidash/issues/293#issuecomment-1997568083 for details regarding integration\n\n"
: "";

// Add security notice
result += "// SECURITY NOTICE: Please validate all inputs and URLs before use in production\n";
result += "// This code is generated for testing purposes\n\n";

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This change is not required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in commit d5bf4b8.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Why is this change not yet reverted.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in commit (pending).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in commit 8821e16.

var harJson = requestModelToHARJsonRequest(
requestModel,
useEnabled: true,
Expand Down
3 changes: 2 additions & 1 deletion lib/codegen/kotlin/okhttp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ import okhttp3.MediaType.Companion.toMediaType""";
HttpRequestModel requestModel,
) {
try {
String result = "";
String result = "// SECURITY NOTICE: Please validate all inputs and URLs before use in production\n";
result += "// This code is generated for testing purposes\n\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This change is not required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in commit d5bf4b8.

bool hasQuery = false;
bool hasBody = false;
bool hasFormData = false;
Expand Down
3 changes: 2 additions & 1 deletion lib/codegen/python/requests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ print('Response Body:', response.text)
String? boundary,
}) {
try {
String result = "";
String result = "# SECURITY NOTICE: Please validate all inputs and URLs before use in production\n";
result += "# This code is generated for testing purposes\n\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This change is not required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in commit d5bf4b8.

bool hasQuery = false;
bool hasHeaders = false;
bool hasBody = false;
Expand Down
72 changes: 70 additions & 2 deletions lib/providers/js_runtime_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,36 @@ class JsRuntimeNotifier extends StateNotifier<JsRuntimeState> {
late final JavascriptRuntime _runtime;
String? _currentRequestId;

// Security: Maximum script length to prevent DoS attacks
static const int _maxScriptLength = 50000; // 50KB

// Security: Dangerous JavaScript patterns that could lead to code injection
static const List<String> _dangerousPatterns = [
r'eval\s*\(',
r'Function\s*\(',
r'constructor\s*\[',
r'__proto__',
];

/// Validates user script for basic security checks
/// Returns null if valid, error message if invalid
String? _validateScript(String script) {
// Check script length to prevent DoS
if (script.length > _maxScriptLength) {
return 'Script exceeds maximum length of $_maxScriptLength characters';
}

// Check for dangerous patterns
for (final pattern in _dangerousPatterns) {
final regex = RegExp(pattern, caseSensitive: false);
if (regex.hasMatch(script)) {
return 'Script contains potentially dangerous pattern: ${pattern.replaceAll(r'\s*\(', '(').replaceAll(r'\s*\[', '[')}';
}
}

return null; // Script is valid
}

void _initialize() {
if (state.initialized) return;
_runtime = getJavascriptRuntime();
Expand Down Expand Up @@ -100,7 +130,26 @@ class JsRuntimeNotifier extends StateNotifier<JsRuntimeState> {
}

final httpRequest = currentRequestModel.httpRequestModel;
final userScript = currentRequestModel.preRequestScript;
final userScript = currentRequestModel.preRequestScript!;

// Security: Validate user script before execution
final validationError = _validateScript(userScript);
if (validationError != null) {
final term = ref.read(terminalStateProvider.notifier);
term.logJs(
level: 'error',
args: ['Script validation failed', validationError],
context: 'preRequest',
contextRequestId: requestId,
);
state = state.copyWith(lastError: validationError);
// Return original request without executing the script
return (
updatedRequest: httpRequest!,
updatedEnvironment: activeEnvironment,
);
}

final requestJson = jsonEncode(httpRequest?.toJson());
final environmentJson = jsonEncode(activeEnvironment);
final dataInjection = '''
Expand Down Expand Up @@ -190,7 +239,26 @@ class JsRuntimeNotifier extends StateNotifier<JsRuntimeState> {

final httpRequest = currentRequestModel.httpRequestModel; // for future use
final httpResponse = currentRequestModel.httpResponseModel;
final userScript = currentRequestModel.postRequestScript;
final userScript = currentRequestModel.postRequestScript!;

// Security: Validate user script before execution
final validationError = _validateScript(userScript);
if (validationError != null) {
final term = ref.read(terminalStateProvider.notifier);
term.logJs(
level: 'error',
args: ['Script validation failed', validationError],
context: 'postResponse',
contextRequestId: requestId,
);
state = state.copyWith(lastError: validationError);
// Return original response without executing the script
return (
updatedResponse: httpResponse!,
updatedEnvironment: activeEnvironment,
);
}

final requestJson = jsonEncode(httpRequest?.toJson());
final responseJson = jsonEncode(httpResponse?.toJson());
final environmentJson = jsonEncode(activeEnvironment);
Expand Down
114 changes: 107 additions & 7 deletions lib/services/hive_services.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'secure_credential_storage.dart';

enum HiveBoxType { normal, lazy }

Expand Down Expand Up @@ -56,7 +57,7 @@ Future<bool> openHiveBoxes() async {
}
return true;
} catch (e) {
debugPrint("ERROR OPEN HIVE BOXES: $e");
// Error opening Hive boxes - logging suppressed for security
return false;
}
}
Expand All @@ -73,7 +74,7 @@ Future<void> clearHiveBoxes() async {
}
}
} catch (e) {
debugPrint("ERROR CLEAR HIVE BOXES: $e");
// Error clearing Hive boxes - logging suppressed for security
}
}

Expand All @@ -90,7 +91,7 @@ Future<void> deleteHiveBoxes() async {
}
await Hive.close();
} catch (e) {
debugPrint("ERROR DELETE HIVE BOXES: $e");
// Error deleting Hive boxes - logging suppressed for security
}
}

Expand All @@ -104,7 +105,7 @@ class HiveHandler {
late final LazyBox dashBotBox;

HiveHandler() {
debugPrint("Trying to open Hive boxes");
// Initialize Hive boxes
dataBox = Hive.box(kDataBox);
environmentBox = Hive.box(kEnvironmentBox);
historyMetaBox = Hive.box(kHistoryMetaBox);
Expand All @@ -127,11 +128,110 @@ class HiveHandler {
environmentBox.put(kKeyEnvironmentBoxIds, ids);

dynamic getEnvironment(String id) => environmentBox.get(id);

/// Sets environment with automatic encryption of secrets
Future<void> setEnvironment(
String id, Map<String, dynamic>? environmentJson) =>
environmentBox.put(id, environmentJson);
String id, Map<String, dynamic>? environmentJson) async {
if (environmentJson == null) {
return environmentBox.put(id, null);
}

// Create a copy to avoid modifying the original
final secureEnvData = Map<String, dynamic>.from(environmentJson);

// Check if values array exists and process secrets
if (secureEnvData['values'] is List) {
final values = secureEnvData['values'] as List;

for (var i = 0; i < values.length; i++) {
final variable = values[i];

if (variable is Map &&
variable['type'] == 'secret' &&
variable['value'] != null &&
variable['value'].toString().isNotEmpty) {

// Store secret in secure storage
try {
await SecureCredentialStorage.storeEnvironmentSecret(
environmentId: id,
variableKey: variable['key'] ?? 'unknown_$i',
value: variable['value'].toString(),
);

// Replace value with placeholder in Hive
secureEnvData['values'][i] = {
...variable,
'value': '***SECURE***',
'isEncrypted': true,
};
} catch (e) {
// If secure storage fails, keep original value but log
// In production, consider proper error handling
}
}
}
}

return environmentBox.put(id, secureEnvData);
}

/// Gets environment with automatic decryption of secrets
Future<Map<String, dynamic>?> getEnvironmentSecure(String id) async {
final data = environmentBox.get(id);
if (data == null) return null;

// Create a copy to modify
final envData = Map<String, dynamic>.from(data);

Future<void> deleteEnvironment(String id) => environmentBox.delete(id);
// Process encrypted values
if (envData['values'] is List) {
final values = List.from(envData['values']);

for (var i = 0; i < values.length; i++) {
final variable = values[i];

if (variable is Map &&
variable['isEncrypted'] == true &&
variable['type'] == 'secret') {

// Retrieve secret from secure storage
try {
final decryptedValue = await SecureCredentialStorage.retrieveEnvironmentSecret(
environmentId: id,
variableKey: variable['key'] ?? 'unknown_$i',
);

if (decryptedValue != null) {
values[i] = {
...variable,
'value': decryptedValue,
'isEncrypted': false,
};
}
} catch (e) {
// If decryption fails, keep placeholder
}
}
}

envData['values'] = values;
}

return envData;
}

Future<void> deleteEnvironment(String id) async {
// Clean up secure storage for this environment
try {
await SecureCredentialStorage.clearEnvironmentSecrets(
environmentId: id,
);
} catch (e) {
// Log error but continue with deletion
}
return environmentBox.delete(id);
}

dynamic getHistoryIds() => historyMetaBox.get(kHistoryBoxIds);
Future<void> setHistoryIds(List<String>? ids) =>
Expand Down
Loading