Merge pull request 'DEV-000049: audit production advisory boundary' (#22) from codex/post-advisory-production-audit-v1 into main
CI / test (push) Successful in 11s
CI / test (push) Successful in 11s
This commit was merged in pull request #22.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000048-creator-advisory-console.json",
|
||||
"task_id": "DEV-000048",
|
||||
"task_revision": 2
|
||||
"task_file": "dev/tasks/DEV-000049-post-advisory-production-audit.json",
|
||||
"task_id": "DEV-000049",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"task_id": "DEV-000049",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0006",
|
||||
"base_repo_revision": "4de34ad8f8389d28b1120a3c6203ea32b8c7ff22",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add and execute a repeatable read-only production audit for the authenticated Creator advisory boundary after DEV-000048 promotion, proving contract limits, authority-field rejection, explicit degraded behavior and cognitive-worker isolation without selecting or calling an external provider.",
|
||||
"requirement_ids": ["ADR-0006", "DEV-000046", "DEV-000048", "INV-AUTH-001", "INV-EPI-001"],
|
||||
"allowed_change_scope": {"modules": [], "paths": ["tests/cognitive_advisory/**", "docs/PROJECT_STATUS.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000049-post-advisory-production-audit.json"]},
|
||||
"forbidden_change_scope": {"paths": ["architecture/**", "ci/**", ".gitea/**", "contracts/**", "modules/**", "adapters/**", "cmd/**", "database/**", "deploy/**", "security/**"]},
|
||||
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0},
|
||||
"contracts": {"consume": ["creator-advisory-v1", "CreatorSession", "Situation FocusBinding"], "may_change": []},
|
||||
"semantic_owner": "none-audit-only",
|
||||
"canonical_state": [],
|
||||
"state_transitions": [],
|
||||
"side_effects_and_privileges": ["read production runtime/container metadata", "issue an ephemeral Creator session and advisory request", "no database mutation, external provider egress or credential persistence"],
|
||||
"persistence_and_migrations": [],
|
||||
"failure_retry_restart": ["audit fails closed on missing focus or credential", "audit never prints credentials or session tokens", "advisory content remains ephemeral and is not retried"],
|
||||
"assumptions": ["production contains at least one active Creator-owned FocusBinding", "Creator credential remains mounted as a Docker secret", "docker-test remains prohibited"],
|
||||
"acceptance_scenarios": ["authenticated 16 KiB advisory returns only PROVIDER_UNAVAILABLE with empty text and citations", "unauthenticated advisory is denied", "browser authority-field injection is rejected", "the audit sentinel is absent from cognitive-worker logs", "cognitive worker has no database, Commit/Executor, Creator or provider credential environment/mount", "Creator console exposes the advisory UI", "production revision and health remain valid"],
|
||||
"required_tests": ["PowerShell parse", "architecture preflight", "exact diff scope", "production advisory audit", "production artifact confirmation", "production health", "CI"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# Assistant project status
|
||||
|
||||
Audit date: 2026-08-16. Active audit: DEV-000044 revision 3.
|
||||
Audit date: 2026-08-16. Active audit: DEV-000049 revision 1.
|
||||
|
||||
## Delivered
|
||||
|
||||
@@ -28,4 +28,6 @@ DEV-000045 closes the DEV-000044 artifact-provenance finding for new promotions
|
||||
|
||||
ADR-0006 and DEV-000046 establish the provider-neutral, non-canonical Creator advisory boundary. DEV-000048 connects it to authenticated Creator Web and adds the advisory console; until a provider/data-egress decision is accepted, it deliberately reports `PROVIDER_UNAVAILABLE` and sends nothing outside the private runtime topology.
|
||||
|
||||
DEV-000049 adds and executes a repeatable production audit for that boundary. It proves that an authenticated 16 KiB message reaches only the deterministic unavailable binding, unauthenticated and browser-supplied authority fields fail closed, the request sentinel is absent from cognitive-worker logs, and the cognitive worker has no database, Commit/Executor, Creator or provider credential environment/mount.
|
||||
|
||||
These are explicit scope decisions, not hidden incomplete steps in DEV-000043. No production secret is stored in the repository.
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$DockerContext = 'docker-prod',
|
||||
[string]$BaseURL = 'https://assistent.cin.su',
|
||||
[string]$CoreContainer = 'assistant-core-runtime-1',
|
||||
[string]$PostgresContainer = 'assistant-postgres-1',
|
||||
[string]$CognitiveContainer = 'assistant-cognitive-worker-1'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
function Get-ContainerEnvironment([string]$Container) {
|
||||
$result = @{}
|
||||
$inspect = ((& docker.exe --context $DockerContext inspect $Container) -join "`n") | ConvertFrom-Json
|
||||
foreach ($entry in $inspect[0].Config.Env) {
|
||||
$parts = $entry -split '=', 2
|
||||
$result[$parts[0]] = if ($parts.Count -eq 2) { $parts[1] } else { '' }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Invoke-StatusRequest([string]$Uri, [hashtable]$Headers, [string]$Body) {
|
||||
$parameters = @{
|
||||
Uri = $Uri
|
||||
Method = 'Post'
|
||||
ContentType = 'application/json'
|
||||
SkipHttpErrorCheck = $true
|
||||
}
|
||||
if ($Headers.Count -gt 0) { $parameters.Headers = $Headers }
|
||||
if ($Body) { $parameters.Body = $Body }
|
||||
return Invoke-WebRequest @parameters
|
||||
}
|
||||
|
||||
$health = Invoke-RestMethod -Uri "$BaseURL/healthz" -Method Get
|
||||
if ($health.status -ne 'ok' -or $health.runtime -ne 'creator-web') { throw 'Creator Web health is invalid' }
|
||||
$page = Invoke-WebRequest -Uri "$BaseURL/" -Method Get
|
||||
if ($page.StatusCode -ne 200 -or $page.Content -notmatch 'Спросить Домового') { throw 'Advisory console is not exposed' }
|
||||
|
||||
$coreEnvironment = Get-ContainerEnvironment $CoreContainer
|
||||
$required = @('CREATOR_USERNAME', 'CREATOR_PASSWORD_FILE', 'POSTGRES_DATABASE', 'POSTGRES_USER', 'POSTGRES_PASSWORD')
|
||||
foreach ($name in $required) {
|
||||
if (-not $coreEnvironment.ContainsKey($name) -or [string]::IsNullOrWhiteSpace($coreEnvironment[$name])) {
|
||||
throw "Core runtime is missing $name"
|
||||
}
|
||||
}
|
||||
|
||||
$creatorPassword = ((& docker.exe --context $DockerContext exec $CoreContainer sh -c "cat '$($coreEnvironment['CREATOR_PASSWORD_FILE'])'") -join "`n").Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($creatorPassword)) { throw 'Creator password secret is unavailable' }
|
||||
$sessionRequest = @{username = $coreEnvironment['CREATOR_USERNAME']; password = $creatorPassword} | ConvertTo-Json -Compress
|
||||
$issued = Invoke-RestMethod -Uri "$BaseURL/api/session" -Method Post -ContentType 'application/json' -Body $sessionRequest
|
||||
$creatorPassword = $null
|
||||
if (-not $issued.Token) { throw 'Creator session was not issued' }
|
||||
$authorization = @{Authorization = "Bearer $($issued.Token)"}
|
||||
|
||||
$focusSql = "select session_id,workspace_id from situation.focus_bindings where active=true and actor_id='domovoy-creator-actor' order by session_id desc limit 1"
|
||||
$focusLine = ((& docker.exe --context $DockerContext exec --env "PGPASSWORD=$($coreEnvironment['POSTGRES_PASSWORD'])" $PostgresContainer psql -U $coreEnvironment['POSTGRES_USER'] -d $coreEnvironment['POSTGRES_DATABASE'] -AtF '|' -c $focusSql) -join '').Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or $focusLine -notmatch '^[-a-zA-Z0-9._:]+\|[-a-zA-Z0-9._:]+$') { throw 'Active Creator focus is unavailable' }
|
||||
$focus = $focusLine -split '\|', 2
|
||||
|
||||
$sentinelPrefix = "audit-$([guid]::NewGuid().ToString('N'))-"
|
||||
$message = $sentinelPrefix + ('a' * ((16 * 1024) - $sentinelPrefix.Length))
|
||||
$advisoryBody = @{message = $message; session_id = $focus[0]; workspace_id = $focus[1]} | ConvertTo-Json -Compress
|
||||
$advisory = Invoke-RestMethod -Uri "$BaseURL/api/advisory" -Method Post -ContentType 'application/json' -Headers $authorization -Body $advisoryBody
|
||||
if ($advisory.Version -ne 'creator-advisory-v1' -or $advisory.FinishReason -ne 'PROVIDER_UNAVAILABLE') { throw 'Advisory did not fail closed' }
|
||||
if (-not [string]::IsNullOrEmpty([string]$advisory.AdvisoryText) -or ($null -ne $advisory.ReferencedExcerptIDs -and @($advisory.ReferencedExcerptIDs).Count -ne 0)) { throw 'Unavailable advisory contained generated content or citations' }
|
||||
|
||||
$unauthenticated = Invoke-StatusRequest "$BaseURL/api/advisory" @{} $advisoryBody
|
||||
if ($unauthenticated.StatusCode -ne 401) { throw "Unauthenticated advisory status=$($unauthenticated.StatusCode)" }
|
||||
$injectedBody = @{message = 'authority-injection'; session_id = $focus[0]; workspace_id = $focus[1]; ActorID = 'attacker'} | ConvertTo-Json -Compress
|
||||
$injected = Invoke-StatusRequest "$BaseURL/api/advisory" $authorization $injectedBody
|
||||
if ($injected.StatusCode -ne 400) { throw "Authority-field injection status=$($injected.StatusCode)" }
|
||||
|
||||
$cognitiveInspect = ((& docker.exe --context $DockerContext inspect $CognitiveContainer) -join "`n") | ConvertFrom-Json
|
||||
$forbiddenEnvironment = @($cognitiveInspect[0].Config.Env | Where-Object { ($_ -split '=', 2)[0] -match '(?i)(POSTGRES|DATABASE|COMMIT|EXECUTOR|CREATOR|PASSWORD|TOKEN|PROVIDER|API_KEY)' })
|
||||
if ($forbiddenEnvironment.Count -ne 0) { throw 'Cognitive worker has forbidden privileged/provider environment' }
|
||||
$forbiddenMounts = @($cognitiveInspect[0].Mounts | Where-Object { $_.Destination -match '(?i)(secret|credential|token|postgres|docker)' })
|
||||
if ($forbiddenMounts.Count -ne 0) { throw 'Cognitive worker has a forbidden credential or infrastructure mount' }
|
||||
$recentLogs = ((& docker.exe --context $DockerContext logs --since 5m $CognitiveContainer 2>&1) -join "`n")
|
||||
if ($recentLogs.Contains($sentinelPrefix)) { throw 'Advisory content leaked into cognitive-worker logs' }
|
||||
|
||||
$issued.Token = $null
|
||||
$authorization.Clear()
|
||||
$message = $null
|
||||
$advisoryBody = $null
|
||||
Write-Output 'DEV-000049 production Creator advisory boundary audit: PASS'
|
||||
Reference in New Issue
Block a user