Skip to content

Commit df55314

Browse files
committed
Validate saved proof before loading
1 parent e6a2d50 commit df55314

3 files changed

Lines changed: 22 additions & 1 deletion

File tree

docs/verification-loop.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ Note: the original request asked for a two-hour loop. A timed runner was started
4343
| 2026-05-26T12:50-04:00 | Does the app load and enforce the X/World gates in a real local server? | Ran `pnpm dev`, probed `/`, `/api/config`, `/api/proofs`, and `/api/world/rp-signature`, then restored generated `next-env.d.ts` noise. | `/` returned 200; `/api/config` returned credential flags; `/api/proofs` returned `401 x_login_required`; `/api/world/rp-signature` returned `503 configuration_error` for missing local World credentials. | No product change needed; the runtime path still matches load, login gate, and World signing gate expectations. |
4444
| 2026-05-26T12:53-04:00 | Can a saved proof action leak across X accounts on the same device? | Restricted the saved "Last proof" panel and "Post to X" action to the matching signed-in X username. | `pnpm typecheck`, `pnpm lint`, `pnpm test`, `pnpm verify:goals`, `pnpm build`, `spectacula validate`, and `git diff --check` passed. | A different X login now gets a clean compose flow instead of another account's stale proof action. |
4545
| 2026-05-26T12:56-04:00 | Can proof creation proceed from an X session without a username? | Required an X username on the client and server before proof creation. | `pnpm typecheck`, `pnpm lint`, `pnpm test`, `pnpm verify:goals`, `pnpm build`, `spectacula validate`, and `git diff --check` passed. | Proofs now consistently bind to the X login context used by the simple posting flow. |
46+
| 2026-05-26T13:00-04:00 | Can malformed saved proof data break "Load app"? | Added a saved proof shape guard that removes invalid `humanx:last-proof` data instead of trusting any valid JSON. | `pnpm typecheck`, `pnpm lint`, `pnpm test`, `pnpm verify:goals`, `pnpm build`, `spectacula validate`, and `git diff --check` passed. | A bad localStorage value no longer blocks the simple compose flow. |

src/components/compose-flow.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ async function readApiError(response: Response): Promise<string> {
4848
return payload?.error?.message ?? "Request failed.";
4949
}
5050

51+
function isProofResult(value: unknown): value is ProofResult {
52+
if (!value || typeof value !== "object") return false;
53+
54+
const result = value as Partial<ProofResult>;
55+
return Boolean(
56+
result.proof &&
57+
typeof result.proof === "object" &&
58+
typeof result.proof.id === "string" &&
59+
typeof result.proof.draftText === "string" &&
60+
typeof result.proof.proofCommitment === "string" &&
61+
typeof result.proofUrl === "string" &&
62+
typeof result.tweetIntentUrl === "string",
63+
);
64+
}
65+
5166
export default function ComposeFlow() {
5267
const { data: session, status } = useSession();
5368
const [config, setConfig] = useState<AppConfig | null>(null);
@@ -67,7 +82,11 @@ export default function ComposeFlow() {
6782
if (!saved) return null;
6883

6984
try {
70-
return JSON.parse(saved) as ProofResult;
85+
const parsed = JSON.parse(saved) as unknown;
86+
if (isProofResult(parsed)) return parsed;
87+
88+
window.localStorage.removeItem(STORAGE_KEY);
89+
return null;
7190
} catch {
7291
window.localStorage.removeItem(STORAGE_KEY);
7392
return null;

src/lib/goals.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe("GOALS.md contract", () => {
2727
expect(composeFlow).toContain('signIn("twitter")');
2828
expect(composeFlow).toContain("Login with X");
2929
expect(composeFlow).toContain("hasXUsername");
30+
expect(composeFlow).toContain("isProofResult");
3031
expect(composeFlow).toContain("IDKitRequestWidget");
3132
expect(composeFlow).toContain("handleWidgetOpenChange");
3233
expect(composeFlow).toContain("proofResult.proof.xUsername === username");

0 commit comments

Comments
 (0)