---
title: "HTTP Sender Scripts"
description: "Intercept and modify HTTP requests and responses in HawkScan - custom headers, request signing, AWS SigV4, and payload modification."
url: https://docs.stackhawk.com/scripting/script-types/httpsender-scripting/
---

# HTTP Sender Scripts

Intercept and modify HTTP requests and responses in HawkScan - custom headers, request signing, AWS SigV4, and payload modification.

## **Usage Overview**

The `httpsender` script type enables hooking into HTTP communication during two main stages - just before sending a request to the target app, and just after receiving a response. This kind of script can touch, inspect, or manipulate every single HTTP message in the test cycle. The two required functions in this type of script are:

1. `sendingRequest`: Manipulate a request just before it is sent to the server.
2. `responseReceived`: Manipulate a message (request or response) after it is received from the server, but before it undergoes final processing by HawkScan.

### **Usage Scenarios**

1. **Custom Authentication:** Modify requests to include specific headers or tokens for applications requiring dynamic, programmatically generated authentication for every request.
   - **Example**: AWS SigV4 Signing ([aws-sigv4.kts](https://github.com/kaakaww/hawkscan-examples/blob/main/scripts/examples/httpsender/aws-sigv4.kts))
2. **Traffic Inspection:** Inspect and analyze HTTP traffic to log sensitive data like cookies or tokens, aiding in security assessments or troubleshooting.
   - **Example**: Enhanced logging for troubleshooting ([troubleshooting.kts](https://github.com/kaakaww/hawkscan-examples/blob/main/scripts/examples/httpsender/troubleshooting.kts))
3. **Behavior Simulation/Modification:** Simulate user behavior by dynamically modifying requests based on application responses, ensuring every message reflects real-world scenarios.
   - **Example**: TBC

### **Simple Script Example and Description ([GitHub Link](https://github.com/kaakaww/hawkscan-examples/blob/main/scripts/templates/httpsender/httpsender-template.kts))**

Consider the following example httpsender script, `sender1.kts`.

```java
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpMessage //required
import com.stackhawk.hste.extension.script.HttpSenderScriptHelper //required
import com.stackhawk.hste.extension.script.ScriptVars

val logger = LogManager.getLogger("sender1")

// modify a request before it's sent to the web application
fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    logger.info("sender1 script $initiator")
}

// modify the response from the web application before sending to the client
fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    val clientHdr = ScriptVars.getScriptVar("sender1.kts", "client_header")
    msg.responseHeader.setHeader("X-Client-Header", clientHdr)
}
```

#### Function signature breakdown:

```java
fun sendingRequest(
  msg: HttpMessage,
  initiator: Int,
  helper: HttpSenderScriptHelper
): Unit {
```

`msg` is an object representing the current, in-transit HTTP message from HawkScan. Key sub-objects and accessors are described here: [Key Objects](https://docs.stackhawk.com/scripting/key-objects-scripting/). Available through the `import org.parosproxy.paros.network.HttpMessage class.` The `msg` is passed into the `sendingRequest` entry point function with a populated RequestHeaders and RequestBody, but an empty Response.

`initiator` is an Integer, that represents a reference to the activity or process in HawkScan that initiated the message. These ints translate to specific initiating activity defined in the `HttpSender` class from org.parosproxy.paros.network.HttpSender class. The most common initiators are:

```java
public static final int PROXY_INITIATOR = 1;
public static final int ACTIVE_SCANNER_INITIATOR = 2;
public static final int SPIDER_INITIATOR = 3;
public static final int FUZZER_INITIATOR = 4;
public static final int AUTHENTICATION_INITIATOR = 5;
public static final int MANUAL_REQUEST_INITIATOR = 6;
```

`helper`, coming from the HttpSenderScriptHelper class is an member-instantiation of the HttpSender class, providing access to member methods, and class functions which assist in building and modifying HTTP messages.

`fun responseReceived` implements a similar signature with the same objects. However, the `msg` object now has a fully populated Response headers and body as appropriate.

#### Script logic breakdown:

```java
val logger = LogManager.getLogger("sender1")
```

The `logger` variable is set up outside of any function, and is therefore available as a global variable for the script, which can be used in any of the default or user-defined functions.

```java
fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    logger.info("sender1 script $initiator")
}
```

`sendingRequest` simply logs a message capturing the integer initiator. Log-level is `info` from `logger.info`. By default these log entries are added to `hawkscan.log` as standard output from the scanner.

```java
fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    val clientHdr = ScriptVars.getScriptVar("sender1.kts", "client_header")
    msg.responseHeader.setHeader("X-Client-Header", clientHdr)
}
```

`responseReceived` modifies the message response. First, it grabs a global variable value defined in the StackHawk config specific to this script:\
`val clientHdr = ScriptVars.getScriptVar(“sender1.kts”, “client_header”)`, where “sender1.kts is the name of the script and “client_header” is the name of the variable.

Second, it adds that value to a custom header on the response: `msg.responseHeader.setHeader(“X-Client-Header”, “clientHdr”)`, where “X-Client-Header” is the name/key of the custom header, and the variable value obtained from the config “clientHdr”, is the value of the header.

### **HawkScan Configuration**

To use this script in HawkScan, configuration is required in the hawkAddOn section of the configuration file as shown:

**Note:** HTTP sender scripts use **single configuration** (hawkAddOn only, no dual config)

```yaml
hawkAddOn:
  scripts:
    - name: sender1.kts
      type: httpsender
      path: scripts              # Parent directory only (will look in scripts/httpsender/)
      language: KOTLIN
      vars:                      # Accessed via ScriptVars.getScriptVars() - array structure
        - name: client_header
          val: ${MYCUSTOMHEADERVAL}  # Env variable interpolation supported
```

**Best Practice:** Use only ONE httpsender script - multiple scripts have undefined execution order

### **Advanced Example: AWS SigV4 Request Signing**

This comprehensive example demonstrates intercepting requests to add AWS Signature Version 4 authentication:

```kotlin
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpMessage
import com.stackhawk.hste.extension.script.HttpSenderScriptHelper
import com.stackhawk.hste.extension.script.ScriptVars
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*

val logger = LogManager.getLogger("aws-sigv4")

fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    // Only sign requests to AWS endpoints
    val host = msg.requestHeader.getHeader("Host") ?: return
    if (!host.contains("amazonaws.com")) {
        return
    }

    // Get AWS credentials from script variables
    val accessKey = ScriptVars.getScriptVar("aws-sigv4.kts", "aws_access_key_id") ?: return
    val secretKey = ScriptVars.getScriptVar("aws-sigv4.kts", "aws_secret_access_key") ?: return
    val region = ScriptVars.getScriptVar("aws-sigv4.kts", "aws_region") ?: "us-east-1"
    val service = ScriptVars.getScriptVar("aws-sigv4.kts", "aws_service") ?: "execute-api"

    logger.debug("Signing AWS request for $host")

    // Generate timestamp
    val dateFormat = SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'")
    dateFormat.timeZone = TimeZone.getTimeZone("UTC")
    val timestamp = dateFormat.format(Date())
    val datestamp = timestamp.substring(0, 8)

    // Add required headers
    msg.requestHeader.setHeader("X-Amz-Date", timestamp)

    // Create canonical request
    val method = msg.requestHeader.method
    val uri = msg.requestHeader.uri.path ?: "/"
    val query = msg.requestHeader.uri.query ?: ""
    val headers = "host:$host\nx-amz-date:$timestamp\n"
    val signedHeaders = "host;x-amz-date"
    val payloadHash = sha256Hex(msg.requestBody.bytes)

    val canonicalRequest = "$method\n$uri\n$query\n$headers\n$signedHeaders\n$payloadHash"

    // Create string to sign
    val algorithm = "AWS4-HMAC-SHA256"
    val credentialScope = "$datestamp/$region/$service/aws4_request"
    val stringToSign = "$algorithm\n$timestamp\n$credentialScope\n${sha256Hex(canonicalRequest.toByteArray())}"

    // Calculate signature
    val signatureKey = getSignatureKey(secretKey, datestamp, region, service)
    val signature = hmacSHA256Hex(stringToSign.toByteArray(), signatureKey)

    // Add authorization header
    val authHeader = "$algorithm Credential=$accessKey/$credentialScope, SignedHeaders=$signedHeaders, Signature=$signature"
    msg.requestHeader.setHeader("Authorization", authHeader)

    logger.debug("AWS request signed successfully")
}

fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    // No modification needed for responses
}

// Helper functions for AWS signing
fun sha256Hex(data: ByteArray): String {
    val digest = MessageDigest.getInstance("SHA-256")
    return digest.digest(data).joinToString("") { "%02x".format(it) }
}

fun hmacSHA256(data: ByteArray, key: ByteArray): ByteArray {
    val mac = Mac.getInstance("HmacSHA256")
    mac.init(SecretKeySpec(key, "HmacSHA256"))
    return mac.doFinal(data)
}

fun hmacSHA256Hex(data: ByteArray, key: ByteArray): String {
    return hmacSHA256(data, key).joinToString("") { "%02x".format(it) }
}

fun getSignatureKey(key: String, dateStamp: String, regionName: String, serviceName: String): ByteArray {
    val kDate = hmacSHA256(dateStamp.toByteArray(), ("AWS4" + key).toByteArray())
    val kRegion = hmacSHA256(regionName.toByteArray(), kDate)
    val kService = hmacSHA256(serviceName.toByteArray(), kRegion)
    return hmacSHA256("aws4_request".toByteArray(), kService)
}
```

**Configuration for AWS SigV4:**

```yaml
hawkAddOn:
  scripts:
    - name: aws-sigv4.kts
      type: httpsender
      path: "hawkscripts"          # Parent directory only
      language: KOTLIN
      vars:
        - name: aws_access_key_id
          val: "${AWS_ACCESS_KEY_ID}"
        - name: aws_secret_access_key
          val: "${AWS_SECRET_ACCESS_KEY}"
        - name: aws_region
          val: "us-east-1"
        - name: aws_service
          val: "execute-api"
```

**Reference:** [aws-sigv4.kts example](https://github.com/kaakaww/hawkscan-examples/blob/main/scripts/examples/httpsender/aws-sigv4.kts)

### **Advanced Example: Request/Response Logging for Debugging**

This example logs complete HTTP traffic for debugging authentication or session issues:

```kotlin
import org.apache.log4j.LogManager
import org.parosproxy.paros.network.HttpMessage
import com.stackhawk.hste.extension.script.HttpSenderScriptHelper
import com.stackhawk.hste.extension.script.ScriptVars

val logger = LogManager.getLogger("http-traffic-logger")

fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    // Get configuration
    val logRequests = ScriptVars.getScriptVar("troubleshooting.kts", "log_requests")?.toBoolean() ?: true
    val logHeaders = ScriptVars.getScriptVar("troubleshooting.kts", "log_headers")?.toBoolean() ?: true
    val logBodies = ScriptVars.getScriptVar("troubleshooting.kts", "log_bodies")?.toBoolean() ?: false

    if (!logRequests) return

    logger.info("=== Outgoing Request (Initiator: ${getInitiatorName(initiator)}) ===")
    logger.info("URI: ${msg.requestHeader.uri}")

    if (logHeaders) {
        logger.info("Request Headers:\n${msg.requestHeader}")
    }

    if (logBodies && msg.requestBody.length() > 0) {
        val body = msg.requestBody.toString()
        logger.info("Request Body (${body.length} bytes):\n$body")
    }
}

fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    // Get configuration
    val logResponses = ScriptVars.getScriptVar("troubleshooting.kts", "log_responses")?.toBoolean() ?: true
    val logHeaders = ScriptVars.getScriptVar("troubleshooting.kts", "log_headers")?.toBoolean() ?: true
    val logBodies = ScriptVars.getScriptVar("troubleshooting.kts", "log_bodies")?.toBoolean() ?: false

    if (!logResponses) return

    logger.info("=== Incoming Response ===")
    logger.info("Status: ${msg.responseHeader.statusCode} ${msg.responseHeader.reasonPhrase}")

    if (logHeaders) {
        logger.info("Response Headers:\n${msg.responseHeader}")
    }

    if (logBodies && msg.responseBody.length() > 0) {
        val body = msg.responseBody.toString()
        val truncatedBody = if (body.length > 1000) {
            body.substring(0, 1000) + "\n... (truncated)"
        } else {
            body
        }
        logger.info("Response Body (${body.length} bytes):\n$truncatedBody")
    }
}

fun getInitiatorName(initiator: Int): String {
    return when (initiator) {
        1 -> "PROXY"
        2 -> "ACTIVE_SCANNER"
        3 -> "SPIDER"
        4 -> "FUZZER"
        5 -> "AUTHENTICATION"
        6 -> "MANUAL_REQUEST"
        else -> "UNKNOWN($initiator)"
    }
}
```

**Configuration for Traffic Logging:**

```yaml
hawkAddOn:
  scripts:
    - name: troubleshooting.kts
      type: httpsender
      path: "hawkscripts"          # Parent directory only
      language: KOTLIN
      vars:
        - name: log_requests
          val: "true"
        - name: log_responses
          val: "true"
        - name: log_headers
          val: "true"
        - name: log_bodies
          val: "false"  # Set to "true" for detailed debugging
```

**Reference:** [troubleshooting.kts example](https://github.com/kaakaww/hawkscan-examples/blob/main/scripts/examples/httpsender/troubleshooting.kts)

### **Common Patterns and Best Practices**

#### Pattern 1: Filtering by Initiator

Only process requests from specific scan phases:

```kotlin
import org.parosproxy.paros.network.HttpSender

fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    // Only modify active scanner requests
    if (initiator != HttpSender.ACTIVE_SCANNER_INITIATOR) {
        return
    }

    // Add special header for active scan requests
    msg.requestHeader.setHeader("X-Scan-Type", "active")
}
```

#### Pattern 2: Conditional Header Addition

```kotlin
fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) {
    // Only add header to API endpoints
    val uri = msg.requestHeader.uri.toString()

    if (uri.contains("/api/")) {
        msg.requestHeader.setHeader("X-API-Version", "v2")
    }
}
```

#### Pattern 3: Using ScriptVars for Configuration

```kotlin
val apiKey = ScriptVars.getScriptVar("my-script.kts", "api_key")
val environment = ScriptVars.getScriptVar("my-script.kts", "environment") ?: "production"

// Use configuration values
if (apiKey != null) {
    msg.requestHeader.setHeader("X-API-Key", apiKey)
}

if (environment == "staging") {
    msg.requestHeader.setHeader("X-Environment", "staging")
}
```

### **Troubleshooting**

#### Issue: “HttpSender script not being called”

**Symptoms:** Log messages don’t appear in `hawkscan.log`.

**Solutions:**

1. Verify `type: httpsender` in hawkAddOn configuration
2. Check script path is correct relative to project root
3. Look for compilation errors at HawkScan startup
4. Ensure both `sendingRequest` and `responseReceived` functions are defined
5. Verify script filename matches the `name` in configuration

#### Issue: “Headers not being added to requests”

**Symptoms:** Custom headers don’t appear in requests sent to the target.

**Solutions:**

1. Verify that `msg.requestHeader` gets modified in `sendingRequest` (not `responseReceived`)
2. Check that header names and values are valid (no special characters in names)
3. Add logging to confirm function is being called: `logger.info("Adding header")`
4. Verify initiator filtering isn’t excluding the requests
5. Check if headers are being overwritten by authentication or session scripts

#### Issue: “Script variables not accessible”

**Symptoms:** `ScriptVars.getScriptVar()` returns null.

**Solutions:**

1. Verify variable name spelling matches exactly between script and configuration
2. Ensure variables are defined in `hawkAddOn.scripts.vars` section
3. Check that script name passed to `getScriptVar()` matches configuration
4. Use logging to debug: `logger.debug("Variable value: $myVar")`

#### Issue: “Performance degradation with httpsender script”

**Symptoms:** Scans are significantly slower with script enabled.

**Solutions:**

1. Avoid complex computations in `sendingRequest` (runs for every request)
2. Cache expensive operations outside function scope
3. Use initiator filtering to reduce processing
4. Minimize logging in high-traffic functions
5. Profile script performance with timing logs

### **When to Use HttpSender Scripts vs Other Script Types**

**Use HttpSender Scripts When:**

- Every request needs the same modification (e.g., custom headers, signing)
- All traffic must be inspected for logging/debugging
- Dynamic behavior based on previous responses is required
- Custom authentication that can’t be handled by auth scripts

**Use Authentication Scripts Instead When:**

- Only the initial login process needs custom handling
- Token extraction from login response is needed

**Use Session Scripts Instead When:**

- Token injection is needed but only after authentication
- Session-specific state management is required

**Use Replacer Rules Instead When:**

- Simple string replacement in headers is sufficient
- Static header values that never change
- Performance is critical (replacer rules are faster than scripts)

### **Key Objects Reference**

For detailed information on classes and methods used in httpsender scripts, see [Key Objects Documentation](https://docs.stackhawk.com/scripting/key-objects-scripting/):

- **HttpMessage** - HTTP request/response encapsulation
- **HttpSenderScriptHelper** - Helper methods for HTTP operations
- **HttpRequestHeader / HttpResponseHeader** - Header manipulation
- **ScriptVars** - Access to script configuration and global variables

### **Additional Resources**

- [GitHub Examples Repository](https://github.com/kaakaww/hawkscan-examples/tree/main/scripts/examples/httpsender)

  - [AWS SigV4 Signing](https://github.com/kaakaww/hawkscan-examples/blob/main/scripts/examples/httpsender/aws-sigv4.kts)
  - [Traffic Logging](https://github.com/kaakaww/hawkscan-examples/blob/main/scripts/examples/httpsender/troubleshooting.kts)
  - [Custom Headers](https://github.com/kaakaww/hawkscan-examples/blob/main/scripts/examples/httpsender/add-custom-headers.kts)
  - [HTTP Payload Logging](https://github.com/kaakaww/hawkscan-examples/blob/main/scripts/examples/httpsender/log-http-payload.kts)

- [Working with the HawkScript SDK](https://docs.stackhawk.com/scripting/working-with-sdk/)

- [Authentication Script Documentation](https://docs.stackhawk.com/scripting/script-types/authentication-scripting/)

- [Session Script Documentation](https://docs.stackhawk.com/scripting/script-types/session-scripting/)
