Skip to content

fix(web): reject trailing-newline and dot-segment model IDs; pin PURL separator (#13, #14) - #121

Open
drk-ram wants to merge 1 commit into
GenAI-Security-Project:mainfrom
drk-ram:fix/14-validate-model-id-strictly
Open

drk-ram wants to merge 1 commit into
GenAI-Security-Project:mainfrom
drk-ram:fix/14-validate-model-id-strictly

Conversation

@drk-ram

@drk-ram drk-ram commented Nov 1, 2026

Copy link
Copy Markdown

Summary

Fixes #14. Fixes #13 (already resolved on main; this adds the missing regression coverage).

#14 - input validation vs. sanitization

/generate in web_controller.py already calls is_valid_hf_input(model_id) on the raw form value before html.escape, so the ordering described in the issue is fixed. Auditing the validator itself turned up gaps that make "validate the raw input" weaker than intended:

  • re.match(r"^...$") lets a trailing newline through ($ matches before a final \n), e.g. org/model\n and https://huggingface.co/org/model\n.
  • . and .. were accepted as owner/repo segments (org/.., ../model).
  • The URL branch accepted arbitrary whitespace/control characters.

Changes (src/controllers/web_controller.py):

  • validate each segment with fullmatch
  • reject whitespace/control characters and ./.. segments
  • docstring noting the function must receive unsanitized input

Valid IDs (owner/model, Hub URLs incl. /tree/main) behave exactly as before.

#13 - PURL slash encoding

generator.py and the .replace('/', '/') no-op no longer exist: PURLs are built by AIBOMService._generate_purl through packageurl-python, giving pkg:huggingface/owner/model@<version>. For the huggingface type the owner is the purl namespace and the repo the name, and / is the namespace delimiter, so encoding it as %2F (the issue's suggestion) would yield a non-conformant purl. No production change is needed; this PR adds tests pinning the literal separator, the namespace/name round trip, and the component purl/bom-ref.

Tests

  • tests/test_input_validation.py (new): validator accept/reject cases; validator receives the raw (not HTML-escaped) value; invalid input never reaches HfApi/AIBOMService; hostile input is escaped in the error page; trailing-newline input is rejected
  • tests/test_purl_construction.py: no %2F separator, round trip through PackageURL.from_string, component purl/bom-ref

The newline, dot-segment and trailing-newline route tests fail against the previous validator.

Validation

  • pytest --no-cov (Python 3.11): 194 passed

… separator (GenAI-Security-Project#13, GenAI-Security-Project#14)

GenAI-Security-Project#14: /generate already validates the raw input before html.escape, but
the validator itself was looser than intended. Regexes anchored with
`$` accept a trailing "\n" (and the URL branch accepted any query or
whitespace), and "." / ".." passed as owner or repo segments. Use
fullmatch per segment, reject whitespace/control characters and dot
segments, and document that the function must receive unsanitized input.

GenAI-Security-Project#13: generator.py and its `.replace('/', '/')` no-op were replaced by
packageurl-python in AIBOMService._generate_purl, which emits
pkg:huggingface/<owner>/<name>@<version>. The slash is the purl
namespace delimiter and must not be encoded as %2F, so the issue's
suggested replacement would produce a non-conformant purl. Add tests
that pin the literal separator and the namespace/name round trip.

Add tests for validation order (validator receives the raw value, invalid
input never reaches the Hub or service, escaped in the error page).

Fixes GenAI-Security-Project#14
Fixes GenAI-Security-Project#13

Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.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.

Bug: Input validation occurs after sanitization Bug: PURL format incorrect - .replace('/', '/') is a no-op

1 participant