Skip to content

Confirm note fields and serve team images - #14

Merged
jserv merged 9 commits into
mainfrom
dev
Oct 5, 2026
Merged

jserv merged 9 commits into
mainfrom
dev

Conversation

@jserv

@jserv jserv commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Several tools reported or implied that HackMD did something nobody had shown it does. The worst case: hackmd_create_note forwarded comment_permission and suggest_edit_permission, which HackMD drops on create, and never checked read_permission, so a dropped owner-only permission left a note more open than asked with no warning. Create and update now refuse the two unsettable permissions. Create compares what it sent against the note it returns and lists anything that never shows in unconfirmed_fields; that is never an error, because the note exists and an error would invite a duplicate create.

Image upload now serves team notes through the same /notes/{id}/images route, which accepted a team note when measured on 2026-10-06. The same sweep closed other checks that could pass without proof: a read without folderPaths confirming a move to the root, child_order confirming IDs that are not children of the folder, and folder read-backs failing over null versus "" or letter case. Push without note_ref refuses only a team_path that contradicts the sync record, so callers passing the note's own team keep working. Tool descriptions and docs now claim only what was measured.

Verified with cargo test --all-targets --all-features (286 unit, 11 stdio; the live suites are ignored by design) and cargo clippy --all-targets --all-features -- -D warnings, both clean. Codex and Claude reviewers checked each round.

Not verified: the claims the destructive live suite exists to settle. It now prints or asserts the default permissions a create gets, a team create's owner permissions, a 7 MiB image upload, the plain-route team upload, and that an anonymous fetch of an owner-only note's image is refused. It has not been run for this change; it needs the dedicated throwaway account.


Summary by cubic

Create now confirms what it sent. comment_permission and suggest_edit_permission are refused on both create and update, because the API drops or rejects them; a dropped read_permission could leave a note more open than asked, so create polls until the sent fields show and lists anything that never does in unconfirmed_fields (tags, description, permalink, and the two permissions only), never as an error. Image upload serves team notes through the plain /notes/{id}/images route, which was measured to accept them. Checks that could pass without proof are closed: a read omitting folderPaths no longer confirms a move to the root, child_order refuses IDs that are not direct children of an existing folder, folder read-backs tolerate null/"" and case differences, and push without note_ref refuses a team_path contradicting the sync record. Tool descriptions and docs now claim only what was measured; the 5 MiB image limit and other unmeasured behaviors are stated as unmeasured.

  • Live destructive suite gains probes for the default permissions, team uploads, 7 MiB uploads (with a valid padded PNG), and anonymous image access.
  • Requires a workspace root for uploads, since an image on a guest-readable note is public.

Written for commit b92285d. Summary will update on new commits.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

jserv added 9 commits October 6, 2026 06:54
hackmd_create_note forwarded comment_permission and
suggest_edit_permission, which HackMD drops on create (201, read back
null) and rejects on PATCH (422). Create now refuses them before any
request, as update already did, and both point to the web UI, so
neither tool appears to set something that never takes.
The plain /notes/{id}/images route accepts a team note by its internal
id (measured 2026-10-06), so the team refusal goes. team_path stays
accepted but unadvertised: the route ignores it, and it only
cross-checks the owner of an @owner/slug URL. Upload still requires a
workspace root, since an image on a guest-readable note is public.

The destructive suite now uploads through that route to an owner-only
team note and asserts an anonymous fetch of the image is refused,
instead of asserting the team-prefixed route is absent. PNG_PROBE gets
its correct IDAT checksum, so every live upload sends a valid image.
folderPaths read a missing field as an empty list, so a read that
omitted it, as a team note read may, confirmed a move to the root that
never happened. The field is now an Option, and only a read that lists
no folders confirms the root.
A read_permission HackMD drops leaves the note at a default that may
be more open than asked, and create never checked. It now compares the
tags, description, permalink, and permissions it sent against the note
it returns, sharing update's comparison, and polls only on a mismatch.
What never shows is listed in unconfirmed_fields, never raised as an
error: the note exists, and an error would invite a duplicate create.
An empty permalink now matches null, as clearing it may read back.

The destructive suite prints the permissions a create without them
gets and asserts a team create's owner permissions show.
HackMD stores any order map it is sent and reads it back intact, so the
read-back confirmed orders of unrelated or made-up IDs. child_order now
refuses an ID that hackmd_list_folders does not show as a direct child
of folder_id, and a named parent that does not exist, before anything
is written. Every folder input check now runs before the first request.
The check is best effort: HackMD has no conditional write.
A cleared folder field may read back as an empty string, and an icon
or color in another case, so a write that landed was reported as a
read-back mismatch. The comparison now treats null and empty as equal
and compares icon and color ignoring case.
Without note_ref the sync record names the note, so a team_path naming
another workspace means the caller has the wrong file or note in mind.
Push now refuses it before any request. A matching team_path and
refresh are still ignored, as they always were, so no existing caller
breaks.
Tool descriptions, field docs, and docs stated as fact what nobody has
measured: patches keeping metadata, folder fields being preserved,
team deletions being permanent, personal folder PATCH not existing,
history order, an unversioned body, and a 5 MB image limit. Each now
states only what was measured, and the reason a refusal gives is the
measured one. note_features is documented as forwarded unchecked.

The destructive suite uploads a 7 MiB image, padded with a private
ancillary chunk so it stays a valid PNG, to measure the size limit.
The docs named CLAUDE.md as an example of the files a pull refuses to
overwrite. They now list AGENTS.md and SKILL.md and say an instruction
file can import any Markdown file. The deny-list itself is unchanged.
@jserv
jserv merged commit e035af6 into main Oct 5, 2026
11 checks passed
@jserv
jserv deleted the dev branch October 5, 2026 23:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant