test(e2e): accept explicit broad ChatGPT write consent

This commit is contained in:
Dotta committed 2026-10-27 04:24:44 -05:00
1 parent b349eb24c4
commit 5c9eaa8ee6
5 files changed
+84 -18

No files matched your search

@@ -46,6 +46,7 @@ export interface ChatgptReceipt {
requestedScopes?: string[];
acceptedScopes?: string[];
configureDisclosed?: boolean;
consentWording?: string;
}
const uuid = (s: unknown) =>
typeof s === "string" &&
@@ -135,6 +136,7 @@ export function validChatgptReceipt(r: ChatgptReceipt, i: ChatgptInput) {
"requestedScopes",
"acceptedScopes",
"configureDisclosed",
"consentWording",
];
return (
!!r &&
@@ -162,6 +164,11 @@ export function validChatgptReceipt(r: ChatgptReceipt, i: ChatgptInput) {
r.actualProviderCallback === true &&
r.boundMcpComment === true &&
r.zeroNativeAdmissions === true &&
chatgptConsentDisclosure(
r.requestedScopes?.includes("paperclip:configure") === true,
r.consentWording,
r.configureDisclosed,
) &&
exactAcceptedChatgptScopes(
r.requestedScopes,
r.acceptedScopes,
@@ -224,3 +231,18 @@ export function exactAcceptedChatgptScopes(
(!requested.includes("paperclip:configure") || configureDisclosed === true)
);
}
/** Broad actual write consent is supported; configuration acceptance also needs
* the selected person's explicit private acknowledgement of requested authority. */
export function chatgptConsentDisclosure(
requestedConfigure: boolean,
wording: unknown,
acknowledged: unknown,
) {
return (
typeof wording === "string" &&
wording.trim().replace(/\s+/g, " ") ===
"Write all of your Paperclip data" &&
(!requestedConfigure || acknowledged === true)
);
}
@@ -7,6 +7,7 @@ import {
import {
namedChatgptRequest,
exactAcceptedChatgptScopes,
chatgptConsentDisclosure,
validChatgptInput,
validChatgptReceipt,
createdChatgptAppId,
@@ -79,6 +80,7 @@ function receipt(): ChatgptReceipt {
requestedScopes: [...scopes],
acceptedScopes: [...scopes],
configureDisclosed: true,
consentWording: "Write all of your Paperclip data",
};
}
function valid(): ExternalObservation {
@@ -308,6 +310,24 @@ describe("finite private named-provider contract", () => {
),
).toBe(false);
});
it("accepts actual broad write wording plus explicit requested configure acknowledgement", () => {
expect(
chatgptConsentDisclosure(true, "Write all of your Paperclip data", true),
).toBe(true);
expect(
chatgptConsentDisclosure(true, "Write all of your Paperclip data", false),
).toBe(false);
expect(
chatgptConsentDisclosure(true, "Read all of your Paperclip data", true),
).toBe(false);
expect(
chatgptConsentDisclosure(
false,
"Write all of your Paperclip data",
false,
),
).toBe(true);
});
it("requires actual named official callback/person/company request", () => {
const r = {
id: "request",
@@ -25,6 +25,7 @@ import {
import {
setupCheckComment,
exactAcceptedChatgptScopes,
chatgptConsentDisclosure,
type ChatgptReceipt,
} from "./external-chatgpt-contract.js";
import { waitForReply } from "./identity-support.js";
@@ -112,7 +113,12 @@ export function gradeChatgptSetup(value: unknown) {
r.realHumanConsent === true &&
r.actualProviderCallback === true &&
r.boundMcpComment === true &&
r.zeroNativeAdmissions === true,
r.zeroNativeAdmissions === true &&
chatgptConsentDisclosure(
r.requestedScopes?.includes("paperclip:configure") === true,
r.consentWording,
r.configureDisclosed,
),
"The selected human really uses ChatGPT's private custom-app UI and submits the actual Paperclip OAuth consent; an operator acknowledgement or generic MCP client alone is insufficient.",
);
@@ -428,6 +434,8 @@ export const chatgptSetupCase = definition(
actualNamedUi: true,
actualHumanConsent: true,
zeroNativeAdmissions: state.zeroNativeAdmissions,
consentWording: receipt.consentWording,
acceptedScopes: receipt.acceptedScopes,
});
await ctx.input.page.goto(source, { waitUntil: "domcontentloaded" });
},
@@ -21,6 +21,7 @@ import {
setupCheckComment,
createdChatgptAppId,
exactAcceptedChatgptScopes,
chatgptConsentDisclosure,
type ChatgptInput,
type ChatgptReceipt,
} from "./external-chatgpt-contract.js";
@@ -320,14 +321,32 @@ async function main() {
throw new Error(
"Actual ChatGPT client/company/write request does not match qualification",
);
const configureDisclosed =
!request.requestedConfigure ||
(await consent
.getByText(/configure agents, projects (?:and|&) skills/i)
.isVisible());
const consentLabel = consent.getByText("Write all of your Paperclip data", {
exact: true,
});
await consentLabel.waitFor({ state: "visible" });
const consentWording = await consentLabel.innerText();
await pane(
operator,
"Review and approve the actual Paperclip consent",
identity +
`<p>On the real Paperclip consent tab, select this exact company and explicitly approve read and write access for the single controlled qualification below. ${request.requestedConfigure ? "Configuration authority is also requested. The actual broad Write all of your Paperclip data choice includes configuring agents, projects and skills in this isolated company; explicitly acknowledge that authority here, then knowingly accept the real broad write choice on the actual consent tab." : "No configuration authority is requested or accepted."} Never approve agent/impersonation scope. Click Connect organization THERE, then finish creating the private ChatGPT app. This panel never approves consent.</p>`,
request.requestedConfigure
? [
"I explicitly accept the requested configuration authority for this isolated company under the real broad write consent choice.",
]
: [],
"I reviewed the real consent tab",
);
await confirm(operator, 120000);
const configureDisclosed = chatgptConsentDisclosure(
request.requestedConfigure === true,
consentWording,
true,
);
if (!configureDisclosed)
throw new Error(
"Actual requested configure authority is not visibly disclosed on Paperclip consent",
"Actual broad write consent and explicit requested configuration acceptance were not established",
);
const submitted = consent.waitForResponse(
(r) =>
@@ -337,14 +356,6 @@ async function main() {
r.request().method() === "POST",
{ timeout: 120000 },
);
await pane(
operator,
"Review and approve the actual Paperclip consent",
identity +
`<p>On the real Paperclip consent tab, select this exact company and explicitly approve read and write access for the single controlled qualification below. ${request.requestedConfigure ? "Configuration authority is also requested: explicitly review the real visible Configure agents, projects and skills disclosure before accepting it for this isolated company." : "No configuration authority is requested or accepted."} Never approve agent/impersonation scope. Click Connect organization THERE, then finish creating the private ChatGPT app. This panel never approves consent.</p>`,
[],
"I reviewed the real consent tab",
);
await consent.bringToFront();
const response = await submitted,
payload = response.request().postDataJSON() as {
@@ -523,6 +534,7 @@ async function main() {
],
acceptedScopes: [...grant.scopes],
configureDisclosed,
consentWording,
};
await writeFile(
path.join(directory, "qualified.json"),
@@ -335,9 +335,13 @@ availability. After the unchanged canonical CEO query, that person actually uses
ChatGPT's supported private custom MCP app UI and the real Paperclip consent tab.
The child observes the actual named-client request, official ChatGPT callback,
explicit company and scope acceptance, and one new saved grant. Configuration
scope is permitted only when requested, visibly disclosed in the real consent,
and explicitly accepted; agent authority is excluded. The current source's
write-only consent label does not itself establish configuration disclosure.
scope is permitted only when actually requested and explicitly accepted by the
authorized person under the real broad **Write all of your Paperclip data**
choice. The private prompt separately requires that person's explicit
acknowledgement of the requested configuration authority; it does not require
an invented separate picker or literal agents/projects/skills enumeration on
the product consent page. Actual visible consent wording and the exact scope
intersection are recorded; agent authority is excluded.
Changed or missing controls and failed provider authorization are nonpassing
observations, never grounds for a generic-client substitution.