Skip to content

fix(core): validate instruction entry keys outside HTTP - #56689

Open
kitlangton wants to merge 2 commits into
devfrom
fix/instruction-entry-key-validation
Open

kitlangton wants to merge 2 commits into
devfrom
fix/instruction-entry-key-validation

Conversation

@kitlangton

@kitlangton kitlangton commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Why

InstructionEntry.Key (/^[a-z0-9][a-z0-9._-]*$/u) was previously enforced by transport schemas and had no maximum length. Direct callers of InstructionEntry.Service.put and remove (including Effect plugins via #56550) could write malformed keys like "Bad Key" or "a/b" directly into session_instruction_entry. On the next step boundary, InstructionEntry.load called Instructions.Key.make(\api/${entry.key}`), which either defected and broke every subsequent step of the session ("Bad Key") or admitted a key containing / ("a/b"`) that could never be addressed or deleted over HTTP.

What Changes

  • Adds InstructionEntry.MaxKeyLength = 128 and Schema.isMaxLength(128) to InstructionEntry.Key in @opencode/schema/instruction-entry (propagated as "maxLength": 128 in openapi.json).
  • Adds InstructionEntry.InvalidKeyError (InstructionEntryInvalidKeyError) alongside ValueTooLargeError and decodes input.key with InstructionEntry.Key at the start of InstructionEntry.put and InstructionEntry.remove, failing with a typed error instead of persisting an invalid key or defecting later.
  • Maps InstructionEntryInvalidKeyError to InvalidRequestError (field: "key") in SessionHandler so the HTTP error contract stays unchanged.
  • Hardens InstructionEntry.load to validate stored row keys with InstructionEntry.Key and skip malformed rows with Effect.logWarning rather than defecting inside Instructions.Key.make, preventing pre-existing bad rows from bricking a session.
Operation Input key Before After
InstructionEntry.put / remove "deploy-target" (1..128 chars matching /^[a-z0-9][a-z0-9._-]*$/u) Succeeds Succeeds
InstructionEntry.put / remove "", "Bad Key", "-leading", "a/b" Stored in SQLite (or no-op on remove) Fails with InstructionEntry.InvalidKeyError
InstructionEntry.put / remove "a".repeat(129) Accepted over HTTP and in-process Rejected (400 InvalidRequestError over HTTP; InstructionEntry.InvalidKeyError in-process)
InstructionEntry.load Stored row with key "Bad Key" or "a/b" Defects on "Bad Key"; admits unaddressable api/a/b on "a/b" Logs warning and skips the malformed row

Scope

Validates instruction entry keys at the Core service boundary, bounds key length to 128 characters in Schema/OpenAPI, and guards InstructionEntry.load against pre-existing invalid rows. This hardens the core path that #56550 (plugin exposure) and the planned graduation of the session instructions API rely on.

Verification

taskpolicy -c utility bun --cwd packages/schema test test/contract-hygiene.test.ts
taskpolicy -c utility bun --cwd packages/protocol test test/openapi-patterns.test.ts
taskpolicy -c utility bun run --cwd packages/core test test/session-runner.test.ts -t "instruction entr|API context entries" --rerun-each 3
taskpolicy -c utility bun run --cwd packages/schema typecheck
taskpolicy -c utility bun run --cwd packages/protocol typecheck
taskpolicy -c utility bun run --cwd packages/protocol check:generated
taskpolicy -c utility bun run --cwd packages/core typecheck
taskpolicy -c utility bun run --cwd packages/server typecheck
taskpolicy -c utility bun run --cwd packages/client typecheck
taskpolicy -c utility bun run --cwd packages/client check:generated
taskpolicy -c utility bun run --cwd services/www check:generated
  • packages/schema/test/contract-hygiene.test.ts: 16 passed (restricts instruction entry keys to slash-free lowercase identifiers up to 128 characters).
  • packages/protocol/test/openapi-patterns.test.ts: 1 passed (verifies InstructionEntry.Key emits maxLength: 128 and pattern in OpenAPI).
  • packages/core/test/session-runner.test.ts: 15 passed across 3 reruns (validates API instruction entry keys on put and remove and skips stored instruction entries with malformed keys when loading).
  • Typechecks, generated OpenAPI/client checks, and pre-push bun run check passed with 0 errors.

Added by the opencode bot (AI-generated).

Public API: adds maxLength 128 to InstructionEntry.Key and a new InstructionEntryInvalidKeyError from put/remove in process (backwards compatible apart from rejecting keys over 128 chars)

@opencode-agent opencode-agent Bot 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.

The fix works and I found nothing that should block a merge. I copied the new session-runner tests onto the base branch: put saved "Bad Key" instead of rejecting it, and load crashed in Instructions.Key.make. With this PR both cases behave as described. The nearby core, schema and protocol tests pass, and so do the core and server type checks.

Public API impact (experimental session instructions API, backwards compatible):

  • OpenAPI: InstructionEntry.Key gains maxLength: 128. Keys longer than 128 characters now get a 400 response; nothing else that was valid before is rejected.
  • @opencode/schema/instruction-entry gains MaxKeyLength and InvalidKeyError (InstructionEntryInvalidKeyError).
  • In-process InstructionEntry.Service.put/remove can now fail with InvalidKeyError. HTTP callers still get the same InvalidRequestError as before.

No concerns.

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant