Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5610637
feat(static): bound path postprocessing
chrisknvidia Sep 17, 2026
2eaf7e4
fix(cli): preserve recursive failure reporting
chrisknvidia Sep 17, 2026
10362c9
fix(cli): close recursive reporting edge cases
chrisknvidia Sep 17, 2026
0a29dbd
test(analyzer): define shell truthiness core contract
chrisknvidia Sep 17, 2026
201e881
fix(analyzer): detect bound shell truthiness
chrisknvidia Sep 17, 2026
f2ae98f
fix(analyzer): harden bound shell facts
chrisknvidia Sep 17, 2026
30766c6
test(analyzer): cover tm1 window identity contracts
chrisknvidia Sep 17, 2026
72b235f
feat(static): add bounded normalized view helpers
chrisknvidia Sep 17, 2026
04a967f
fix(analyzer): reconcile tm1 window identity
chrisknvidia Sep 17, 2026
d370b5d
style(tests): format tm1 window contracts
chrisknvidia Sep 17, 2026
e614b31
fix(analyzer): preserve colliding tm1 call identities
chrisknvidia Sep 17, 2026
8fcc330
test(analyzer): cover tm1 cap-stable identity
chrisknvidia Sep 17, 2026
6d31205
fix(analyzer): stabilize tm1 cap identity and order
chrisknvidia Sep 17, 2026
631f215
test(analyzer): cover mixed tm1 identity collision
chrisknvidia Sep 17, 2026
0c5b1b8
fix(analyzer): preserve tm1 lexical cap prefix
chrisknvidia Sep 17, 2026
d856d88
test(analyzer): cover cross-line tm1 identity
chrisknvidia Sep 17, 2026
8ccaffd
test(python): define execution-surface classification contract
chrisknvidia Sep 17, 2026
fa7d369
feat(python): classify executable source surfaces
chrisknvidia Sep 17, 2026
d4d4414
test(python): cover execution surfaces end to end
chrisknvidia Sep 17, 2026
bb7e309
fix(context): preserve provider cache boundaries
chrisknvidia Sep 17, 2026
03e85b1
fix(context): retain exact truncated Python text
chrisknvidia Sep 17, 2026
d697c5a
fix(context): publish bounded decoded Python views
chrisknvidia Sep 17, 2026
ec7df73
fix(python): classify uv script launchers
chrisknvidia Sep 17, 2026
7134c5d
fix(python): honor uv filesystem aliases
chrisknvidia Sep 17, 2026
e0a780c
fix(python): fail closed on uv global options
chrisknvidia Sep 17, 2026
c0be0a7
test(python): gate uv shebang probes to POSIX
chrisknvidia Sep 17, 2026
83d52c5
fix(python): account for option-like uv paths
chrisknvidia Sep 17, 2026
266d42e
fix(context): separate artifact reason type
chrisknvidia Sep 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 120 additions & 16 deletions src/skillspector/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ class SecurityTextView:
text: str
source_offsets: array[int] | None = None
reconstructions: tuple[SecurityTextReconstruction, ...] = ()
right_boundary_is_fixed: bool = False
right_boundary_recovery_start: int | None = None

def source_offset(self, derived_offset: int) -> int:
"""Map a derived character offset to the corresponding source offset."""
Expand Down Expand Up @@ -213,6 +215,7 @@ class _ObfuscatedIgnoreState:
".markdown",
".txt",
".py",
".pyw",
".sh",
".json",
".yaml",
Expand Down Expand Up @@ -366,6 +369,20 @@ def classify_artifact(path: str, data: bytes, *, referenced: bool = False) -> Ar
}


def promote_artifact_to_decoded_text(artifact: ArtifactRecord) -> None:
"""Apply a successful format-aware text decode without erasing prior limits."""
generic_binary_scope = artifact["content_kind"] is ContentKind.BINARY and (
artifact["disposition"] is ArtifactDisposition.OUT_OF_SCOPE
or artifact["disposition"] is ArtifactDisposition.PARTIAL
and "reason" not in artifact
)
artifact["content_kind"] = ContentKind.TEXT
artifact["decodable"] = True
artifact["misleading_extension"] = _suffix(artifact["path"]) in _BINARY_EXTENSIONS
if generic_binary_scope:
artifact["disposition"] = ArtifactDisposition.ANALYZED


def decode_text(data: bytes) -> str:
"""Return the loss-tolerant local text projection for static analyzers."""
return data.decode("utf-8", errors="replace")
Expand Down Expand Up @@ -1568,28 +1585,37 @@ def _contextual_default_ignorable_spans(text: str) -> Iterator[tuple[int, int]]:
yield span_start, end


def _normalization_ignored_spans_in_gap(
text: str,
gap_start: int,
gap_end: int,
) -> Iterator[tuple[int, int]]:
"""Yield the normalized-view removals inside one contextual gap."""
for match in _DEFAULT_IGNORABLE_RUN_PATTERN.finditer(text, gap_start, gap_end):
start, end = match.span()
ignored_start = (
start
if _is_unconditionally_ignored(text[start])
or _is_contextual_default_ignorable_offset(text, start)
else start + 1
)
ignored_end = (
end
if _is_unconditionally_ignored(text[end - 1])
or _is_contextual_default_ignorable_offset(text, end - 1)
else end - 1
)
if ignored_start < ignored_end:
yield ignored_start, ignored_end


def _normalization_ignored_spans(text: str) -> Iterator[tuple[int, int]]:
"""Yield whole default-ignorable runs removable by the normalized view."""
for gap_start, gap_end in _token_bridging_gap_spans(
text,
require_word_boundaries=False,
):
for match in _DEFAULT_IGNORABLE_RUN_PATTERN.finditer(text, gap_start, gap_end):
start, end = match.span()
ignored_start = (
start
if _is_unconditionally_ignored(text[start])
or _is_contextual_default_ignorable_offset(text, start)
else start + 1
)
ignored_end = (
end
if _is_unconditionally_ignored(text[end - 1])
or _is_contextual_default_ignorable_offset(text, end - 1)
else end - 1
)
if ignored_start < ignored_end:
yield ignored_start, ignored_end
yield from _normalization_ignored_spans_in_gap(text, gap_start, gap_end)


def _contextual_default_ignorable_offsets(text: str) -> Iterator[int]:
Expand Down Expand Up @@ -1669,6 +1695,55 @@ def normalized_security_view(text: str) -> SecurityTextView:
return SecurityTextView("normalized", output.getvalue(), offsets)


def normalized_security_prefix(text: str, max_chars: int) -> str:
"""Return an exact bounded prefix of the normalized security projection."""
if max_chars <= 0:
return ""

output = StringIO()
output_chars = 0

def append(character: str) -> bool:
nonlocal output_chars
normalized = unicodedata.normalize("NFKC", character).translate(ASCII_CONFUSABLE_SKELETON)
remaining = max_chars - output_chars
output.write(normalized[:remaining])
output_chars += min(len(normalized), remaining)
return output_chars >= max_chars

source_offset = 0
while source_offset < len(text) and output_chars < max_chars:
if not _is_token_gap_character(text[source_offset]):
if append(text[source_offset]):
break
source_offset += 1
continue

gap_start = source_offset
while source_offset < len(text) and _is_token_gap_character(text[source_offset]):
source_offset += 1
gap_end = source_offset
before_is_word = gap_start > 0 and _is_word_character(text[gap_start - 1])
after_is_word = gap_end < len(text) and _is_word_character(text[gap_end])
ignored_spans = iter(
_normalization_ignored_spans_in_gap(text, gap_start, gap_end)
if before_is_word or after_is_word
else ()
)
next_ignored = next(ignored_spans, None)
gap_offset = gap_start
while gap_offset < gap_end and output_chars < max_chars:
if next_ignored is not None and gap_offset == next_ignored[0]:
gap_offset = next_ignored[1]
next_ignored = next(ignored_spans, None)
continue
if not _is_unconditionally_ignored(text[gap_offset]) and append(text[gap_offset]):
break
gap_offset += 1

return output.getvalue()


def obfuscated_instruction_view(text: str) -> SecurityTextView:
"""Normalize text while removing only context-bound instruction fillers."""
output = StringIO()
Expand Down Expand Up @@ -1938,6 +2013,35 @@ def _requires_normalized_security_view(text: str) -> bool:
return not text.translate(_REMOVE_ALLOWED_FORMAT_CHARACTERS).isprintable()


def _has_derived_security_view(text: str) -> bool:
"""Return whether security projection produces a distinct text view."""
if text.isascii():
return (
_IGNORED_ASCII_CONTROL.search(text) is not None
or _has_letter_spacing_run(text)
or next(_obfuscated_instruction_matches(text), None) is not None
)
if any(
unicodedata.normalize("NFKC", character).translate(ASCII_CONFUSABLE_SKELETON) != character
for character in text
):
return True
if any(_is_unconditionally_ignored(character) for character in text):
return True
if next(_normalization_ignored_spans(text), None) is not None:
return True
if "\ufffd" in text or next(_compact_gap_offsets(text), None) is not None:
return True
return (
_has_letter_spacing_run(text)
or next(
_obfuscated_instruction_matches(text),
None,
)
is not None
)


def security_text_views(text: str) -> tuple[SecurityTextView, ...]:
"""Return distinct raw, normalized, and compact views deterministically."""
raw = SecurityTextView("raw", text)
Expand Down
Loading
Loading