feat(orchestration): compose security scenarios
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
This commit is contained in:
parent
ad46cc89fc
commit
d96aab2321
20 changed files with 1464 additions and 30 deletions
|
|
@ -60,6 +60,9 @@ TRUST_STATES = {
|
|||
"tenant_onboarding_trust",
|
||||
}
|
||||
SCENARIO_AUTHORITIES = {"platform", "netkingdom", "tenant"}
|
||||
SECRET_REFERENCE_PATTERN = re.compile(
|
||||
r"(?:[a-z][a-z0-9+.-]*://\S+|(?:/|\./|\.\./)\S+)"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -127,6 +130,10 @@ def is_type(value: Any, declared_type: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def is_secret_reference(value: Any) -> bool:
|
||||
return isinstance(value, str) and SECRET_REFERENCE_PATTERN.fullmatch(value) is not None
|
||||
|
||||
|
||||
def validate_constraints(value: Any, param: dict[str, Any], path: str) -> list[Issue]:
|
||||
issues: list[Issue] = []
|
||||
constraints = param.get("constraints", {})
|
||||
|
|
@ -277,6 +284,18 @@ def validate_parameters(spec: dict[str, Any]) -> list[Issue]:
|
|||
if item.get("sensitivity") in {"security_sensitive", "secret_reference"} and item.get("tuning_authority") == "tenant_tunable":
|
||||
issues.append(issue("ERROR", path, "security-sensitive parameters cannot be tenant_tunable"))
|
||||
|
||||
if item.get("sensitivity") == "secret_reference":
|
||||
if declared_type != "string":
|
||||
issues.append(issue("ERROR", f"{path}.type", "secret_reference parameters must use string type"))
|
||||
if "default" in item and not is_secret_reference(item["default"]):
|
||||
issues.append(
|
||||
issue(
|
||||
"ERROR",
|
||||
f"{path}.default",
|
||||
"secret_reference default must be a URI or explicit absolute/relative path",
|
||||
)
|
||||
)
|
||||
|
||||
if "default" in item and declared_type in PARAM_TYPES and not is_type(item["default"], declared_type):
|
||||
issues.append(issue("ERROR", f"{path}.default", f"default does not match type {declared_type!r}"))
|
||||
if declared_type in PARAM_TYPES:
|
||||
|
|
@ -405,6 +424,15 @@ def validate_override_allowed(param: dict[str, Any], value: Any, scenario_author
|
|||
if scenario_authority == "tenant" and sensitivity in {"security_sensitive", "secret_reference"}:
|
||||
issues.append(issue("ERROR", path, f"tenant authority cannot override {sensitivity} parameter {name!r}"))
|
||||
|
||||
if sensitivity == "secret_reference" and not is_secret_reference(value):
|
||||
issues.append(
|
||||
issue(
|
||||
"ERROR",
|
||||
path,
|
||||
f"override for {name!r} must be a secret-reference URI or explicit absolute/relative path",
|
||||
)
|
||||
)
|
||||
|
||||
if not is_type(value, declared_type):
|
||||
issues.append(issue("ERROR", path, f"override for {name!r} does not match type {declared_type!r}"))
|
||||
issues.extend(validate_constraints(value, param, path))
|
||||
|
|
@ -433,10 +461,23 @@ def compose_scenario(declarations: list[Declaration], scenario: dict[str, Any])
|
|||
|
||||
selected: list[Declaration] = []
|
||||
for cap_id in required_caps:
|
||||
matches = [declaration for declaration in declarations if cap_id in declaration.capabilities]
|
||||
matches = sorted(
|
||||
[declaration for declaration in declarations if cap_id in declaration.capabilities],
|
||||
key=lambda declaration: declaration.id,
|
||||
)
|
||||
if not matches:
|
||||
issues.append(issue("ERROR", "scenario.requires.capabilities", f"no declaration provides {cap_id!r}"))
|
||||
continue
|
||||
if len(matches) > 1:
|
||||
issues.append(
|
||||
issue(
|
||||
"ERROR",
|
||||
"scenario.requires.capabilities",
|
||||
f"ambiguous providers {[declaration.id for declaration in matches]}; "
|
||||
"use the Security Scenario Composer to pin one explicitly",
|
||||
)
|
||||
)
|
||||
continue
|
||||
selected.append(matches[0])
|
||||
|
||||
# Preserve order while deduplicating declarations selected for several capabilities.
|
||||
|
|
@ -444,6 +485,16 @@ def compose_scenario(declarations: list[Declaration], scenario: dict[str, Any])
|
|||
for declaration in selected:
|
||||
selected_by_id.setdefault(declaration.id, declaration)
|
||||
|
||||
for declaration_id in overrides:
|
||||
if declaration_id not in selected_by_id:
|
||||
issues.append(
|
||||
issue(
|
||||
"ERROR",
|
||||
f"scenario.parameter_overrides.{declaration_id}",
|
||||
"override targets an unselected declaration",
|
||||
)
|
||||
)
|
||||
|
||||
composed = {
|
||||
"scenario": scenario.get("id", "scenario:unnamed"),
|
||||
"authority": authority,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue