Skip to content

fix(sysmlapi): keep the bearer token on the configured server and the branch read honest - #1029

Merged
HuiJun merged 2 commits into
developfrom
fix/sysmlapi-client-hardening
Oct 9, 2026
Merged

HuiJun merged 2 commits into
developfrom
fix/sysmlapi-client-hardening

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Four defects in the SysML v2 API repository client behind %repo, %projects, %load and %publish, each fixed at its root in internal/translate/interop/sysmlapi and the REPL's repository commands:

  • A Link: rel="next" header could carry the bearer token to another host. paged and Elements followed nextLink wherever it resolved, and Do attaches Authorization: Bearer to any URL. The rule checkRedirect already applied to redirects is now one method, tokenStays, shared by redirects and linked pages (nextPage): with a token, a successor URL that is plaintext, leaves https, or names another host or effective port is refused ("the token is for host only") and never requested. Without a token the link is followed as before.
  • The environment's default repository URL bypassed the plaintext check. repoBase returned DefaultURL() untested, so FLEXO_SYSMLV2_URL=http://… off this machine plus FLEXO_INTEROP_TOKEN sent the token in the clear on every command. repoBase now holds the default to the same CheckURL as %repo <url>, so %repo, %projects, %load, %publish and the project-name completions surface the same PlaintextError before any request, lifted the same way by FLEXO_ALLOW_PLAIN_HTTP=1.
  • Repository.Commit skipped the head re-read after reading a headless branch. seen == "" meant both "never read" and "read, no head yet", so a change set computed against the empty graph was posted onto a branch another writer had since given its first commit. Repository now tracks read apart from seen; a head read by Graph, resumed or written is always re-read before a commit, and a branch read without a head must still have none, else StaleBranchError.
  • %publish by name could not reach the project a session loaded by id. Publish resolved the target by name only, so a namesake on the server made the loaded project unreachable. When the tracked state is for the selected server and the requested name is the tracked project's name, the stored id resolves the project — provided the server still has it under that name: a tracked project since renamed or deleted is no match, and the name is resolved afresh under the usual rules. --project naming another project and the ambiguous-name refusal when no tracked project matches are unchanged.

Docs (docs/reference/repl-commands.md, docs/guide/12-jupyter.md) now say the plaintext rule covers the environment's URL and that a redirect or next-page link off the server is refused with the token.

How it was verified

New tests, each failing on develop's code and passing here:

  • sysmlapi: TestLinkedPagesKeepTheTokenOnTheServer — two httptest servers, the first linking rel="next" to the second: the error names both hosts and not the token, the second server sees no request, and a tokenless client still follows the link (Projects and Elements); TestCommitRefusesAHeadThatAppearedSinceTheRead — branch with no head at Graph, a head at Commit → StaleBranchError{Seen: "", Head: "c1"}, no POST; and the commit proceeds once the branch is headless again.
  • repl: TestDefaultURLIsHeldToThePlaintextRule — t.Setenv of an off-host http:// URL and a token: %repo, %projects, %load --id, %publish and completion refuse before any request, the message names FLEXO_ALLOW_PLAIN_HTTP and not the token, and the opt-in lifts it; TestPublishByNameUpdatesTheLoadedNamesake — two projects named Vehicles, %load --id the first, %publish Vehicles updates it, while an untracked session is still refused as ambiguous and --project still selects explicitly; once the tracked project is renamed, and once it is deleted, %publish Vehicles goes to the namesake and creates nothing.

Gates run locally:

$ gofmt -l .
$ go build ./...
$ go vet ./...
$ make lint
✓ Lint passed
$ go test -race -count=1 ./internal/translate/interop/... ./internal/frontend/repl/... ./internal/frontend/jupyter/...
ok  	github.com/Open-MBEE/OpenSysML/internal/translate/interop/flexo	2.481s
ok  	github.com/Open-MBEE/OpenSysML/internal/translate/interop/modelsync	23.781s
ok  	github.com/Open-MBEE/OpenSysML/internal/translate/interop/sysmlapi	1.073s
ok  	github.com/Open-MBEE/OpenSysML/internal/frontend/repl	672.526s
ok  	github.com/Open-MBEE/OpenSysML/internal/frontend/repl/replext/repository	1.022s
ok  	github.com/Open-MBEE/OpenSysML/internal/frontend/jupyter	4.259s
$ go test ./...
(all packages ok; internal/frontend/repl and cmd/sysml hit the default 10m timeout while run concurrently with the race suite on an 8-core box and passed when re-run alone: repl 672s under -race, cmd/sysml 249s)
$ python3 scripts/changelog.py check
$ python3 scripts/check-doc-ids.py
No internal work-item labels in reader-facing documentation

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build) — no gate count moved
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/650e2bc343d24b67a3333c972e20b8b7
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/650e2bc343d24b67a3333c972e20b8b7?variant=devin
Requested by: @HuiJun

… branch read honest

A next-page link to another host, port or plaintext is refused like a redirect there instead of being followed with the token; the repository URL the environment supplies is held to the plaintext rule before any request; a branch read before its first commit is still checked for a head that appeared since; and %publish by name resolves the project the session loaded by id, so a namesake no longer makes the name ambiguous.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

End-to-end verification used the built bin/sysml against disposable local SysML v2 API fixtures. All 28 runtime assertions passed.

Token isolation verified through the CLI
  • Non-loopback plaintext environment URL: all four repository commands refused; zero requests reached the server.
  • Cross-port pagination with a token: refusal named both hosts; zero requests reached the linked server.
  • Explicit plaintext opt-in, loopback HTTP, tokenless cross-port pagination, and authenticated same-host pagination behaved as expected.
  • The synthetic token did not appear in CLI output.
Plaintext default refusal Cross-port pagination refusal
Four refusals; zero requests Linked server received zero requests
Branch and project identity verification
  • A head appearing after an empty branch read produced the stale-branch diagnostic and zero commit POSTs.
  • A stable headless branch accepted its first commit.
  • Loading a project by id and publishing by its shared name updated that project, stored the new Bus element, and left its namesake untouched.
  • An untracked publish still rejected duplicate names without writing.
Stale branch refusal Loaded project updated
No commit POST after head changed Namesake remains untouched

Coverage limits: local HTTP fixtures rather than a production deployment; the https downgrade rule, element-pagination links and the completion UI were exercised by the Go tests only. %repo itself only displays or sets the URL and issues no request.

@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review October 9, 2026 13:13
devin-ai-integration[bot]

This comment was marked as resolved.

…till names so

A tracked project renamed or deleted on the server no longer answers to the requested name, so the publish falls back to resolving the name afresh under the ambiguity rules instead of committing into the renamed project or creating a new one over its namesake.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun merged commit e19b663 into develop Oct 9, 2026
26 checks passed
@HuiJun
HuiJun deleted the fix/sysmlapi-client-hardening branch October 9, 2026 14:31
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