Skip to content

ACL should be set to Read Append only #16

Description

@bourgeoa

Actually the chat ACL is not set and uses default from parent chat folder

  • If you use Read Write for authenticatedAgent it will mean anyone logged can edit any year/month/day/chatttl
  • using Read Append for authenticatedAgent as the draw-back that edit cannot be done directly you need to add a replacement content
    This is expected by the Chat specification https://solid.github.io/chat/#access-control

The consequence is https://solid.github.io/chat/#modified for deleted and modified messages.

Activity

  1. changed the title [-]ACL should be set to `Append` only[/-] [+]ACL should be set to `Read Append` only[/+] on Jan 10, 2026
  2. melvincarvalho commented on Jan 11, 2026

    @melvincarvalho
    Contributor

    Deep Research Analysis: ACL Read Append

    Current State ✅

    The current solid-chat/app ACL setup is correct for public chats:

    # Public: Read + Append (can post, can't edit/delete others)
    <#public>
        a acl:Authorization ;
        acl:agentClass foaf:Agent ;
        acl:mode acl:Read, acl:Append .
    
    # Owner: Full control
    <#owner>
        a acl:Authorization ;
        acl:agent <owner-webid> ;
        acl:mode acl:Read, acl:Write, acl:Control .

    This aligns with the Solid Chat Specification.


    The Problem ⚠️

    Edit/Delete currently uses destructive SPARQL operations:

    // Edit - requires Write permission
    const updateQuery = `
      DELETE { <${msgUri}> sioc:content ?old }
      INSERT { <${msgUri}> sioc:content "new text" }
      WHERE { <${msgUri}> sioc:content ?old }
    `
    
    // Delete - requires Write permission  
    const deleteQuery = `DELETE DATA { ${triples} }`

    These operations won't work with Append-only ACLs because:

    • DELETE requires Write permission
    • Participants only have Append permission

    Solid Chat Spec Solution: Append-Only Patterns

    Use dct:replaces and schema:dateDeleted instead of destructive deletes:

    # Original message
    <#msg-123>
        a sioc:Post ;
        sioc:content "Hello" ;
        dct:created "2026-01-11T10:00:00Z" ;
        foaf:maker <https://alice.example/#me> .
    
    # "Delete" by appending (works with Append permission)
    <#msg-123-deleted>
        a sioc:Post ;
        dct:replaces <#msg-123> ;
        schema:dateDeleted "2026-01-11T12:00:00Z" ;
        foaf:maker <https://alice.example/#me> .
    
    # "Edit" by appending (works with Append permission)
    <#msg-123-edited>
        a sioc:Post ;
        dct:replaces <#msg-123> ;
        sioc:content "Hello (edited)" ;
        dct:created "2026-01-11T12:00:00Z" ;
        foaf:maker <https://alice.example/#me> .

    Benefits:

    • ✅ Works with Append-only ACLs
    • ✅ Maintains full audit trail
    • ✅ No destructive operations
    • ✅ Follows Solid Chat spec

    Security Comparison

    Permission Post Edit Own Delete Own Edit Others Delete Others
    Read+Append ✅ ✅ (append) ✅ (append) ❌ ❌
    Read+Write ✅ ✅ ✅ ⚠️ YES ⚠️ YES

    Security Risk: With Write permission, any authenticated user can delete/modify ANY message in the chat!


    Implementation Recommendations

    1. Refactor Delete to Append-Only

    // CURRENT (requires Write)
    async function deleteMessage(msgUri) {
      const deleteQuery = `DELETE DATA { ... }`
      await store.fetcher.webOperation('PATCH', doc, { body: deleteQuery })
    }
    
    // RECOMMENDED (works with Append)
    async function deleteMessage(msgUri, doc) {
      const deletionUri = `${doc.value}#deletion-${Date.now()}`
      const ins = [
        $rdf.st($rdf.sym(deletionUri), DCT('replaces'), $rdf.sym(msgUri), doc),
        $rdf.st($rdf.sym(deletionUri), SCHEMA('dateDeleted'), $rdf.lit(new Date().toISOString()), doc),
        $rdf.st($rdf.sym(deletionUri), FOAF('maker'), $rdf.sym(currentUser), doc)
      ]
      await store.updater.update([], ins)
    }

    2. Refactor Edit to Append-Only

    // RECOMMENDED (works with Append)
    async function editMessage(msgUri, newContent, doc) {
      const editUri = `${doc.value}#edit-${Date.now()}`
      const ins = [
        $rdf.st($rdf.sym(editUri), DCT('replaces'), $rdf.sym(msgUri), doc),
        $rdf.st($rdf.sym(editUri), SIOC('content'), $rdf.lit(newContent), doc),
        $rdf.st($rdf.sym(editUri), FOAF('maker'), $rdf.sym(currentUser), doc)
      ]
      await store.updater.update([], ins)
    }

    3. Update Message Rendering

    // When loading messages, check for replacements
    function isMessageDeleted(msgUri, store, doc) {
      const replacement = store.any(null, DCT('replaces'), msgUri, doc)
      if (replacement) {
        const deleted = store.any(replacement, SCHEMA('dateDeleted'), null, doc)
        return !!deleted
      }
      return false
    }
    
    function getLatestContent(msgUri, store, doc) {
      const replacement = store.any(null, DCT('replaces'), msgUri, doc)
      if (replacement) {
        const newContent = store.any(replacement, SIOC('content'), null, doc)
        if (newContent) return { content: newContent.value, edited: true }
      }
      return { content: store.any(msgUri, SIOC('content'))?.value, edited: false }
    }

    4. UI Indicators

    // Show edit/delete status
    if (isMessageDeleted(msgUri)) {
      messageEl.classList.add('deleted')
      messageEl.textContent = '(message deleted)'
    } else {
      const { content, edited } = getLatestContent(msgUri)
      messageEl.textContent = content
      if (edited) {
        messageEl.appendChild(createEditedBadge())  // Shows "(edited)"
      }
    }

    Summary

    Aspect Current Recommended
    ACL Setup ✅ Correct (Read+Append) Keep as-is
    Edit Method ❌ SPARQL DELETE+INSERT Use dct:replaces append
    Delete Method ❌ SPARQL DELETE Use schema:dateDeleted append
    Audit Trail ❌ None (data destroyed) ✅ Full history preserved
    Security ⚠️ Relies on ACL ✅ Append-only by design

    References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions