Skip to content

GEN-09: model untrusted artefacts and seed injection canaries - #1340

Merged
Chris0Jeky merged 7 commits into
mainfrom
issue-1323/artefact-threat-model
Jul 13, 2026
Merged

GEN-09: model untrusted artefacts and seed injection canaries#1340
Chris0Jeky merged 7 commits into
mainfrom
issue-1323/artefact-threat-model

Conversation

@Chris0Jeky

@Chris0Jeky Chris0Jeky commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a dated untrusted-artefact threat model covering prompt injection, resource exhaustion, malformed containers, stored/preview XSS, content sniffing, link traps, cross-user access, consent/egress, and provenance.
  • Maps each scenario to current controls, required owner/gate, and accepted residual risk without claiming injection is solved.
  • Adds hostile transcript, PDF-text, and image-text canaries plus extra-field, vocabulary-escape, and malformed response fixtures.
  • Adds an independent fixture-contract oracle for case identity, hostile semantics, expectation vocabulary, bounded strict UTF-8, exact JSON-format handling, and manifest/directory agreement.

Refs #1323
Refs #1311

Deliberate partial scope

Per the overnight coordinator brief, this PR contains the documentation + inert fixtures only. It does not edit PR #1312's shared LlmCaptureTriagePrompt/parser files, wire runtime prompt rails, add extraction budgets, change egress consent, implement GEN-06 rendering, or change the proposal executor. The threat model marks those as open delivery gates and the issue remains open.

The prompt/extractor follow-up must bind these fixtures and prove hostile content produces only grounded task candidates or an empty verdict, while malformed/extra-field/vocabulary-escape responses take deterministic fallback. Until then, the PR explicitly says prompt-injection behavior is not verified.

Apply-time binding of effective cardId/boardId/columnId parameters to the authorized proposal scope is also an open gate owned by #1319 / PR #1339; this PR documents that boundary without changing runtime authorization.

Verification

  • UntrustedArtefactFixtureContractTests: 4 passed.
  • Full Taskdeck.Application.Tests: 3,221 passed, 0 failed, 0 skipped.
  • node scripts/check-docs-governance.mjs: passed.
  • node scripts/check-golden-principles.mjs: passed.
  • node scripts/check-github-ops-governance.mjs: passed.
  • New relative security-document links resolve locally.
  • Repository search found no runtime references to the fixture directory or canaries.
  • git diff --check origin/main...HEAD: passed.

Docs impact

Not verified / residual risk

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Self-review (FULL security posture)

Reviewed exact head 49c376c6 against the user-bounded #1323 doc+fixtures-only scope. No CRITICAL/HIGH/MEDIUM/LOW finding remains in this slice.

Checked factual current-vs-planned wording, all requested threat classes, explicit existing/new/residual columns, #1311/#1312/#1315-#1320 ownership, no claim that injection is solved, fixture manifest/file agreement, malformed/extra-field/vocabulary-escape cases, UTF-8/bounds/canaries, security index links, and the explicit NOT-verified list. Runtime rails remain deliberately deferred and #1323 stays open.

Fresh independent FULL review and final-head CI are still required.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the threat model for untrusted artefacts along with a set of hostile test fixtures and a contract test suite to validate them. The feedback focuses on improving the robustness of the contract tests, specifically by ensuring that the source kind assertions can handle duplicate kinds in the future using .Distinct(), and by explicitly validating the format types in the response fixture tests to prevent silent test passes on unexpected formats.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Merge-blocking CI evidence: CI Extended run https://github.com/Chris0Jeky/Taskdeck/actions/runs/29223718718 completed with startup_failure before any jobs. This is tracked in #1330 and is not being dismissed; under tonight's zero-failing/skipped-check gate, this PR is not merge-eligible even if required CI is green.

@Chris0Jeky Chris0Jeky left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh independent adversarial review — FULL security posture

Reviewed exact head 49c376c6fbb50c91230133891a7da4a97dfbccb0, issue #1323, the PR body/diff, all existing issue comments, review submissions, and both unresolved Gemini threads. I also checked the live #1312 prompt/parser seam and the shipped apply pipeline rather than treating the threat-model claims as self-proving.

All findings below are actionable under the repository's zero-skip review policy.

MEDIUM

  1. The threat model overstates the shipped apply-time authorization boundary.
    Evidence: docs/security/UNTRUSTED_ARTEFACT_THREAT_MODEL.md:58 says the executor “revalidates permission” as an existing control against operation-vocabulary mimicry. The shipped executor calls ValidatePermissionsAsync with the proposal BoardId and operations (AutomationExecutorService.cs:137-142), but the policy currently checks only that proposal board plus operation.TargetId for card targets (AutomationPolicyEngine.cs:72-95). The registry then executes mutable parameters.cardId, parameters.boardId, and parameters.columnId through unscoped service overloads. Consequently the effective operation target is not comprehensively bound to the authorized proposal scope. This is the same active repair seam now being handled in PR #1339; it is not yet a shipped control on this PR's base.
    Required: narrow the “existing control” wording to what is actually checked, identify the parameter-target binding as an open gate (cross-link #1339/#1319), and do not let the threat model imply that this defense is complete until that repair lands.

  2. The fixture contract checks that expectations are nonempty, not that the fixtures still represent the declared attacks.
    Evidence: UntrustedArtefactFixtureContractTests.cs:41-44 only requires each source to contain a generic phrase/canary and nonempty allowedVerdicts/forbiddenOutcomes; those arrays can be inverted or replaced with unknown vocabulary while the test remains green. At :54-68, JSON response fixtures only need to parse as an object. response-extra-field.json could lose every forbidden extra field, and response-vocabulary-escape.json could become {}, without failing the contract. That silently converts future security regressions into benign inputs.
    Required: validate the exact allowed/forbidden vocabulary and case-specific semantics (for example manifest-declared required tokens/fields and expected grounded title/evidence), and assert each response fixture contains the condition its ID/disposition claims.

  3. The documented “uniquely identified, bounded” fixture contract is only enforced for source cases, and unknown formats are accepted.
    Evidence: docs/security/UNTRUSTED_ARTEFACT_THREAT_MODEL.md:112 describes the fixture files as uniquely identified and bounded. The test makes only source IDs unique (:20) and applies strict UTF-8/size checks only to source files (:35-40). Response IDs/files/canaries are not unique or bounded, response-malformed.txt's canary is not declared/checked, and :65-68 treats every format other than literal json as “invalid-json.” Thus a typo/new format, duplicate response case, oversized response, or invalid UTF-8 response can remain green. This also confirms the unresolved Gemini format thread is valid.
    Required: use one strict bounded UTF-8 loader for every manifest-referenced fixture; enforce unique IDs, paths, and canaries across the relevant sets; explicitly allow only json/invalid-json; and prove the manifest references exactly the intended fixture files (or narrow the documentation claim).

LOW

  1. The source-kind assertion prevents legitimate suite growth within an existing kind.
    Evidence: the unresolved Gemini thread at UntrustedArtefactFixtureContractTests.cs:21-24 is correct: adding a second transcript/PDF/image attack case creates a duplicate and fails BeEquivalentTo, even though the coverage requirement is “all three kinds exist,” not “exactly one case per kind forever.”
    Required: assert the distinct kind set (while separately enforcing unique case IDs/files/canaries), or explicitly document and defend the exactly-one-case invariant in the thread.

Confirmed coverage / residual risk

  • GitHub reports zero closing issue references; #1323 remains open.
  • The changed-file set contains only docs, inert text/JSON fixtures, and the fixture-contract test. No #1312 prompt/parser/extractor wiring or other runtime rail was smuggled in.
  • Repository search found the canaries referenced only by this fixture directory, its contract test, and the threat-model documentation; they are not executable or runtime-loaded.
  • Exact-head local checks passed: fixture contract 3/3, docs governance, link targets, and git diff --check. The final-head required CI set is also green.
  • Runtime prompt resistance, strict #1312 containment, extraction budgets, serving headers, UI escaping, consent, and E2E triage remain deliberately unverified/open; that partial scope is documented correctly and is not itself a finding.

Copy link
Copy Markdown
Owner Author

Review fix evidence — exact head d28abd04021b3fdfa17ef308e08de760172ede37

All findings from the fresh FULL adversarial review and both pre-existing Gemini threads are addressed in one pushed batch:

Finding Fix commit(s) Evidence
MEDIUM — threat model overstated apply-time effective-target authorization d28abd04 Existing-control wording now names the shipped proposal-board/TargetId limit, marks parameter cardId/boardId/columnId binding as an open gate, and assigns #1319 / PR #1339 in both the matrix and delivery-gate table.
MEDIUM — hostile fixture semantics could drift benign or invert dfa230cd, 64d90e61 Response canaries are stable and manifest-declared; the test code independently pins every case's IDs, files, canaries, allowed/forbidden vocabulary, required hostile signals, and JSON attack shape.
MEDIUM — response identity/bounds/UTF-8/format coverage was incomplete dfa230cd, 64d90e61 All case IDs/files/canaries are globally unique and bounded; every manifest/fixture payload is bounded strict UTF-8; only exact json/invalid-json formats are accepted; the fixture directory must equal the manifest; JSON and malformed cases take explicit paths.
LOW / Gemini — duplicate source kinds blocked suite growth 64d90e61 Source-kind coverage now compares the distinct set while independent identity checks retain unique cases/files/canaries.

The two Gemini inline threads have commit-specific replies and are resolved.

Final local verification

  • Focused UntrustedArtefactFixtureContractTests: 4 passed
  • Full Taskdeck.Application.Tests: 3,221 passed; 0 failed; 0 skipped
  • Docs governance: passed
  • Golden principles: passed
  • GitHub operations governance: passed
  • Security-doc link targets: resolved
  • Runtime fixture/canary reference search: none
  • git diff --check origin/main...HEAD: passed
  • GitHub closing issue references after the PR-body update: zero; GEN-09: Untrusted-artefact threat model + prompt-injection rails #1323 remains open

Final-head CI and a fresh exact-head review remain required before this FULL-tier review gate is complete.

Copy link
Copy Markdown
Owner Author

@codex review

Please run a fresh independent FULL security-posture review of exact head d28abd04021b3fdfa17ef308e08de760172ede37, including all existing comments/threads and the repaired fixture-contract semantics. Confirm zero runtime rails were added and #1323 still has no closing reference.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: d28abd0402

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Chris0Jeky Chris0Jeky left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh exact-head adversarial re-review — FULL security posture

Reviewed exact head d28abd04021b3fdfa17ef308e08de760172ede37 independently against issue #1323, the complete 600-line final diff, the PR body, all five issue comments (including the final-head Codex no-major-issues comment), all four review submissions, all four inline comments, and both resolved Gemini threads. I also checked the shipped apply policy/handler seam and live PR #1339 rather than relying on the earlier fix summary.

LOW

  1. The claimed exact manifest/directory agreement ignores nested files.
    • Evidence: backend/tests/Taskdeck.Application.Tests/Services/UntrustedArtefactFixtureContractTests.cs:187-199 names the test FixtureDirectory_ShouldContainOnlyManifestReferencedFiles, but Directory.EnumerateFiles(..., SearchOption.TopDirectoryOnly) only compares direct children. Any file placed under Fixtures/untrusted-artefacts/<subdir>/ is invisible to the assertion, while docs/security/UNTRUSTED_ARTEFACT_THREAT_MODEL.md:113 says the manifest and fixture directory agree exactly. That lets an unmanifested fixture sit beside this security suite while the contract and documentation remain green.
    • Required: either reject all subdirectories explicitly, or enumerate recursively and compare normalized relative paths against the manifest. Keep the manifest path-safety constraint consistent with the chosen model.

Confirmed at this head

  • GitHub reports zero closing issue references; #1323 remains open.
  • The changed-file set is limited to security docs, inert text/JSON fixtures, and their test oracle. Repository search found no fixture/canary reference in backend/src, frontend runtime source, scripts, deploy, or workflows; no prompt rail or runtime execution path was added.
  • The apply-time authorization wording is now accurate: it names the shipped proposal-board/card-TargetId checks, explicitly leaves effective parameter binding open, and cross-links #1319 / PR #1339 in the matrix and delivery gates.
  • Case IDs, source kinds, exact allowed/forbidden expectation vocabulary, response dispositions/formats, global manifest ID/file/canary uniqueness, payload size, strict UTF-8 decoding, JSON-vs-invalid-JSON behavior, hostile source signals, and hostile response shapes are independently pinned. The earlier benign/inversion drift paths are closed for the six declared top-level cases.
  • Links resolve and the dated doc passes governance. Local exact-head verification passed: fixture contract 4/4, docs governance, golden-principles governance, GitHub-ops governance, and git diff --check.
  • Required CI is green except E2E was still running when this review was posted; the separately documented CI Extended startup failure remains tracked in #1330. Runtime prompt resistance, #1312 parser containment, extraction budgets, headers, UI escaping, consent, apply-target repair, and E2E artefact triage remain deliberately open/not verified.

No code or thread state was changed during this review.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Review fix evidence — exact head 1f7d4c99f729f4d42a32403d37a59884fa2366c8

The remaining LOW finding from review #1340 (review) is addressed:

Finding Fix commit Evidence
Nested unmanifested fixture files bypassed the claimed exact directory agreement 1f7d4c99 FixtureDirectory_ShouldContainOnlyManifestReferencedFilesAndNoSubdirectories now enumerates every descendant directory with SearchOption.AllDirectories and requires the result to be empty. This matches the existing basename-only manifest rule, so any nested fixture necessarily fails the oracle.

Verification after the fix:

  • Focused UntrustedArtefactFixtureContractTests: 4 passed; 0 failed; 0 skipped
  • Docs governance: passed
  • Golden-principles governance: passed
  • GitHub-operations governance: passed
  • Security-document index links: present and target resolves
  • git diff --check for the PR range and repair: passed
  • Worktree clean after commit; GitHub head is the SHA above
  • GitHub closing issue references: zero; GEN-09: Untrusted-artefact threat model + prompt-injection rails #1323 remains open

The finding was posted in a review summary rather than an inline review comment, so GitHub created no resolvable thread node for it. A GraphQL thread audit shows only the two prior Gemini threads, both already resolved; this evidence comment completes the applicable reply/fix mapping.

No production/runtime file, prompt rail, canonical direction doc, or PR body was changed.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

@codex review

Please run a fresh independent FULL security-posture review of exact head 1f7d4c99f729f4d42a32403d37a59884fa2366c8. Re-read all comments/reviews/threads and verify the nested-fixture repair, zero runtime rails, partial doc+inert-fixtures scope, and zero closing references.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 1f7d4c99f7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

CI Extended at exact head 1f7d4c99 ended in startup_failure before creating any jobs: https://github.com/Chris0Jeky/Taskdeck/actions/runs/29225553146. This is recorded on #1330 and remains a real merge blocker independent of required CI.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Required CI at exact head 1f7d4c99 is fully green: all 20 final-head checks including E2E passed. Fresh connector review found no major issues and zero closing refs remain. This partial docs+fixtures PR still does not close #1323 and remains merge-blocked by CI Extended #1330.

@Chris0Jeky
Chris0Jeky marked this pull request as ready for review July 13, 2026 21:54
Copilot AI review requested due to automatic review settings July 13, 2026 21:54
@Chris0Jeky
Chris0Jeky merged commit 5849e66 into main Jul 13, 2026
35 checks passed
@github-project-automation github-project-automation Bot moved this from Pending to Done in Taskdeck Execution Jul 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants