Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ All notable changes to OriginWeave are documented in this file. The format follo

- Added `originweave_core::release_acceptance`, a deterministic fail-closed benchmark release-decision contract that requires one authoritative result for every mandatory suite, bounds explicit buyer-visible limitations, rejects duplicate limitation claim identities, and rejects non-canonical surrounding whitespace rather than normalizing it into an alternate claim spelling.
- Added active PR #237's versioned controlled-deterministic benchmark registry and raw-evidence threshold evaluator: exactly 100 clean trials are required per case, missing or inconclusive required cases cannot pass the suite, conditional cases follow the declared support profile, registry versions are exact, and impossible aggregate counters fail closed.
- Refreshed the product and technical gap baseline with the 2026-08-24 live inventory: 158 open pull requests (44 ready, 114 draft), refreshed exact base/head evidence for the #208–#222 release, enterprise-approval, BAP, and WARC/PROV chains, the governance issue additions #212 and #215, and a required-check provider-failure record for the fail-closed Strix re-dispatches on #208/#218/#220.
- Refreshed the product and technical gap baseline with the 2026-08-24 live inventory: 158 open pull requests (44 ready, 114 draft), refreshed exact base/head evidence for the #208–#222 release, enterprise-approval, BAP, WARC/PROV chains, the governance issue additions #212 and #215, and a required-check provider-failure record for the fail-closed Strix re-dispatches on #208/#218/#220.
- Added a dated product and technical gap baseline that separates protected-main implementation truth, active pull-request evidence, live review/check blockers, and the next buyer-visible Phase 1 acceptance work.
- Refreshed the product and technical gap baseline with the current open-PR inventory and exact base/head evidence for the newest Chromium, BAP, extraction, WARC, and idempotency slices.
- Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline.
Expand Down Expand Up @@ -71,6 +71,7 @@ All notable changes to OriginWeave are documented in this file. The format follo

### Security

- Active PR #322 rejects C0/C1 controls, Unicode `U+2028` LINE SEPARATOR / `U+2029` PARAGRAPH SEPARATOR, and `Bidi_Control` formatting scalars in controlled-benchmark run-context evidence identities while preserving ordinary visible RTL scripts, preventing byte-exact benchmark metadata from rendering as a misleading direction or multiple log/report records.
- Explicit proxy server identifiers require ASCII decimal port tokens before numeric range parsing, preventing Rust-specific leading-plus spellings from widening proxy authority.
- Raw page content cannot become a trusted instruction.
- Raw secrets are rejected and secret-capable actions require an opaque broker handle.
Expand Down Expand Up @@ -103,4 +104,4 @@ All notable changes to OriginWeave are documented in this file. The format follo
- The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it.
- The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels.

[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD
[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD
51 changes: 39 additions & 12 deletions crates/originweave-core/src/controlled_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ pub enum ControlledBenchmarkSuiteError {
/// Name of the non-canonical reproducibility-context field.
field: &'static str,
},
/// A required or observed reproducibility-context field contains a control character.
/// A required or observed reproducibility-context field contains a C0/C1 control, Unicode line/paragraph separator, or bidirectional formatting character.
ControlCharacterRunContext {
/// Name of the invalid reproducibility-context field.
field: &'static str,
Expand Down Expand Up @@ -491,7 +491,7 @@ impl fmt::Display for ControlledBenchmarkSuiteError {
),
Self::ControlCharacterRunContext { field } => write!(
formatter,
"controlled benchmark run context field {field} contains a control character"
"controlled benchmark run context field {field} contains a C0/C1 control, Unicode line/paragraph separator, or bidirectional formatting character"
),
Self::RunContextMismatch { field } => write!(
formatter,
Expand Down Expand Up @@ -609,18 +609,20 @@ fn evaluate_valid_controlled_benchmark_case(
/// Evaluate raw controlled-suite evidence only when execution context is reproducible.
///
/// Every required identity in the expected and observed contexts must be nonblank,
/// free of surrounding whitespace, and free of control characters before the
/// observed context is compared byte-for-byte with the expected context. This does
/// not authenticate either context; it is a fail-closed comparison boundary for a
/// benchmark runner or durable evidence pipeline that performs that authentication.
/// free of surrounding whitespace, and free of C0/C1 controls, Unicode line/paragraph
/// separators, or bidirectional formatting characters before the observed context is
/// compared byte-for-byte with the expected context. This does not authenticate either
/// context; it is a fail-closed comparison boundary for a benchmark runner or durable
/// evidence pipeline that performs that authentication.
///
/// # Errors
///
/// Returns [`ControlledBenchmarkSuiteError::InvalidRunContext`] for a blank
/// required or observed identity, [`ControlledBenchmarkSuiteError::NonCanonicalRunContext`]
/// for surrounding whitespace, [`ControlledBenchmarkSuiteError::ControlCharacterRunContext`]
/// for control characters, and [`ControlledBenchmarkSuiteError::RunContextMismatch`]
/// for the first mismatched identity. After context validation, all errors from
/// for C0/C1 controls, Unicode line/paragraph separators, or bidirectional formatting
/// characters, and [`ControlledBenchmarkSuiteError::RunContextMismatch`] for the first
/// mismatched identity. After context validation, all errors from
/// [`evaluate_controlled_benchmark_suite`] are preserved unchanged.
pub fn evaluate_controlled_benchmark_suite_for_run(
expected_context: ControlledBenchmarkRunContext<'_>,
Expand Down Expand Up @@ -728,9 +730,10 @@ pub fn evaluate_controlled_benchmark_suite(

/// Rejects benchmark-owned run identities that would make evidence boundaries ambiguous.
///
/// The controlled benchmark owns these labels, so surrounding whitespace and control
/// characters are invalid here even though browser-issued protocol identifiers are
/// preserved losslessly at their own bounded-context boundary.
/// The controlled benchmark owns these labels, so surrounding whitespace, C0/C1
/// controls, Unicode line/paragraph separators, and bidirectional formatting controls
/// are invalid here even though browser-issued protocol identifiers are preserved
/// losslessly at their own bounded-context boundary.
fn validate_run_context_field(
field: &'static str,
value: &str,
Expand All @@ -742,12 +745,36 @@ fn validate_run_context_field(
if trimmed != value {
return Err(ControlledBenchmarkSuiteError::NonCanonicalRunContext { field });
}
if value.chars().any(char::is_control) {
if value.chars().any(|character| {
character.is_control() || is_unicode_line_separator(character) || is_bidi_control(character)
}) {
return Err(ControlledBenchmarkSuiteError::ControlCharacterRunContext { field });
}
Ok(())
}

/// Identifies Unicode separators that create a new rendered line or paragraph.
///
/// `char::is_control` covers General Category `Cc`, not `Zl`/`Zp`. Allowing these
/// separators inside benchmark-owned evidence identity would therefore let one
/// byte-exact identity render as multiple log or report records.
fn is_unicode_line_separator(character: char) -> bool {
matches!(character, '\u{2028}' | '\u{2029}')
}

/// Identifies the Unicode `Bidi_Control` set without rejecting ordinary RTL scripts.
///
/// These format characters can reorder the visual presentation of otherwise byte-exact
/// benchmark identity strings. The benchmark owns this metadata grammar, so admitting
/// them would make logs, reports, and signed-summary review ambiguous even when storage
/// preserves the original scalar sequence.
fn is_bidi_control(character: char) -> bool {
matches!(
character,
'\u{061c}' | '\u{200e}' | '\u{200f}' | '\u{202a}'..='\u{202e}' | '\u{2066}'..='\u{2069}'
)
}

/// Verifies that a per-trial outcome counter cannot claim more observations than trials.
fn validate_counter(
counter: &'static str,
Expand Down
93 changes: 82 additions & 11 deletions crates/originweave-core/tests/controlled_benchmark_run_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,89 @@ fn control_character_in_reproducibility_context_fails_closed() {
}

#[test]
fn visible_unicode_reproducibility_context_remains_valid() {
let mut context = run_context();
context.reasoning_configuration = "결정적-ブラウザ-oráculo-v1";
fn unicode_line_separator_in_reproducibility_context_fails_closed() {
for hostile in ["runner\u{2028}spoofed=passed", "runner\u{2029}spoofed=passed"] {
let mut context = run_context();
context.reasoning_configuration = hostile;

assert_eq!(
evaluate_controlled_benchmark_suite_for_run(
context,
context,
CONTROLLED_DETERMINISTIC_REGISTRY_VERSION,
base_profile(),
&[],
),
Err(ControlledBenchmarkSuiteError::ControlCharacterRunContext {
field: "reasoning_configuration",
}),
"Unicode line/paragraph separators must not split benchmark evidence identity rendering: {hostile:?}"
);
}
}

#[test]
fn bidi_control_in_reproducibility_context_fails_closed() {
for hostile in [
"runner\u{061c}suffix",
"runner\u{200e}suffix",
"runner\u{200f}suffix",
"runner\u{202a}suffix",
"runner\u{202b}suffix",
"runner\u{202c}suffix",
"runner\u{202d}suffix",
"runner\u{202e}suffix",
"runner\u{2066}suffix",
"runner\u{2067}suffix",
"runner\u{2068}suffix",
"runner\u{2069}suffix",
] {
let mut context = run_context();
context.reasoning_configuration = hostile;

assert_eq!(
evaluate_controlled_benchmark_suite_for_run(
context,
context,
CONTROLLED_DETERMINISTIC_REGISTRY_VERSION,
base_profile(),
&[],
),
Err(ControlledBenchmarkSuiteError::ControlCharacterRunContext {
field: "reasoning_configuration",
}),
"bidi formatting controls must not become benchmark evidence identity: {hostile:?}"
);
}

let invalid = ControlledBenchmarkSuiteError::ControlCharacterRunContext {
field: "reasoning_configuration",
};
assert_eq!(
evaluate_controlled_benchmark_suite_for_run(
context,
context,
CONTROLLED_DETERMINISTIC_REGISTRY_VERSION,
base_profile(),
&[],
),
Ok(BenchmarkSuiteOutcome::Inconclusive)
invalid.to_string(),
"controlled benchmark run context field reasoning_configuration contains a C0/C1 control, Unicode line/paragraph separator, or bidirectional formatting character"
);
}

#[test]
fn visible_unicode_reproducibility_context_remains_valid() {
for visible in [
"결정적-ブラウザ-oráculo-v1",
"محرك-מבחן-v1",
] {
let mut context = run_context();
context.reasoning_configuration = visible;

assert_eq!(
evaluate_controlled_benchmark_suite_for_run(
context,
context,
CONTROLLED_DETERMINISTIC_REGISTRY_VERSION,
base_profile(),
&[],
),
Ok(BenchmarkSuiteOutcome::Inconclusive),
"visible Unicode and RTL scripts remain valid without bidi controls: {visible:?}"
);
}
}
64 changes: 64 additions & 0 deletions docs/doctoring/controlled-benchmark-unicode-identity-security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Controlled benchmark Unicode identity security

Status: active-PR doctoring for #322, stacked on #237. This record does not describe protected-main shipment.

## Problem

`ControlledBenchmarkRunContext` carries OriginWeave-owned reproducibility identities such as source revision, Chromium revision, OS image, hardware profile, protocol-adapter set, model/provider route, reasoning configuration, fixture/corpus version, and seed-set identity. These strings are compared byte-for-byte and are expected to appear in logs, reports, retained benchmark evidence, and later signed evidence summaries.

The #237 parent rejects blank values, surrounding whitespace, and C0/C1 control characters. The first #322 slice then closed Unicode bidirectional formatting controls. A second review of the same evidence-rendering boundary found a separate gap: Rust `char::is_control` covers General Category `Cc`, while `U+2028` LINE SEPARATOR and `U+2029` PARAGRAPH SEPARATOR are `Zl` and `Zp`. An internal LS or PS survives surrounding-whitespace checks yet can render one byte-exact identity as multiple lines or paragraphs. That can make log/report review disagree with the stored scalar sequence even without a bidi override.

Unicode UAX #44 defines `Zl` as `U+2028 LINE SEPARATOR` only, `Zp` as `U+2029 PARAGRAPH SEPARATOR` only, and `Cc` separately as C0/C1 controls. The Unicode Standard's newline guidance states that LS and PS are unambiguous Unicode line and paragraph separators and, unlike the other newline forms it discusses, are not encoded as control codes. The application therefore cannot rely on `char::is_control` to close this rendering boundary.

This differs from browser-issued protocol identity. OriginWeave must preserve WebDriver BiDi addresses such as `browser.UserContext` losslessly because Browser Session does not own that external identifier grammar. The controlled benchmark does own its reproducibility-label grammar, so a narrower fail-closed admission rule is appropriate here.

## Decision

Reject rendering controls that can make controlled-benchmark identity presentation ambiguous:

- C0/C1 control characters already covered by `char::is_control`;
- `U+2028` LINE SEPARATOR and `U+2029` PARAGRAPH SEPARATOR;
- `U+061C` ARABIC LETTER MARK;
- `U+200E` LEFT-TO-RIGHT MARK and `U+200F` RIGHT-TO-LEFT MARK;
- `U+202A..U+202E` embedding, override, and pop-directional-formatting controls; and
- `U+2066..U+2069` isolate controls.

Do not reject ordinary visible right-to-left scripts. The regression therefore keeps Arabic and Hebrew text admissible when no formatting control or line/paragraph separator is present. No Unicode normalization, confusable folding, ASCII-only restriction, or browser-protocol normalization is introduced by this slice.

The implementation remains dependency-free. One private helper matches `U+2028`/`U+2029`; another matches the exact `Bidi_Control` scalar set. Both run before run-context equality can influence suite evidence. The existing typed `ControlCharacterRunContext` failure remains the fail-closed public diagnostic so this repair does not widen the public error surface unnecessarily.

## Test-first evidence

Parent exact: `ea92c326e2dc4e3daa869aff1266c10b05453e7d`.

Bidi test-first exact: `a02b1d6e3a2034944ba337973bc9d0385976907c`. The test injects all 12 `Bidi_Control` scalar values and requires `ControlCharacterRunContext`; it also adds a visible Arabic/Hebrew acceptance case. The parent source only used `char::is_control`, so the bidi-control assertions were semantic RED by source inspection. Hosted execution was unavailable at creation time; this is not an executed RED claim.

Bidi production repair exact: `11b2c16422481eb674a90ae61e941a5b247c4024`. `validate_run_context_field` rejects C0/C1 controls or the exact bidi-control set before equality comparison.

Line-separator test-first exact: `4c17e0b36aa627e036dcbe3d82332a7bc0cc594c`. It adds hostile `U+2028` and `U+2029` identities and requires the same typed fail-closed error. The predecessor source accepted both because they are not `Cc` and are outside `Bidi_Control`; this is again a semantic RED established from the exact predecessor source, not a claim that a hosted runner executed the failing test.

Line-separator production repair exact: `c89f76c5761a46745a5d1d2176828bfd6c3c2f32`. The validator adds the narrow `is_unicode_line_separator` predicate, and rustdoc plus the public diagnostic are widened only enough to describe the actual admitted rendering-control boundary. Exact `4f8bce55d2f03a73260c2629c0eca2012f79a539` aligns the diagnostic regression with that public message.

None of these changes alter benchmark thresholds, registry membership/versioning, browser authority, model/provider routing, evidence signing/persistence, workflows, or release authority.

## Alternatives considered

Rejecting all non-ASCII text was rejected because it would conflate script diversity with rendering-control risk and would break legitimate internationalized operator-controlled labels. Applying Unicode normalization was rejected because canonicalization would change exact evidence identity and does not by itself solve directional reordering or explicit line/paragraph separation. Treating byte equality as sufficient was rejected because UAX #9 separates logical order from rendered order for bidirectional text, while the Unicode Standard explicitly assigns LS and PS line/paragraph boundary semantics.

A broader Unicode security profile may be warranted later for externally supplied product identifiers. That is not silently introduced here: any expansion to confusable, default-ignorable, script-restriction, or normalization policy needs its own threat model, compatibility analysis, tests, and versioned contract.

## Risks and follow-up

The current repair prevents the reviewed directional and line/paragraph rendering controls from entering benchmark-owned run-context identities but does not authenticate those identities. The durable evidence owner must still bind them to execution artifacts. It also does not claim complete Unicode spoofing resistance; UTS #39 covers a wider space of identifier-security mechanisms.

The parent #237 and child #322 remain active-PR evidence. Exact-head repository contracts, rustfmt, locked tests, strict Clippy, rustdoc/API docs, 100% production function/line/region/branch coverage, and required security/review workflows must execute before promotion. Cancelled, skipped, queued-only, predecessor, or status-only jobs are not passing evidence.

## References

Unicode Consortium. (2026, August 27). *Unicode security mechanisms* (Unicode Technical Standard #39, Version 18.0.0, Revision 34). https://www.unicode.org/reports/tr39/tr39-34.html

Unicode Consortium. (2025, August 13). *Unicode bidirectional algorithm* (Unicode Standard Annex #9, Version 17.0.0, Revision 51). https://www.unicode.org/reports/tr9/tr9-51.html

Unicode Consortium. (2025). *Unicode Character Database* (Unicode Standard Annex #44, Version 17.0.0). https://www.unicode.org/reports/tr44/

Unicode Consortium. (2025). *The Unicode Standard, Version 17.0.0: Chapter 5, Implementation guidelines—Newline guidelines*. https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-5/
Loading