-
Notifications
You must be signed in to change notification settings - Fork 4
Update qa tests to v1.0.12 #280
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
base: main
Are you sure you want to change the base?
Changes from all commits
632062a
6698e27
2b37ebf
e2e233e
8e49f49
afeba2d
7177e50
64b2cc2
cdc06ae
b595c3e
31e9c70
7caa870
2364aea
49c169d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||||||
| import java.lang.reflect.Executable; | ||||||||||
| import java.lang.reflect.InvocationTargetException; | ||||||||||
| import java.lang.reflect.Method; | ||||||||||
| import java.net.IDN; | ||||||||||
| import java.net.InetAddress; | ||||||||||
| import java.net.MalformedURLException; | ||||||||||
| import java.net.URL; | ||||||||||
|
|
@@ -32,6 +33,29 @@ public static class InetAdvice { | |||||||||
| // Since we have to wrap a native Java Class stuff gets more complicated | ||||||||||
| // The classpath is not the same anymore, and we can't import our modules directly. | ||||||||||
| // To bypass this issue we load collectors from a .jar file | ||||||||||
|
|
||||||||||
| // Java's system resolver rejects non-ASCII hostnames, so convert IDN to Punycode | ||||||||||
| // before the real lookup runs. Without this, getAllByName throws UnknownHostException | ||||||||||
| // and OnMethodExit never fires β meaning DNSRecordCollector can't block or track | ||||||||||
| // the hostname. | ||||||||||
| @Advice.OnMethodEnter(suppress = Throwable.class) | ||||||||||
| public static void before( | ||||||||||
| @Advice.Argument(value = 0, readOnly = false) String hostname | ||||||||||
| ) { | ||||||||||
| if (hostname == null) { | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| for (int i = 0; i < hostname.length(); i++) { | ||||||||||
| if (hostname.charAt(i) > 0x7F) { | ||||||||||
| try { | ||||||||||
| hostname = IDN.toASCII(hostname, IDN.ALLOW_UNASSIGNED); | ||||||||||
| } catch (IllegalArgumentException ignored) { | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty catch of IllegalArgumentException in the hostname normalization block silently swallows errors; add logging or explicit handling so failures are observable.
Suggested change
Details⨠AI Reasoning Reply |
||||||||||
| } | ||||||||||
| return; | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| @Advice.OnMethodExit | ||||||||||
| public static void after( | ||||||||||
| @Advice.Argument(0) String hostname, | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||||||||
| package dev.aikido.agent_api.collectors; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import dev.aikido.agent_api.context.Context; | ||||||||||||||||||||||||||||||||||||
| import dev.aikido.agent_api.storage.BypassedContextStore; | ||||||||||||||||||||||||||||||||||||
| import dev.aikido.agent_api.storage.HostnamesStore; | ||||||||||||||||||||||||||||||||||||
| import dev.aikido.agent_api.storage.PendingHostnamesStore; | ||||||||||||||||||||||||||||||||||||
| import dev.aikido.agent_api.storage.ServiceConfigStore; | ||||||||||||||||||||||||||||||||||||
|
|
@@ -34,20 +35,24 @@ public static void report(String hostname, InetAddress[] inetAddresses) { | |||||||||||||||||||||||||||||||||||
| // store stats | ||||||||||||||||||||||||||||||||||||
| StatisticsStore.registerCall("java.net.InetAddress.getAllByName", OperationKind.OUTGOING_HTTP_OP); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| boolean bypassed = BypassedContextStore.isBypassed(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Consume pending ports recorded by URLCollector for this hostname. | ||||||||||||||||||||||||||||||||||||
| // Removing them here ensures each (hostname, port) pair is counted exactly once. | ||||||||||||||||||||||||||||||||||||
| Set<Integer> ports = PendingHostnamesStore.getAndRemove(hostname); | ||||||||||||||||||||||||||||||||||||
| if (!ports.isEmpty()) { | ||||||||||||||||||||||||||||||||||||
| for (int port : ports) { | ||||||||||||||||||||||||||||||||||||
| HostnamesStore.incrementHits(hostname, port); | ||||||||||||||||||||||||||||||||||||
| if (!bypassed) { | ||||||||||||||||||||||||||||||||||||
| // Bypassed IPs are trusted β don't report their outbound hostnames in heartbeats. | ||||||||||||||||||||||||||||||||||||
| if (!ports.isEmpty()) { | ||||||||||||||||||||||||||||||||||||
| for (int port : ports) { | ||||||||||||||||||||||||||||||||||||
| HostnamesStore.incrementHits(hostname, port); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| HostnamesStore.incrementHits(hostname, 0); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+43
to
51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The if (!bypassed) block in report adds nesting around the main hostname-hit logic; invert the condition or use an early return/guard to flatten the method for clarity. Show fix
Suggested change
Details⨠AI Reasoning Reply |
||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| // We still need to report a hit to the hostname for outbound domain blocking | ||||||||||||||||||||||||||||||||||||
| HostnamesStore.incrementHits(hostname, 0); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Block if the hostname is in the blocked domains list | ||||||||||||||||||||||||||||||||||||
| if (ServiceConfigStore.shouldBlockOutgoingRequest(hostname)) { | ||||||||||||||||||||||||||||||||||||
| if (ServiceConfigStore.shouldBlockOutgoingRequest(hostname) && !bypassed) { | ||||||||||||||||||||||||||||||||||||
| logger.debug("Blocking DNS lookup for domain: %s", hostname); | ||||||||||||||||||||||||||||||||||||
| throw BlockedOutboundException.get(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package dev.aikido.agent_api.storage; | ||
|
|
||
| /** | ||
| * Thread-local flag recording whether the current request's remote IP is in the bypass list. | ||
| * Needed because bypassed requests intentionally do not set a context, but for Stored SSRF we still want to skip. | ||
| */ | ||
| public final class BypassedContextStore { | ||
| private BypassedContextStore() {} | ||
|
|
||
| private static final ThreadLocal<Boolean> store = ThreadLocal.withInitial(() -> false); | ||
|
|
||
| public static void setBypassed(boolean bypassed) { | ||
| store.set(bypassed); | ||
| } | ||
|
|
||
| public static boolean isBypassed() { | ||
| return store.get(); | ||
| } | ||
|
|
||
| public static void clear() { | ||
| store.remove(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package dev.aikido.agent_api.vulnerabilities; | ||
|
|
||
| public class DangerousBodyException extends AikidoException { | ||
| public DangerousBodyException(String reason) { | ||
| super(generateDefaultMessage("Dangerous Body") + ": " + reason); | ||
| } | ||
|
|
||
| public static DangerousBodyException jwtTooLarge() { | ||
| return new DangerousBodyException("JWT payload too large"); | ||
| } | ||
|
|
||
| public static DangerousBodyException bodyTooDeep() { | ||
| return new DangerousBodyException("Body is too deeply nested to scan"); | ||
| } | ||
| } |
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.
Converting and replacing the original 'hostname' argument (IDN.toASCII) before lookup can obscure the original domain and enable bypasses. Preserve or log the original hostname (or avoid in-place mutation) so downstream checks see the true input.
Details
β¨ AI Reasoning
βThe change adds an entry advice that converts non-ASCII hostnames to ASCII (Punycode) before DNS lookup. Transforming inputs prior to resolution can hide the original domain representation from later inspection (e.g., logs or collectors) and may be used to bypass domain-based detection or allowlist checks. The transformation is done silently (catching IllegalArgumentException and returning) and replaces the hostname argument in place, which can obscure the original value from downstream code. This behavior changes how hostnames are presented to the rest of the system and therefore can degrade transparency and enable hiding malicious domains.
π§ How do I fix it?
Ensure code is transparent and not intentionally obfuscated. Avoid hiding functionality from code review. Focus on intent and deception, not specific patterns.
Reply
@AikidoSec feedback: [FEEDBACK]to get better review comments in the future.Reply
@AikidoSec ignore: [REASON]to ignore this issue.More info