Skip to content

web: add integrity check for marked CDN and cap highlight regex input - #109

Merged
ilblackdragon merged 4 commits into
nearai:mainfrom
lawyered0:chore/web-csp-query-hardening
Feb 17, 2026
Merged

ilblackdragon merged 4 commits into
nearai:mainfrom
lawyered0:chore/web-csp-query-hardening

Conversation

@lawyered0

Copy link
Copy Markdown
Contributor

Summary

  • Add Subresource Integrity and crossorigin attributes for the CDN markdown script in the web UI.
  • Pin the script to with the computed SHA-384 hash.
  • Bound regex highlighting query input to 100 chars before escape/compile to reduce pathological runtime risk from very long user search terms.

Validation

  • Verified committed diff is minimal (2 files, 2 functional edits).
  • passes.
  • SRI hash for the pinned URL was re-verified before submission.

Notes

  • Note: server-side search input hardening is not changed in this PR (client-side regex path only).

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @lawyered0, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on two key areas: bolstering the security of the web UI by adding integrity checks for a third-party CDN script, and enhancing client-side stability by preventing potential performance degradation from overly complex or long user-provided regex queries.

Highlights

  • CDN Security Enhancement: Implemented Subresource Integrity (SRI) and "crossorigin" attributes for the "marked" CDN script, pinning it to version "17.0.2" with a specific SHA-384 hash to ensure script authenticity and prevent tampering.
  • Client-Side Performance Improvement: Introduced a character limit of 100 for the regex highlighting query input in "app.js" to mitigate potential pathological runtime risks from excessively long user search terms.
Changelog
  • src/channels/web/static/app.js
    • Limited the "highlightQuery" function's input "query" to 100 characters using "slice(0, 100)" before escaping and compiling it into a regular expression.
  • src/channels/web/static/index.html
    • Updated the "marked" CDN script tag to specify version "17.0.2".
    • Added "integrity="sha384-pN9zSKOnTZwXRtYZAu0PBPEgR2B7DOC1aeLxQ33oJ0oy5iN1we6gm57xldM2irDG"" attribute for Subresource Integrity.
    • Included "crossorigin="anonymous"" attribute for proper SRI validation.
Activity
  • The author verified that the committed diff was minimal, affecting only two files with two functional edits.
  • All tests passed successfully.
  • The Subresource Integrity (SRI) hash for the pinned CDN URL was re-verified by the author before submission.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces two important security and stability improvements. First, it adds Subresource Integrity (SRI) to the marked script loaded from a CDN, which prevents the execution of a compromised script. It also pins the script to a specific version, which is a best practice. Second, it limits the length of the search query used for client-side highlighting to mitigate the risk of Regular Expression Denial of Service (ReDoS) attacks with very long inputs. My feedback focuses on improving the maintainability of the query length limit.

Comment thread src/channels/web/static/app.js Outdated
Comment on lines +1041 to +1042
const normalizedQuery = query.slice(0, 100);
const queryEscaped = normalizedQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

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.

medium

To improve readability and maintainability, it's a good practice to avoid magic numbers. I suggest defining 100 as a constant. This suggestion also combines the slicing and escaping into a single line for conciseness. Ideally, the constant would be defined at the top of the file with other constants.

Suggested change
const normalizedQuery = query.slice(0, 100);
const queryEscaped = normalizedQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const HIGHLIGHT_QUERY_MAX_LEN = 100;
const queryEscaped = query.slice(0, HIGHLIGHT_QUERY_MAX_LEN).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

@lawyered0

Copy link
Copy Markdown
Contributor Author

Addressed the maintainability feedback: the query length magic number is now defined as a top-level constant and reused by memory search normalization/snippet/highlight paths in the latest commit. Please re-review when convenient; no functional changes besides this consistency cleanup.

@lawyered0

Copy link
Copy Markdown
Contributor Author

Follow-up patch in latest commit: add compatibility fallback in session DB load path. If is absent, we now read legacy , then proceed. This directly addresses issue #108 / legacy key auth failure without broad behavior changes.

@lawyered0

Copy link
Copy Markdown
Contributor Author

Follow-up patch in latest commit: add compatibility fallback in session DB load path. If nearai.session_token is absent, we now read legacy nearai.session, then proceed. This directly addresses issue #108 / legacy key auth failure without broad behavior changes.

@lawyered0
lawyered0 force-pushed the chore/web-csp-query-hardening branch from b3f25c0 to dbad950 Compare February 16, 2026 16:18
@lawyered0

Copy link
Copy Markdown
Contributor Author

Split applied: moved the DB compatibility fallback for legacy nearai session key into a separate PR for cleaner scope.

What remains here (PR #109):

  • CDN SRI + crossorigin hardening for marked
  • Memory search query normalization/highlight hardening

Compatibility fallback moved to: #111

@ilblackdragon ilblackdragon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review: LGTM with minor suggestions

SRI for marked CDN (index.html)

The SRI change is solid:

  • Pins marked to a specific version (17.0.2) instead of floating latest, preventing supply-chain attacks via CDN compromise.
  • The sha384 integrity hash is verified correct -- I independently fetched the file and recomputed the digest.
  • crossorigin="anonymous" is correctly set (required for SRI with cross-origin scripts).
  • The switch from marked.min.js to marked.umd.min.js is correct for <script> tag usage (UMD exposes the marked global that app.js references at line 283).

One note: this jumps from the previously-unpinned version (which resolved to v15.0.12) to v17.0.2. That is a two-major-version bump. The call site (marked.parse(text)) is simple enough that it likely works fine, but worth being aware of if any rendering regressions appear.

Regex input cap (app.js)

The normalizeSearchQuery function and MEMORY_SEARCH_QUERY_MAX_LENGTH = 100 limit are a reasonable defense against ReDoS. The regex-escape already exists (replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), but capping input length before escape+compile is a good belt-and-suspenders measure.

Minor observations (non-blocking):

  1. Double normalization in snippetAround: The searchMemory function already normalizes the query before passing it to snippetAround, but snippetAround normalizes again internally. This is harmless (idempotent) but slightly redundant. The same applies to highlightQuery -- the caller already passes a normalized query, but the function normalizes again. If these functions are only called from searchMemory, the internal normalization is unnecessary. That said, having the defensive check at both layers is fine for a small codebase like this.

  2. Consider adding maxlength="100" to the HTML input element (<input type="text" id="memory-search"> at line 78 of index.html). This gives the user immediate visual feedback about the limit and prevents typing beyond 100 chars, complementing the JS-side truncation. Not required, just a UX nicety.

  3. Trailing blank line removal (line 1044-1045 in the diff): The PR removes a blank line between highlightQuery and the // --- Logs --- section comment. This is fine but cosmetic -- mentioning it only for completeness.

Approving -- both changes are clear security improvements with no functional regressions.

@tribendu

Copy link
Copy Markdown

Batch 2 PR Review: nearai/ironclaw PRs 111, 110, 109, 103, 95, 74

Reviewer: AI Sub-Agent
Date: February 17, 2026
Review Method: Manual diff analysis with focus on code quality, bugs, security, performance, test coverage, and Rust best practices


PR #111: Fix backwards compatibility for nearai.session_token

Summary

This PR adds backwards compatibility for the nearai session management by implementing a fallback mechanism. When nearai.session_token is not found, the system now attempts to fall back to the legacy nearai.session setting. The change is minimal and focused on the session token retrieval logic in SessionManager::renew_session(). The implementation uses a graceful fallback pattern with proper error handling and logging.

Pros

  • Graceful degradation: Implements backwards compatibility without breaking existing functionality
  • Clear error handling: Preserves original error handling for both primary and fallback paths
  • Informative logging: Adds a warning message when falling back to legacy session storage
  • Minimal change scope: Only affects session token retrieval logic, reducing risk of regression
  • Proper pattern: Uses idiomatic Rust if let Some(...) for optional value handling

Concerns

  • Technical debt: This adds maintenance burden by keeping two parallel session storage schemas
  • No migration path: There's no clear deprecation timeline or migration strategy for the legacy schema
  • Silent fallback: The warning log may be missed in production, leading to continued use of deprecated schema
  • Error propagation: The error messages remain generic - they could be more specific about whether the primary or fallback path failed

Suggestions

  • Add a migration function to convert legacy sessions to the new format periodically
  • Consider adding telemetry/metrics to track usage of the legacy fallback path
  • Document the expected lifecycle and deprecation timeline for nearai.session
  • Add a configuration option to disable the fallback after migration is complete
  • Consider making the fallback opt-in rather than silent to encourage migration

PR #110: Add env docs for local LLM providers

Summary

This PR updates the .env.example file to document environment variables for local LLM providers including Ollama, LM Studio, vLLM, and other OpenAI-compatible endpoints. The changes are purely documentation-focused, providing clear examples of environment variable configurations for users who want to use local models instead of the default NEAR AI backend.

Pros

  • User-friendly: Makes it easy for new users to configure local LLM providers
  • Comprehensive: Covers multiple backend options (Ollama, OpenAI-compatible)
  • Clear defaults: Shows default values for URLs and ports
  • Well-organized: Groups related configurations logically
  • No code changes: Documentation-only PR, zero risk of breaking functionality

Concerns

  • No validation: Environment variables are documented but there's no validation that the backend value matches the selected provider
  • Incomplete examples: Example model names are specific but may not match what users have installed
  • Missing authentication notes: Doesn't document that LLM_API_KEY is optional for local servers

Suggestions

  • Add comments explaining when LLM_API_KEY is optional vs required
  • Include a link to documentation for each provider
  • Add a note about model availability requiring local installation
  • Consider adding environment variable validation logic in a future PR
  • Document how to switch between providers at runtime
  • Add example commands to test connections (e.g., curl http://localhost:11434/api/tags)

PR #109: Normalize memory search query and update marked.js

Summary

This PR addresses two security and stability issues in the web interface. First, it adds input normalization for memory search queries to prevent excessive query lengths and invalid input types. Second, it updates the marked.js dependency to a specific version with integrity hashing for supply chain security. The changes are defensive in nature, preventing potential DoS attacks and ensuring the integrity of third-party JavaScript dependencies.

Pros

  • Input validation: Adds MEMORY_SEARCH_QUERY_MAX_LENGTH constant (100 chars) to prevent excessively long queries
  • Type safety: Normalizes queries to string type, preventing crashes from non-string input
  • Supply chain security: Uses SRI (Subresource Integrity) hashing for marked.js CDN dependency
  • Consistent application: Applies normalization throughout search functions (searchMemory, snippetAround, highlightQuery)
  • Clear error handling: Early return when normalized query is empty

Concerns

  • Arbitrary limit: 100-character limit may be too restrictive for complex semantic search queries
  • Silent truncation: Queries longer than the limit are silently truncated without user feedback
  • CDN dependency: Still relies on external CDN for marked.js despite SRI - could use bundled version
  • No rate limiting: Query length limit doesn't prevent rapid-fire spam queries
  • Client-side only: Validation happens on client side only - server should also validate

Suggestions

  • Increase the limit or make it configurable (e.g., 200-500 characters)
  • Add user feedback when query is truncated (toast message or inline warning)
  • Implement rate limiting on the /api/memory/search endpoint
  • Add server-side query validation to defend against bypassed client checks
  • Consider bundling marked.js with the application to eliminate CDN dependency
  • Add unit tests for the normalization function covering edge cases (null, undefined, empty string, very long string)

PR #103: Per-request model override for OpenAI-compatible API

Summary

This is a significant feature PR that adds per-request model override capability across the entire LLM provider ecosystem. Previously, all requests used the active model, but now clients can specify a different model per request. The changes span multiple modules: request structs now include optional model fields, providers check for request-level models before falling back to defaults, OpenAI-compatible endpoint validates model names, and comprehensive integration tests verify functionality. The PR includes migration documentation and removes the previous strict model validation that returned 404 for non-active models.

Pros

  • Well-architected: Uses Option with builder pattern (with_model()) for clean API
  • Comprehensive testing: Updated integration tests to mock model tracking and verify model override works
  • Full stack coverage: Changes propagate from API entry points through worker layer to actual providers
  • Good error handling: Validates model name length (MAX_MODEL_NAME_BYTES: 256) before processing
  • Backwards compatible: Existing behavior preserved when model field is not provided
  • Code quality: Consistent pattern across CompletionRequest and ToolCompletionRequest
  • Documentation: Updates FEATURE_PARITY.md to reflect new capability

Concerns

  • No model validation: The endpoint now accepts ANY model name without checking if it exists or is configured
  • Validation bypasses providers: Validation happens at HTTP layer, but providers don't validate model availability
  • Security implications: Users can request models they shouldn't have access to; cost tracking may break
  • Incomplete fallback: If provider doesn't support the requested model, behavior is undefined
  • Missing permissions: No RBAC or policy checking for model override capability
  • Potential for confusion: Active model concept remains but can be overridden per request

Suggestions

  • Add a method to validate if a model exists/can be used before making the request
  • Implement optional model allowlist/denylist configuration per user or API key
  • Add telemetry to track model usage patterns across different requests
  • Document the precedence: request model > active model > provider default
  • Consider adding a force_model flag that fails fast if model is unavailable
  • Add tests for error cases (non-existent model, invalid characters, empty string)
  • Update cost tracking to handle per-request model pricing differences
  • Consider adding model availability check to LlmProvider trait's set_model() method

PR #95: Add Venice AI provider and embeddings

Summary

This PR adds comprehensive support for Venice AI as both an LLM provider and an embeddings provider. It introduces a new VeniceProvider module with full API integration including Venice-specific features like web search, web scraping, and dynamic model pricing fetched from the /models endpoint. The PR also adds VeniceEmbeddings for semantic search, updates configuration handling, and integrates Venice into the setup wizard. The implementation includes extensive unit tests, proper error handling, caching of model catalogs with TTL, and follows the existing provider patterns.

Pros

  • Fully featured: Implements complete Venice API including proprietary parameters (web search, scraping, system prompt)
  • Dynamic pricing: Fetches model catalog with pricing from Venice API, updates cost tracking automatically
  • Good caching: Uses 1-hour TTL for model catalog to reduce API calls
  • Comprehensive testing: Extensive unit tests for message conversion, serialization, cost calculation
  • Type safety:Uses properly typed structures and enums for Venice-specific functionality
  • Consistent patterns: Follows existing provider patterns (NearAiProvider, etc.)
  • Error handling: Proper HTTP error mapping (401→AuthFailed, 429→RateLimited)
  • Config validation: Validates Venice-specific config values (web_search must be "off"/"on"/"auto")

Concerns

  • Large file: venice.rs is 880 lines - consider splitting into smaller modules
  • Lock choice: Uses std::sync::RwLock with comment about async safety - risk if async code is added later
  • Secret handling: API key exposed as String in multiple places despite being wrapped in SecretString in Config
  • No retry logic: Network requests don't have exponential backoff for transient failures
  • Hardcoded timeout: 120-second timeout may be too long or too short for different use cases
  • Cache misses on errors: If catalog fetch fails, stale cache is kept indefinitely
  • Embeddings dimension hardcoding: Magic numbers for dimensions (1536, 3072) scattered in code

Suggestions

  • Split venice.rs into multiple modules: provider.rs, models.rs, api.rs
  • Consider using tokio::sync::RwLock for future-proofing with async code
  • Add retry logic with exponential backoff for API requests
  • Make timeout configurable via VeniceConfig
  • Add metrics for cache hit/miss ratio
  • Document the security model: when are secrets exposed?
  • Extract dimension mapping to a constant or helper function
  • Add integration test that calls actual Venice API with test credentials
  • Consider adding health check endpoint for provider connectivity
  • Add support for streaming responses (if Venice API supports it)

PR #74: Security fix: Enhanced HTTP response size validation

Summary

This PR strengthens the HTTP tool's defense against OOM attacks by implementing two-stage response size validation. First, it checks the Content-Length header before downloading any content to immediately reject oversized responses. Second, it streams the response body with a hard size cap during download, protecting against malicious or misconfigured servers that may send incorrect or missing Content-Length headers. The implementation uses streaming with futures::StreamExt and enforces a consistent 5 MB limit across both stages.

Pros

  • Defense in depth: Two-stage validation (header check + streaming cap) protects against multiple attack vectors
  • Early rejection: Aborts request before downloading potentially malicious content
  • Streaming approach: Memory-efficient, doesn't load entire response before checking size
  • Informative logging: Warns with URL and size details when rejecting responses
  • Consistent constant: Uses same 5 MB limit as WASM HTTP wrapper
  • Well-documented: Clear comments explain the security rationale and 5 MB justification
  • Test coverage: Includes test verifying the constant value is reasonable

Concerns

  • Header spoofing: Relies on server sending accurate Content-Length - malicious servers can send small value then send large body
  • No byte limit on individual chunks: Only checks cumulative size; individual chunks could still be large
  • Error message exposure: Returns detailed size information in errors which might leak information
  • No rate limiting: An attacker could still send many small requests to exhaust resources
  • Hardcoded limit: 5 MB may be too small for some legitimate use cases (e.g., downloading large JSON datasets)

Suggestions

  • Add per-chunk size limit (e.g., 1 MB max per chunk) in addition to cumulative limit
  • Consider making MAX_RESPONSE_SIZE configurable per tool or per request
  • Add telemetry/metrics for rejected responses to detect DDoS patterns
  • Implement request rate limiting in addition to size limiting
  • Add option to override limit for authorized users/admins
  • Consider adding a timeout for the streaming operation
  • Add test cases for Content-Length header spoofing attempt
  • Document how users can work around the limit if needed (e.g., using multiple smaller requests with pagination)

Overall Recommendations

High Priority

  1. PR feat: support per-request model override in /v1/chat/completions #103: Address the security implications of unrestricted model override before merging - add allowlist/denylist functionality
  2. PR fix: check Content-Length before downloading HTTP response body #74: Consider making the size limit configurable for flexibility while maintaining security

Medium Priority

  1. PR feat: add Venice AI as first-class LLM backend #95: Refactor the large venice.rs file and add retry logic for network resilience
  2. PR web: add integrity check for marked CDN and cap highlight regex input #109: Add server-side validation to complement client-side checks
  3. PR llm: fallback to legacy nearai.session key when loading DB session #111: Add migration strategy for legacy session tokens

Low Priority

  1. PR docs: add .env.example examples for Ollama and OpenAI-compatible #110: Add validation documentation and testing commands
  2. PR feat: add Venice AI as first-class LLM backend #95: Extract magic numbers and add integration tests
  3. PR web: add integrity check for marked CDN and cap highlight regex input #109: Consider removing CDN dependency for marked.js

General Observations

  • All PRs follow Rust best practices and maintain code quality
  • Test coverage is generally good, though some integration tests could be expanded
  • Error handling is consistent across the codebase
  • Documentation (comments and inline docs) is clear and helpful
  • Most PRs are backwards compatible, which is good for production deployments

@ilblackdragon
ilblackdragon merged commit d04af5c into nearai:main Feb 17, 2026
2 checks passed
@github-actions github-actions Bot mentioned this pull request Feb 17, 2026
jaswinder6991 pushed a commit to jaswinder6991/ironclaw that referenced this pull request Feb 26, 2026
…nearai#109)

* web: add integrity check for marked CDN and cap highlight regex input

* web: normalize memory search query before snippet+highlight matching

* web: place memory query length constant with top-level config

---------

Co-authored-by: Clawyered <clawyered@macbookair.home>
Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com>
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
…nearai#109)

* web: add integrity check for marked CDN and cap highlight regex input

* web: normalize memory search query before snippet+highlight matching

* web: place memory query length constant with top-level config

---------

Co-authored-by: Clawyered <clawyered@macbookair.home>
Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com>
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.

3 participants