-
Notifications
You must be signed in to change notification settings - Fork 16
feat/TE-29598-Added Dynamic gRPC Client Action with JSON Request Support #226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat/TE-29598-Added Dynamic gRPC Client Action with JSON Request Support #226
Conversation
WalkthroughAdds a new Maven module dynamic_grpc_client with dependencies/plugins and resource configuration. Introduces platform-specific GrpcClientAction classes (android, ios, web, restapi) that compile .proto files at runtime, build dynamic requests from JSON, perform unary gRPC calls, and store JSON responses into runtime variables, with cleanup and error handling. Changes
Sequence Diagram(s)sequenceDiagram
actor T as Test Runner
participant A as GrpcClientAction
participant P as Embedded protoc
participant D as Descriptor Loader
participant S as gRPC Server
T->>A: execute(protoPath, url, service, method, json, variable)
A->>P: generateDescriptor(protoDir, protoFile)
P-->>A: descriptorSetFile
A->>D: load(service, method) from descriptor
D-->>A: MethodDescriptor
A->>A: sanitize/parse JSON -> DynamicMessage
A->>S: unary call(MethodDescriptor, request)
S-->>A: DynamicMessage response
A->>A: to JSON
A-->>T: set runtime variable, Result.SUCCESS
A->>A: shutdown channel, delete temp descriptor
note over A: On error: set message, Result.FAILED
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🧹 Nitpick comments (19)
dynamic_grpc_client/src/main/java/com/testsigma/addons/ios/GrpcClientAction.java (4)
68-73: Validate proto file and handle null parent path.Avoid NPE and clearer error if the file is missing.
Apply:
- File protoFile = new File(protoFilePath.getValue().toString()); - String protoDir = protoFile.getParent(); + File protoFile = new File(protoFilePath.getValue().toString()); + if (!protoFile.exists()) { + throw new java.io.FileNotFoundException("Proto file not found: " + protoFile.getPath()); + } + String protoDir = protoFile.getParent(); + if (protoDir == null) { + protoDir = protoFile.getAbsoluteFile().getParent(); + } String protoFileName = protoFile.getName();
162-172: Guard against non-unary RPCs.Fail early if the target method is streaming; the action constructs a UNARY descriptor.
- private MethodDescriptor<DynamicMessage, DynamicMessage> buildGrpcMethod(Descriptors.MethodDescriptor methodDescriptor) { + private MethodDescriptor<DynamicMessage, DynamicMessage> buildGrpcMethod(Descriptors.MethodDescriptor methodDescriptor) { + if (methodDescriptor.isClientStreaming() || methodDescriptor.isServerStreaming()) { + throw new UnsupportedOperationException("Only unary gRPC methods are supported by this action."); + } return MethodDescriptor.<DynamicMessage, DynamicMessage>newBuilder()
174-178: Add per-call deadline to avoid indefinite blocking.Prevent hangs when the server is slow/unreachable.
- return ClientCalls.blockingUnaryCall(channel, method, CallOptions.DEFAULT, request); + return ClientCalls.blockingUnaryCall( + channel, method, CallOptions.DEFAULT.withDeadlineAfter(30, TimeUnit.SECONDS), request);
83-86: Consider TLS configurability (avoid unconditional plaintext).Expose a flag or detect scheme to use TLS (useTransportSecurity) when needed; plaintext-only limits secure endpoints.
dynamic_grpc_client/src/main/java/com/testsigma/addons/web/GrpcClientAction.java (3)
162-172: Fail if method is streaming.Unary-only action; add a guard.
- private MethodDescriptor<DynamicMessage, DynamicMessage> buildGrpcMethod(Descriptors.MethodDescriptor methodDescriptor) { + private MethodDescriptor<DynamicMessage, DynamicMessage> buildGrpcMethod(Descriptors.MethodDescriptor methodDescriptor) { + if (methodDescriptor.isClientStreaming() || methodDescriptor.isServerStreaming()) { + throw new UnsupportedOperationException("Only unary gRPC methods are supported by this action."); + } return MethodDescriptor.<DynamicMessage, DynamicMessage>newBuilder()
174-178: Add deadline to the RPC call.Avoid indefinite blocking.
- return ClientCalls.blockingUnaryCall(channel, method, CallOptions.DEFAULT, request); + return ClientCalls.blockingUnaryCall( + channel, method, CallOptions.DEFAULT.withDeadlineAfter(30, TimeUnit.SECONDS), request);
83-86: Make TLS optional/configurable.Don’t hardcode plaintext; allow secure endpoints.
dynamic_grpc_client/src/main/java/com/testsigma/addons/android/GrpcClientAction.java (3)
162-172: Guard against streaming RPCs.Support unary only.
- private MethodDescriptor<DynamicMessage, DynamicMessage> buildGrpcMethod(Descriptors.MethodDescriptor methodDescriptor) { + private MethodDescriptor<DynamicMessage, DynamicMessage> buildGrpcMethod(Descriptors.MethodDescriptor methodDescriptor) { + if (methodDescriptor.isClientStreaming() || methodDescriptor.isServerStreaming()) { + throw new UnsupportedOperationException("Only unary gRPC methods are supported by this action."); + } return MethodDescriptor.<DynamicMessage, DynamicMessage>newBuilder()
174-178: Add RPC deadline.Avoid hangs on network issues.
- return ClientCalls.blockingUnaryCall(channel, method, CallOptions.DEFAULT, request); + return ClientCalls.blockingUnaryCall( + channel, method, CallOptions.DEFAULT.withDeadlineAfter(30, TimeUnit.SECONDS), request);
83-86: TLS support.Expose a toggle to use TLS instead of plaintext.
dynamic_grpc_client/pom.xml (5)
37-41: Use a stable JUnit 5 release (avoid milestone).Upgrade from 5.8.0-M1 to a stable version.
- <version>${junit.jupiter.version}</version> + <version>5.11.3</version>
43-46: Bump TestNG to address known vulnerability.6.14.3 has a path traversal issue (GHSA-rc2q-x9mf-w3vf). Upgrade to 7.x.
- <version>6.14.3</version> + <version>7.10.2</version>
60-63: Update Jackson Annotations.2.13.0 is outdated; align to current stable to pick up fixes.
- <version>2.13.0</version> + <version>2.17.2</version>
99-111: Shade relocation to avoid dependency conflicts.Relocating common packages reduces classpath clashes when embedding.
Example configuration (adjust as needed):
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> + <configuration> + <relocations> + <relocation> + <pattern>io.grpc</pattern> + <shadedPattern>com.testsigma.shadow.io.grpc</shadedPattern> + </relocation> + <relocation> + <pattern>com.google.protobuf</pattern> + <shadedPattern>com.testsigma.shadow.com.google.protobuf</shadedPattern> + </relocation> + </relocations> + </configuration> </execution> </executions> </plugin>
24-95: Consider resource filtering for secrets.If a properties file is required, inject secrets via env/CI and Maven filtering, not in VCS.
Add:
<build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.3.1</version> </plugin> </plugins> </build>Then reference ${TESTSIGMA_SDK_API_KEY} in a non-committed template if needed.
dynamic_grpc_client/src/main/java/com/testsigma/addons/restapi/GrpcClientAction.java (4)
68-73: Handle null proto parent directory.When the proto path has no parent, protoDir is null and protoc args break.
- String protoDir = protoFile.getParent(); + String protoDir = protoFile.getParent(); + if (protoDir == null) { + protoDir = "."; + }
83-86: Support TLS optionally; avoid hard-coded plaintext.Allow grpcs by toggling transport security based on the target string.
- channel = ManagedChannelBuilder.forTarget(grpcUrl.getValue().toString()) - .usePlaintext() - .build(); + String target = grpcUrl.getValue().toString(); + ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forTarget(target); + if (target.startsWith("grpcs://")) { + channelBuilder = channelBuilder.useTransportSecurity(); + } else { + channelBuilder = channelBuilder.usePlaintext(); + } + channel = channelBuilder.build();Also clarify in the action docs that:
- Use grpcs://host:port to enable TLS, grpc:// or host:port for plaintext.
- If auth is needed, consider adding optional headers/metadata in a follow-up.
74-76: Guard against streaming methods (currently only UNARY is supported).Fail fast with a clear message if the method is client/server streaming.
Descriptors.MethodDescriptor methodDescriptor = loadServiceDescriptors(tempDescriptorFile, serviceName.getValue().toString(), methodName.getValue().toString()); + if (methodDescriptor.isClientStreaming() || methodDescriptor.isServerStreaming()) { + throw new UnsupportedOperationException("Only UNARY gRPC methods are supported by this action."); + }
97-101: Reduce stack traces at warn level.Consider logging e.getMessage() at warn and full stack at debug to keep logs cleaner.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
dynamic_grpc_client/pom.xml(1 hunks)dynamic_grpc_client/src/main/java/com/testsigma/addons/android/GrpcClientAction.java(1 hunks)dynamic_grpc_client/src/main/java/com/testsigma/addons/ios/GrpcClientAction.java(1 hunks)dynamic_grpc_client/src/main/java/com/testsigma/addons/restapi/GrpcClientAction.java(1 hunks)dynamic_grpc_client/src/main/java/com/testsigma/addons/web/GrpcClientAction.java(1 hunks)dynamic_grpc_client/src/main/resources/testsigma-sdk.properties(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
dynamic_grpc_client/src/main/java/com/testsigma/addons/ios/GrpcClientAction.java (3)
dynamic_grpc_client/src/main/java/com/testsigma/addons/android/GrpcClientAction.java (1)
Data(33-194)dynamic_grpc_client/src/main/java/com/testsigma/addons/restapi/GrpcClientAction.java (1)
Data(33-194)dynamic_grpc_client/src/main/java/com/testsigma/addons/web/GrpcClientAction.java (1)
Data(33-194)
dynamic_grpc_client/src/main/java/com/testsigma/addons/web/GrpcClientAction.java (3)
dynamic_grpc_client/src/main/java/com/testsigma/addons/android/GrpcClientAction.java (1)
Data(33-194)dynamic_grpc_client/src/main/java/com/testsigma/addons/ios/GrpcClientAction.java (1)
Data(33-194)dynamic_grpc_client/src/main/java/com/testsigma/addons/restapi/GrpcClientAction.java (1)
Data(33-194)
dynamic_grpc_client/src/main/java/com/testsigma/addons/android/GrpcClientAction.java (3)
dynamic_grpc_client/src/main/java/com/testsigma/addons/ios/GrpcClientAction.java (1)
Data(33-194)dynamic_grpc_client/src/main/java/com/testsigma/addons/restapi/GrpcClientAction.java (1)
Data(33-194)dynamic_grpc_client/src/main/java/com/testsigma/addons/web/GrpcClientAction.java (1)
Data(33-194)
dynamic_grpc_client/src/main/java/com/testsigma/addons/restapi/GrpcClientAction.java (2)
dynamic_grpc_client/src/main/java/com/testsigma/addons/android/GrpcClientAction.java (1)
Data(33-194)dynamic_grpc_client/src/main/java/com/testsigma/addons/ios/GrpcClientAction.java (1)
Data(33-194)
🪛 Gitleaks (8.28.0)
dynamic_grpc_client/src/main/resources/testsigma-sdk.properties
[high] 1-1: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
🪛 OSV Scanner (2.2.2)
dynamic_grpc_client/pom.xml
[HIGH] 1-1: com.google.protobuf:protobuf-java 3.22.3: protobuf-java has potential Denial of Service issue
[HIGH] 1-1: org.json:json 20160810: json stack overflow vulnerability
[HIGH] 1-1: org.json:json 20160810: Java: DoS Vulnerability in JSON-JAVA
[HIGH] 1-1: org.testng:testng 6.14.3: TestNG is vulnerable to Path Traversal
🔇 Additional comments (1)
dynamic_grpc_client/src/main/java/com/testsigma/addons/restapi/GrpcClientAction.java (1)
39-40: Verify base class vs ApplicationType.Class extends WebAction while applicationType is REST_API. Confirm this pairing is intended in the SDK (or switch to a REST_API-specific base, if available).
Would you like me to propagate any necessary base-class adjustments across android/ios/web variants for consistency?
| <groupId>io.grpc</groupId> | ||
| <artifactId>grpc-netty-shaded</artifactId> | ||
| <version>1.57.2</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>io.grpc</groupId> | ||
| <artifactId>grpc-protobuf</artifactId> | ||
| <version>1.57.2</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>io.grpc</groupId> | ||
| <artifactId>grpc-stub</artifactId> | ||
| <version>1.57.2</version> | ||
| </dependency> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify gRPC versions and update if needed.
1.57.2 is behind; consider upgrading all gRPC artifacts in lockstep.
🌐 Web query:
What is the latest stable version of gRPC Java (grpc-netty-shaded, grpc-protobuf, grpc-stub), and are there known CVEs affecting 1.57.2?
💡 Result:
Short answer
- Latest stable grpc-java release series is newer than 1.57.2 (see project releases); current releases in 2025 are in the 1.7x line (examples: v1.72.0). [1][2]
- Known security issues relevant to 1.57.2:
- CVE-2024-7246 (HPACK table poisoning / header-index corruption) was fixed in gRPC Java starting at 1.58.3 (and later stable fixes). 1.57.2 is vulnerable. [3]
- CVE-2023-33953 (HPACK/DoS) is a gRPC issue that affected several implementations; the published advisories indicate the C/C++/other implementations were affected and fixes were released in the 1.56/1.57 timeframe — Java was not listed as affected in the grpc-java release notes (treat as not applicable to grpc-java 1.57.2 unless your dependency scanner flags it). [4]
- CVE-2024-7254 is a Google Protobuf parsing (stack overflow/DoS) issue in protobuf libraries; because grpc-protobuf/grpc-protobuf-lite depend on protobuf, services using untrusted protobuf input with older protobuf versions may be vulnerable — ensure protobuf is upgraded to the fixed protobuf versions. 1.57.2 pulls older protobuf transitive versions that may require an explicit protobuf upgrade. [5]
Recommendations
- Upgrade grpc-java artifacts (grpc-netty-shaded, grpc-protobuf, grpc-stub) to a currently supported 1.7x+ release that includes the fixes (e.g., 1.58.3+ for the HPACK fix and the latest grpc-java release for protobuf dependency updates). Verify the exact newest Maven/GitHub release before upgrading. [1][2][3][5]
Sources
- grpc-java releases (GitHub). [1]
- Maven Central repository listing for grpc-netty-shaded (example release metadata). [2]
- NVD / gRPC advisory for HPACK/HPACK-table issue (CVE-2024-7246). [3]
- Published advisory / CVE details for CVE-2023-33953. [4]
- CVE / advisories for protobuf parsing vulnerability (CVE-2024-7254). [5]
If you want, I can: (A) look up the single newest published Maven coordinates for grpc-netty-shaded / grpc-protobuf / grpc-stub right now and give the exact version numbers to upgrade to, or (B) produce a short Gradle/Maven dependency snippet that pins safe versions. Which would you prefer?
Upgrade gRPC artifacts to a patched release (>= 1.58.3)
- 1.57.2 is vulnerable to CVE-2024-7246 (HPACK header-index poisoning) — fixed in grpc-java 1.58.3+. Upgrade grpc-netty-shaded, grpc-protobuf, grpc-stub in lockstep (prefer the latest 1.7x stable release).
- Explicitly bump protobuf to a patched release to address protobuf parsing CVEs (e.g., CVE-2024-7254) and re-scan transitive deps.
Location: dynamic_grpc_client/pom.xml (lines 65–78)
| File protoFile = new File(protoFilePath.getValue().toString()); | ||
| String protoDir = protoFile.getParent(); | ||
| String protoFileName = protoFile.getName(); | ||
|
|
||
| tempDescriptorFile = generateDescriptor(protoDir, protoFileName); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Check proto existence and null parent path.
Parity with web/iOS.
- File protoFile = new File(protoFilePath.getValue().toString());
- String protoDir = protoFile.getParent();
+ File protoFile = new File(protoFilePath.getValue().toString());
+ if (!protoFile.exists()) {
+ throw new java.io.FileNotFoundException("Proto file not found: " + protoFile.getPath());
+ }
+ String protoDir = protoFile.getParent();
+ if (protoDir == null) {
+ protoDir = protoFile.getAbsoluteFile().getParent();
+ }
String protoFileName = protoFile.getName();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| File protoFile = new File(protoFilePath.getValue().toString()); | |
| String protoDir = protoFile.getParent(); | |
| String protoFileName = protoFile.getName(); | |
| tempDescriptorFile = generateDescriptor(protoDir, protoFileName); | |
| File protoFile = new File(protoFilePath.getValue().toString()); | |
| if (!protoFile.exists()) { | |
| throw new java.io.FileNotFoundException("Proto file not found: " + protoFile.getPath()); | |
| } | |
| String protoDir = protoFile.getParent(); | |
| if (protoDir == null) { | |
| protoDir = protoFile.getAbsoluteFile().getParent(); | |
| } | |
| String protoFileName = protoFile.getName(); | |
| tempDescriptorFile = generateDescriptor(protoDir, protoFileName); |
🤖 Prompt for AI Agents
In
dynamic_grpc_client/src/main/java/com/testsigma/addons/android/GrpcClientAction.java
around lines 68 to 73, add defensive checks before using the proto file: ensure
protoFilePath.getValue() is not null, ensure the File created from it exists and
is a file, and handle a null parent by either using the current directory (".")
or failing with a clear error; if any check fails, throw or log a descriptive
exception and abort descriptor generation rather than calling generateDescriptor
with invalid inputs.
| private Descriptors.MethodDescriptor loadServiceDescriptors(Path descriptorPath, String serviceName, String methodName) throws Exception { | ||
| try (FileInputStream descriptorStream = new FileInputStream(descriptorPath.toFile())) { | ||
| DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(descriptorStream); | ||
| if (descriptorSet.getFileCount() == 0) { | ||
| throw new RuntimeException("Descriptor set is empty. Check your proto file."); | ||
| } | ||
| DescriptorProtos.FileDescriptorProto fileDescriptorProto = descriptorSet.getFile(0); | ||
|
|
||
| Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[]{}); | ||
|
|
||
| Descriptors.ServiceDescriptor serviceDescriptor = fileDescriptor.findServiceByName(serviceName); | ||
| if (serviceDescriptor == null) { | ||
| throw new RuntimeException("Service not found: " + serviceName); | ||
| } | ||
|
|
||
| Descriptors.MethodDescriptor methodDescriptor = serviceDescriptor.findMethodByName(methodName); | ||
| if (methodDescriptor == null) { | ||
| throw new RuntimeException("Method not found: " + methodName); | ||
| } | ||
| return methodDescriptor; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Descriptor dependency resolution needed.
Same issue as other modules.
Replace method body with the dependency-aware version and add imports:
- java.util.Map, HashMap, Objects
| private Descriptors.MethodDescriptor loadServiceDescriptors(Path descriptorPath, String serviceName, String methodName) throws Exception { | ||
| try (FileInputStream descriptorStream = new FileInputStream(descriptorPath.toFile())) { | ||
| DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(descriptorStream); | ||
| if (descriptorSet.getFileCount() == 0) { | ||
| throw new RuntimeException("Descriptor set is empty. Check your proto file."); | ||
| } | ||
| DescriptorProtos.FileDescriptorProto fileDescriptorProto = descriptorSet.getFile(0); | ||
|
|
||
| Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[]{}); | ||
|
|
||
| Descriptors.ServiceDescriptor serviceDescriptor = fileDescriptor.findServiceByName(serviceName); | ||
| if (serviceDescriptor == null) { | ||
| throw new RuntimeException("Service not found: " + serviceName); | ||
| } | ||
|
|
||
| Descriptors.MethodDescriptor methodDescriptor = serviceDescriptor.findMethodByName(methodName); | ||
| if (methodDescriptor == null) { | ||
| throw new RuntimeException("Method not found: " + methodName); | ||
| } | ||
| return methodDescriptor; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Load descriptors with dependencies; current logic breaks with imports.
Using only descriptorSet.getFile(0) and empty deps fails for imported types.
Apply:
- private Descriptors.MethodDescriptor loadServiceDescriptors(Path descriptorPath, String serviceName, String methodName) throws Exception {
- try (FileInputStream descriptorStream = new FileInputStream(descriptorPath.toFile())) {
- DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(descriptorStream);
- if (descriptorSet.getFileCount() == 0) {
- throw new RuntimeException("Descriptor set is empty. Check your proto file.");
- }
- DescriptorProtos.FileDescriptorProto fileDescriptorProto = descriptorSet.getFile(0);
-
- Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[]{});
-
- Descriptors.ServiceDescriptor serviceDescriptor = fileDescriptor.findServiceByName(serviceName);
- if (serviceDescriptor == null) {
- throw new RuntimeException("Service not found: " + serviceName);
- }
-
- Descriptors.MethodDescriptor methodDescriptor = serviceDescriptor.findMethodByName(methodName);
- if (methodDescriptor == null) {
- throw new RuntimeException("Method not found: " + methodName);
- }
- return methodDescriptor;
- }
- }
+ private Descriptors.MethodDescriptor loadServiceDescriptors(Path descriptorPath, String serviceName, String methodName) throws Exception {
+ try (FileInputStream descriptorStream = new FileInputStream(descriptorPath.toFile())) {
+ DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(descriptorStream);
+ if (descriptorSet.getFileCount() == 0) {
+ throw new RuntimeException("Descriptor set is empty. Check your proto file.");
+ }
+ java.util.Map<String, Descriptors.FileDescriptor> built = new java.util.HashMap<>();
+ Descriptors.FileDescriptor targetFd = null;
+ for (DescriptorProtos.FileDescriptorProto fdp : descriptorSet.getFileList()) {
+ Descriptors.FileDescriptor[] deps = fdp.getDependencyList().stream()
+ .map(built::get)
+ .filter(java.util.Objects::nonNull)
+ .toArray(Descriptors.FileDescriptor[]::new);
+ Descriptors.FileDescriptor fd = Descriptors.FileDescriptor.buildFrom(fdp, deps);
+ built.put(fd.getName(), fd);
+ if (fd.findServiceByName(serviceName) != null) {
+ targetFd = fd;
+ }
+ }
+ if (targetFd == null) {
+ throw new RuntimeException("Service not found: " + serviceName);
+ }
+ Descriptors.ServiceDescriptor serviceDescriptor = targetFd.findServiceByName(serviceName);
+ Descriptors.MethodDescriptor methodDescriptor = serviceDescriptor.findMethodByName(methodName);
+ if (methodDescriptor == null) {
+ throw new RuntimeException("Method not found: " + methodName);
+ }
+ return methodDescriptor;
+ }
+ }Add imports (outside this hunk):
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;🤖 Prompt for AI Agents
In
dynamic_grpc_client/src/main/java/com/testsigma/addons/ios/GrpcClientAction.java
around lines 139 to 160, the current implementation only uses
descriptorSet.getFile(0) and builds a FileDescriptor with no dependencies which
breaks when the proto imports types from other files; replace this by loading
all FileDescriptorProto entries from the descriptor set into a Map<String,
DescriptorProtos.FileDescriptorProto>, then iteratively (or topologically) build
Descriptors.FileDescriptor instances by resolving and passing the dependent
Descriptors.FileDescriptor[] for each file (using a cache/map of already-built
FileDescriptors) so imported types are available when you call
findServiceByName/findMethodByName; also add the recommended imports
(java.util.HashMap, java.util.Map, java.util.Objects).
| private Descriptors.MethodDescriptor loadServiceDescriptors(Path descriptorPath, String serviceName, String methodName) throws Exception { | ||
| try (FileInputStream descriptorStream = new FileInputStream(descriptorPath.toFile())) { | ||
| DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(descriptorStream); | ||
| if (descriptorSet.getFileCount() == 0) { | ||
| throw new RuntimeException("Descriptor set is empty. Check your proto file."); | ||
| } | ||
| DescriptorProtos.FileDescriptorProto fileDescriptorProto = descriptorSet.getFile(0); | ||
|
|
||
| Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[]{}); | ||
|
|
||
| Descriptors.ServiceDescriptor serviceDescriptor = fileDescriptor.findServiceByName(serviceName); | ||
| if (serviceDescriptor == null) { | ||
| throw new RuntimeException("Service not found: " + serviceName); | ||
| } | ||
|
|
||
| Descriptors.MethodDescriptor methodDescriptor = serviceDescriptor.findMethodByName(methodName); | ||
| if (methodDescriptor == null) { | ||
| throw new RuntimeException("Method not found: " + methodName); | ||
| } | ||
| return methodDescriptor; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Descriptor loading is incorrect: only first file is used and dependencies are ignored (breaks protos with imports).
This will fail for most real-world protos. Build all FileDescriptors with dependencies and search across them.
Apply this diff to replace the method body:
- private Descriptors.MethodDescriptor loadServiceDescriptors(Path descriptorPath, String serviceName, String methodName) throws Exception {
- try (FileInputStream descriptorStream = new FileInputStream(descriptorPath.toFile())) {
- DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(descriptorStream);
- if (descriptorSet.getFileCount() == 0) {
- throw new RuntimeException("Descriptor set is empty. Check your proto file.");
- }
- DescriptorProtos.FileDescriptorProto fileDescriptorProto = descriptorSet.getFile(0);
-
- Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[]{});
-
- Descriptors.ServiceDescriptor serviceDescriptor = fileDescriptor.findServiceByName(serviceName);
- if (serviceDescriptor == null) {
- throw new RuntimeException("Service not found: " + serviceName);
- }
-
- Descriptors.MethodDescriptor methodDescriptor = serviceDescriptor.findMethodByName(methodName);
- if (methodDescriptor == null) {
- throw new RuntimeException("Method not found: " + methodName);
- }
- return methodDescriptor;
- }
- }
+ private Descriptors.MethodDescriptor loadServiceDescriptors(Path descriptorPath, String serviceName, String methodName) throws Exception {
+ try (FileInputStream descriptorStream = new FileInputStream(descriptorPath.toFile())) {
+ DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(descriptorStream);
+ if (descriptorSet.getFileCount() == 0) {
+ throw new RuntimeException("Descriptor set is empty. Check your proto file.");
+ }
+
+ Map<String, DescriptorProtos.FileDescriptorProto> protoByName = new HashMap<>();
+ for (DescriptorProtos.FileDescriptorProto fdp : descriptorSet.getFileList()) {
+ protoByName.put(fdp.getName(), fdp);
+ }
+
+ Map<String, Descriptors.FileDescriptor> built = new HashMap<>();
+ for (DescriptorProtos.FileDescriptorProto fdp : descriptorSet.getFileList()) {
+ buildFileDescriptor(fdp.getName(), protoByName, built);
+ }
+
+ for (Descriptors.FileDescriptor fd : built.values()) {
+ Descriptors.ServiceDescriptor serviceDescriptor = fd.findServiceByName(serviceName);
+ if (serviceDescriptor != null) {
+ Descriptors.MethodDescriptor methodDescriptor = serviceDescriptor.findMethodByName(methodName);
+ if (methodDescriptor == null) {
+ throw new RuntimeException("Method not found: " + methodName + " in service: " + serviceName);
+ }
+ return methodDescriptor;
+ }
+ }
+ throw new RuntimeException("Service not found: " + serviceName);
+ }
+ }Add this helper and imports elsewhere in the class:
// imports to add
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
// helper to add inside the class
private Descriptors.FileDescriptor buildFileDescriptor(
String name,
Map<String, DescriptorProtos.FileDescriptorProto> protoByName,
Map<String, Descriptors.FileDescriptor> built) throws Descriptors.DescriptorValidationException {
if (built.containsKey(name)) return built.get(name);
DescriptorProtos.FileDescriptorProto fdp = protoByName.get(name);
if (fdp == null) throw new RuntimeException("Missing descriptor proto for dependency: " + name);
List<Descriptors.FileDescriptor> deps = new ArrayList<>();
for (String depName : fdp.getDependencyList()) {
deps.add(buildFileDescriptor(depName, protoByName, built));
}
Descriptors.FileDescriptor fd = Descriptors.FileDescriptor.buildFrom(
fdp, deps.toArray(new Descriptors.FileDescriptor[0]));
built.put(name, fd);
return fd;
}🤖 Prompt for AI Agents
In
dynamic_grpc_client/src/main/java/com/testsigma/addons/restapi/GrpcClientAction.java
around lines 139-160, the current loadServiceDescriptors reads only the first
FileDescriptorProto and ignores imports/dependencies which breaks protos with
dependencies; update the method to parse all FileDescriptorProto entries into a
map, add the provided recursive buildFileDescriptor helper and required imports
(Map, HashMap, List, ArrayList), use the helper to construct
Descriptors.FileDescriptor for each proto respecting dependencies, then search
across the built descriptors for the requested service and method (throwing the
existing RuntimeExceptions if not found), and replace the current method body
with this dependency-aware construction and lookup logic.
| private DynamicMessage performDynamicCall(ManagedChannel channel, | ||
| MethodDescriptor<DynamicMessage, DynamicMessage> method, | ||
| DynamicMessage request) { | ||
| return ClientCalls.blockingUnaryCall(channel, method, CallOptions.DEFAULT, request); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a per-call deadline to prevent indefinite hangs.
Blocking without a deadline can stall the action/thread.
- return ClientCalls.blockingUnaryCall(channel, method, CallOptions.DEFAULT, request);
+ return ClientCalls.blockingUnaryCall(
+ channel, method, CallOptions.DEFAULT.withDeadlineAfter(30, TimeUnit.SECONDS), request);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private DynamicMessage performDynamicCall(ManagedChannel channel, | |
| MethodDescriptor<DynamicMessage, DynamicMessage> method, | |
| DynamicMessage request) { | |
| return ClientCalls.blockingUnaryCall(channel, method, CallOptions.DEFAULT, request); | |
| } | |
| private DynamicMessage performDynamicCall(ManagedChannel channel, | |
| MethodDescriptor<DynamicMessage, DynamicMessage> method, | |
| DynamicMessage request) { | |
| return ClientCalls.blockingUnaryCall( | |
| channel, method, CallOptions.DEFAULT.withDeadlineAfter(30, TimeUnit.SECONDS), request); | |
| } |
🤖 Prompt for AI Agents
In
dynamic_grpc_client/src/main/java/com/testsigma/addons/restapi/GrpcClientAction.java
around lines 174 to 178, the blockingUnaryCall is invoked without a deadline
which can hang indefinitely; modify the call to use a CallOptions instance that
applies a per-call deadline (for example
CallOptions.DEFAULT.withDeadlineAfter(<timeout>, TimeUnit.<UNIT>)) and choose a
sensible default timeout (or make it configurable), then pass that CallOptions
to ClientCalls.blockingUnaryCall so the RPC will fail fast on timeout instead of
blocking forever.
| File protoFile = new File(protoFilePath.getValue().toString()); | ||
| String protoDir = protoFile.getParent(); | ||
| String protoFileName = protoFile.getName(); | ||
|
|
||
| tempDescriptorFile = generateDescriptor(protoDir, protoFileName); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Validate proto file and handle null parent path.
Same as iOS action.
- File protoFile = new File(protoFilePath.getValue().toString());
- String protoDir = protoFile.getParent();
+ File protoFile = new File(protoFilePath.getValue().toString());
+ if (!protoFile.exists()) {
+ throw new java.io.FileNotFoundException("Proto file not found: " + protoFile.getPath());
+ }
+ String protoDir = protoFile.getParent();
+ if (protoDir == null) {
+ protoDir = protoFile.getAbsoluteFile().getParent();
+ }
String protoFileName = protoFile.getName();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| File protoFile = new File(protoFilePath.getValue().toString()); | |
| String protoDir = protoFile.getParent(); | |
| String protoFileName = protoFile.getName(); | |
| tempDescriptorFile = generateDescriptor(protoDir, protoFileName); | |
| File protoFile = new File(protoFilePath.getValue().toString()); | |
| if (!protoFile.exists()) { | |
| throw new java.io.FileNotFoundException("Proto file not found: " + protoFile.getPath()); | |
| } | |
| String protoDir = protoFile.getParent(); | |
| if (protoDir == null) { | |
| protoDir = protoFile.getAbsoluteFile().getParent(); | |
| } | |
| String protoFileName = protoFile.getName(); | |
| tempDescriptorFile = generateDescriptor(protoDir, protoFileName); |
🤖 Prompt for AI Agents
In
dynamic_grpc_client/src/main/java/com/testsigma/addons/web/GrpcClientAction.java
around lines 68 to 73, validate protoFilePath and the File object before using
getParent(): ensure protoFilePath.getValue() is non-null and points to an
existing file (throw or log a clear error if missing or not a file), compute
protoDir defensively by using protoFile.getParent() and if that returns null
fall back to protoFile.getAbsoluteFile().getParent() and finally to "." (or an
explicit temp directory) so you never pass a null directory to
generateDescriptor, and keep protoFileName = protoFile.getName() as before.
| private Descriptors.MethodDescriptor loadServiceDescriptors(Path descriptorPath, String serviceName, String methodName) throws Exception { | ||
| try (FileInputStream descriptorStream = new FileInputStream(descriptorPath.toFile())) { | ||
| DescriptorProtos.FileDescriptorSet descriptorSet = DescriptorProtos.FileDescriptorSet.parseFrom(descriptorStream); | ||
| if (descriptorSet.getFileCount() == 0) { | ||
| throw new RuntimeException("Descriptor set is empty. Check your proto file."); | ||
| } | ||
| DescriptorProtos.FileDescriptorProto fileDescriptorProto = descriptorSet.getFile(0); | ||
|
|
||
| Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fileDescriptorProto, new Descriptors.FileDescriptor[]{}); | ||
|
|
||
| Descriptors.ServiceDescriptor serviceDescriptor = fileDescriptor.findServiceByName(serviceName); | ||
| if (serviceDescriptor == null) { | ||
| throw new RuntimeException("Service not found: " + serviceName); | ||
| } | ||
|
|
||
| Descriptors.MethodDescriptor methodDescriptor = serviceDescriptor.findMethodByName(methodName); | ||
| if (methodDescriptor == null) { | ||
| throw new RuntimeException("Method not found: " + methodName); | ||
| } | ||
| return methodDescriptor; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Resolve descriptor dependencies; first-file-only approach is fragile.
Mirror the iOS fix to support imports.
Apply the same method body as suggested for the iOS file and add imports:
- java.util.Map, HashMap, Objects
| @@ -0,0 +1 @@ | |||
| testsigma-sdk.api.key=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyMjMyMmM2Ni04NWYzLWIyN2UtN2FiOS0zM2U2M2Q4OWM1MGIiLCJ1bmlxdWVJZCI6IjUzOTkiLCJpZGVudGl0eUFjY291bnRVVUlkIjoiNDMifQ.ExHn2NqMjR0VelPGawCDpn9g530QTzrrmL4OnnGl26R76fqJcSWS6PFPWp9hQj4zLoxKxY3in-u8oBz_hTRKSQ No newline at end of file | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Remove committed JWT immediately; rotate the key.
A real JWT secret is committed under main/resources and will be shipped in the JAR. This is a critical secret leak and a compliance risk.
Apply this diff to remove the hardcoded secret (prefer deleting the file entirely or moving to src/test/resources with a placeholder; load via env/CI secrets instead):
-testsigma-sdk.api.key=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyMjMyMmM2Ni04NWYzLWIyN2UtN2FiOS0zM2U2M2Q4OWM1MGIiLCJ1bmlxdWVJZCI6IjUzOTkiLCJpZGVudGl0eUFjY291bnRVVUlkIjoiNDMifQ.ExHn2NqMjR0VelPGawCDpn9g530QTzrrmL4OnnGl26R76fqJcSWS6PFPWp9hQj4zLoxKxY3in-u8oBz_hTRKSQRun to locate other exposures:
Follow-ups:
- Revoke/rotate the leaked token server-side.
- Purge from git history if needed and add the file/path to .gitignore.
- Use env vars or Maven filtering with CI-injected secrets; never commit real tokens.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C2 -i 'testsigma-sdk\.api\.key|eyJhbGciOiJIUz' || trueLength of output: 45674
Remove all committed JWTs immediately — revoke/rotate the keys now.
Search output shows testsigma-sdk.api.key present in dozens of src/main/resources/testsigma-sdk.properties files across the repo (including dynamic_grpc_client/src/main/resources/testsigma-sdk.properties). These are live JWTs that will be packaged in JARs — critical secret leak.
Actions (urgent):
- Remove the hardcoded keys from all src/main/resources/testsigma-sdk.properties (delete or move to src/test/resources and use a placeholder).
- Revoke/rotate every leaked token on the issuing service immediately.
- Purge the secrets from git history if any leaked token was active (git filter-repo or BFG) and coordinate with security/ops.
- Stop committing secrets: load keys from env/CI secrets (Maven/Gradle filtering, Vault, GitHub/GitLab secrets) and add the files/paths to .gitignore.
- Add automated secret scanning (pre-commit hook/CI) to prevent recurrence.
Find remaining occurrences:
rg -n -S 'testsigma-sdk.api.key|eyJhbGciOiJIUz'
🧰 Tools
🪛 Gitleaks (8.28.0)
[high] 1-1: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.
(jwt)
🤖 Prompt for AI Agents
dynamic_grpc_client/src/main/resources/testsigma-sdk.properties lines 1-1
contain a hardcoded JWT secret; remove this secret immediately from the file and
replace it with a non-secret placeholder (or move the file to src/test/resources
for tests), update code to load the real key from environment/CI secret
management (e.g., env var, Maven/Gradle filtered property, or Vault), add the
properties path to .gitignore to prevent future commits, coordinate with
security/ops to revoke/rotate the leaked token and purge it from git history
(git filter-repo or BFG), and enable automated secret scanning (pre-commit
hook/CI) to block similar commits going forward.
Addon Name: Dynamic gRPC Client
Jarvis Link: https://jarvis.testsigma.com/ui/tenants/3072/addons
Jira : https://testsigma.atlassian.net/browse/TE-29598
Added Dynamic gRPC Client Action with JSON Request Support
Summary by CodeRabbit
New Features
Chores