Browser Security

Altis includes a framework to ensure frontend security in browsers. Out of the box, Altis sends basic security headers, but we recommend sending more specific headers where you can.

To set browser security settings, set values in your composer.json configuration under extra.altis.modules.security.browser:

{
    "extra": {
        "altis": {
            "modules": {
                "security": {
                    "browser": {
                        "automatic-integrity": true
                    }
                }
            }
        }
    }
}

Note: In this document, we'll only show the browser configuration for brevity.

You can also disable browser security altogether by setting browser to false:

{
    "browser": false
}

Content-Security-Policy

Altis can automatically gather and send Content-Security-Policy policies for you.

Site-wide policy

Out of the box, only basic policies are sent:

  • base-uri 'self' is sent, which disables the ability to output <base> tags, ensuring links and assets cannot be hijacked
  • object-src 'none' is sent, which blocks <object>, <embed>, and <applet> tags entirely

(These are considered best practices for all sites, but can be overridden if needed.)

To change the default Content-Security-Policy, you can configure it in your composer.json under extra.altis.modules.security.browser.content-security-policy:

{
    "browser": {
        "content-security-policy": {
            "base-uri": [
                "self"
            ],
            "object-src": [
                "none"
            ]
        }
    }
}

Keys under content-security-policy should be a valid directive, and the value should be either a string or list of strings:

{
    "browser": {
        "content-security-policy": {
            "base-uri": [
                "self"
            ],
            "object-src": [
                "none"
            ],
            "font-src": [
                "https://fonts.gstatic.com",
                "https://cdnjs.cloudflare.com"
            ],
            "script-src": [
                "https:",
                "unsafe-inline"
            ]
        }
    }
}

Special directives ('self', 'unsafe-inline', 'unsafe-eval', 'none', 'strict-dynamic') do not need to be double-quoted.

To build Content-Security-Policy policies, we recommend using the Laboratory CSP toolkit extension for Firefox, and the CSP Evaluator tool.

Page-specific policies

For page-specific policies, you can add a filter to altis.security.browser.content_security_policies to set policies. This filter receives an array, where the keys are the policy directive names. Each item can either be a string or a list of directive value strings:

add_filter( 'altis.security.browser.content_security_policies', function ( array $policies ) : array {
    // Policies can be set as strings.
    $policies['object-src'] = 'none';
    $policies['base-uri'] = 'self';

    // Policies can also be set as arrays.
    $policies['font-src'] = [
        'https://fonts.gstatic.com',
        'https://cdnjs.cloudflare.com',
    ];

    // Special directives (such as `unsafe-inline`) are handled for you.
    $policies['script-src'] = [
        'https:',
        'unsafe-inline',
    ];

    return $policies;
} );

You can also modify individual directives if desired:

// You can filter specific keys via the filter name.
add_filter( 'altis.security.browser.filter_policy_value.font-src', function ( array $values ) : array {
    $values[] = 'https://fonts.gstatic.com';
    return $values;
} );

// A filter is also available with the directive name in a parameter.
add_filter( 'altis.security.browser.filter_policy_value', function ( array $values, string $name ) : array {
    if ( $name === 'font-src' ) {
        $values[] = 'https://cdnjs.cloudflare.com';
    }

    return $values;
} );

Subresource Integrity

Altis automatically adds subresource integrity hashes where possible. These are generated for any files on the same server; i.e. any plugin or theme assets.

For external assets, you can manually set the integrity hash. After enqueuing (or registering) your asset, use the set_hash_for_script() or set_hash_for_style() helpers:

// Setting hashes for scripts.
use function Altis\Security\Browser\set_hash_for_script;
wp_enqueue_script( 'my-handle', 'https://...' );
set_hash_for_script( 'my-handle', 'sha384-...' );

// Setting hashes for styles.
use function Altis\Security\Browser\set_hash_for_style;
wp_enqueue_style( 'my-handle', 'https://...' );
set_hash_for_style( 'my-handle', 'sha384-...' );

Automatically-generated hashes are automatically cached in the object cache, linked to the filename and version of the script or stylesheet.

You can disable the automatic generation of the integrity hashes if desired by setting browser.automatic-integrity to false:

{
    "browser": {
        "automatic-integrity": false
    }
}

Security Headers

Altis automatically adds various miscellaneous security headers by default. These follow best-practices for web security and aim to provide a sensible, secure default.

In some cases, you may want to adjust or disable these headers depending on the use cases of your site.

Strict-Transport-Security

The Strict-Transport-Security header ( sometimes called HSTS) is used to enforce HTTPS (TLS/SSL) connections when loading a site and can be used to enhance the site's security.

By default, Altis enables HSTS with the value max-age=31536000; includeSubDomains. You can configure the header using the strict-transport-security setting:

{
    "browser": {
        "strict-transport-security": "max-age=3600"
    }
}

You can also switch the header off completely by setting this to false:

{
    "browser": {
        "strict-transport-security": false
    }
}

Finally, if you set the value to null then Altis will send the header but only if the current request is already using HTTPS.

X-Content-Type-Options

By default, Altis adds a X-Content-Type-Options header with the value set to nosniff. This prevents browsers from attempting to guess the content type based on the content, and instead forces them to follow the type set in the Content-Type header.

This should generally be sent, and your content type should always be set explicitly. If you need to disable it, set browser.nosniff-header to false:

{
    "browser": {
        "nosniff-header": false
    }
}

X-Frame-Options

By default, Altis adds a X-Frame-Options header with the value set to sameorigin. This prevents your site from being embedded into another site with an iframe, which can prevent clickjacking attacks.

This should generally be sent, but in some cases, you may want to allow specific sites to embed your site. To disable the automatic header, set browser.frame-options-header to false:

{
    "browser": {
        "frame-options-header": false
    }
}

You can then send your own headers as needed. We recommend hooking into the template_redirect hook to send these headers.

X-XSS-Protection

By default, Altis adds a X-XSS-Protection header with the value set to 1; mode=block. This prevents browsers from loading if they detect cross-site scripting (XSS) attacks.

This should generally be sent. If you need to disable it, set browser.xss-protection-header to false:

{
    "browser": {
        "xss-protection-header": false
    }
}

Restrict CORS origins

By default, WordPress will allow REST API requests from any origin. You can add a filter to altis.security.browser.rest_allow_origin to restrict CORS origins.

To completely disallow external requests, set the filter to false:

add_filter( 'altis.security.browser.rest_allow_origin', '__return_false' );

To allow specific origins only:

add_filter( 'altis.security.browser.rest_allow_origin', function ( bool $allow, string $origin ) : bool {

    $allowed_origins = [
        'https://www.example.com',
    ];

    if ( in_array( $origin, $allow_origins, true ) ) {
        return true;
    }

    return false;
}, 10, 2 );

To disallow all .local domains:

add_filter( 'altis.security.browser.rest_allow_origin', function ( bool $allow, string $origin ) : bool {
    if ( false !== strpos( $origin, '.local' ) ) {
        return false;
    }

    return $allow;
}, 10, 2 );