Files
node/SECURITY.md
JATMNandClaude Opus 5.5 e2ae21795a chore: rebrand user-facing gitlawb references to Twigpine (#520)
* chore: rebrand user-facing gitlawb references to Twigpine

Rename the product name in docs, README, CLI help/output, MCP tool
descriptions, macOS app strings, installers, npm metadata and license
headers from gitlawb/Gitlawb to Twigpine. Point GitHub links at the
renamed Twigpine org (and fix the release workflow's repository guard,
which still matched Gitlawb/node), and switch the public node hosts and
website/install URLs to twigpine.com.

Protocol and operator identifiers are intentionally unchanged
(did:gitlawb, gitlawb://, signing domains, GITLAWB_* env vars, crate
and binary names, X-Gitlawb-* headers, icaptcha and peer-network URLs);
they need a follow-up migration.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* docs: point image pulls at ghcr.io/twigpine/node and fix macOS DMG name

The release workflow publishes the container image to
ghcr.io/${GITHUB_REPOSITORY,,}, which is ghcr.io/twigpine/node since the
org rename; the old ghcr.io/gitlawb/node path no longer resolves for
anonymous pulls. Point the RUN-A-NODE docker run example, the OSS
readiness audit's claim about it, and the macOS app's generated compose
file at the published path.

The README listed the macOS DMG as "Twigpine Node.dmg", but
build-macos-app.sh writes GitlawbNode-macOS.dmg (kept on purpose), so
document the real file name.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(infra): keep the AWS security group description to avoid replacement

`description` on aws_security_group is ForceNew, so renaming it forced a
replace of the in-use group on the next apply of an existing deployment.
Restore the original text and note why it must stay until a separate
infra migration.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(macos): regenerate the app's compose file instead of treating it as a repo's

Config.repoPath fell back to the data directory when no repo clone was
found, and DockerCompose then picked up the compose file it had generated
there as if it belonged to a repo. The generator therefore only ran on
first launch, so upgraded installs kept their original compose, including
the old ghcr.io/gitlawb/node image, which no longer serves anonymous pulls.

repoPath now returns nil when there is no clone, and the app regenerates
its compose file whenever no repo compose is in use. Project directory and
named volumes are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(infra): default the AWS node image to ghcr.io/twigpine/node

release.yml publishes to ghcr.io/${GITHUB_REPOSITORY,,}, which is now
ghcr.io/twigpine/node; ghcr.io/gitlawb/node denies anonymous pulls, so the
default made new deployments fail to start and the upgrade command fail
on existing ones. On an existing deployment `terraform apply` only updates
the upgrade SSM document in place (user_data drift is ignored); the new
image is pulled on the next upgrade_command.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(macos): keep operator edits to the generated compose, migrate only the image

e04efe4 regenerated the app's compose file on every access, overwriting
edits operators make to pass settings the generated environment block
omits (e.g. GITLAWB_OPERATOR_STRICT_MODE). Generate the file only when it
is missing; when it exists, keep it and rewrite only references to the
retired ghcr.io/gitlawb/node image, keeping tags and digests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* fix(macos): only migrate complete ghcr.io/gitlawb/node references

Without a left boundary the migration also rewrote the retired path when
it appeared inside a longer reference, such as a registry mirror
(registry.example.com/ghcr.io/gitlawb/node), pointing it at a path the
mirror may not have.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-04 21:58:29 +00:00

7.0 KiB

Security Policy

Reporting a Vulnerability

If you discover a security vulnerability in Twigpine, please do not open a public issue.

Report it privately by emailing [email protected] with:

  • A description of the vulnerability
  • Steps to reproduce
  • Potential impact assessment
  • (Optional) Suggested fix

We will acknowledge receipt within 48 hours and aim to release a fix within 14 days for critical issues.


Current Security Architecture

Current live security controls

Ed25519 identity and HTTP Signatures

  • Every write operation is signed with RFC 9421 HTTP Signatures
  • Full Ed25519 signature verification on every authenticated request
  • Keys are stored as PKCS#8 PEM files with 0600 permissions
  • DIDs are derived deterministically from the public key (did:key)

Content addressing

  • Every git object is content-addressed via CIDv1 (SHA-256)
  • Tamper-evident by construction — a modified object changes its CID

UCAN token validation

  • Bootstrap UCAN tokens are issued at registration.
  • A supplied token's signature, audience, expiry, and proof-chain attenuation are validated.
  • Tokens use a signed JSON wire format with expiry.
  • Capability grants are not yet consulted by repository write authorization; see the limitations below.

Smart contracts (Base Sepolia testnet)

  • GitlawbDIDRegistry — on-chain DID → document registry
  • GitlawbNameRegistry — human name → DID registry
  • Both auditable on-chain, no admin keys

Dependency Vulnerability Status

Area Status
Dependabot alerts Current open Rust alerts were addressed by updating vulnerable dependencies, removing libp2p mDNS, and moving P2P transport from TCP/Yamux to QUIC/UDP.

Known Limitations

These are documented limitations of the current live release. They should be prioritized without breaking existing nodes during rolling upgrades.

Repository write authorization defaults

  • git-receive-pack verifies HTTP Signatures, but GITLAWB_ENFORCE_OWNER_PUSH defaults to false for compatibility during rollout.
  • Impact: With the default setting, a valid signature authenticates the pusher but does not require that DID to be the repository owner.
  • Mitigation: Set GITLAWB_ENFORCE_OWNER_PUSH=true on nodes where owner-only pushes are required. Confirm that every legitimate pusher uses the owner DID before enabling it.

UCAN delegation and revocation

  • The middleware validates a supplied UCAN's complete proof chain, but a root token is accepted without an independently trusted issuer anchor. Ucan::can is not yet used by write handlers, so a UCAN does not grant scoped repository access.
  • Agent lifecycle revocation is not checked by HTTP Signature authorization. Removing or revoking an agent record does not itself block a compromised DID from authenticating.
  • Impact: Do not use UCANs for collaborator permissions or rely on agent-record revocation as a key-compromise response.
  • Mitigation: Keep sensitive deployments behind operational network controls and enable owner-only push enforcement where it fits the deployment until trusted delegation and authorization-aware revocation are implemented.

git-receive-pack authentication

  • The git-receive-pack endpoint enforces HTTP Signature auth. Plain Git smart-HTTP clients do not generate those headers, so the git-remote-gitlawb helper is required for pushes.
  • Impact: Direct HTTP pushes without RFC 9421 headers are rejected; users need gitlawb:// remotes or equivalent signed clients.
  • Mitigation: Use gitlawb:// remote URLs and keep git-remote-gitlawb on the user's PATH.
  • Fix target: v0.2

Private repository reads

  • Repository and path-scoped visibility checks are enforced for repository API and Git content reads. A denied whole-repository or root read returns the same 404 shape as a missing repository, so the denial does not reveal private-repository existence.
  • Sparse-clone support exposes withheld path globs to callers who may read the repository root. Do not put sensitive information in withheld path names.
  • GET /api/v1/tasks, /api/v1/ipfs/pins, and /api/v1/arweave/anchors are not repository-gated. Task records include a UCAN token; pin and anchor listings expose object and ref metadata.
  • Changing a repository's visibility controls future serving, but cannot retract ref metadata or configured external pins and anchors already announced while the repository was public. Do not push secrets to an announceable repository.
  • Impact: Visibility policies protect the repository and Git content routes they gate, not every metadata endpoint or previously published content.
  • Remaining boundary: This read control does not address the independent write-authorization and UCAN-delegation limitations described above.

Pull-request review enforcement

  • Pull-request review comments are not yet threaded or line-anchored, and merges do not enforce required approvals.
  • Impact: Teams must use their own review policy or external controls for merge approval requirements.

GraphQL mutation coverage

  • Existing GraphQL mutations require an authenticated signer, but a mutation-specific source-level guardrail has not yet been added for future mutations.
  • Impact: A new mutation could accidentally omit its signer check without an explicit test fence.

Peer route hardening rollout

  • Peer announce and sync notification routes accept signed requests and verify DID matches when a signature is present.
  • Impact: Unsigned peer writes are still accepted by default so existing live nodes can keep communicating during rolling upgrades.
  • Mitigation: After all active peers run signed-node builds, operators can set GITLAWB_REQUIRE_SIGNED_PEER_WRITES=true.
  • Fix target: staged rollout

Supported Versions

Version Supported
main Active development
Latest tagged release Security fixes

Cryptographic Primitives

Component Algorithm
Identity keypairs Ed25519 (ed25519-dalek v2)
Key storage PKCS#8 PEM, 0600 permissions
Content hashing SHA-256 via CIDv1
HTTP Signatures RFC 9421 (Ed25519 + SHA-256 Content-Digest)
UCAN tokens Signed JSON object (Ed25519 signature)
On-chain ECDSA secp256k1 (Base L2 / Ethereum)

Threat Model

Twigpine is designed to be secure against:

  • Unauthenticated writes — HTTP Signature auth on protected write endpoints; see Known Limitations for owner and capability authorization gaps
  • Tampered git objects — CIDv1 content addressing detects modification
  • Identity spoofing — DIDs derived from public keys, unforgeable without the private key
  • Centralized takedown — no single point of control; data on IPFS + Arweave

Twigpine is not yet designed to defend against:

  • A compromised node operator (node operators are trusted for their own node)
  • Sybil attacks on the DHT (trust score system mitigates, not eliminates)
  • Timing attacks on signature verification (not constant-time compared in v0.1)