Skip to content

feat: evaluate DSL helpers in -H values per request - #7618

Open
aviadavi wants to merge 3 commits into
projectdiscovery:devfrom
aviadavi:feat/dsl-in-custom-headers
Open

feat: evaluate DSL helpers in -H values per request#7618
aviadavi wants to merge 3 commits into
projectdiscovery:devfrom
aviadavi:feat/dsl-in-custom-headers

Conversation

@aviadavi

@aviadavi aviadavi commented Jul 27, 2026

Copy link
Copy Markdown

Closes #7617

What

  1. rand_user_agent() DSL helper, backed by projectdiscovery/useragent (already a direct dependency, already used for UA randomization). Registered with cacheable=false, since a cached result would freeze the UA for the whole run.
  2. setCustomHeaders now resolves {{...}} in global header values. It already runs once per generated request, so this gives per-request evaluation with no new plumbing.

33 lines of non-test code across 2 files.

Why this shape

Implements @dwisiswant0's suggestion from #7446 rather than the -ua-tag flag from #7445 (closed). No new CLI flag, no new dependency.

Note that {{rand_ua}} as written in #7446 does not exist yet: nuclei wires only dsl.HelperFunctions(), and the faker generators from projectdiscovery/dsl#234 sit in a separate dsl.FakerFunctions() that nuclei never merges. Hence piece 1. If you would rather merge FakerFunctions() wholesale, I will swap it.

Behaviour

  • Values with no {{ skip evaluation entirely, so the common path is unaffected
  • Compile or evaluation errors keep the original value and log at debug, so a typo degrades one header instead of failing the scan
  • Values come from the operator's own -H flag or config file, the same trust boundary as a template. Response data is not evaluated on this path.
  • Both request paths are covered. Unsafe raw requests take their headers from UnsafeRawBytes via
    TryFillCustomHeaders rather than from setCustomHeaders, so they are evaluated at the splice
    point instead; both paths share one evaluation helper so they cannot drift

Verification

Unit tests cover per-request rotation, tag retention, no-expression passthrough, broken-expression fallback, the non-cached helper, and argument rejection.

Live run, 4 requests against a local server with -H "User-Agent: {{rand_user_agent()}} myprop/value":

Mozilla/5.0 (X11; Linux i686; rv:1.9.5.20) Gecko/ Firefox/3.6.20 myprop/value
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5 myprop/value
Mozilla/5.0 (Knoppix; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 myprop/value
Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-en) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4 myprop/value

Four distinct UAs, tag on each.

go build ./..., go vet, and go test ./pkg/protocols/http/... pass.

Summary by CodeRabbit

  • New Features
    • Added a rand_user_agent() helper for generating random user-agent values in expressions.
    • Custom HTTP header values can now include dynamic expressions evaluated separately for each request.
  • Bug Fixes
    • Header values safely retain their original content when expression evaluation fails.
    • Dynamic expressions now work for unsafe raw HTTP requests.
  • Tests
    • Added coverage for randomization, argument validation, and per-request header evaluation.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 32947e31-bc05-4a85-b283-4cafb74af6a9

📥 Commits

Reviewing files that changed from the base of the PR and between ae10190 and 0f5934c.

📒 Files selected for processing (5)
  • pkg/operators/common/dsl/dsl.go
  • pkg/operators/common/dsl/rand_user_agent_test.go
  • pkg/protocols/http/build_request.go
  • pkg/protocols/http/custom_headers_dsl_test.go
  • pkg/protocols/http/request.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • pkg/protocols/http/build_request.go
  • pkg/operators/common/dsl/dsl.go
  • pkg/protocols/http/request.go
  • pkg/operators/common/dsl/rand_user_agent_test.go
  • pkg/protocols/http/custom_headers_dsl_test.go

Walkthrough

The DSL adds a non-cacheable rand_user_agent() helper. Custom HTTP header values now evaluate DSL expressions per generated request, including unsafe raw requests, while preserving static values and falling back to raw values on errors.

Changes

Dynamic custom header DSL

Layer / File(s) Summary
Random user-agent DSL helper
pkg/operators/common/dsl/dsl.go, pkg/operators/common/dsl/rand_user_agent_test.go
Registers a zero-argument, non-cacheable rand_user_agent() helper and tests regeneration plus argument rejection.
Per-request custom header evaluation
pkg/protocols/http/request.go, pkg/protocols/http/build_request.go, pkg/protocols/http/custom_headers_dsl_test.go
Evaluates marked custom headers for normal and unsafe request paths, retains static values, and falls back to the original value on evaluation errors with corresponding tests.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GeneratedRequest
  participant setCustomHeaders
  participant evaluateCustomHeaderExpressions
  participant rand_user_agent
  GeneratedRequest->>setCustomHeaders: apply custom headers
  setCustomHeaders->>evaluateCustomHeaderExpressions: resolve embedded expressions
  evaluateCustomHeaderExpressions->>rand_user_agent: evaluate helper
  rand_user_agent-->>evaluateCustomHeaderExpressions: return random user-agent
  evaluateCustomHeaderExpressions-->>setCustomHeaders: return resolved or raw fallback
  setCustomHeaders-->>GeneratedRequest: set outgoing header
Loading

Poem

A bunny twirls a header bright,
With fresh new words on every flight.
The tag stays still, the agent hops,
Broken spells leave text in place.
Carrots cheer the DSL night!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes per-request evaluation of DSL helpers in custom header values.
Linked Issues check ✅ Passed The changes satisfy issue #7617 by evaluating header expressions per request and adding the focused rand_user_agent() helper.
Out of Scope Changes check ✅ Passed All production and test changes directly support per-request DSL evaluation in custom HTTP headers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/protocols/http/request.go`:
- Around line 1302-1304: Update the custom-header handling around
evaluateCustomHeaderValue so raw requests preserve the literal header value and
only non-raw requests evaluate custom expressions; keep assigning the resulting
value to req.rawRequest.Headers for the raw path, and add a regression test
covering a raw request header such as -H '{{rand_user_agent()}}'.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d9b651b9-8884-4fb3-8966-58451c5863bb

📥 Commits

Reviewing files that changed from the base of the PR and between ba05210 and 2c95915.

📒 Files selected for processing (4)
  • pkg/operators/common/dsl/dsl.go
  • pkg/operators/common/dsl/rand_user_agent_test.go
  • pkg/protocols/http/custom_headers_dsl_test.go
  • pkg/protocols/http/request.go

Comment thread pkg/protocols/http/request.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/protocols/http/custom_headers_dsl_test.go`:
- Around line 72-88: Update TestUnsafeRawCustomHeaderIsEvaluated to exercise
production request generation through generateRawRequest and
TryFillCustomHeaders instead of calling evaluateCustomHeaderExpressions
directly. Inspect the generated raw request bytes and assert the DSL marker is
absent while the static “myprop/value” tag remains present; retain the
repeated-request check to confirm user-agent evaluation is not frozen.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e2dfeb1-d659-429d-80dd-c6690cf7c84b

📥 Commits

Reviewing files that changed from the base of the PR and between 2c95915 and 5d0e71f.

📒 Files selected for processing (3)
  • pkg/protocols/http/build_request.go
  • pkg/protocols/http/custom_headers_dsl_test.go
  • pkg/protocols/http/request.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/protocols/http/request.go

Comment thread pkg/protocols/http/custom_headers_dsl_test.go Outdated
Adds a rand_user_agent() DSL helper and evaluates helper expressions in
global custom header values once per generated request.

This implements the approach proposed in projectdiscovery#7446 instead of a dedicated
CLI flag, so attribution tagging needs no new global option:

  -H "User-Agent: {{rand_user_agent()}} myprop/value"

The helper is registered with cacheable=false, otherwise a cached result
would freeze the user agent for the whole run. It is backed by the
existing projectdiscovery/useragent pool that nuclei already uses for UA
randomization, so tagged scans send the same class of user agent as
untagged ones and no new dependency is introduced. The name matches the
slug dsl.FakerFunctions() would expose, so wiring faker later is a
drop in.

Values come from the operator's own -H flag or config file, the same
trust boundary as a template, and response data is never evaluated here.
Values with no {{ }} marker skip evaluation entirely, and compile or
evaluation errors keep the original value so a typo degrades the header
rather than failing the scan. The unsafe raw request path is unchanged.
Unsafe raw requests do not go through setCustomHeaders for the value that
reaches the wire: TryFillCustomHeaders splices the -H line straight into
UnsafeRawBytes, and rawhttp sends those bytes. So a header carrying an
expression was sent with the literal {{...}} marker intact.

Evaluate the -H strings before they are spliced in, and share one
evaluation helper between both paths so they cannot drift.

Verified against a local server with an unsafe: true template:

  before: {{rand_user_agent()}} myprop/value
  after:  Mozilla/5.0 (Macintosh ...) Safari/605.1.15 myprop/value
The previous test called the resolver directly, so it passed even with the
TryFillCustomHeaders wiring reverted and therefore guarded nothing.

Drive real request generation instead and read UnsafeRawBytes, asserting
the marker is absent, the tag survives, and the user agent still varies
across requests. Confirmed by reverting the fix: the test now fails with
"expression reached the wire unevaluated".
@aviadavi
aviadavi force-pushed the feat/dsl-in-custom-headers branch from ae10190 to 0f5934c Compare August 3, 2026 16:40
@aviadavi

aviadavi commented Aug 3, 2026

Copy link
Copy Markdown
Author

@dogancanbakir could you approve the workflow run here? CI passed on the first commit, but the pushes since then are sitting at action_required, so there is currently no green signal on the branch.

Just rebased onto current dev (now 3 ahead, 0 behind) and re-ran locally: go build ./..., go vet, and go test ./pkg/protocols/http/... ./pkg/operators/common/dsl/ all pass.

No review needed from anyone yet, just the CI approval. Thanks.

@aviadavi

aviadavi commented Aug 7, 2026

Copy link
Copy Markdown
Author

@Mzack9999 sorry for the second ping. This is still waiting on a CI workflow approval, nothing else, and I asked the wrong person the first time.

Happy to rebase again if that helps, but I held off since each push re-queues the runs and there are already a few sitting at action_required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Evaluate DSL helper expressions in -H header values per request

1 participant