Skip to content

feat(templates): bind js execution to verified content - #7636

Merged
Mzack9999 merged 1 commit into
projectdiscovery:devfrom
dwisiswant0:dwisiswant0/feat/templates/bind-javascript-execution-to-verified-content
Aug 5, 2026
Merged

feat(templates): bind js execution to verified content#7636
Mzack9999 merged 1 commit into
projectdiscovery:devfrom
dwisiswant0:dwisiswant0/feat/templates/bind-javascript-execution-to-verified-content

Conversation

@dwisiswant0

@dwisiswant0 dwisiswant0 commented Aug 4, 2026

Copy link
Copy Markdown
Member

Proposed changes

Verify signatures before protocol compilation so
unsigned init blocks cannot run, and enforce the
same check at request execution.

Bind cached results to the template, imported file
contents, and verifier key. Use the loaded import
snapshot for signing and verification, and apply
the executable-template re-signing rule to
JavaScript.

Proof

Checklist

  • Pull request is created against the dev branch
  • All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened template verification by detecting changes to content, imported files, and verifier identity.
    • Prevented execution of unverified JavaScript templates.
    • Improved JavaScript syntax validation during compilation.
    • Preserved verification state safely when reusing cached templates.
  • Security
    • Prevented unauthorized re-signing of executable JavaScript templates.
    • Bound verification and signing to captured imported-file contents.

Verify signatures before protocol compilation so
unsigned init blocks cannot run, and enforce the
same check at request execution.

Bind cached results to the template, imported file
contents, and verifier key. Use the loaded import
snapshot for signing and verification, and apply
the executable-template re-signing rule to
JavaScript.

Signed-off-by: Dwi Siswanto <git@dw1.io>
@dwisiswant0
dwisiswant0 requested a review from Mzack9999 August 4, 2026 03:25
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Template verification now binds cached metadata to verifier fingerprints and SHA-256 digests of templates and imported files. Compilation and JavaScript execution enforce verification state. Catalog loading revalidates cached metadata when verification values change.

Changes

Verified template execution

Layer / File(s) Summary
Import snapshots and signing
pkg/templates/templates.go, pkg/templates/signer/*, pkg/templates/template_sign_test.go
Templates capture imported-file contents. Signing and verification use captured contents and expose signer fingerprints. JavaScript templates cannot be re-signed with a foreign signer.
Verification and compilation
pkg/protocols/protocols.go, pkg/templates/compile.go, pkg/templates/compile_test.go, pkg/templates/fuzz_harness.go
Template parsing separates verification from compilation. Verification records content digests and verifier fingerprints, validates cached metadata, and preserves verification state.
JavaScript execution gating
pkg/protocols/javascript/js.go, pkg/protocols/javascript/js_test.go
JavaScript initialization compiles once and executes only for verified templates. Unverified execution returns an error without invoking the callback.
Cache verification revalidation
pkg/catalog/index/*, pkg/catalog/loader/*
Catalog metadata stores verification fingerprints and content digests. The loader refreshes cached metadata when verification metadata differs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CatalogLoader
  participant TemplateParser
  participant TemplateSigner
  participant JavaScriptExecutor
  CatalogLoader->>TemplateParser: load and parse template
  TemplateParser->>TemplateSigner: verify template and imported contents
  TemplateSigner-->>TemplateParser: return verification metadata
  TemplateParser->>JavaScriptExecutor: compile JavaScript initialization
  JavaScriptExecutor->>JavaScriptExecutor: execute only when verified
Loading

Possibly related PRs

Suggested reviewers: mzack9999, dogancanbakir

Poem

A rabbit checks each code-filled leaf,
Hashes guard the cache from grief.
Signed imports stay in view,
JavaScript runs when trust is true.
Fresh metadata hops in place.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: binding JavaScript execution to verified template content.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

🧹 Nitpick comments (1)
pkg/catalog/loader/loader_test.go (1)

526-537: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add a verifier-fingerprint-only revalidation test.

This fixture uses a zero-value VerifierFingerprint. The parsed unsigned template also has a zero-value fingerprint. The incorrect ContentDigest alone causes the refresh.

Add a case with the correct digest and an intentionally different fingerprint. Assert that the loader refreshes the cached metadata. The PR objective requires cache binding to the verifier key.

🤖 Prompt for 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.

In `@pkg/catalog/loader/loader_test.go` around lines 526 - 537, Add a revalidation
case near the cached metadata fixture using the correct ContentDigest but a
deliberately different VerifierFingerprint from the parsed unsigned template.
Assert that the loader refreshes the cached metadata, proving verifier-key
changes invalidate the cache independently of content. Keep the existing
digest-mismatch coverage unchanged.
🤖 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/javascript/js.go`:
- Around line 295-298: Update the SDK parse path in NucleiEngine.ParseTemplate
to validate or skip unsigned JavaScript templates before they reach target
execution, while preserving the existing request.options verification gate as a
fail-closed safeguard.

---

Nitpick comments:
In `@pkg/catalog/loader/loader_test.go`:
- Around line 526-537: Add a revalidation case near the cached metadata fixture
using the correct ContentDigest but a deliberately different VerifierFingerprint
from the parsed unsigned template. Assert that the loader refreshes the cached
metadata, proving verifier-key changes invalidate the cache independently of
content. Keep the existing digest-mismatch coverage unchanged.
🪄 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

Run ID: 87fb16bd-2c07-4425-87bf-76d30484e0e1

📥 Commits

Reviewing files that changed from the base of the PR and between 4a6f7c5 and 9de96e4.

📒 Files selected for processing (14)
  • pkg/catalog/index/index.go
  • pkg/catalog/index/metadata.go
  • pkg/catalog/loader/loader.go
  • pkg/catalog/loader/loader_test.go
  • pkg/protocols/javascript/js.go
  • pkg/protocols/javascript/js_test.go
  • pkg/protocols/protocols.go
  • pkg/templates/compile.go
  • pkg/templates/compile_test.go
  • pkg/templates/fuzz_harness.go
  • pkg/templates/signer/tmpl_signer.go
  • pkg/templates/signer/tmpl_signer_test.go
  • pkg/templates/template_sign_test.go
  • pkg/templates/templates.go

Comment on lines +295 to +298
if request.options == nil || !request.options.Verified {
return errkit.New("refusing to execute unverified javascript template; sign it (-sign) or run a verified template")
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Trace unsigned-JavaScript gates and callers that can reach javascript Request.ExecuteWithResults.
set -uo pipefail

# 1. Where is the unsigned-javascript loader gate applied?
rg -n -C5 'IsUnsignedJavascriptTemplate|SkippedUnverifiedJavascriptTemplateStats' --type=go

# 2. Who propagates ExecutorOptions.Verified into protocol requests?
rg -n -C4 '\bVerified\b' --type=go -g '!**/*_test.go' pkg/protocols pkg/templates pkg/tmplexec 2>/dev/null

# 3. Callers of ExecuteWithResults on protocol requests (executer/flow/multiproto paths).
rg -n -C4 '\.ExecuteWithResults\(' --type=go -g '!**/*_test.go'

# 4. Does the SDK expose a path that compiles/executes javascript requests without templates.Parse?
rg -n -C4 'javascript\.Request|JavascriptProtocol' --type=go -g '!**/*_test.go'

Repository: projectdiscovery/nuclei

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(pkg/protocols/javascript/js\.go|.*(template|exec|flow|cluster|sdk|protocol).*\.go$)' | head -200

printf '%s\n' '--- JavaScript protocol symbols ---'
rg -n -C6 'ExecuteWithResults|IsUnsignedJavascriptTemplate|type Request|Verified|javascript\.Request|JavascriptProtocol' pkg --glob '*.go' 2>/dev/null | head -1200

printf '%s\n' '--- all ExecuteWithResults callers ---'
rg -n -C5 '\.ExecuteWithResults\(' --glob '*.go' --glob '!**/*_test.go' . 2>/dev/null | head -1200

Repository: projectdiscovery/nuclei

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- unsigned-template filtering ---'
rg -n -C8 'IsUnsignedJavascriptTemplate|SkippedUnverifiedJavascript|disable-unsigned|UnsignedJavascript|SkippedUnverifiedTemplate' \
  internal pkg lib --glob '*.go' 2>/dev/null | head -1600

printf '%s\n' '--- JavaScript request execution call sites ---'
rg -n -C8 'RequestsJavascript|JavascriptProtocol|javascript\.Request|ExecuteWithResults\(' \
  internal pkg lib --glob '*.go' --glob '!**/*_test.go' 2>/dev/null |
  rg -n -C5 'javascript|Javascript|ExecuteWithResults|RequestsJavascript|ClusterExecuter|Flow|Workflow|SDK|request' |
  head -2200

printf '%s\n' '--- relevant executor option propagation ---'
rg -n -C10 'options\.Verified|Verified *=|Verified:' \
  pkg/templates pkg/core internal/runner internal/server lib --glob '*.go' --glob '!**/*_test.go' 2>/dev/null | head -1800

Repository: projectdiscovery/nuclei

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- workflow loader and template execution ---'
sed -n '1,145p' pkg/templates/workflows.go
sed -n '300,375p' pkg/templates/compile.go
sed -n '220,370p' pkg/templates/cluster.go

printf '%s\n' '--- request container and executor construction ---'
rg -n -C8 'type .*Executer|type .*Request|NewTemplateExecuter|ExecuteWithResults\(inputItem|requests \[\]|requests\.' \
  pkg/tmplexec pkg/templates --glob '*.go' --glob '!**/*_test.go' 2>/dev/null | head -1800

printf '%s\n' '--- SDK/template loading and execution entry points ---'
rg -n -C8 'ParseTemplate|LoadTemplates|ExecuteWithResults|Execute\(|New.*Execut|Template.*Options|DisableUnsignedTemplates' \
  lib internal/server internal/runner --glob '*.go' --glob '!**/*_test.go' 2>/dev/null | head -2200

Repository: projectdiscovery/nuclei

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- public SDK execution APIs ---'
rg -n -C12 'func (.*)(Parse|Load|Execute|Scan|Template)|ParseTemplate|LoadTemplates|TemplateLoader|ExecutorOptions|DisableUnsignedTemplates' \
  lib/sdk.go lib/sdk_private.go internal/server/nuclei_sdk.go --glob '*.go' 2>/dev/null | head -2400

printf '%s\n' '--- Parse call sites outside the catalog loader ---'
rg -n -C6 '\btemplates\.Parse\(|ParseTemplateFromReader|NewTemplateExecuter|NewFlowExecutor|NewMultiProtocol|NewGenericEngine' \
  lib internal pkg --glob '*.go' --glob '!**/*_test.go' 2>/dev/null | head -2400

printf '%s\n' '--- workflow top-level loading and execution ---'
rg -n -C10 'WorkflowLoader|CompiledWorkflow|Executers|ProtocolExecuterPair|parseWorkflowTemplate|Load.*Workflow' \
  pkg/loader pkg/core pkg/workflows internal lib --glob '*.go' --glob '!**/*_test.go' 2>/dev/null | head -2400

Repository: projectdiscovery/nuclei

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- direct SDK parse/compile path ---'
sed -n '431,490p' pkg/templates/compile.go
sed -n '200,214p' lib/sdk.go
sed -n '145,175p' internal/server/nuclei_sdk.go

printf '%s\n' '--- SDK consumers of ParseTemplate and exported executers ---'
rg -n -C10 'ParseTemplate\(|\.Executer|GetExecuterOptions\(' \
  lib internal --glob '*.go' 2>/dev/null | head -1800

printf '%s\n' '--- source-level path verifier ---'
python3 - <<'PY'
from pathlib import Path

loader = Path("pkg/catalog/loader/loader.go").read_text()
workflow = Path("pkg/templates/workflows.go").read_text()
sdk = Path("lib/sdk.go").read_text()
compile_src = Path("pkg/templates/compile.go").read_text()
js = Path("pkg/protocols/javascript/js.go").read_text()

checks = {
    "loader rejects unsigned JavaScript": "if parsed.IsUnsignedJavascriptTemplate()" in loader,
    "workflow rejects unsigned JavaScript": "if template.IsUnsignedJavascriptTemplate()" in workflow,
    "SDK exposes direct ParseTemplate": "func (e *NucleiEngine) ParseTemplate(data []byte)" in sdk,
    "SDK ParseTemplate bypasses catalog loader": "templates.ParseTemplateFromReader(bytes.NewReader(data), nil, e.executerOpts)" in sdk,
    "direct compile includes JavaScript requests": "template.HasJavascriptRequest()" in compile_src,
    "execution gate rejects nil/unverified options": "if request.options == nil || !request.options.Verified" in js,
}
for name, ok in checks.items():
    print(f"{name}: {'YES' if ok else 'NO'}")
if not all(checks.values()):
    raise SystemExit(1)
PY

Repository: projectdiscovery/nuclei

Length of output: 8958


Handle unsigned JavaScript in the SDK parse path. NucleiEngine.ParseTemplate bypasses the catalog loader and returns a compiled template that can reach this gate for every target. Add one SDK-level validation or skip before target execution. Keep this fail-closed gate.

🤖 Prompt for 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.

In `@pkg/protocols/javascript/js.go` around lines 295 - 298, Update the SDK parse
path in NucleiEngine.ParseTemplate to validate or skip unsigned JavaScript
templates before they reach target execution, while preserving the existing
request.options verification gate as a fail-closed safeguard.

@dwisiswant0

Copy link
Copy Markdown
Member Author

@coderabbitai create an issue to track this.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="projectdiscovery/nuclei"
pr_number="7636"

echo "== Issue comments =="
gh api "repos/$repo/issues/$pr_number/comments?per_page=100" \
  --jq '.[] | {id, html_url, user: .user.login, body, created_at}'

echo "== Pull-request review comments =="
gh api "repos/$repo/pulls/$pr_number/comments?per_page=100" \
  --jq '.[] | {id, html_url, user: .user.login, path, line, original_line, body, created_at}'

echo "== Pull-request reviews =="
gh api "repos/$repo/pulls/$pr_number/reviews?per_page=100" \
  --jq '.[] | {id, html_url, user: .user.login, state, body, submitted_at}'

Length of output: 19968


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="projectdiscovery/nuclei"
body=$(cat <<'EOF'
## Summary

`NucleiEngine.ParseTemplate` bypasses the catalog loader. An unsigned JavaScript template can therefore reach JavaScript request execution through the SDK parse path.

## Required changes

Add SDK-level validation or skipping for unsigned JavaScript templates before target execution in the `NucleiEngine.ParseTemplate` flow.

Keep the existing `request.options.Verified` check in `pkg/protocols/javascript/js.go` as a fail-closed execution safeguard.

## Rationale

The catalog loader rejects unsigned JavaScript templates. The SDK parse path must apply equivalent protection so unsigned JavaScript does not proceed to per-target execution.

## Affected areas

- `lib/sdk.go`: `NucleiEngine.ParseTemplate`
- `pkg/protocols/javascript/js.go`: `Request.ExecuteWithResults` verification gate

## Acceptance criteria

- The SDK parse path validates or skips unsigned JavaScript templates before target execution.
- The JavaScript execution gate continues to reject nil or unverified executor options.
- Tests cover the SDK parse path for an unsigned JavaScript template.

## Backlinks

- Pull request: https://github.com/projectdiscovery/nuclei/pull/7636
- CodeRabbit review comment: https://github.com/projectdiscovery/nuclei/pull/7636#discussion_r3709243018
- Requested by: `@dwisiswant0`
EOF
)

gh issue create \
  --repo "$repo" \
  --title "Harden the SDK parse path for unsigned JavaScript templates" \
  --body "$body" \
  --assignee "dwisiswant0"

Length of output: 1736


@dwisiswant0 Created and assigned issue #7637 to track the SDK parse-path handling for unsigned JavaScript templates.

You are interacting with an AI system.

@Mzack9999
Mzack9999 merged commit 37bba07 into projectdiscovery:dev Aug 5, 2026
19 checks passed
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.

2 participants