Conventional application security depends on developer discipline: training, coding guidelines and code review catch mistakes after they are written. FOAM puts the security controls in the framework, where they are written once, reviewed once and applied uniformly, so that an application’s security does not depend on every developer remembering to apply them in every feature. It does this in three ways:
Because features are generated deterministically from models, this also means:
Training and code review still matter, but as additional layers of defence rather than the primary control.
Most frameworks treat injection attacks as problems to be mitigated: sanitise inputs, escape outputs, document what not to do, and hope developers follow the rules consistently. FOAM takes a different approach — it eliminates the attack surface at the architectural level so that the normal API provides no path to injection in the first place.
U2, FOAM’s UI library, was designed in conjunction with Google’s security team during the Chrome Apps era. Chrome Apps enforced a strict security model that prohibited three things:
eval() — no runtime code generation from strings<script> tags in <body> — no inline script injectiononclick="...") — no HTML-level callbacksThese constraints forced a fully component-based, code-wired architecture. U2 was built to satisfy all three by construction:
this.start().add().end()) never concatenates strings into HTML. Content is always set as text nodes or DOM properties — never parsed as markup.The result is that XSS is structurally impossible in a FOAM application. User input handled through the normal U2 API cannot become executable markup, because U2 never hands strings to the HTML parser.
Chrome Apps no longer exist, but FOAM retains the inline-script and inline-handler constraints through a self-imposed Content Security Policy. (The policy’s script-src does allow 'unsafe-eval'.) These security properties remain, independent of the platform that originally motivated them.
The policy is built from foam.core.http.csp.CSPDirective rows in src/cspdirectives.jrl. Its script-src does not include 'unsafe-inline', so a <script> tag written into an HTML page is not executed, and an inline listener such as onclick="code" is not evaluated either. This is a second, independent layer: even if markup were somehow injected into a page, the script in it would not execute.
The FOAM client has no third-party runtime dependencies. A few optional libraries are loaded from a CDN, and only when the component that needs them is used (via JsLib):
src/io/c9/ace/Editor.js).src/org/chartjs/Lib.js).src/foam/core/reflow/Console.js).Each external script URL must also be allowed by the script-src directive, so a new library cannot be loaded without a reviewed change to cspdirectives.jrl.
The same principle — build from objects, not strings — applies one layer down at the query layer.
SQL injection works because queries are built by string concatenation: user input lands inside the string, the database parser cannot distinguish data from syntax, and the injection succeeds. MLang makes this structurally impossible:
M.AND(
M.EQ(Invoice.CUSTOMER_ID, customerId), // a typed value, not a string fragment
M.GT(Invoice.AMOUNT, userInput) // typed and bounds-checked, not interpolated
)
There is no query string to inject into. The predicate tree is a graph of typed objects. When a ClientDAO sends a query over the network, it serialises the predicate tree to JSON and reconstructs it server-side — the server never receives a raw query string that user input could escape. When a JDBC DAO (PostgresDAO, MySQLDAO) executes against a SQL database, it generates parameterised queries from the predicate tree, with data always kept separate from query structure.
Both protections follow from the same architectural decision made throughout FOAM: use typed object composition rather than string interpolation at every layer. There is no injection surface because there are no strings being parsed as code or markup anywhere in the normal execution path.
| Layer | Conventional risk | FOAM’s approach | Result |
|---|---|---|---|
| UI rendering | XSS via template injection | Builder API sets DOM properties, never innerHTML | XSS structurally impossible |
| Query construction | SQL injection via string concatenation | MLang predicate objects, parameterised execution | SQL injection structurally impossible |
| Runtime code | Script injection via <script> tags or inline handlers (onclick="...") |
Strict CSP with no 'unsafe-inline' |
Injected inline script never executes |
FOAM application security centers around services and permissions to those services.
Services are known as CORE Services and their access configuration is modelled under the name CSpec (CORE Service Specification). The CSpec controls how a service is exposed to the rest of the system.
doc/guides/Services.md for documenation regarding Services themselves.doc/guides/Permissions.md for documentation regarding Permissions themselves.- see src/foam/core/auth/README.md for documentation on the Authentication system itself - how authentication is implemented and how permission checking occurs. Overlaps with this documentation.CSpecs are deployed in services.jrl journals and can also be created and manipulated at runtime.
foam/src/services.jrl for many examples.Services, by default, are only accessible within the server application. They can be exposed to clients with CSpec property serve.
Services which are served, are authenticated. Authentication is enabled by default via CSpec property authenticate.
A client’s permissions are those of the *user that is associated with the session of the client connection.*
Autenticated services require the client (connecting user) to have permission
service.<cspec-name>Unauthenticated services are open to all clients for reading.
Developers do not write REST or other network APIs by hand. The client stub and server skeleton for a served service are generated, and every request is checked by SessionServerBox, which calls CSpec.checkAuthorization() before the request reaches the service (src/foam/box/SessionServerBox.java:149, src/foam/core/boot/CSpec.js:332). No served endpoint can therefore be shipped without its authentication and authorization check.
Every permission is denied unless explicitly granted. User groups descend from the basicUser group (src/groups.jrl), which grants only the minimum a logged-in user needs (src/groupPermissionJunctions.jrl). The same permission system governs both users and service-to-service calls, and can be applied at three levels: access to a service, individual objects (rows, via the model’s authorizer), and individual properties (columns, via PermissionedPropertyDAO), each described below.
Administrators can create users and groups and grant permissions, but cannot grant any permission they do not hold themselves:
group.update.<groupId> and the permission itself (GroupPermissionJunctionRefinement.js, message “You cannot add or remove a permission that you do not have.”).checkUserHasAllPermissionsInGroupAndAncestors in Group.js), so a group cannot be re-parented under admin to inherit its rights.A compromised or careless administrator account therefore cannot grant more access than it already has. These cases are covered by src/foam/core/auth/test/PreventPrivilegeEscalationTest.java.
Once a client connects to a service, security managing data manipulation is controlled at the model level (discussed below in Model Security).
The service that the CSpec describes is specified by one of the following properties, which resolve to a modelled class.
When the service is served, then the client property must be configured.
Additionally if the service is modelled as an INTERFACE, then boxClass property must also be provided. See /#flowdoc/Boxes.flow for a discussion of FOAM’s Box messaging protocol.
When the service is a DAO, then typically, a serviceScript is used to describe the DAO stack with EasyDAO (discussed below).
Model security applies to both client and server operations, and is independent of the service’s accessibility.
Model security is implemented via permissions. The permissins are imposed at two levels: 1) the model itself controlling instance visibility and manipulation, and 2) model properties controlling visibility and manipulation of individual properties.
Model level authentication is imposed via AuthorizationDAO, verifying the user has appropriate permissions for each CRUD operation.
Two interfaces control how the AuthorizationDAO is configured for each service:
AuthorizableAuthorizer which delegates to the model for permission checking.Authorizable when requiring behaviour different from the StandardAuthorizer.
foam.core.auth.User as an example.Typically the AuthorizationDAO is configured by EasyDAO.
EasyDAO is a support model designed to hide the complexities of, correctly and securely, configuring DAO decoration for a service (DAO stack).
EasyDAO properties relevant to Model level authentication:
Model property authentication allows for fine grained per-property permissioning. It is imposed by PermissionedPropertyDAO.
Model property permissioning affects both client and server.
Typically, PermissionedPropertyDAO is configured by EasyDAO, with property permissioned.
EasyDAO properties relevant to Model level authentication:
puts replace the incomming value with the previous value, nullify any change.finds replace the current property value with it’s default.src/foam/u2/Element2.js, foam/core/auth/PermissionedPropertyDAO.js:118 & 222FOAM’s default persistence is its own journalled DAO (JDAO over an in-memory MDAO) rather than an external database server. There is no database login, network port or administrative console: data is reachable only through the application’s DAO stack and its authentication, authorization and audit layers. Schema changes come from model changes, which go through code review, not from manual DDL.
System credentials, such as keys and passwords for external services, belong in the key store managed by foam.core.security.KeyStoreManager, not in journals, source code or configuration. User passwords are stored only as salted PBKDF2 hashes (foam.util.Password.hash).
Auditing is a property of the storage layer, not something each service must remember to call.
When a DAO is persisted with JDAO (the default EasyDAO journal), the journal is itself the audit log. Before an update record, the journal writes a comment saying who made the change and when. Changes made by the system user get no comment, and consecutive writes by the same user within the journal’s commentWindowMs share one comment (writeComment_ in src/foam/dao/AbstractF3FileJournal.js):
// Modified by Jane Smith (1234) at 2026-10-30T14:02:11.517Z
p({"class":"com.example.Invoice","id":"INV-001","amount":150})
Because the journal is how the data is stored, this audit trail cannot be switched off without switching off persistence: there is no separate audit setting to disable or pause. Objects that implement LastModifiedByAware carry the same information on the record itself, so no comment is written for them.
A DAO persisted some other way, such as a JDBC DAO, has no journal and therefore no built-in audit trail. Auditing for such a DAO must be enabled explicitly, for example with EasyDAO’s history: true, which adds a HistoryDAO that writes a foam.dao.history.HistoryRecord (object id, user, agent, timestamp, and each changed property’s old and new value) for every update.
Failed logins are also recorded: FailedLoginAuthService logs each one and stores a LoginAttempt with the identifier, IP address and failure message in loginAttemptDAO.
authenticate on the cspec do? Is it safe to set it false?
authenticate comes into play when a CSpec is served (serve:true). A served service is exposed to clients and by default is authenticated. Authentication requires the user to have permission service.<CSpec.name>. An unauthenticated service (authenticate:false), will be visible to all clients.authenticate:false and EasyDAO authorize:false.authenticate:false, makes the service accessible to all clients.service.<modelDAO> and model.read.* are required. If EasyDAO authorizer is configured with the GlobalReadAuthoriizer, then all clients can read all objects, but permissions to create, update, delete (ex. model.update.*) are required.foam.core.auth.Authorizable and implement each of the authorizeOn... methods.foam.core.auth.Authorizable or, for a DAO, EasyDAO.authorize is enabled (which it is by default), then any read request by a client must have permission <modelname>.read.* or one or more <modelname>.read.id for each id of interest.model.read.* is a wildcard permission which allows a user to see all ids (all instances) of the model in question. By default a user is restricted to only see instances they have been given explicit access to via a permission of the form model.read.id.Auth.sudo is used when logic requires reduced scoped or scope of another user (other than system). For example, Approvals, approved by an operator but when put, the context of the user that the Approval affects is used so that rules of the post approval flow execute in the user’s scope.-Dfoam.box.debug=true to log the stack trace of every exception a service returns (DEBUG in src/foam/java/Skeleton.js).