foam3

FOAM Application Security

Structural Security — Immunity by Design

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.

XSS Immunity — U2 and Content Security Policy

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:

These constraints forced a fully component-based, code-wired architecture. U2 was built to satisfy all three by construction:

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 these constraints through a self-imposed Content Security Policy. The security properties remain, independent of the platform that originally motivated them.

SQL Injection Immunity — MLang

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 JDBCDAO executes against a SQL database, it generates parameterised queries from the predicate tree, with data always kept separate from query structure.

The Common Principle

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 Code injection via eval No eval, strict CSP eval-based injection impossible

Application Security — Services and Permissions

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.

CSpecs are deployed in services.jrl journals and can also be created and manipulated at runtime.

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

Unauthenticated services are open to all clients for reading.

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

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

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:

  1. Authorizable
    • implemented by the model itself. AuthorizationDAO is configured with the AuthorizableAuthorizer which delegates to the model for permission checking.
    • A model would implement Authorizable when requiring behaviour different from the StandardAuthorizer.
      • see foam.core.auth.User as an example.
  2. Authorizer - implemented as a stand alone class. FOAM provides implementations for common scenarios that can be applied to most models.
    1. StandardAuthorizer (default)
      • verifies user has permission for each CRUD operation.
      • permission format (all lowercase):
        • modelname.read.id
        • modelname.create
        • modelname.update.id
        • modelname.remove.id
    2. Global Authorizers:
      • Global authorizers extend StandardAuthorizer and make particular CRUD operations unauthenticated.
        • GlobalReadAuthorizer: permission not required to read all DAO entries.
        • GlobalFindAuthorizer: permission not required to find all DAO entries.
        • GlobalPutAuthorizer: permission not required to create or update DAO entries.
        • GlobalCreateAuthorizer: permission not require to create new DAO entries.
        • GlobalFindOrPutAuthorizer: permission not required to find or put DAO entries.

EasyDAO

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 Level (incomplete)

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:

Q & A (incomplete)

  1. What does setting 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.
    • If the CSpec is for a DAO, then security shifts to the model level (described elsewhere). It is unwise, without careful review, to have both CSpec authenticate:false and EasyDAO authorize:false.
    • If the CSpec is for a non-DAO service, then authenticate:false, makes the service accessible to all clients.
  2. Do I need to set both service.somethingDAO and something.read.* permissions?
    • If your model is using the default EasyDAO authorization, then both permissions 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.
  3. What do I need to make my object Authorizable? Does it just work by default?
    • EasyDAO, by default, will decorate your model’s DAO with an AuthorizationDAO which will enforce permissions for each CRUD operation (read, create, update, and delete). If you require permission check behaviour different from the StandardAuthorizer, then have your model implement foam.core.auth.Authorizable and implement each of the authorizeOn... methods.
  4. How do the something.read.* permissions come into play if my object is authorizable
    • if a model implements 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.
  5. What is the “global read permission” in the standardauthorizer class for?
    • The global read permission 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.
  6. How is the context configured in the authorizable callback for an Authorizable service? Does it have permissions scoped to the user making the request or system permissions or what? If I want to check something in a different dao that the user doesn’t have permission for which context do i use? Can I sudo?
    • Every client request is performed with the context scoped to the user. If server logic requires access the user does not have then generally it can use the context of this.getX(). The service instance context is set at creation time and is that of the system.
    • 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.
  7. Also feature request: some sort of debugging mode or debugging flag i can put on my requests so I can check why i’m not seeing object I expected in a DAO. Debugging failed permission check is a bit of a black box.
    • uncomment src/foam/java/Skeleton.js:157
    • TODO: implement support for this.

Other Security Topics (TODO)