Skip to content

Commit 8048f16

Browse files
author
DavidQ
committed
Add runtime assertions to enforce no hidden tool input - PR 11.133
1 parent d792bfe commit 8048f16

6 files changed

Lines changed: 198 additions & 29 deletions

File tree

docs/dev/codex_commands.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# CODEX COMMANDS
22

33
Model: GPT-5.3-codex
4-
Reasoning: high
4+
Reasoning: medium
55

66
STRICT SCOPE MODE
77

@@ -10,18 +10,16 @@ ALLOWED FILES:
1010

1111
TASK:
1212

13-
1. Find tool launch calls
14-
2. Enforce signature:
15-
launch(toolId, payloadJson, paletteJson?)
13+
1. Add assertions before tool execution:
14+
- ensure only payloadJson + paletteJson used
1615

17-
3. Remove:
18-
- implicit/global input
19-
- hidden dependencies
16+
2. Detect:
17+
- global reads
18+
- implicit inputs
19+
- parent JSON usage
2020

21-
VERIFY:
22-
- inputs are explicit only
21+
3. If detected:
22+
- throw error
2323

2424
REPORT:
25-
docs/dev/reports/final_tool_input_contract_11_132.txt
26-
27-
FAIL if ambiguity remains
25+
docs/dev/reports/runtime_assertions_11_133.txt

docs/dev/commit_comment.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Enforce explicit tool input contract (payload + optional palette only) - PR 11.132
1+
Add runtime assertions to enforce no hidden tool input - PR 11.133
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
Runtime Assertions Report: 11_133
2+
Date: 2026-04-30
3+
Repo: C:/Users/davidq/Documents/GitHub/HTML-JavaScript-Gaming
4+
Mode: STRICT SCOPE
5+
6+
Scope
7+
- Routing files only
8+
9+
Files changed
10+
- tools/shared/toolHostRuntime.js
11+
- tools/Workspace Manager/main.js
12+
13+
Task Results
14+
1) Added assertions before tool execution
15+
- Added `assertExplicitLaunchInputs(...)` in host runtime launch path.
16+
- Assertion is executed at start of `launch(toolId, payloadJson, paletteJson?)` before iframe/tool execution.
17+
18+
2) Detection coverage
19+
A. Global reads / implicit inputs
20+
- Rejects payloadJson/paletteJson containing implicit/global keys:
21+
- launchParams, sharedContext, hostContextId, hostToolId, hosted
22+
- sampleId, sampleTitle, samplePresetPath
23+
- gameId, gameTitle, gameHref
24+
- workspaceHref, returnTo, state
25+
- Throws error when any are present.
26+
27+
B. Parent JSON usage
28+
- Detects and rejects parent-style documents in payload/palette:
29+
- documentKind=workspace-manifest
30+
- schema=html-js-gaming.project / html-js-gaming.workspace
31+
- presence of top-level tools object
32+
- Throws error when detected.
33+
34+
C. Signature enforcement
35+
- Enforces exact launch call shape:
36+
- requires toolId
37+
- requires payloadJson object
38+
- paletteJson must be object or null
39+
- rejects argument count outside 2..3
40+
- Throws on violation.
41+
42+
3) Workspace Manager launch hardening
43+
- Launch now throws when explicit payloadJson is missing.
44+
- Launch now throws when implicit state JSON input is present.
45+
- Launch call remains explicit-only:
46+
- runtime.launch(toolId, payloadJson, paletteJson)
47+
48+
Verification
49+
- Syntax checks:
50+
- node --check tools/shared/toolHostRuntime.js -> PASS
51+
- node --check tools/Workspace Manager/main.js -> PASS
52+
53+
- Contract evidence present in code:
54+
- `assertExplicitLaunchInputs` exists and is invoked before launch execution.
55+
- `launch contract violation` throw paths exist for:
56+
- missing toolId
57+
- invalid arg count
58+
- invalid payloadJson
59+
- invalid paletteJson
60+
- parent JSON payload
61+
- parent JSON palette
62+
- implicit/global keys in payload/palette
63+
- Workspace Manager throws on:
64+
- missing explicit payloadJson
65+
- implicit state JSON
66+
67+
Result
68+
- PASS
69+
- Runtime assertions added.
70+
- Detection + throw behavior enforced for implicit/global inputs and parent JSON usage.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# BUILD_PR_LEVEL_11_133_RUNTIME_ASSERTION_NO_HIDDEN_INPUT
2+
3+
## Purpose
4+
Add runtime assertions to guarantee no hidden/global/implicit input is used by any tool.
5+
6+
## STRICT SCOPE
7+
8+
ALLOWED FILES:
9+
- routing files
10+
- tool launch handlers
11+
12+
ALLOWED CHANGES:
13+
- add assertion checks only
14+
- no behavior change beyond failing invalid paths
15+
16+
## RULE
17+
18+
Tool must ONLY use:
19+
- payloadJson
20+
- optional paletteJson
21+
22+
## REQUIRED ASSERTIONS
23+
24+
Before tool execution:
25+
26+
ASSERT:
27+
- no global state read
28+
- no workspace/game JSON passed
29+
- no inferred data used
30+
31+
## VALIDATION
32+
33+
If violation detected:
34+
- throw visible error
35+
- stop execution
36+
37+
## REPORT
38+
39+
docs/dev/reports/runtime_assertions_11_133.txt:
40+
- assertions added
41+
- violations detected (if any)
42+
43+
## FAILURE
44+
45+
FAIL if:
46+
- tool runs without assertions

tools/Workspace Manager/main.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,16 +1238,10 @@ function mountSelectedTool(source = "manual") {
12381238
: null;
12391239
const payloadJson = explicitToolPayloadById ? (explicitToolPayloadById.get(toolId) || null) : null;
12401240
if (!payloadJson || typeof payloadJson !== "object" || Array.isArray(payloadJson)) {
1241-
writeStatus(`Launch blocked for ${toolId}: explicit payloadJson is required.`);
1242-
renderMountDiagnostic(
1243-
`Launch blocked for ${toolId}.`,
1244-
"Workspace Manager now enforces explicit launch(toolId, payloadJson, paletteJson?) inputs."
1245-
);
1246-
syncControlState();
1247-
return false;
1241+
throw new Error(`launch contract violation: explicit payloadJson is required for ${toolId}.`);
12481242
}
12491243
if (hasStateInput) {
1250-
writeStatus("State JSON is ignored for explicit launch signature enforcement.");
1244+
throw new Error("launch contract violation: implicit input detected (state JSON).");
12511245
}
12521246
const paletteJson = workspaceManifestToolDiagnostics?.explicitPalettePayload || null;
12531247
const mountResult = runtime.launch(toolId, payloadJson, paletteJson);

tools/shared/toolHostRuntime.js

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,69 @@ function isPlainObject(value) {
1212
return !!value && typeof value === "object" && !Array.isArray(value);
1313
}
1414

15+
function isParentJsonLike(value) {
16+
if (!isPlainObject(value)) {
17+
return false;
18+
}
19+
const documentKind = typeof value.documentKind === "string" ? value.documentKind.trim().toLowerCase() : "";
20+
const schema = typeof value.schema === "string" ? value.schema.trim().toLowerCase() : "";
21+
if (documentKind === "workspace-manifest" || schema === "html-js-gaming.project" || schema === "html-js-gaming.workspace") {
22+
return true;
23+
}
24+
return isPlainObject(value.tools);
25+
}
26+
27+
function hasImplicitGlobalKey(value) {
28+
if (!isPlainObject(value)) {
29+
return false;
30+
}
31+
const blockedKeys = new Set([
32+
"launchparams",
33+
"sharedcontext",
34+
"hostcontextid",
35+
"hosttoolid",
36+
"hosted",
37+
"sampleid",
38+
"sampletitle",
39+
"samplepresetpath",
40+
"gameid",
41+
"gametitle",
42+
"gamehref",
43+
"workspacehref",
44+
"returnto",
45+
"state"
46+
]);
47+
return Object.keys(value).some((key) => blockedKeys.has(String(key).trim().toLowerCase()));
48+
}
49+
50+
function assertExplicitLaunchInputs({ toolId = "", payloadJson = null, paletteJson = null, argumentCount = 0 }) {
51+
const normalizedToolId = normalizeToolId(toolId);
52+
if (!normalizedToolId) {
53+
throw new Error("launch contract violation: toolId is required.");
54+
}
55+
if (argumentCount < 2 || argumentCount > 3) {
56+
throw new Error(`launch contract violation: launch(toolId, payloadJson, paletteJson?) expected 2-3 args, received ${argumentCount}.`);
57+
}
58+
if (!isPlainObject(payloadJson)) {
59+
throw new Error(`launch contract violation: payloadJson must be an object for ${normalizedToolId}.`);
60+
}
61+
if (paletteJson !== null && !isPlainObject(paletteJson)) {
62+
throw new Error(`launch contract violation: paletteJson must be an object or null for ${normalizedToolId}.`);
63+
}
64+
if (isParentJsonLike(payloadJson)) {
65+
throw new Error(`launch contract violation: parent JSON usage detected in payloadJson for ${normalizedToolId}.`);
66+
}
67+
if (paletteJson !== null && isParentJsonLike(paletteJson)) {
68+
throw new Error(`launch contract violation: parent JSON usage detected in paletteJson for ${normalizedToolId}.`);
69+
}
70+
if (hasImplicitGlobalKey(payloadJson)) {
71+
throw new Error(`launch contract violation: implicit/global input keys detected in payloadJson for ${normalizedToolId}.`);
72+
}
73+
if (paletteJson !== null && hasImplicitGlobalKey(paletteJson)) {
74+
throw new Error(`launch contract violation: implicit/global input keys detected in paletteJson for ${normalizedToolId}.`);
75+
}
76+
}
77+
1578
function buildHostLaunchUrl(toolEntry, hostContextId = "") {
1679
const url = new URL(toolEntry.launchPath, window.location.href);
1780
url.searchParams.set("hosted", "1");
@@ -150,15 +213,13 @@ export function createToolHostRuntime(options = {}) {
150213
return null;
151214
}
152215

216+
assertExplicitLaunchInputs({
217+
toolId,
218+
payloadJson,
219+
paletteJson,
220+
argumentCount: arguments.length
221+
});
153222
const normalizedToolId = normalizeToolId(toolId);
154-
if (!isPlainObject(payloadJson)) {
155-
onStatus(`Tool launch blocked: explicit payloadJson is required for ${normalizedToolId || "(empty)"}.`);
156-
return null;
157-
}
158-
if (paletteJson !== null && !isPlainObject(paletteJson)) {
159-
onStatus(`Tool launch blocked: paletteJson must be an object when provided for ${normalizedToolId || "(empty)"}.`);
160-
return null;
161-
}
162223
const toolEntry = getToolHostEntryById(manifest, normalizedToolId);
163224
if (!toolEntry) {
164225
onStatus(`Tool id not found: ${normalizedToolId || "(empty)"}.`);

0 commit comments

Comments
 (0)