Add SSRF detector (#643)

Server Side Request Forgery (SSRF) attacks occur when requests to
destinations based on untrusted data are executed. This could lead to
exfiltration of sensitive data or exposure of internal services.

By default, all network connections are reported as a medium severity 
security issues. The new `BugDetectors#allowNetworkConnections`
method provides the means to configure this on per-fuzz test basis, or,
using try-with-resources, on a per-scope basis.

Checks are implemented via `java.net.Socket.connect` and
`java.nio.channels.SocketChannel.connect` methods.

Co-authored-by: psy <patrice.salathe@code-intelligence.com>
Co-authored-by: Fabian Meumertzheim <meumertzheim@code-intelligence.com>
diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl
index f033caa..62c7a7b 100644
--- a/bazel/fuzz_target.bzl
+++ b/bazel/fuzz_target.bzl
@@ -57,6 +57,7 @@
         deps = deps,
         runtime_deps = runtime_deps,
         testonly = True,
+        tags = tags,
         **kwargs
     )
 
@@ -68,6 +69,7 @@
             name = target_name + "_import",
             jars = [target_deploy_jar],
             testonly = True,
+            tags = tags,
         )
         target_with_driver_name = target_name + "_driver"
         native.java_binary(
@@ -78,6 +80,7 @@
             ],
             main_class = "com.code_intelligence.jazzer.Jazzer",
             testonly = True,
+            tags = tags,
         )
 
     if launcher_variant == "native":
diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel
index 10d6e51..703f283 100644
--- a/examples/BUILD.bazel
+++ b/examples/BUILD.bazel
@@ -417,11 +417,31 @@
     ],
 )
 
+java_fuzz_target_test(
+    name = "BatikTranscoderFuzzer",
+    srcs = [
+        "src/main/java/com/example/BatikTranscoderFuzzer.java",
+    ],
+    allowed_findings = [
+        "com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium",
+    ],
+    target_class = "com.example.BatikTranscoderFuzzer",
+    verify_crash_reproducer = False,
+    deps = [
+        "@maven//:org_apache_xmlgraphics_batik_anim",
+        "@maven//:org_apache_xmlgraphics_batik_bridge",
+        "@maven//:org_apache_xmlgraphics_batik_css",
+        "@maven//:org_apache_xmlgraphics_batik_transcoder",
+        "@maven//:org_apache_xmlgraphics_batik_util",
+    ],
+)
+
 java_binary(
     name = "examples",
     create_executable = False,
     visibility = ["//visibility:public"],
     runtime_deps = [
+        ":BatikTranscoderFuzzer_target_deploy.jar",
         ":ExampleFuzzer_target_deploy.jar",
         ":ExampleValueProfileFuzzer_target_deploy.jar",
         ":FastJsonFuzzer_target_deploy.jar",
diff --git a/examples/src/main/java/com/example/BatikTranscoderFuzzer.java b/examples/src/main/java/com/example/BatikTranscoderFuzzer.java
new file mode 100644
index 0000000..cdd2216
--- /dev/null
+++ b/examples/src/main/java/com/example/BatikTranscoderFuzzer.java
@@ -0,0 +1,44 @@
+// Copyright 2023 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.io.*;
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.JPEGTranscoder;
+
+public class BatikTranscoderFuzzer {
+  public static void fuzzerTestOneInput(FuzzedDataProvider data) throws IOException {
+    String host = data.consumeRemainingAsString();
+
+    byte[] svg =
+        ("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" >\n"
+            + "<image width=\"50\" height=\"50\" xlink:href=\"https://" + host + "/\"></image>\n"
+            + "</svg>")
+            .getBytes();
+
+    // Convert SVG to JPEG
+    try {
+      JPEGTranscoder transcoder = new JPEGTranscoder();
+      TranscoderInput input = new TranscoderInput(new ByteArrayInputStream(svg));
+      TranscoderOutput output = new TranscoderOutput(new ByteArrayOutputStream());
+      transcoder.transcode(input, output);
+    } catch (TranscoderException | IllegalArgumentException e) {
+      // Ignored
+    }
+  }
+}
diff --git a/maven.bzl b/maven.bzl
index 3a2adcd..815b6bb 100644
--- a/maven.bzl
+++ b/maven.bzl
@@ -54,6 +54,11 @@
     "org.opentest4j:opentest4j:jar:1.2.0",
     "org.assertj:assertj-core:jar:3.23.1",
     "org.mockito:mockito-core:5.2.0",
+    "org.apache.xmlgraphics:batik-bridge:1.14",
+    "org.apache.xmlgraphics:batik-util:1.14",
+    "org.apache.xmlgraphics:batik-anim:1.14",
+    "org.apache.xmlgraphics:batik-transcoder:1.14",
+    "org.apache.xmlgraphics:batik-css:1.14",
     maven.artifact("org.apache.logging.log4j", "log4j-api", "2.14.1", testonly = True),
     maven.artifact("org.apache.logging.log4j", "log4j-core", "2.14.1", testonly = True),
     maven.artifact("com.h2database", "h2", "2.1.212", testonly = True),
diff --git a/maven_install.json b/maven_install.json
index 8bcacfd..efc0828 100644
--- a/maven_install.json
+++ b/maven_install.json
@@ -1,7 +1,7 @@
 {
   "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
-  "__INPUT_ARTIFACTS_HASH": 1509694585,
-  "__RESOLVED_ARTIFACTS_HASH": 365471450,
+  "__INPUT_ARTIFACTS_HASH": 25727711,
+  "__RESOLVED_ARTIFACTS_HASH": 1912979319,
   "conflict_resolution": {
     "junit:junit:4.12": "junit:junit:4.13.2"
   },
@@ -144,6 +144,18 @@
       },
       "version": "6.0.3"
     },
+    "commons-io:commons-io": {
+      "shasums": {
+        "jar": "3307319ddc221f1b23e8a1445aef10d2d2308e0ec46977b3f17cbb15c0ef335b"
+      },
+      "version": "1.3.1"
+    },
+    "commons-logging:commons-logging": {
+      "shasums": {
+        "jar": "e94af49749384c11f5aa50e8d0f5fe679be771295b52030338d32843c980351e"
+      },
+      "version": "1.0.4"
+    },
     "javax.activation:javax.activation-api": {
       "shasums": {
         "jar": "43fdef0b5b6ceb31b0424b208b930c74ab58fac2ceeb7b3f6fd3aeb8b5ca4393"
@@ -216,6 +228,114 @@
       },
       "version": "2.14.1"
     },
+    "org.apache.xmlgraphics:batik-anim": {
+      "shasums": {
+        "jar": "a1953099e04c202ee32d8e13912326d15cc488538051c691e897b0b7d2523b40"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-awt-util": {
+      "shasums": {
+        "jar": "9cbaeae98dacad502aa2b08f206a5c04f14703afe9d99d905f0f7f8b733db5e7"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-bridge": {
+      "shasums": {
+        "jar": "fc137699f14f9289732d4ff8214f0a14a7ea4f5ea7a6b24b745d9d6d943c259e"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-constants": {
+      "shasums": {
+        "jar": "7882eb789257905413bcb0adcb1562dd50b8103cadef9534c33612bf51527990"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-css": {
+      "shasums": {
+        "jar": "968ba271cab6dfdd0458eb9ff42cc51e258d471499225b9063edbda61becbe17"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-dom": {
+      "shasums": {
+        "jar": "6b71b91514f2cbe9b9e46f9699803c1fe7434addf61388b07755d586e20c57de"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-ext": {
+      "shasums": {
+        "jar": "1f74fc638058c5d05b6da7b207e895d1a04c19d64250ae9f52c059689e681a7a"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-gvt": {
+      "shasums": {
+        "jar": "5230e4339035867bbb9c82683a065fb2abe135779f57c43a70b7766395aeab38"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-i18n": {
+      "shasums": {
+        "jar": "fb1ad02ccaa36f5a60c4115316e15ac071386f96445e5d89bdad0c7e45da9560"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-parser": {
+      "shasums": {
+        "jar": "aca5e08b52e54af0a1acaf9c134082a0ed0b357b8eef74de369bdba054d1e1e2"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-script": {
+      "shasums": {
+        "jar": "d3584655227b4f1b56beea081a5b0b1fa228aeb165f19895f10dfbae999bc4e4"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-shared-resources": {
+      "shasums": {
+        "jar": "987ceb566e2101418465bb3227ec60812b71e7c4b2c3e842d977efa732fa90f5"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-svg-dom": {
+      "shasums": {
+        "jar": "c6023eca4fe1c6c2616173d2308217713c76895b780167f19382db0e57eac412"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-svggen": {
+      "shasums": {
+        "jar": "c2a7fba84eddc3815992a1f14f554feeccaabf9c1730867b28569dcbbbc272e2"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-transcoder": {
+      "shasums": {
+        "jar": "c5f863137fbfe440911b376a0a3e605c9e1b196d1b4a83854007e303a95b9889"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-util": {
+      "shasums": {
+        "jar": "ad76103ecb3fcad91aac1cd0a34f6d46cd1e4224d0406a5d58b269a64f6c3788"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:batik-xml": {
+      "shasums": {
+        "jar": "673a82f56185a023e5196a59eb16f8503071477af4a82bb58e7267bb3d4ff828"
+      },
+      "version": "1.14"
+    },
+    "org.apache.xmlgraphics:xmlgraphics-commons": {
+      "shasums": {
+        "jar": "25f21c93462d767d05e340f1dc754862995b9bf8b4618ab5b07cd703d400d413"
+      },
+      "version": "2.6"
+    },
     "org.apiguardian:apiguardian-api": {
       "shasums": {
         "jar": "b509448ac506d607319f182537f0b35d71007582ec741832a1f111e5b5b70b38"
@@ -395,6 +515,30 @@
         "jar": "aabf9bd23091a4ebfc109c1f3ee7cf3e4b89f6ba2d3f51c5243f16b3cffae011"
       },
       "version": "9.2"
+    },
+    "xalan:serializer": {
+      "shasums": {
+        "jar": "e8f5b4340d3b12a0cfa44ac2db4be4e0639e479ae847df04c4ed8b521734bb4a"
+      },
+      "version": "2.7.2"
+    },
+    "xalan:xalan": {
+      "shasums": {
+        "jar": "a44bd80e82cb0f4cfac0dac8575746223802514e3cec9dc75235bc0de646af14"
+      },
+      "version": "2.7.2"
+    },
+    "xml-apis:xml-apis": {
+      "shasums": {
+        "jar": "a840968176645684bb01aed376e067ab39614885f9eee44abe35a5f20ebe7fad"
+      },
+      "version": "1.4.01"
+    },
+    "xml-apis:xml-apis-ext": {
+      "shasums": {
+        "jar": "d0b4887dc34d57de49074a58affad439a013d0baffa1a8034f8ef2a5ea191646"
+      },
+      "version": "1.3.04"
     }
   },
   "dependencies": {
@@ -455,6 +599,119 @@
     "org.apache.logging.log4j:log4j-core": [
       "org.apache.logging.log4j:log4j-api"
     ],
+    "org.apache.xmlgraphics:batik-anim": [
+      "org.apache.xmlgraphics:batik-awt-util",
+      "org.apache.xmlgraphics:batik-css",
+      "org.apache.xmlgraphics:batik-dom",
+      "org.apache.xmlgraphics:batik-ext",
+      "org.apache.xmlgraphics:batik-parser",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-svg-dom",
+      "org.apache.xmlgraphics:batik-util",
+      "xml-apis:xml-apis-ext"
+    ],
+    "org.apache.xmlgraphics:batik-awt-util": [
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-util",
+      "org.apache.xmlgraphics:xmlgraphics-commons"
+    ],
+    "org.apache.xmlgraphics:batik-bridge": [
+      "org.apache.xmlgraphics:batik-anim",
+      "org.apache.xmlgraphics:batik-awt-util",
+      "org.apache.xmlgraphics:batik-css",
+      "org.apache.xmlgraphics:batik-dom",
+      "org.apache.xmlgraphics:batik-gvt",
+      "org.apache.xmlgraphics:batik-parser",
+      "org.apache.xmlgraphics:batik-script",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-svg-dom",
+      "org.apache.xmlgraphics:batik-util",
+      "org.apache.xmlgraphics:batik-xml",
+      "org.apache.xmlgraphics:xmlgraphics-commons",
+      "xml-apis:xml-apis-ext"
+    ],
+    "org.apache.xmlgraphics:batik-constants": [
+      "org.apache.xmlgraphics:batik-shared-resources"
+    ],
+    "org.apache.xmlgraphics:batik-css": [
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-util",
+      "org.apache.xmlgraphics:xmlgraphics-commons",
+      "xml-apis:xml-apis-ext"
+    ],
+    "org.apache.xmlgraphics:batik-dom": [
+      "org.apache.xmlgraphics:batik-css",
+      "org.apache.xmlgraphics:batik-ext",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-util",
+      "org.apache.xmlgraphics:batik-xml",
+      "xalan:xalan",
+      "xml-apis:xml-apis",
+      "xml-apis:xml-apis-ext"
+    ],
+    "org.apache.xmlgraphics:batik-ext": [
+      "org.apache.xmlgraphics:batik-shared-resources"
+    ],
+    "org.apache.xmlgraphics:batik-gvt": [
+      "org.apache.xmlgraphics:batik-awt-util",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-util"
+    ],
+    "org.apache.xmlgraphics:batik-i18n": [
+      "org.apache.xmlgraphics:batik-shared-resources"
+    ],
+    "org.apache.xmlgraphics:batik-parser": [
+      "org.apache.xmlgraphics:batik-awt-util",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-util",
+      "org.apache.xmlgraphics:batik-xml",
+      "xml-apis:xml-apis-ext"
+    ],
+    "org.apache.xmlgraphics:batik-script": [
+      "org.apache.xmlgraphics:batik-anim",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-util"
+    ],
+    "org.apache.xmlgraphics:batik-svg-dom": [
+      "org.apache.xmlgraphics:batik-awt-util",
+      "org.apache.xmlgraphics:batik-css",
+      "org.apache.xmlgraphics:batik-dom",
+      "org.apache.xmlgraphics:batik-ext",
+      "org.apache.xmlgraphics:batik-parser",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-util",
+      "xml-apis:xml-apis-ext"
+    ],
+    "org.apache.xmlgraphics:batik-svggen": [
+      "org.apache.xmlgraphics:batik-awt-util",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-util"
+    ],
+    "org.apache.xmlgraphics:batik-transcoder": [
+      "org.apache.xmlgraphics:batik-anim",
+      "org.apache.xmlgraphics:batik-awt-util",
+      "org.apache.xmlgraphics:batik-bridge",
+      "org.apache.xmlgraphics:batik-dom",
+      "org.apache.xmlgraphics:batik-gvt",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-svggen",
+      "org.apache.xmlgraphics:batik-util",
+      "org.apache.xmlgraphics:batik-xml",
+      "xml-apis:xml-apis-ext"
+    ],
+    "org.apache.xmlgraphics:batik-util": [
+      "org.apache.xmlgraphics:batik-constants",
+      "org.apache.xmlgraphics:batik-i18n",
+      "org.apache.xmlgraphics:batik-shared-resources"
+    ],
+    "org.apache.xmlgraphics:batik-xml": [
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-util"
+    ],
+    "org.apache.xmlgraphics:xmlgraphics-commons": [
+      "commons-io:commons-io",
+      "commons-logging:commons-logging"
+    ],
     "org.assertj:assertj-core": [
       "net.bytebuddy:byte-buddy"
     ],
@@ -533,6 +790,12 @@
     ],
     "org.ow2.asm:asm-tree": [
       "org.ow2.asm:asm"
+    ],
+    "xalan:serializer": [
+      "xml-apis:xml-apis"
+    ],
+    "xalan:xalan": [
+      "xalan:serializer"
     ]
   },
   "packages": {
@@ -775,6 +1038,16 @@
       "com.unboundid.util.ssl",
       "com.unboundid.util.ssl.cert"
     ],
+    "commons-io:commons-io": [
+      "org.apache.commons.io",
+      "org.apache.commons.io.filefilter",
+      "org.apache.commons.io.input",
+      "org.apache.commons.io.output"
+    ],
+    "commons-logging:commons-logging": [
+      "org.apache.commons.logging",
+      "org.apache.commons.logging.impl"
+    ],
     "javax.activation:javax.activation-api": [
       "javax.activation"
     ],
@@ -1052,6 +1325,136 @@
       "org.apache.logging.log4j.core.util",
       "org.apache.logging.log4j.core.util.datetime"
     ],
+    "org.apache.xmlgraphics:batik-anim": [
+      "org.apache.batik.anim",
+      "org.apache.batik.anim.dom",
+      "org.apache.batik.anim.timing",
+      "org.apache.batik.anim.values"
+    ],
+    "org.apache.xmlgraphics:batik-awt-util": [
+      "org.apache.batik.ext.awt",
+      "org.apache.batik.ext.awt.color",
+      "org.apache.batik.ext.awt.font",
+      "org.apache.batik.ext.awt.g2d",
+      "org.apache.batik.ext.awt.geom",
+      "org.apache.batik.ext.awt.image",
+      "org.apache.batik.ext.awt.image.renderable",
+      "org.apache.batik.ext.awt.image.rendered",
+      "org.apache.batik.ext.awt.image.spi",
+      "org.apache.batik.ext.swing"
+    ],
+    "org.apache.xmlgraphics:batik-bridge": [
+      "org.apache.batik.bridge",
+      "org.apache.batik.bridge.svg12"
+    ],
+    "org.apache.xmlgraphics:batik-constants": [
+      "org.apache.batik.constants"
+    ],
+    "org.apache.xmlgraphics:batik-css": [
+      "org.apache.batik.css.dom",
+      "org.apache.batik.css.engine",
+      "org.apache.batik.css.engine.sac",
+      "org.apache.batik.css.engine.value",
+      "org.apache.batik.css.engine.value.css2",
+      "org.apache.batik.css.engine.value.svg",
+      "org.apache.batik.css.engine.value.svg12",
+      "org.apache.batik.css.parser"
+    ],
+    "org.apache.xmlgraphics:batik-dom": [
+      "org.apache.batik.dom",
+      "org.apache.batik.dom.events",
+      "org.apache.batik.dom.traversal",
+      "org.apache.batik.dom.util",
+      "org.apache.batik.dom.xbl"
+    ],
+    "org.apache.xmlgraphics:batik-ext": [
+      "org.apache.batik.w3c.dom",
+      "org.apache.batik.w3c.dom.events"
+    ],
+    "org.apache.xmlgraphics:batik-gvt": [
+      "org.apache.batik.gvt",
+      "org.apache.batik.gvt.event",
+      "org.apache.batik.gvt.filter",
+      "org.apache.batik.gvt.flow",
+      "org.apache.batik.gvt.font",
+      "org.apache.batik.gvt.renderer",
+      "org.apache.batik.gvt.text"
+    ],
+    "org.apache.xmlgraphics:batik-i18n": [
+      "org.apache.batik.i18n"
+    ],
+    "org.apache.xmlgraphics:batik-parser": [
+      "org.apache.batik.parser"
+    ],
+    "org.apache.xmlgraphics:batik-script": [
+      "org.apache.batik.script",
+      "org.apache.batik.script.jpython",
+      "org.apache.batik.script.rhino"
+    ],
+    "org.apache.xmlgraphics:batik-svg-dom": [
+      "org.apache.batik.dom.svg",
+      "org.apache.batik.dom.svg12"
+    ],
+    "org.apache.xmlgraphics:batik-svggen": [
+      "org.apache.batik.svggen",
+      "org.apache.batik.svggen.font",
+      "org.apache.batik.svggen.font.table"
+    ],
+    "org.apache.xmlgraphics:batik-transcoder": [
+      "org.apache.batik.transcoder",
+      "org.apache.batik.transcoder.image",
+      "org.apache.batik.transcoder.image.resources",
+      "org.apache.batik.transcoder.keys",
+      "org.apache.batik.transcoder.print",
+      "org.apache.batik.transcoder.svg2svg",
+      "org.apache.batik.transcoder.wmf",
+      "org.apache.batik.transcoder.wmf.tosvg"
+    ],
+    "org.apache.xmlgraphics:batik-util": [
+      "org.apache.batik",
+      "org.apache.batik.util",
+      "org.apache.batik.util.io",
+      "org.apache.batik.util.resources"
+    ],
+    "org.apache.xmlgraphics:batik-xml": [
+      "org.apache.batik.xml"
+    ],
+    "org.apache.xmlgraphics:xmlgraphics-commons": [
+      "org.apache.xmlgraphics.fonts",
+      "org.apache.xmlgraphics.image",
+      "org.apache.xmlgraphics.image.codec.png",
+      "org.apache.xmlgraphics.image.codec.tiff",
+      "org.apache.xmlgraphics.image.codec.util",
+      "org.apache.xmlgraphics.image.loader",
+      "org.apache.xmlgraphics.image.loader.cache",
+      "org.apache.xmlgraphics.image.loader.impl",
+      "org.apache.xmlgraphics.image.loader.impl.imageio",
+      "org.apache.xmlgraphics.image.loader.pipeline",
+      "org.apache.xmlgraphics.image.loader.spi",
+      "org.apache.xmlgraphics.image.loader.util",
+      "org.apache.xmlgraphics.image.rendered",
+      "org.apache.xmlgraphics.image.writer",
+      "org.apache.xmlgraphics.image.writer.imageio",
+      "org.apache.xmlgraphics.image.writer.internal",
+      "org.apache.xmlgraphics.io",
+      "org.apache.xmlgraphics.java2d",
+      "org.apache.xmlgraphics.java2d.color",
+      "org.apache.xmlgraphics.java2d.color.profile",
+      "org.apache.xmlgraphics.java2d.ps",
+      "org.apache.xmlgraphics.ps",
+      "org.apache.xmlgraphics.ps.dsc",
+      "org.apache.xmlgraphics.ps.dsc.events",
+      "org.apache.xmlgraphics.ps.dsc.tools",
+      "org.apache.xmlgraphics.util",
+      "org.apache.xmlgraphics.util.dijkstra",
+      "org.apache.xmlgraphics.util.i18n",
+      "org.apache.xmlgraphics.util.io",
+      "org.apache.xmlgraphics.util.uri",
+      "org.apache.xmlgraphics.xmp",
+      "org.apache.xmlgraphics.xmp.merge",
+      "org.apache.xmlgraphics.xmp.schemas",
+      "org.apache.xmlgraphics.xmp.schemas.pdf"
+    ],
     "org.apiguardian:apiguardian-api": [
       "org.apiguardian.api"
     ],
@@ -1495,6 +1898,99 @@
     ],
     "org.ow2.asm:asm-tree": [
       "org.objectweb.asm.tree"
+    ],
+    "xalan:serializer": [
+      "org.apache.xml.serializer",
+      "org.apache.xml.serializer.dom3",
+      "org.apache.xml.serializer.utils"
+    ],
+    "xalan:xalan": [
+      "java_cup.runtime",
+      "org.apache.bcel",
+      "org.apache.bcel.classfile",
+      "org.apache.bcel.generic",
+      "org.apache.bcel.util",
+      "org.apache.bcel.verifier",
+      "org.apache.bcel.verifier.exc",
+      "org.apache.bcel.verifier.statics",
+      "org.apache.bcel.verifier.structurals",
+      "org.apache.regexp",
+      "org.apache.xalan",
+      "org.apache.xalan.client",
+      "org.apache.xalan.extensions",
+      "org.apache.xalan.lib",
+      "org.apache.xalan.lib.sql",
+      "org.apache.xalan.processor",
+      "org.apache.xalan.res",
+      "org.apache.xalan.serialize",
+      "org.apache.xalan.templates",
+      "org.apache.xalan.trace",
+      "org.apache.xalan.transformer",
+      "org.apache.xalan.xslt",
+      "org.apache.xalan.xsltc",
+      "org.apache.xalan.xsltc.cmdline",
+      "org.apache.xalan.xsltc.cmdline.getopt",
+      "org.apache.xalan.xsltc.compiler",
+      "org.apache.xalan.xsltc.compiler.util",
+      "org.apache.xalan.xsltc.dom",
+      "org.apache.xalan.xsltc.runtime",
+      "org.apache.xalan.xsltc.runtime.output",
+      "org.apache.xalan.xsltc.trax",
+      "org.apache.xalan.xsltc.util",
+      "org.apache.xml.dtm",
+      "org.apache.xml.dtm.ref",
+      "org.apache.xml.dtm.ref.dom2dtm",
+      "org.apache.xml.dtm.ref.sax2dtm",
+      "org.apache.xml.res",
+      "org.apache.xml.utils",
+      "org.apache.xml.utils.res",
+      "org.apache.xpath",
+      "org.apache.xpath.axes",
+      "org.apache.xpath.compiler",
+      "org.apache.xpath.domapi",
+      "org.apache.xpath.functions",
+      "org.apache.xpath.jaxp",
+      "org.apache.xpath.objects",
+      "org.apache.xpath.operations",
+      "org.apache.xpath.patterns",
+      "org.apache.xpath.res"
+    ],
+    "xml-apis:xml-apis": [
+      "javax.xml",
+      "javax.xml.datatype",
+      "javax.xml.namespace",
+      "javax.xml.parsers",
+      "javax.xml.stream",
+      "javax.xml.stream.events",
+      "javax.xml.stream.util",
+      "javax.xml.transform",
+      "javax.xml.transform.dom",
+      "javax.xml.transform.sax",
+      "javax.xml.transform.stax",
+      "javax.xml.transform.stream",
+      "javax.xml.validation",
+      "javax.xml.xpath",
+      "org.apache.xmlcommons",
+      "org.w3c.dom",
+      "org.w3c.dom.bootstrap",
+      "org.w3c.dom.css",
+      "org.w3c.dom.events",
+      "org.w3c.dom.html",
+      "org.w3c.dom.ls",
+      "org.w3c.dom.ranges",
+      "org.w3c.dom.stylesheets",
+      "org.w3c.dom.traversal",
+      "org.w3c.dom.views",
+      "org.w3c.dom.xpath",
+      "org.xml.sax",
+      "org.xml.sax.ext",
+      "org.xml.sax.helpers"
+    ],
+    "xml-apis:xml-apis-ext": [
+      "org.w3c.css.sac",
+      "org.w3c.css.sac.helpers",
+      "org.w3c.dom.smil",
+      "org.w3c.dom.svg"
     ]
   },
   "repositories": {
@@ -1522,6 +2018,8 @@
       "com.h2database:h2",
       "com.mikesamuel:json-sanitizer",
       "com.unboundid:unboundid-ldapsdk",
+      "commons-io:commons-io",
+      "commons-logging:commons-logging",
       "javax.activation:javax.activation-api",
       "javax.el:javax.el-api",
       "javax.validation:validation-api",
@@ -1534,6 +2032,24 @@
       "org.apache.commons:commons-math3",
       "org.apache.logging.log4j:log4j-api",
       "org.apache.logging.log4j:log4j-core",
+      "org.apache.xmlgraphics:batik-anim",
+      "org.apache.xmlgraphics:batik-awt-util",
+      "org.apache.xmlgraphics:batik-bridge",
+      "org.apache.xmlgraphics:batik-constants",
+      "org.apache.xmlgraphics:batik-css",
+      "org.apache.xmlgraphics:batik-dom",
+      "org.apache.xmlgraphics:batik-ext",
+      "org.apache.xmlgraphics:batik-gvt",
+      "org.apache.xmlgraphics:batik-i18n",
+      "org.apache.xmlgraphics:batik-parser",
+      "org.apache.xmlgraphics:batik-script",
+      "org.apache.xmlgraphics:batik-shared-resources",
+      "org.apache.xmlgraphics:batik-svg-dom",
+      "org.apache.xmlgraphics:batik-svggen",
+      "org.apache.xmlgraphics:batik-transcoder",
+      "org.apache.xmlgraphics:batik-util",
+      "org.apache.xmlgraphics:batik-xml",
+      "org.apache.xmlgraphics:xmlgraphics-commons",
       "org.apiguardian:apiguardian-api",
       "org.assertj:assertj-core",
       "org.checkerframework:checker-compat-qual",
@@ -1563,7 +2079,11 @@
       "org.ow2.asm:asm",
       "org.ow2.asm:asm-analysis",
       "org.ow2.asm:asm-commons",
-      "org.ow2.asm:asm-tree"
+      "org.ow2.asm:asm-tree",
+      "xalan:serializer",
+      "xalan:xalan",
+      "xml-apis:xml-apis",
+      "xml-apis:xml-apis-ext"
     ]
   },
   "version": "2"
diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl
index 4cb0222..280ccfd 100644
--- a/sanitizers/sanitizers.bzl
+++ b/sanitizers/sanitizers.bzl
@@ -15,6 +15,7 @@
 _sanitizer_package_prefix = "com.code_intelligence.jazzer.sanitizers."
 
 _sanitizer_class_names = [
+    # keep sorted
     "Deserialization",
     "ExpressionLanguageInjection",
     "LdapInjection",
@@ -23,6 +24,7 @@
     "ReflectiveCall",
     "RegexInjection",
     "RegexRoadblocks",
+    "ServerSideRequestForgery",
     "SqlInjection",
     "XPathInjection",
 ]
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
index c103926..21217db 100644
--- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
@@ -14,6 +14,12 @@
 )
 
 java_library(
+    name = "server_side_request_forgery",
+    srcs = ["ServerSideRequestForgery.java"],
+    deps = ["//src/main/java/com/code_intelligence/jazzer/api:hooks"],
+)
+
+java_library(
     name = "sql_injection",
     srcs = ["SqlInjection.java"],
     deps = [
@@ -38,6 +44,7 @@
     visibility = ["//sanitizers:__pkg__"],
     runtime_deps = [
         ":regex_roadblocks",
+        ":server_side_request_forgery",
         ":sql_injection",
     ],
     deps = [
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java
new file mode 100644
index 0000000..3ff48e3
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ServerSideRequestForgery.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.sanitizers;
+
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium;
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.Jazzer;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiPredicate;
+
+public class ServerSideRequestForgery {
+  // Set via reflection by Jazzer's BugDetectors API.
+  public static final AtomicReference<BiPredicate<String, Integer>> connectionPermitted =
+      new AtomicReference<>((host, port) -> false);
+
+  /**
+   * {@link java.net.Socket} is used in many JDK classes to open network connections. Internally it
+   * delegates to {@link java.net.SocketImpl}, hence, for most situations it's sufficient to hook
+   * the call site {@link java.net.Socket} itself. As {@link java.net.SocketImpl} is an abstract
+   * class all call sites invoking "connect" on concrete implementations get hooked. As JKD internal
+   * classes are normally ignored, they have to be marked for hooking explicitly. In this case, all
+   * internal classes calling "connect" on {@link java.net.SocketImpl} should be listed below.
+   * Internal classes using {@link java.net.SocketImpl#connect(String, int)}:
+   * <ul>
+   *   <li>java.net.Socket (hook required)
+   *   <li>java.net.AbstractPlainSocketImpl (no direct usage, no hook required)
+   *   <li>java.net.PlainSocketImpl (no direct usage, no hook required)
+   *   <li>java.net.HttpConnectSocketImpl (only used in Socket, which is already listed)
+   *   <li>java.net.SocksSocketImpl (used in Socket, but also invoking super.connect directly,
+   *       hook required)
+   *   <li>java.net.ServerSocket (security check, no hook required)
+   * </ul>
+   */
+  @MethodHook(type = HookType.BEFORE, targetClassName = "java.net.SocketImpl",
+      targetMethod = "connect",
+      additionalClassesToHook =
+          {
+              "java.net.Socket",
+              "java.net.SocksSocketImpl",
+          })
+  public static void
+  checkSsrfSocket(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+    checkSsrf(arguments);
+  }
+
+  /**
+   * {@link java.nio.channels.SocketChannel} is used in many JDK classes to open (non-blocking)
+   * network connections, e.g. {@link java.net.http.HttpClient} uses it internally. The actual
+   * connection is established in the abstract "connect" method. Hooking that also hooks invocations
+   * of all concrete implementations, from which only one exists in {@link
+   * sun.nio.ch.SocketChannelImpl}. "connect" is only called in {@link
+   * java.nio.channels.SocketChannel} itself and the two mentioned classes below.
+   */
+  @MethodHook(type = HookType.BEFORE, targetClassName = "java.nio.channels.SocketChannel",
+      targetMethod = "connect",
+      additionalClassesToHook =
+          {
+              "sun.nio.ch.SocketAdaptor",
+              "jdk.internal.net.http.PlainHttpConnection",
+          })
+  public static void
+  checkSsrfHttpConnection(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+    checkSsrf(arguments);
+  }
+
+  private static void checkSsrf(Object[] arguments) {
+    if (arguments.length == 0) {
+      return;
+    }
+
+    String host;
+    int port;
+    if (arguments[0] instanceof InetSocketAddress) {
+      // Only implementation of java.net.SocketAddress.
+      InetSocketAddress address = (InetSocketAddress) arguments[0];
+      host = address.getHostName();
+      port = address.getPort();
+    } else if (arguments.length >= 2 && arguments[1] instanceof Integer) {
+      if (arguments[0] instanceof InetAddress) {
+        host = ((InetAddress) arguments[0]).getHostName();
+      } else if (arguments[0] instanceof String) {
+        host = (String) arguments[0];
+      } else {
+        return;
+      }
+      port = (int) arguments[1];
+    } else {
+      return;
+    }
+
+    if (port < 0 || port > 65535) {
+      return;
+    }
+
+    if (!connectionPermitted.get().test(host, port)) {
+      Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium(String.format(
+          "Server Side Request Forgery (SSRF)\n"
+              + "Attempted connection to: %s:%d\n"
+              + "Requests to destinations based on untrusted data could lead to exfiltration of "
+              + "sensitive data or exposure of internal services.\n\n"
+              + "If the fuzz test is expected to perform network connections, call "
+              + "com.code_intelligence.jazzer.api.BugDetectors#allowNetworkConnections at the "
+              + "beginning of your fuzz test and optionally provide a predicate matching the "
+              + "expected hosts.",
+          host, port)));
+    }
+  }
+}
diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel
index 3003784..bbb5e3c 100644
--- a/sanitizers/src/test/java/com/example/BUILD.bazel
+++ b/sanitizers/src/test/java/com/example/BUILD.bazel
@@ -210,3 +210,52 @@
     # Fuzz target catches the syntax exception triggered by the reproducer without the sanitizer.
     verify_crash_reproducer = False,
 )
+
+java_fuzz_target_test(
+    name = "SsrfSocketConnect",
+    srcs = [
+        "SsrfSocketConnect.java",
+    ],
+    allowed_findings = [
+        "com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium",
+    ],
+    target_class = "com.example.SsrfSocketConnect",
+    verify_crash_reproducer = False,
+)
+
+java_fuzz_target_test(
+    name = "SsrfSocketConnectToHost",
+    srcs = [
+        "SsrfSocketConnectToHost.java",
+    ],
+    allowed_findings = [
+        "com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium",
+    ],
+    target_class = "com.example.SsrfSocketConnectToHost",
+    verify_crash_reproducer = False,
+)
+
+java_fuzz_target_test(
+    name = "SsrfUrlConnection",
+    srcs = [
+        "SsrfUrlConnection.java",
+    ],
+    allowed_findings = [
+        "com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium",
+    ],
+    target_class = "com.example.SsrfUrlConnection",
+    verify_crash_reproducer = False,
+)
+
+java_fuzz_target_test(
+    name = "SsrfHttpClient",
+    srcs = [
+        "SsrfHttpClient.java",
+    ],
+    allowed_findings = [
+        "com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium",
+    ],
+    tags = ["no-jdk8"],
+    target_class = "com.example.SsrfHttpClient",
+    verify_crash_reproducer = False,
+)
diff --git a/sanitizers/src/test/java/com/example/SsrfHttpClient.java b/sanitizers/src/test/java/com/example/SsrfHttpClient.java
new file mode 100644
index 0000000..6da561a
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/SsrfHttpClient.java
@@ -0,0 +1,39 @@
+// Copyright 2023 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.io.IOException;
+import java.net.*;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+
+public class SsrfHttpClient {
+  private static final HttpClient CLIENT =
+      HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build();
+
+  public static void fuzzerTestOneInput(FuzzedDataProvider data)
+      throws IOException, InterruptedException {
+    String hostname = data.consumeString(15);
+    URI uri;
+    try {
+      uri = URI.create("https://" + hostname);
+      HttpRequest request = HttpRequest.newBuilder().uri(uri).GET().build();
+      CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
+    } catch (IllegalArgumentException ignored) {
+    }
+  }
+}
diff --git a/sanitizers/src/test/java/com/example/SsrfSocketConnect.java b/sanitizers/src/test/java/com/example/SsrfSocketConnect.java
new file mode 100644
index 0000000..f1d7a59
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/SsrfSocketConnect.java
@@ -0,0 +1,27 @@
+// Copyright 2023 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.net.Socket;
+
+public class SsrfSocketConnect {
+  public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception {
+    String hostname = data.consumeString(15);
+    try (Socket s = new Socket(hostname, 80)) {
+      s.getInetAddress();
+    }
+  }
+}
diff --git a/sanitizers/src/test/java/com/example/SsrfSocketConnectToHost.java b/sanitizers/src/test/java/com/example/SsrfSocketConnectToHost.java
new file mode 100644
index 0000000..3e60e50
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/SsrfSocketConnectToHost.java
@@ -0,0 +1,46 @@
+// Copyright 2023 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.example;
+
+import com.code_intelligence.jazzer.api.BugDetectors;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import com.code_intelligence.jazzer.api.Jazzer;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+public class SsrfSocketConnectToHost {
+  // We don't actually care about establishing a connection and thus choose the lowest possible
+  // timeout.
+  private static final int CONNECTION_TIMEOUT_MS = 1;
+
+  public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception {
+    String host = data.consumeAsciiString(15);
+    int port = data.consumeInt(1, 65535);
+
+    try (AutoCloseable ignored = BugDetectors.allowNetworkConnections()) {
+      // Verify that policies nest properly.
+      try (AutoCloseable ignored1 = BugDetectors.allowNetworkConnections(
+               (String h, Integer p) -> h.equals("localhost"))) {
+        try (AutoCloseable ignored2 = BugDetectors.allowNetworkConnections()) {
+        }
+        try (Socket s = new Socket()) {
+          s.connect(new InetSocketAddress(host, port), CONNECTION_TIMEOUT_MS);
+        } catch (IOException ignored3) {
+        }
+      }
+    }
+  }
+}
diff --git a/sanitizers/src/test/java/com/example/SsrfUrlConnection.java b/sanitizers/src/test/java/com/example/SsrfUrlConnection.java
new file mode 100644
index 0000000..8ea940a
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/SsrfUrlConnection.java
@@ -0,0 +1,33 @@
+// Copyright 2023 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.example;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class SsrfUrlConnection {
+  public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Exception {
+    String hostname = data.consumeString(15);
+    try {
+      URL url = new URL("https://" + hostname);
+      HttpURLConnection con = (HttpURLConnection) url.openConnection();
+      con.setRequestMethod("GET");
+      con.getInputStream();
+    } catch (IOException | IllegalArgumentException ignored) {
+    }
+  }
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel
index 69ea877..03ac791 100644
--- a/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel
+++ b/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel
@@ -6,6 +6,7 @@
         "Autofuzz.java",
         "AutofuzzConstructionException.java",
         "AutofuzzInvocationException.java",
+        "BugDetectors.java",
         "CannedFuzzedDataProvider.java",
         "Consumer1.java",
         "Consumer2.java",
@@ -18,6 +19,7 @@
         "Function4.java",
         "Function5.java",
         "FuzzedDataProvider.java",
+        "SilentCloseable.java",
     ],
     visibility = ["//visibility:public"],
     runtime_deps = [
diff --git a/src/main/java/com/code_intelligence/jazzer/api/BugDetectors.java b/src/main/java/com/code_intelligence/jazzer/api/BugDetectors.java
new file mode 100644
index 0000000..64f0193
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/api/BugDetectors.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.api;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiPredicate;
+
+/**
+ * Provides static functions that configure the behavior of bug detectors provided by Jazzer.
+ */
+public final class BugDetectors {
+  private static final AtomicReference<BiPredicate<String, Integer>> currentPolicy =
+      getConnectionPermittedReference();
+
+  /**
+   * Allows all network connections.
+   *
+   * <p>See {@link #allowNetworkConnections(BiPredicate)} for an alternative that provides
+   * fine-grained control over which network connections are expected.
+   *
+   * <p>By default, all attempted network connections are considered unexpected and result in a
+   * finding being reported.
+   *
+   * <p>By wrapping the call into a try-with-resources statement, network connection permissions
+   * can be configured to apply to individual parts of the fuzz test only:
+   *
+   * <pre>{@code
+   *   Image image = parseImage(bytes);
+   *   Response response;
+   *   try (SilentCloseable unused = BugDetectors.allowNetworkConnections()) {
+   *     response = uploadImage(image);
+   *   }
+   *   handleResponse(response);
+   * }</pre>
+   *
+   * @return a {@link SilentCloseable} that restores the previously set permissions when closed
+   */
+  public static SilentCloseable allowNetworkConnections() {
+    return allowNetworkConnections((host, port) -> true);
+  }
+
+  /**
+   * Allows all network connections for which the provided predicate returns {@code true}.
+   *
+   * <p>By default, all attempted network connections are considered unexpected and result in a
+   * finding being reported.
+   *
+   * <p>By wrapping the call into a try-with-resources statement, network connection permissions
+   * can be configured to apply to individual parts of the fuzz test only:
+   *
+   * <pre>{@code
+   *   Image image = parseImage(bytes);
+   *   Response response;
+   *   try (SilentCloseable unused = BugDetectors.allowNetworkConnections(
+   *       (host, port) -> host.equals("example.org"))) {
+   *     response = uploadImage(image, "example.org");
+   *   }
+   *   handleResponse(response);
+   * }</pre>
+   *
+   * @param connectionPermitted a predicate that evaluate to {@code true} if network connections to
+   *                            the provided combination of host and port are permitted
+   * @return a {@link SilentCloseable} that restores the previously set predicate when closed
+   */
+  public static SilentCloseable allowNetworkConnections(
+      BiPredicate<String, Integer> connectionPermitted) {
+    if (connectionPermitted == null) {
+      throw new IllegalArgumentException("connectionPermitted must not be null");
+    }
+    if (currentPolicy == null) {
+      throw new IllegalStateException("Failed to set network connection policy");
+    }
+    BiPredicate<String, Integer> previousPolicy = currentPolicy.getAndSet(connectionPermitted);
+    return () -> {
+      if (!currentPolicy.compareAndSet(connectionPermitted, previousPolicy)) {
+        throw new IllegalStateException(
+            "Failed to reset network connection policy - using try-with-resources is highly recommended");
+      }
+    };
+  }
+
+  private static AtomicReference<BiPredicate<String, Integer>> getConnectionPermittedReference() {
+    try {
+      Class<?> ssrfSanitizer =
+          Class.forName("com.code_intelligence.jazzer.sanitizers.ServerSideRequestForgery");
+      return (AtomicReference<BiPredicate<String, Integer>>) ssrfSanitizer
+          .getField("connectionPermitted")
+          .get(null);
+    } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
+      System.err.println("WARNING: ");
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  private BugDetectors() {}
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/api/SilentCloseable.java b/src/main/java/com/code_intelligence/jazzer/api/SilentCloseable.java
new file mode 100644
index 0000000..3f2d6e3
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/api/SilentCloseable.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.api;
+
+/**
+ * A specialization of {@link AutoCloseable} without a {@code throws} declarations on
+ * {@link #close()}.
+ */
+public interface SilentCloseable extends AutoCloseable {
+  @Override void close();
+}