Skip to content

ti_abusech: switch URL data stream to incremental API - #18945

Merged
kcreddy merged 7 commits into
elastic:mainfrom
kcreddy:ti_abusech-incremental-api
May 19, 2026
Merged

ti_abusech: switch URL data stream to incremental API#18945
kcreddy merged 7 commits into
elastic:mainfrom
kcreddy:ti_abusech-incremental-api

Conversation

@kcreddy

@kcreddy kcreddy commented May 11, 2026

Copy link
Copy Markdown
Contributor

Proposed commit message

ti_abusech: switch URL data stream to incremental API

Replace the full-export ZIP download (/downloads/json) with the
incremental /v1/urls/recent/ endpoint. The full export ingested all
active indicators every interval as a single batch, causing large
memory spikes. The incremental API returns only recently added URLs
as plain JSON, eliminating ZIP decompression and bulk allocation.

IOC expiration is now user-configurable via a new
ioc_expiration_duration setting (default 90d), computed from
first_seen rather than event.ingested. This matches the pattern
already used by the malware, threatfox, and malwarebazaar sibling
data streams.

The /v1/urls/recent/ endpoint returns at most 1000 entries from the
last 3 days. CEL guards reject the deprecated /downloads/json URL
and intervals longer than 72h with clear error messages instead of
silent failures. The interval description and README document the
1000-entry and 3-day constraints.

Changes:
- CEL program: GET to /v1/urls/recent/, decode body.urls, no ZIP
- CEL guards: reject deprecated URL, reject interval > 72h
- Manifest: new default URL, ioc_expiration_duration var, 10m interval
  with documented API limits
- Pipeline: replace interval-based expiration with duration-based,
  rename _conf.ioc_expiration_duration to abusech.url field
- Fields: replace labels.interval with abusech.url.ioc_expiration_duration
- Tests: update fixtures to incremental API response format
- Script tests: deprecated URL, interval too long
- README: upgrade guidance, API limits, IOC expiration duration
- Version: 3.6.0 -> 4.0.0 (breaking: labels.interval field removed)

Checklist

  • I have reviewed tips for building integrations and this pull request is aligned with them.
  • I have verified that all data streams collect metrics or logs.
  • I have added an entry to my package's changelog.yml file.
  • I have verified that Kibana version constraints are current according to guidelines.
  • I have verified that any added dashboard complies with Kibana's Dashboard good practices

How to test this PR locally

System tests (with incremental API) are successful

--- Test results for package: ti_abusech - START ---
╭────────────┬─────────────┬───────────┬───────────┬────────┬───────────────╮
│ PACKAGE    │ DATA STREAM │ TEST TYPE │ TEST NAME │ RESULT │  TIME ELAPSED │
├────────────┼─────────────┼───────────┼───────────┼────────┼───────────────┤
│ ti_abusech │ url         │ system    │ auth      │ PASS   │ 38.823285375s │
╰────────────┴─────────────┴───────────┴───────────┴────────┴───────────────╯
--- Test results for package: ti_abusech - END   ---
Done

Updated policy tests pass

--- Test results for package: ti_abusech - START ---
╭────────────┬─────────────┬───────────┬──────────────────┬────────┬───────────────╮
│ PACKAGE    │ DATA STREAM │ TEST TYPE │ TEST NAME        │ RESULT │  TIME ELAPSED │
├────────────┼─────────────┼───────────┼──────────────────┼────────┼───────────────┤
│ ti_abusech │ url         │ policy    │ test-all.yml     │ PASS   │ 13.076738333s │
│ ti_abusech │ url         │ policy    │ test-default.yml │ PASS   │ 12.032978875s │
╰────────────┴─────────────┴───────────┴──────────────────┴────────┴───────────────╯
--- Test results for package: ti_abusech - END   ---
Done

Added new script tests

  • deprecated_url: Configuring the deprecated full export URL (/downloads/json) results in a configuration error event and no data collection.
  • interval_too_long: Exceeding 72h results in a configuration error event and no data collection.
--- Test results for package: ti_abusech - START ---
╭────────────┬─────────────┬───────────┬───────────────────┬��───────┬────────────────╮
│ PACKAGE    │ DATA STREAM │ TEST TYPE │ TEST NAME         │ RESULT │   TIME ELAPSED │
├────────────┼─────────────┼───────────┼───────────────────┼────────┼────────────────┤
│ ti_abusech │ url         │ script    │ deprecated_url    │ PASS   │ 1m6.596685584s │
│ ti_abusech │ url         │ script    │ interval_too_long │ PASS   │ 1m7.995157459s │
╰────────────┴─────────────┴───────────┴───────────────────┴────────┴────────────────╯
--- Test results for package: ti_abusech - END   ---
Done
Replace the full-export ZIP download (/downloads/json) with the
incremental /v1/urls/recent/ endpoint. The full export ingested all
active indicators every interval as a single batch, causing large
memory spikes. The incremental API returns only recently added URLs
as plain JSON, eliminating ZIP decompression and bulk allocation.

IOC expiration is now user-configurable via a new
ioc_expiration_duration setting (default 90d), computed from
first_seen rather than event.ingested. This matches the pattern
already used by the malware, threatfox, and malwarebazaar sibling
data streams.

Changes:
- CEL program: GET to /v1/urls/recent/, decode body.urls, no ZIP
- Manifest: new default URL, ioc_expiration_duration var, 10m interval
- Pipeline: replace interval-based expiration with duration-based,
  rename _conf.ioc_expiration_duration to abusech.url field
- Fields: replace labels.interval with abusech.url.ioc_expiration_duration
- Tests: update fixtures to incremental API response format
- Version: 3.6.0 -> 4.0.0 (breaking: labels.interval field removed)
@github-actions

github-actions Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Vale Linting Results

Summary: 11 suggestions found

💡 Suggestions (11)
File Line Rule Message
packages/ti_abusech/_dev/build/docs/README.md 28 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.
packages/ti_abusech/_dev/build/docs/README.md 99 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.
packages/ti_abusech/_dev/build/docs/README.md 100 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.
packages/ti_abusech/_dev/build/docs/README.md 101 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.
packages/ti_abusech/_dev/build/docs/README.md 196 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.
packages/ti_abusech/docs/README.md 28 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.
packages/ti_abusech/docs/README.md 99 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.
packages/ti_abusech/docs/README.md 100 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.
packages/ti_abusech/docs/README.md 101 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.
packages/ti_abusech/docs/README.md 322 Elastic.WordChoice Consider using 'blocklist' instead of 'blacklist', unless the term is in the UI.
packages/ti_abusech/docs/README.md 889 Elastic.WordChoice Consider using 'misuse' instead of 'abuse', unless the term is in the UI.

The Vale linter checks documentation changes against the Elastic Docs style guide.

To use Vale locally or report issues, refer to Elastic style guide for Vale.

@kcreddy kcreddy self-assigned this May 11, 2026
@kcreddy kcreddy added Integration:ti_abusech abuse.ch Team:Security-Service Integrations Security Service Integrations team [elastic/security-service-integrations] enhancement New feature or request breaking change labels May 11, 2026
@elastic-vault-github-plugin-prod

elastic-vault-github-plugin-prod Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

🚀 Benchmarks report

To see the full report comment with /test benchmark fullreport

@andrewkroh andrewkroh added the documentation Improvements or additions to documentation. Applied to PRs that modify *.md files. label May 11, 2026
kcreddy and others added 3 commits May 12, 2026 19:57
Add a CEL program guard that detects when the URL is still set to the
old full export endpoint (/downloads/json) after an upgrade and returns
a clear error message instead of failing with a cryptic JSON unmarshal
error.

Update the README troubleshooting section with v4.0.0 upgrade steps
and revise the breaking-change changelog entry to cover the URL
migration, labels.interval removal, and the new IOC Expiration Duration
setting.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kcreddy
kcreddy marked this pull request as ready for review May 14, 2026 07:37
@kcreddy
kcreddy requested a review from a team as a code owner May 14, 2026 07:37
@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

Pinging @elastic/security-service-integrations (Team:Security-Service Integrations)

required: true
show_user: false
default: https://urlhaus.abuse.ch/downloads/json
default: https://urlhaus-api.abuse.ch/v1/urls/recent/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Similar affect applies here as well.

Existing installations are unaffected. Fleet's package_policy API copies the current input defaults into the policy at creation time, so only package policies created after this change will pick up the new default.

A specific surface level error message is added to CEL program to help with user intervention.

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.

I think we need to flag the two maxima in play here: three days, and 1000 entries. If the interval is greater than three days (or slightly less than three days for grace), we should raise an error (or at the very least, warn in the UI docs). But we still have the issue that if the entries come in at greater than 1000 entries within the configured interval, the oldest ones will be lost. Do we have an estimate of document addition to the service and the degree of burstiness?

@kcreddy kcreddy May 18, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated README, manifest and also CEL with an error 122a7c2.

Do we have an estimate of document addition to the service and the degree of burstiness?

During steady-state, I noticed anywhere between 1-6 URLs being fetched every 10 min. However, the burst could vary significantly as it is community-driven.

show_user: true
default: 1h
description: Duration between requests to the URLhaus API. Supported units for this parameter are h/m/s. Example `24h`. As data dump is generated every 5 minutes, it should be greater than 5 minutes.
default: 10m

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.

This value has significant implications for data loss and network costs. I think that needs to be made clear; too long and we lose documents, too short and re-collect the same documents repeatedly without benefit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've updated the documentation and manifest description with relevant information: 122a7c2

kcreddy added 2 commits May 18, 2026 16:25
The URLhaus /v1/urls/recent/ endpoint returns at most 1000 entries
from the last 3 days. Add a CEL guard that rejects intervals longer
than 72h, since the API cannot serve data beyond that window.

Update the interval description in the manifest and the README to
explain the 1000-entry and 3-day constraints and the trade-off
between too-long (data loss) and too-short (redundant collection)
intervals.

Add script tests for both CEL guards: deprecated URL detection and
interval-too-long rejection.
@github-actions

Copy link
Copy Markdown
Contributor

TL;DR

The Check integrations ti_abusech Buildkite step failed because the URL stream policy golden files were not updated after adding the new interval > 72h CEL guard. Update the policy expected outputs to include the new guard branch and state.interval_value.

Remediation

  • Regenerate/update:
    • packages/ti_abusech/data_stream/url/_dev/test/policy/test-default.expected
    • packages/ti_abusech/data_stream/url/_dev/test/policy/test-all.expected
      so they match the current template output from packages/ti_abusech/data_stream/url/agent/stream/cel.yml.hbs.
  • Re-run package checks/tests for ti_abusech in CI after updating the snapshots.
Investigation details

Root Cause

122a7c2170 introduces new CEL logic in packages/ti_abusech/data_stream/url/agent/stream/cel.yml.hbs:

  • duration(state.interval_value) > duration("72h") guard (see lines 38-48)
  • state.interval_value usage in the generated program

But in the same failing commit, policy expected files still contain the older program form and do not include the new guard branch nor state.interval_value under state:

  • packages/ti_abusech/data_stream/url/_dev/test/policy/test-default.expected (program at lines 17-67, state at lines 78-79)
  • packages/ti_abusech/data_stream/url/_dev/test/policy/test-all.expected (program at lines 29-78, state at lines 169-170)

A subsequent PR commit (8de7614f40, message: fix policy tests) updates exactly those two policy expected files to add the missing guard/state entries, which is consistent with this failure mode.

Evidence

  • Build: https://buildkite.com/elastic/integrations/builds/42991
  • Job/step: Check integrations ti_abusech
  • Key log excerpt:
    • 🚨 Error: The command exited with status 1
    • user command error: exit status 1
  • Build command from failure summary:
    • .buildkite/scripts/test_one_package.sh packages/ti_abusech origin/main 122a7c21703d8922933c982696bc85551004a571

Verification

  • elastic-package check -v was runnable locally and passed on 122a7c2170.
  • Full package test reproduction was not runnable in this environment due missing Docker daemon (Cannot connect to the Docker daemon at unix:///var/run/docker.sock).

Follow-up

If CI still fails after snapshot update, share the full failing section from the Buildkite test phase (before teardown) to confirm whether there is an additional non-policy failure.

Note

🔒 Integrity filter blocked 2 items

The following items were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

What is this? | From workflow: PR Buildkite Detective

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

@elasticmachine

Copy link
Copy Markdown

💚 Build Succeeded

History

cc @kcreddy

@kcreddy
kcreddy requested a review from efd6 May 18, 2026 12:03
@kcreddy
kcreddy merged commit 70926ca into elastic:main May 19, 2026
11 checks passed
@elastic-vault-github-plugin-prod

Copy link
Copy Markdown
Contributor

Package ti_abusech - 4.0.0 containing this change is available at https://epr.elastic.co/package/ti_abusech/4.0.0/

herrBez pushed a commit to herrBez/integrations that referenced this pull request Jun 1, 2026
Replace the full-export ZIP download (/downloads/json) with the
incremental /v1/urls/recent/ endpoint. The full export ingested all
active indicators every interval as a single batch, causing large
memory spikes. The incremental API returns only recently added URLs
as plain JSON, eliminating ZIP decompression and bulk allocation.

IOC expiration is now user-configurable via a new
ioc_expiration_duration setting (default 90d), computed from
first_seen rather than event.ingested. This matches the pattern
already used by the malware, threatfox, and malwarebazaar sibling
data streams.

The /v1/urls/recent/ endpoint returns at most 1000 entries from the
last 3 days. CEL guards reject the deprecated /downloads/json URL
and intervals longer than 72h with clear error messages instead of
silent failures. The interval description and README document the
1000-entry and 3-day constraints.

Changes:
- CEL program: GET to /v1/urls/recent/, decode body.urls, no ZIP
- CEL guards: reject deprecated URL, reject interval > 72h
- Manifest: new default URL, ioc_expiration_duration var, 10m interval
  with documented API limits
- Pipeline: replace interval-based expiration with duration-based,
  rename _conf.ioc_expiration_duration to abusech.url field
- Fields: replace labels.interval with abusech.url.ioc_expiration_duration
- Tests: update fixtures to incremental API response format
- Script tests: deprecated URL, interval too long
- README: upgrade guidance, API limits, IOC expiration duration
- Version: 3.6.0 -> 4.0.0 (breaking: labels.interval field removed)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change documentation Improvements or additions to documentation. Applied to PRs that modify *.md files. enhancement New feature or request Integration:ti_abusech abuse.ch Team:Security-Service Integrations Security Service Integrations team [elastic/security-service-integrations]

4 participants