Repository: shapesecurity/salvation
Pull request #216 (2018-12-27 23:38:20)

Introduce new directives
- script-src-elem, script-src-attr
- style-src-elem, style-src-attr

Adopt renaming 'unsafe-hashed-attributes' to 'unsafe-hashes'

fixes https://github.com/shapesecurity/salvation/issues/208
fixes https://github.com/shapesecurity/salvation/issues/207

fixes various bugs around optimisation and union merging

michaelficarra: We should combine identical `script-src-elem` and `script-src-attr` into `script-src`.
michaelficarra: Should we warn about the presence of `unsafe-hashed-attributes`?
shekyan: @michaelficarra comments are addressed.
bakkot: @michaelficarra:
> We should combine identical `script-src-elem` and `script-src-attr` into script-src.

If I understand correctly, that's not safe unless there is a `worker-src` defined, since `worker-src` will fall back to `script-src` but not `script-src-elem` / `script-src-attr`.

Which would mean the current implementation, which added that combining logic, is wrong.
bakkot: That said, you can do the merging for `style-src`, and for `script-src-elem` / `script-src-attr` if they are identical to each other _and_ to `script-src`.
bakkot: Actually, there's another problem with merging them: [`EnsureCSPDoesNotBlockStringCompilation`](https://www.w3.org/TR/CSP3/#can-compile-strings) is based on the presence or absence of `"unsafe-eval"` in `script-src` or `default-src` only, not `script-src-elem` / `script-src-attr`. I can will try to come up with a correct description of when it's safe to merge.

bakkot: Also: `worker-src a; child-src a;` should get optimized to `child-src a`.

Patch:
diff --git a/src/main/java/com/shapesecurity/salvation/Constants.java b/src/main/java/com/shapesecurity/salvation/Constants.java
index c99e35f..7cefe82 100644
--- a/src/main/java/com/shapesecurity/salvation/Constants.java
+++ b/src/main/java/com/shapesecurity/salvation/Constants.java
@@ -17,7 +17,7 @@ public class Constants {
 			.compile("^(?:script|style)$", Pattern.CASE_INSENSITIVE);
 	// RFC 2045 appendix A: productions of type and subtype
 	public static final Pattern mediaTypePattern = Pattern.compile("^(?<type>[a-zA-Z0-9!#$%^&*\\-_+{}|'.`~]+)/(?<subtype>[a-zA-Z0-9!#$%^&*\\-_+{}|'.`~]+)$");
-	public static final Pattern unquotedKeywordPattern = Pattern.compile("^(?:self|unsafe-inline|unsafe-eval|unsafe-redirect|none|strict-dynamic|unsafe-hashed-attributes|report-sample|unsafe-allow-redirects)$");
+	public static final Pattern unquotedKeywordPattern = Pattern.compile("^(?:self|unsafe-inline|unsafe-eval|unsafe-redirect|none|strict-dynamic|unsafe-hashes|report-sample|unsafe-allow-redirects)$");
 	// port-part constants
 	public static final int WILDCARD_PORT = -200;
 	public static final int EMPTY_PORT = -1;
diff --git a/src/main/java/com/shapesecurity/salvation/Parser.java b/src/main/java/com/shapesecurity/salvation/Parser.java
index f24efdd..f404a10 100644
--- a/src/main/java/com/shapesecurity/salvation/Parser.java
+++ b/src/main/java/com/shapesecurity/salvation/Parser.java
@@ -3,6 +3,7 @@ package com.shapesecurity.salvation;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -52,8 +53,12 @@ import com.shapesecurity.salvation.directives.ReportToDirective;
 import com.shapesecurity.salvation.directives.ReportUriDirective;
 import com.shapesecurity.salvation.directives.RequireSriForDirective;
 import com.shapesecurity.salvation.directives.SandboxDirective;
+import com.shapesecurity.salvation.directives.ScriptSrcAttrDirective;
 import com.shapesecurity.salvation.directives.ScriptSrcDirective;
+import com.shapesecurity.salvation.directives.ScriptSrcElemDirective;
+import com.shapesecurity.salvation.directives.StyleSrcAttrDirective;
 import com.shapesecurity.salvation.directives.StyleSrcDirective;
+import com.shapesecurity.salvation.directives.StyleSrcElemDirective;
 import com.shapesecurity.salvation.directives.UpgradeInsecureRequestsDirective;
 import com.shapesecurity.salvation.directives.WorkerSrcDirective;
 import com.shapesecurity.salvation.tokens.DirectiveNameToken;
@@ -105,9 +110,9 @@ public class Parser {
 	private static final String explanation = "Ensure that this pattern is only used for backwards compatibility with older CSP implementations and is not an oversight.";
 	private static final String unsafeInlineWarningMessage = "The \"'unsafe-inline'\" keyword-source has no effect in source lists that contain hash-source or nonce-source in CSP2 and later. " + explanation;
 	private static final String strictDynamicWarningMessage = "The host-source and scheme-source expressions, as well as the \"'unsafe-inline'\" and \"'self'\" keyword-sources have no effect in source lists that contain \"'strict-dynamic'\" in CSP3 and later. " + explanation;
-	private static final String unsafeHashedWithoutHashWarningMessage = "The \"'unsafe-hashed-attributes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.";
+	private static final String unsafeHashesWithoutHashWarningMessage = "The \"'unsafe-hashes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.";
 
-	private enum SeenStates { SEEN_HASH, SEEN_HOST_OR_SCHEME_SOURCE, SEEN_NONE, SEEN_NONCE, SEEN_SELF, SEEN_STRICT_DYNAMIC, SEEN_UNSAFE_EVAL, SEEN_UNSAFE_INLINE, SEEN_UNSAFE_HASHED_ATTR, SEEN_REPORT_SAMPLE, SEEN_UNSAFE_ALLOW_REDIRECTS }
+	private enum SeenStates { SEEN_HASH, SEEN_HOST_OR_SCHEME_SOURCE, SEEN_NONE, SEEN_NONCE, SEEN_SELF, SEEN_STRICT_DYNAMIC, SEEN_UNSAFE_EVAL, SEEN_UNSAFE_INLINE, SEEN_UNSAFE_HASHES, SEEN_REPORT_SAMPLE, SEEN_UNSAFE_ALLOW_REDIRECTS }
 
 	@Nonnull
 	protected final Token[] tokens;
@@ -219,6 +224,7 @@ public class Parser {
 	@Nonnull
 	protected Policy parsePolicy() {
 		Policy policy = new Policy(this.origin);
+		LinkedHashMap<Class<? extends Directive>, Directive<? extends DirectiveValue>> directives = new LinkedHashMap<>();
 		while (this.hasNext()) {
 			if (this.hasNext(PolicySeparatorToken.class)) {
 				break;
@@ -229,14 +235,15 @@ public class Parser {
 			try {
 				Directive<? extends DirectiveValue> directive = this.parseDirective();
 				// only add a directive if it doesn't exist; used for handling duplicate directives in CSP headers
-				if (policy.getDirectiveByType(directive.getClass()) == null) {
-					policy.addDirective(directive);
+				if (!directives.containsKey(directive.getClass())) {
+					directives.put(directive.getClass(), directive);
 				} else {
 					this.warn(this.tokens[this.index - 2], "Policy contains more than one " + directive.name + " directive. All but the first instance will be ignored.");
 				}
 			} catch (DirectiveParseException ignored) {
 			}
 		}
+		policy.addDirectives(directives.values());
 		return policy;
 	}
 
@@ -368,9 +375,21 @@ public class Parser {
 				case ScriptSrc:
 					result = new ScriptSrcDirective(this.parseSourceList());
 					break;
+				case ScriptSrcElem:
+					result = new ScriptSrcElemDirective(this.parseSourceList());
+					break;
+				case ScriptSrcAttr:
+					result = new ScriptSrcAttrDirective(this.parseSourceList());
+					break;
 				case StyleSrc:
 					result = new StyleSrcDirective(this.parseSourceList());
 					break;
+				case StyleSrcElem:
+					result = new StyleSrcElemDirective(this.parseSourceList());
+					break;
+				case StyleSrcAttr:
+					result = new StyleSrcAttrDirective(this.parseSourceList());
+					break;
 				case UpgradeInsecureRequests:
 					warnFutureDirective(token);
 					this.enforceMissingDirectiveValue(token);
@@ -475,8 +494,8 @@ public class Parser {
 					seenStates.add(SeenStates.SEEN_STRICT_DYNAMIC);
 				} else if (se instanceof HostSource || se instanceof SchemeSource) {
 					seenStates.add(SeenStates.SEEN_HOST_OR_SCHEME_SOURCE);
-				} else if (se == KeywordSource.UnsafeHashedAttributes) {
-					seenStates.add(SeenStates.SEEN_UNSAFE_HASHED_ATTR);
+				} else if (se == KeywordSource.UnsafeHashes) {
+					seenStates.add(SeenStates.SEEN_UNSAFE_HASHES);
 				} else if (se == KeywordSource.ReportSample) {
 					seenStates.add(SeenStates.SEEN_REPORT_SAMPLE);
 				} else if (se == KeywordSource.UnsafeAllowRedirects) {
@@ -489,8 +508,8 @@ public class Parser {
 				parseException = true;
 			}
 		}
-		if (seenStates.contains(SeenStates.SEEN_UNSAFE_HASHED_ATTR) && !seenStates.contains(SeenStates.SEEN_HASH)) {
-			this.warn(this.tokens[0], unsafeHashedWithoutHashWarningMessage);
+		if (seenStates.contains(SeenStates.SEEN_UNSAFE_HASHES) && !seenStates.contains(SeenStates.SEEN_HASH)) {
+			this.warn(this.tokens[0], unsafeHashesWithoutHashWarningMessage);
 		}
 		if (parseException) {
 			throw INVALID_SOURCE_LIST;
@@ -532,8 +551,8 @@ public class Parser {
 			case "'unsafe-redirect'":
 				this.warn(token, "'unsafe-redirect' has been removed from CSP as of version 2.0.");
 				return KeywordSource.UnsafeRedirect;
-			case "'unsafe-hashed-attributes'":
-				return KeywordSource.UnsafeHashedAttributes;
+			case "'unsafe-hashes'":
+				return KeywordSource.UnsafeHashes;
 			case "'report-sample'":
 				return KeywordSource.ReportSample;
 			case "'unsafe-allow-redirects'":
@@ -595,6 +614,8 @@ public class Parser {
 						this.info(token, strictDynamicWarningMessage);
 					}
 					return new SchemeSource(token.value.substring(0, token.value.length() - 1));
+				} else if (token.value.equalsIgnoreCase("'unsafe-hashed-attributes'")) {
+					this.warn(token, "The CSP specification renamed 'unsafe-hashed-attributes' to 'unsafe-hashes' (June 2018).");
 				} else {
 					Matcher matcher = Constants.hostSourcePattern.matcher(token.value);
 					if (matcher.find()) {
diff --git a/src/main/java/com/shapesecurity/salvation/Tokeniser.java b/src/main/java/com/shapesecurity/salvation/Tokeniser.java
index 9d76451..32fc749 100644
--- a/src/main/java/com/shapesecurity/salvation/Tokeniser.java
+++ b/src/main/java/com/shapesecurity/salvation/Tokeniser.java
@@ -148,7 +148,11 @@ public class Tokeniser {
 					case ObjectSrc:
 					case PrefetchSrc:
 					case ScriptSrc:
+					case ScriptSrcElem:
+					case ScriptSrcAttr:
 					case StyleSrc:
+					case StyleSrcElem:
+					case StyleSrcAttr:
 					case WorkerSrc:
 						// media-type-list
 					case PluginTypes:
diff --git a/src/main/java/com/shapesecurity/salvation/data/Policy.java b/src/main/java/com/shapesecurity/salvation/data/Policy.java
index 923d2c5..dfe65c5 100644
--- a/src/main/java/com/shapesecurity/salvation/data/Policy.java
+++ b/src/main/java/com/shapesecurity/salvation/data/Policy.java
@@ -152,16 +152,24 @@ public class Policy implements Show {
 		// expand child-src
 		if (this.directives.containsKey(ChildSrcDirective.class) && !this.directives.containsKey(FrameSrcDirective.class)) {
 			ChildSrcDirective childSrcDirective = this.getDirectiveByType(ChildSrcDirective.class);
-			Set<SourceExpression> childSources = childSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
-			this.directives.put(FrameSrcDirective.class, new FrameSrcDirective(childSources));
+			Set<Directive> expandedDirectives = expandDirective(childSrcDirective);
+			expandedDirectives.forEach(this::insert);
 		}
 
 		// expand script-src
-		if (this.directives.containsKey(ScriptSrcDirective.class) && !this.directives.containsKey(WorkerSrcDirective.class)) {
+		if (this.directives.containsKey(ScriptSrcDirective.class)) {
 			ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
-			Set<SourceExpression> scriptSources = scriptSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
-			this.directives.put(WorkerSrcDirective.class, new WorkerSrcDirective(scriptSources));
+			Set<Directive> expandedDirectives = expandDirective(scriptSrcDirective);
+			expandedDirectives.forEach(this::insert);
+		}
+
+		// expand style-src
+		if (this.directives.containsKey(StyleSrcDirective.class)) {
+			StyleSrcDirective styleSrcDirective = this.getDirectiveByType(StyleSrcDirective.class);
+			Set<Directive> expandedDirectives = expandDirective(styleSrcDirective);
+			expandedDirectives.forEach(this::insert);
 		}
+
 	}
 
 	private <V extends SourceExpression, T extends Directive<V>> void eliminateRedundantSourceExpression(
@@ -177,7 +185,7 @@ public class Policy implements Show {
 		}
 	}
 
-	private void optimise() {
+	public void optimise() {
 		for (Map.Entry<Class<?>, Directive<? extends DirectiveValue>> entry : this.directives.entrySet()) {
 			Directive<? extends DirectiveValue> directive = entry.getValue();
 			if (directive instanceof SourceListDirective) {
@@ -216,20 +224,75 @@ public class Policy implements Show {
 			}
 		}
 
-		// * remove frame source directive if equivalent to child-src
+		// merge into script-src if script-src-elem and script-src-attr are identical and worker-src is present
+		ScriptSrcElemDirective scriptSrcElemDirective = this.getDirectiveByType(ScriptSrcElemDirective.class);
+		ScriptSrcAttrDirective scriptSrcAttrDirective = this.getDirectiveByType(ScriptSrcAttrDirective.class);
+		WorkerSrcDirective workerSrcDirective = this.getDirectiveByType(WorkerSrcDirective.class);
+		if (scriptSrcElemDirective != null && scriptSrcAttrDirective != null && workerSrcDirective != null) {
+			Set<SourceExpression> a = scriptSrcElemDirective.values().filter(x -> x != KeywordSource.UnsafeEval).collect(Collectors.toCollection(LinkedHashSet::new));
+			Set<SourceExpression> b = scriptSrcAttrDirective.values().filter(x -> x != KeywordSource.UnsafeEval).collect(Collectors.toCollection(LinkedHashSet::new));
+			if (a.equals(b)) {
+				ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
+				Set<SourceExpression> scriptSources = a;
+				if (scriptSrcDirective != null && scriptSrcDirective.contains(KeywordSource.UnsafeEval)) {
+					// unsafe-eval only applies when in script-src, not script-src-elem or script-src-attr.
+					// but if both of those and worker-src are present, script-src has no other effect.
+					scriptSources.add(KeywordSource.UnsafeEval);
+				}
+				scriptSrcDirective = new ScriptSrcDirective(scriptSources);
+				this.directives.put(ScriptSrcDirective.class, scriptSrcDirective);
+				this.directives.remove(ScriptSrcElemDirective.class);
+				this.directives.remove(ScriptSrcAttrDirective.class);
+			}
+		}
+
+		// merge into style-src if style-src-elem and style-src-attr are identical
+		StyleSrcElemDirective styleSrcElemDirective = this.getDirectiveByType(StyleSrcElemDirective.class);
+		StyleSrcAttrDirective styleSrcAttrDirective = this.getDirectiveByType(StyleSrcAttrDirective.class);
+		if (styleSrcElemDirective != null && styleSrcAttrDirective != null) {
+			Set<SourceExpression> a = styleSrcElemDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
+			Set<SourceExpression> b = styleSrcAttrDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
+			if (a.equals(b)) {
+				StyleSrcDirective styleSrcDirective = this.getDirectiveByType(StyleSrcDirective.class);
+				Set<SourceExpression> styleSources = a;
+				if (styleSrcDirective != null) {
+					styleSources.addAll(styleSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new)));
+				}
+				styleSrcDirective = new StyleSrcDirective(styleSources);
+				this.directives.put(StyleSrcDirective.class, styleSrcDirective);
+				this.directives.remove(StyleSrcElemDirective.class);
+				this.directives.remove(StyleSrcAttrDirective.class);
+			}
+		}
+
 		ChildSrcDirective childSrcDirective = this.getDirectiveByType(ChildSrcDirective.class);
 		if (childSrcDirective != null) {
 			Set<SourceExpression> childSources = childSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
+			// * remove worker source directive if equivalent to child-src
+			this.eliminateRedundantSourceExpression(childSources, WorkerSrcDirective.class);
+			// * remove frame source directive if equivalent to child-src
 			this.eliminateRedundantSourceExpression(childSources, FrameSrcDirective.class);
 		}
 
-		// * remove worker source directive if equivalent to script-src
 		ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
 		if (scriptSrcDirective != null) {
 			Set<SourceExpression> scriptSources = scriptSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
+			// * remove worker source directive if equivalent to script-src
 			this.eliminateRedundantSourceExpression(scriptSources, WorkerSrcDirective.class);
+			// * remove script-src-elem and script-src-attr directives if equivalent to script-src
+			this.eliminateRedundantSourceExpression(scriptSources, ScriptSrcElemDirective.class);
+			this.eliminateRedundantSourceExpression(scriptSources, ScriptSrcAttrDirective.class);
 		}
 
+		StyleSrcDirective styleSrcDirective = this.getDirectiveByType(StyleSrcDirective.class);
+		if (styleSrcDirective != null) {
+			Set<SourceExpression> styleSources = styleSrcDirective.values().collect(Collectors.toCollection(LinkedHashSet::new));
+			// * remove style-src-elem and style-src-attr directives if equivalent to script-src
+			this.eliminateRedundantSourceExpression(styleSources, StyleSrcElemDirective.class);
+			this.eliminateRedundantSourceExpression(styleSources, StyleSrcAttrDirective.class);
+		}
+
+
 		DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
 
 		Set<SourceExpression> defaultSources;
@@ -250,6 +313,7 @@ public class Policy implements Show {
 				this.directives.put(DefaultSrcDirective.class, defaultSrcDirective);
 			}
 
+
 			// * remove unnecessary default-src directives if all source directives exist
 			if (all(Directive.getFetchDirectives(), this.directives::containsKey)) {
 				this.directives.remove(DefaultSrcDirective.class);
@@ -329,7 +393,8 @@ public class Policy implements Show {
 		if (!(directive instanceof DefaultSrcDirective)) {
 			this.expandDefaultSrc();
 		}
-		this.unionDirectivePrivate(directive);
+		Set<Directive> expandedDirectives = expandDirective(directive);
+		expandedDirectives.forEach(x -> this.unionDirectivePrivate(x));
 		this.optimise();
 	}
 
@@ -341,10 +406,33 @@ public class Policy implements Show {
 		if (!(directive instanceof DefaultSrcDirective)) {
 			this.expandDefaultSrc();
 		}
-		this.intersectDirectivePrivate(directive);
+		Set<Directive> expandedDirectives = expandDirective(directive);
+		expandedDirectives.forEach(x -> this.intersectDirectivePrivate(x));
 		this.optimise();
 	}
 
+	private static Set<Directive> expandDirective(@Nonnull Directive<? extends DirectiveValue> directive) {
+		if (!(directive instanceof FetchDirective)) {
+			return Collections.singleton(directive);
+		}
+		Set<Directive> directives = new LinkedHashSet<>();
+		directives.add(directive);
+		Stream<SourceExpression> stream = (Stream<SourceExpression>) directive.values();
+		Set<SourceExpression> sources = stream.collect(Collectors.toCollection(LinkedHashSet::new));
+		if (directive instanceof ChildSrcDirective) {
+			directives.add(new FrameSrcDirective(sources));
+			directives.add(new WorkerSrcDirective(sources));
+		} else if (directive instanceof ScriptSrcDirective) {
+			directives.add(new ScriptSrcElemDirective(sources));
+			directives.add(new ScriptSrcAttrDirective(sources));
+			directives.add(new WorkerSrcDirective(sources));
+		} else if (directive instanceof StyleSrcDirective) {
+			directives.add(new StyleSrcElemDirective(sources));
+			directives.add(new StyleSrcAttrDirective(sources));
+		}
+		return directives;
+	}
+
 	// union a directive if it does not exist; used for policy manipulation and composition
 	private <V extends DirectiveValue, T extends Directive<V>> void unionDirectivePrivate(@Nonnull T directive) {
 		@SuppressWarnings("unchecked") T oldDirective = (T) this.directives.get(directive.getClass());
@@ -373,6 +461,16 @@ public class Policy implements Show {
 		}
 	}
 
+	// differs from the above in that it will override things
+	public void addDirectives(@Nonnull Iterable<Directive<? extends DirectiveValue>> directives) {
+		for (Directive<? extends DirectiveValue> d : directives) {
+			this.directives.put(d.getClass(), d);
+		}
+		this.expandDefaultSrc();
+		this.resolveSelf();
+		this.optimise();
+	}
+
 	@Nonnull
 	public Collection<Directive<? extends DirectiveValue>> getDirectives() {
 		return this.directives.values();
@@ -421,7 +519,7 @@ public class Policy implements Show {
 	}
 
 	private boolean defaultsAllowAttributeWithHash(@Nonnull HashAlgorithm algorithm, @Nonnull Base64Value hashValue) {
-		if (!this.defaultsHaveUnsafeHashedAttributes()) {
+		if (!this.defaultsHaveUnsafeHashes()) {
 			return false;
 		}
 		DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
@@ -477,12 +575,12 @@ public class Policy implements Show {
 		return defaultSrcDirective.values().anyMatch(x -> x == KeywordSource.UnsafeInline);
 	}
 
-	private boolean defaultsHaveUnsafeHashedAttributes() {
+	private boolean defaultsHaveUnsafeHashes() {
 		DefaultSrcDirective defaultSrcDirective = this.getDirectiveByType(DefaultSrcDirective.class);
 		if (defaultSrcDirective == null) {
 			return false;
 		}
-		return defaultSrcDirective.values().anyMatch(x -> x == KeywordSource.UnsafeHashedAttributes);
+		return defaultSrcDirective.values().anyMatch(x -> x == KeywordSource.UnsafeHashes);
 	}
 
 	private boolean defaultsHaveNonceSource() {
@@ -601,33 +699,66 @@ public class Policy implements Show {
 		if (this.allowsUnsafeInlineStyle()) {
 			return true;
 		}
+
+		StyleSrcElemDirective styleSrcElemDirective = this.getDirectiveByType(StyleSrcElemDirective.class);
+		if (styleSrcElemDirective != null) {
+			return styleSrcElemDirective.matchesHash(algorithm, hashValue);
+		}
+
 		StyleSrcDirective styleSrcDirective = this.getDirectiveByType(StyleSrcDirective.class);
-		if (styleSrcDirective == null) {
-			return this.defaultsAllowHash(algorithm, hashValue);
+		if (styleSrcDirective != null) {
+			return styleSrcDirective.matchesHash(algorithm, hashValue);
 		}
-		return styleSrcDirective.matchesHash(algorithm, hashValue);
+
+		return this.defaultsAllowHash(algorithm, hashValue);
+
 	}
 
 	public boolean allowsScriptWithHash(@Nonnull HashAlgorithm algorithm, @Nonnull Base64Value hashValue) {
 		if (this.allowsUnsafeInlineScript()) {
 			return true;
 		}
+		ScriptSrcElemDirective scriptSrcElemDirective = this.getDirectiveByType(ScriptSrcElemDirective.class);
+		if (scriptSrcElemDirective != null) {
+			return scriptSrcElemDirective.matchesHash(algorithm, hashValue);
+		}
+
 		ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
-		if (scriptSrcDirective == null) {
-			return this.defaultsAllowHash(algorithm, hashValue);
+		if (scriptSrcDirective != null) {
+			return scriptSrcDirective.matchesHash(algorithm, hashValue);
 		}
-		return scriptSrcDirective.matchesHash(algorithm, hashValue);
+
+		return this.defaultsAllowHash(algorithm, hashValue);
 	}
 
-	public boolean allowsAttributeWithHash(@Nonnull HashAlgorithm algorithm, @Nonnull Base64Value hashValue) {
-		if (!this.haveUnsafeHashedAttributes()) {
+	public boolean allowsScriptAttributeWithHash(@Nonnull HashAlgorithm algorithm, @Nonnull Base64Value hashValue) {
+		if (!this.haveUnsafeScriptHashes()) {
 			return false;
 		}
-		ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
-		if (scriptSrcDirective == null) {
-			return this.defaultsAllowAttributeWithHash(algorithm, hashValue);
+		ScriptSrcAttrDirective scriptSrcAttrDirective = this.getDirectiveByType(ScriptSrcAttrDirective.class);
+		if (scriptSrcAttrDirective == null) {
+			ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
+			if (scriptSrcDirective == null) {
+				return this.defaultsAllowAttributeWithHash(algorithm, hashValue);
+			}
+			return scriptSrcDirective.matchesHash(algorithm, hashValue);
+		}
+		return scriptSrcAttrDirective.matchesHash(algorithm, hashValue);
+	}
+
+	public boolean allowsStyleAttributeWithHash(@Nonnull HashAlgorithm algorithm, @Nonnull Base64Value hashValue) {
+		if (!this.haveUnsafeStyleHashes()) {
+			return false;
+		}
+		StyleSrcAttrDirective styleSrcAttrDirective = this.getDirectiveByType(StyleSrcAttrDirective.class);
+		if (styleSrcAttrDirective == null) {
+			StyleSrcDirective styleSrcDirective = this.getDirectiveByType(StyleSrcDirective.class);
+			if (styleSrcDirective == null) {
+				return this.defaultsAllowAttributeWithHash(algorithm, hashValue);
+			}
+			return styleSrcDirective.matchesHash(algorithm, hashValue);
 		}
-		return scriptSrcDirective.matchesHash(algorithm, hashValue);
+		return styleSrcAttrDirective.matchesHash(algorithm, hashValue);
 	}
 
 	public boolean allowsUnsafeInlineScript() {
@@ -638,9 +769,16 @@ public class Policy implements Show {
 
 	}
 
-	public boolean haveUnsafeHashedAttributes() {
-		return containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeHashedAttributes) ||
-				defaultsHaveUnsafeHashedAttributes();
+	public boolean haveUnsafeScriptHashes() {
+		return containsSourceExpression(ScriptSrcAttrDirective.class, x -> x == KeywordSource.UnsafeHashes) ||
+				containsSourceExpression(ScriptSrcDirective.class, x -> x == KeywordSource.UnsafeHashes) ||
+				defaultsHaveUnsafeHashes();
+	}
+
+	public boolean haveUnsafeStyleHashes() {
+		return containsSourceExpression(StyleSrcAttrDirective.class, x -> x == KeywordSource.UnsafeHashes) ||
+				containsSourceExpression(StyleSrcDirective.class, x -> x == KeywordSource.UnsafeHashes) ||
+				defaultsHaveUnsafeHashes();
 	}
 
 	public <T extends SourceListDirective> boolean containsSourceExpression(Class<T> type, @Nonnull Predicate<SourceExpression> predicate) {
@@ -678,11 +816,16 @@ public class Policy implements Show {
 		if (this.allowsUnsafeInlineScript()) {
 			return true;
 		}
+		ScriptSrcElemDirective scriptSrcElemDirective = this.getDirectiveByType(ScriptSrcElemDirective.class);
+		if (scriptSrcElemDirective != null) {
+			return scriptSrcElemDirective.matchesNonce(nonce);
+		}
+
 		ScriptSrcDirective scriptSrcDirective = this.getDirectiveByType(ScriptSrcDirective.class);
-		if (scriptSrcDirective == null) {
-			return this.defaultsAllowNonce(nonce);
+		if (scriptSrcDirective != null) {
+			return scriptSrcDirective.matchesNonce(nonce);
 		}
-		return scriptSrcDirective.matchesNonce(nonce);
+		return this.defaultsAllowNonce(nonce);
 	}
 
 	public boolean allowsScriptWithNonce(@Nonnull Base64Value nonce) {
@@ -693,11 +836,16 @@ public class Policy implements Show {
 		if (this.allowsUnsafeInlineStyle()) {
 			return true;
 		}
+		StyleSrcElemDirective styleSrcElemDirective = this.getDirectiveByType(StyleSrcElemDirective.class);
+		if (styleSrcElemDirective != null) {
+			return styleSrcElemDirective.matchesNonce(nonce);
+		}
+
 		StyleSrcDirective styleSrcDirective = this.getDirectiveByType(StyleSrcDirective.class);
-		if (styleSrcDirective == null) {
-			return this.defaultsAllowNonce(nonce);
+		if (styleSrcDirective != null) {
+			return styleSrcDirective.matchesNonce(nonce);
 		}
-		return styleSrcDirective.matchesNonce(nonce);
+		return this.defaultsAllowNonce(nonce);
 	}
 
 	public boolean allowsStyleWithNonce(@Nonnull Base64Value nonce) {
@@ -874,4 +1022,10 @@ public class Policy implements Show {
 		}
 		return false;
 	}
+
+	private void insert(@Nonnull Directive x) {
+		if (this.getDirectiveByType(x.getClass()) == null) {
+			this.directives.put(x.getClass(), x);
+		}
+	}
 }
diff --git a/src/main/java/com/shapesecurity/salvation/directiveValues/KeywordSource.java b/src/main/java/com/shapesecurity/salvation/directiveValues/KeywordSource.java
index a25892e..8acc6be 100644
--- a/src/main/java/com/shapesecurity/salvation/directiveValues/KeywordSource.java
+++ b/src/main/java/com/shapesecurity/salvation/directiveValues/KeywordSource.java
@@ -20,7 +20,7 @@ public class KeywordSource implements SourceExpression, AncestorSource, MatchesS
 	@Nonnull
 	public static final KeywordSource StrictDynamic = new KeywordSource("strict-dynamic");
 	@Nonnull
-	public static final KeywordSource UnsafeHashedAttributes = new KeywordSource("unsafe-hashed-attributes");
+	public static final KeywordSource UnsafeHashes = new KeywordSource("unsafe-hashes");
 	@Nonnull
 	public static final KeywordSource ReportSample = new KeywordSource("report-sample");
 	@Nonnull
diff --git a/src/main/java/com/shapesecurity/salvation/directives/Directive.java b/src/main/java/com/shapesecurity/salvation/directives/Directive.java
index 649d8c7..a890e55 100644
--- a/src/main/java/com/shapesecurity/salvation/directives/Directive.java
+++ b/src/main/java/com/shapesecurity/salvation/directives/Directive.java
@@ -41,7 +41,7 @@ public abstract class Directive<Value extends DirectiveValue> implements Show {
 	static void register(Class<? extends Directive> directiveClass) {
 		if (NestedContextDirective.class.isAssignableFrom(directiveClass)) {
 			nestedContextDirectives.add(directiveClass);
-		} else if (FetchDirective.class.isAssignableFrom(directiveClass) && directiveClass != DefaultSrcDirective.class && directiveClass != FrameSrcDirective.class && directiveClass != WorkerSrcDirective.class) {
+		} else if (FetchDirective.class.isAssignableFrom(directiveClass) && directiveClass != DefaultSrcDirective.class && directiveClass != FrameSrcDirective.class && directiveClass != WorkerSrcDirective.class && directiveClass != ScriptSrcElemDirective.class && directiveClass != ScriptSrcAttrDirective.class && directiveClass != StyleSrcElemDirective.class && directiveClass != StyleSrcAttrDirective.class) {
 			fetchDirectives.add(directiveClass);
 		}
 	}
@@ -49,7 +49,11 @@ public abstract class Directive<Value extends DirectiveValue> implements Show {
 	static {
 		// NOTE: new directive types must be registered here
 		register(ScriptSrcDirective.class);
+		register(ScriptSrcElemDirective.class);
+		register(ScriptSrcAttrDirective.class);
 		register(StyleSrcDirective.class);
+		register(StyleSrcElemDirective.class);
+		register(StyleSrcAttrDirective.class);
 		register(ImgSrcDirective.class);
 		register(ChildSrcDirective.class);
 		register(ConnectSrcDirective.class);
diff --git a/src/main/java/com/shapesecurity/salvation/directives/ScriptSrcAttrDirective.java b/src/main/java/com/shapesecurity/salvation/directives/ScriptSrcAttrDirective.java
new file mode 100644
index 0000000..0dc3f97
--- /dev/null
+++ b/src/main/java/com/shapesecurity/salvation/directives/ScriptSrcAttrDirective.java
@@ -0,0 +1,22 @@
+package com.shapesecurity.salvation.directives;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import com.shapesecurity.salvation.directiveValues.SourceExpression;
+
+public class ScriptSrcAttrDirective extends FetchDirective {
+	@Nonnull
+	private static final String NAME = "script-src-attr";
+
+	public ScriptSrcAttrDirective(@Nonnull Set<SourceExpression> sourceExpressions) {
+		super(ScriptSrcAttrDirective.NAME, sourceExpressions);
+	}
+
+	@Nonnull
+	@Override
+	public Directive<SourceExpression> construct(Set<SourceExpression> newValues) {
+		return new ScriptSrcAttrDirective(newValues);
+	}
+}
diff --git a/src/main/java/com/shapesecurity/salvation/directives/ScriptSrcElemDirective.java b/src/main/java/com/shapesecurity/salvation/directives/ScriptSrcElemDirective.java
new file mode 100644
index 0000000..ef32a08
--- /dev/null
+++ b/src/main/java/com/shapesecurity/salvation/directives/ScriptSrcElemDirective.java
@@ -0,0 +1,22 @@
+package com.shapesecurity.salvation.directives;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import com.shapesecurity.salvation.directiveValues.SourceExpression;
+
+public class ScriptSrcElemDirective extends FetchDirective {
+	@Nonnull
+	private static final String NAME = "script-src-elem";
+
+	public ScriptSrcElemDirective(@Nonnull Set<SourceExpression> sourceExpressions) {
+		super(ScriptSrcElemDirective.NAME, sourceExpressions);
+	}
+
+	@Nonnull
+	@Override
+	public Directive<SourceExpression> construct(Set<SourceExpression> newValues) {
+		return new ScriptSrcElemDirective(newValues);
+	}
+}
diff --git a/src/main/java/com/shapesecurity/salvation/directives/StyleSrcAttrDirective.java b/src/main/java/com/shapesecurity/salvation/directives/StyleSrcAttrDirective.java
new file mode 100644
index 0000000..23b0e28
--- /dev/null
+++ b/src/main/java/com/shapesecurity/salvation/directives/StyleSrcAttrDirective.java
@@ -0,0 +1,22 @@
+package com.shapesecurity.salvation.directives;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import com.shapesecurity.salvation.directiveValues.SourceExpression;
+
+public class StyleSrcAttrDirective extends FetchDirective {
+	@Nonnull
+	private static final String NAME = "style-src-attr";
+
+	public StyleSrcAttrDirective(@Nonnull Set<SourceExpression> sourceExpressions) {
+		super(StyleSrcAttrDirective.NAME, sourceExpressions);
+	}
+
+	@Nonnull
+	@Override
+	public Directive<SourceExpression> construct(Set<SourceExpression> newValues) {
+		return new StyleSrcAttrDirective(newValues);
+	}
+}
diff --git a/src/main/java/com/shapesecurity/salvation/directives/StyleSrcElemDirective.java b/src/main/java/com/shapesecurity/salvation/directives/StyleSrcElemDirective.java
new file mode 100644
index 0000000..cb44b0e
--- /dev/null
+++ b/src/main/java/com/shapesecurity/salvation/directives/StyleSrcElemDirective.java
@@ -0,0 +1,22 @@
+package com.shapesecurity.salvation.directives;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import com.shapesecurity.salvation.directiveValues.SourceExpression;
+
+public class StyleSrcElemDirective extends FetchDirective {
+	@Nonnull
+	private static final String NAME = "style-src-elem";
+
+	public StyleSrcElemDirective(@Nonnull Set<SourceExpression> sourceExpressions) {
+		super(StyleSrcElemDirective.NAME, sourceExpressions);
+	}
+
+	@Nonnull
+	@Override
+	public Directive<SourceExpression> construct(Set<SourceExpression> newValues) {
+		return new StyleSrcElemDirective(newValues);
+	}
+}
diff --git a/src/main/java/com/shapesecurity/salvation/tokens/DirectiveNameToken.java b/src/main/java/com/shapesecurity/salvation/tokens/DirectiveNameToken.java
index 642a5a5..fc33548 100644
--- a/src/main/java/com/shapesecurity/salvation/tokens/DirectiveNameToken.java
+++ b/src/main/java/com/shapesecurity/salvation/tokens/DirectiveNameToken.java
@@ -42,7 +42,11 @@ public class DirectiveNameToken extends Token {
 		RequireSriFor, // defined in https://w3c.github.io/webappsec-subresource-integrity/#opt-in-require-sri-for
 		Sandbox,
 		ScriptSrc,
+		ScriptSrcElem,
+		ScriptSrcAttr,
 		StyleSrc,
+		StyleSrcElem,
+		StyleSrcAttr,
 		WorkerSrc,
 
 		UpgradeInsecureRequests, // W3C Candidate Recommendation at https://www.w3.org/TR/upgrade-insecure-requests/#delivery as of 2015-10-08
@@ -96,8 +100,16 @@ public class DirectiveNameToken extends Token {
 					return Sandbox;
 				case "script-src":
 					return ScriptSrc;
+				case "script-src-elem":
+					return ScriptSrcElem;
+				case "script-src-attr":
+					return ScriptSrcAttr;
 				case "style-src":
 					return StyleSrc;
+				case "style-src-elem":
+					return StyleSrcElem;
+				case "style-src-attr":
+					return StyleSrcAttr;
 				case "upgrade-insecure-requests":
 					return UpgradeInsecureRequests;
 				case "worker-src":


Test patch:
diff --git a/src/test/java/com/shapesecurity/salvation/LocationTest.java b/src/test/java/com/shapesecurity/salvation/LocationTest.java
index df5395f..2c12494 100644
--- a/src/test/java/com/shapesecurity/salvation/LocationTest.java
+++ b/src/test/java/com/shapesecurity/salvation/LocationTest.java
@@ -1,11 +1,12 @@
 package com.shapesecurity.salvation;
 
+import java.util.ArrayList;
+
 import com.shapesecurity.salvation.data.Notice;
 import com.shapesecurity.salvation.data.URI;
 import com.shapesecurity.salvation.tokens.Token;
-import org.junit.Test;
 
-import java.util.ArrayList;
+import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -451,9 +452,9 @@ public class LocationTest extends CSPTest {
 	public void testPotentialTyposWarnings() {
 		ArrayList<Notice> notices = new ArrayList<>();
 		ParserWithLocation
-				.parse("script-src unsafe-redirect self none unsafe-inline unsafe-eval unsafe-allow-redirects", URI.parse("https://origin"),
+				.parse("script-src unsafe-redirect self none unsafe-inline unsafe-eval unsafe-allow-redirects unsafe-hashes", URI.parse("https://origin"),
 						notices);
-		assertEquals(6, notices.size());
+		assertEquals(7, notices.size());
 		Notice notice = notices.get(0);
 		assertEquals(
 				"1:12: This host name is unusual, and likely meant to be a keyword that is missing the required quotes: 'unsafe-redirect'.",
@@ -499,27 +500,27 @@ public class LocationTest extends CSPTest {
 	public void testUnsafeHashedAttributesWarnings() {
 		ArrayList<Notice> notices = new ArrayList<>();
 		ParserWithLocation
-				.parse("script-src 'unsafe-hashed-attributes'", URI.parse("https://origin"), notices);
+				.parse("script-src 'unsafe-hashes'", URI.parse("https://origin"), notices);
 		assertEquals(1, notices.size());
 		Notice notice = notices.get(0);
 		assertEquals(
-				"1:1: The \"'unsafe-hashed-attributes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.",
+				"1:1: The \"'unsafe-hashes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.",
 				notice.show());
 		assertEquals(
-				"Warning: The \"'unsafe-hashed-attributes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.",
+				"Warning: The \"'unsafe-hashes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.",
 				notice.toString());
 
 		notices.clear();
 		ParserWithLocation
-				.parse("script-src self 'unsafe-redirect' 'unsafe-hashed-attributes'", URI.parse("https://origin"), notices);
+				.parse("script-src self 'unsafe-redirect' 'unsafe-hashes'", URI.parse("https://origin"), notices);
 		assertEquals(3, notices.size());
 		notice = notices.get(2);
 		// TODO implement location tracking, 1:1: below is not very presize here
 		assertEquals(
-				"1:1: The \"'unsafe-hashed-attributes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.",
+				"1:1: The \"'unsafe-hashes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.",
 				notice.show());
 		assertEquals(
-				"Warning: The \"'unsafe-hashed-attributes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.",
+				"Warning: The \"'unsafe-hashes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.",
 				notice.toString());
 
 	}
diff --git a/src/test/java/com/shapesecurity/salvation/ParserTest.java b/src/test/java/com/shapesecurity/salvation/ParserTest.java
index 778aae4..94b674d 100644
--- a/src/test/java/com/shapesecurity/salvation/ParserTest.java
+++ b/src/test/java/com/shapesecurity/salvation/ParserTest.java
@@ -117,14 +117,38 @@ public class ParserTest extends CSPTest {
 		assertNotNull("policy should not be null", p);
 		assertEquals("directive count", 1, p.getDirectives().size());
 
+		p = parse("script-src-elem a");
+		assertNotNull("policy should not be null", p);
+		assertEquals("directive count", 1, p.getDirectives().size());
+
+		p = parse("script-src-attr a");
+		assertNotNull("policy should not be null", p);
+		assertEquals("directive count", 1, p.getDirectives().size());
+
 		p = parse("style-src http://*.example.com:*");
 		assertNotNull("policy should not be null", p);
 		assertEquals("directive count", 1, p.getDirectives().size());
 
+		p = parse("style-src-elem http://*.example.com:*");
+		assertNotNull("policy should not be null", p);
+		assertEquals("directive count", 1, p.getDirectives().size());
+
+		p = parse("style-src-attr http://*.example.com:*");
+		assertNotNull("policy should not be null", p);
+		assertEquals("directive count", 1, p.getDirectives().size());
+
 		p = parse("style-src samba://*.example.com");
 		assertNotNull("policy should not be null", p);
 		assertEquals("directive count", 1, p.getDirectives().size());
 
+		p = parse("style-src-elem samba://*.example.com");
+		assertNotNull("policy should not be null", p);
+		assertEquals("directive count", 1, p.getDirectives().size());
+
+		p = parse("style-src-attr samba://*.example.com");
+		assertNotNull("policy should not be null", p);
+		assertEquals("directive count", 1, p.getDirectives().size());
+
 		p = parse("prefetch-src http://*.example.com:*");
 		assertNotNull("policy should not be null", p);
 		assertEquals("directive count", 1, p.getDirectives().size());
@@ -356,6 +380,111 @@ public class ParserTest extends CSPTest {
 		assertEquals("policy origin", "http://qwe.zz", a.getOrigin().show());
 	}
 
+	@Test()
+	public void testScriptSrcElemAndAttrPolicies() {
+		Policy p = parse("script-src-elem a; script-src-attr a");
+		assertEquals("script-src-elem a; script-src-attr a", p.show());
+
+		p = parse("script-src-elem a; script-src-attr a; worker-src a; script-src b");
+		assertEquals("script-src a", p.show());
+
+		p = parse("script-src-elem a; script-src-attr a; worker-src a");
+		assertEquals("script-src a", p.show());
+
+		p = parse("script-src-elem a 'unsafe-inline'; script-src-attr a 'unsafe-inline'; worker-src a");
+		assertEquals("worker-src a; script-src a 'unsafe-inline'", p.show());
+
+		p = parse("script-src-elem a 'unsafe-eval'; script-src-attr a 'unsafe-eval'; worker-src a");
+		assertEquals("script-src a", p.show());
+
+		p = parse("script-src a 'unsafe-eval'; script-src-attr b; script-src-elem b; worker-src c");
+		assertEquals("script-src b 'unsafe-eval'; worker-src c", p.show());
+
+		p = parse("script-src-elem a; script-src-elem a");
+		assertEquals("script-src-elem a", p.show());
+
+		p = parse("script-src-attr a; script-src-attr a");
+		assertEquals("script-src-attr a", p.show());
+
+		p = parse("script-src a; script-src-elem a");
+		assertEquals("script-src a", p.show());
+
+		p = parse("script-src a; script-src-attr a");
+		assertEquals("script-src a", p.show());
+
+		p = parse("script-src b; script-src-elem a; script-src-attr a");
+		assertEquals("script-src a; worker-src b", p.show());
+
+		p = parse("script-src b; worker-src a; script-src-elem a; script-src-attr a");
+		assertEquals("script-src a", p.show());
+
+		p = parse("script-src; worker-src; script-src-elem; script-src-attr");
+		assertEquals("script-src", p.show());
+
+		p = parse("worker-src; script-src-elem; script-src-attr");
+		assertEquals("script-src", p.show());
+
+		p = parse("worker-src; script-src-elem a; script-src-attr b");
+		assertEquals("worker-src; script-src-elem a; script-src-attr b", p.show());
+
+		p = parse("script-src a; worker-src b; script-src-elem c; script-src-attr d");
+		assertEquals("script-src a; worker-src b; script-src-elem c; script-src-attr d", p.show());
+
+		p = parse("default-src a; script-src-elem c; script-src-attr d");
+		assertEquals("default-src a; script-src-elem c; script-src-attr d", p.show());
+
+		p = parse("default-src a; script-src-elem a; script-src-attr a");
+		assertEquals("default-src a", p.show());
+	}
+
+	@Test()
+	public void testWorkerSrcOptimisation() {
+		Policy p = parse("worker-src a; child-src a");
+		assertEquals("child-src a", p.show());
+	}
+
+	@Test()
+	public void testStyleSrcElemAndAttrPolicies() {
+		Policy p = parse("style-src-elem a; style-src-attr a");
+		assertEquals("style-src a", p.show());
+
+		p = parse("style-src-elem a; style-src-elem a");
+		assertEquals("style-src-elem a", p.show());
+
+		p = parse("style-src-attr a; style-src-attr a");
+		assertEquals("style-src-attr a", p.show());
+
+		p = parse("style-src a; style-src-elem a");
+		assertEquals("style-src a", p.show());
+
+		p = parse("style-src a; style-src-attr a");
+		assertEquals("style-src a", p.show());
+
+		p = parse("style-src b; style-src-elem a; style-src-attr a");
+		assertEquals("style-src a b", p.show());
+
+		p = parse("style-src b; style-src-elem a; style-src-attr a");
+		assertEquals("style-src a b", p.show());
+
+		p = parse("style-src; style-src-elem; style-src-attr");
+		assertEquals("style-src", p.show());
+
+		p = parse("style-src-elem; style-src-attr");
+		assertEquals("style-src", p.show());
+
+		p = parse("style-src-elem a; style-src-attr b");
+		assertEquals("style-src-elem a; style-src-attr b", p.show());
+
+		p = parse("style-src a; style-src-elem c; style-src-attr d");
+		assertEquals("style-src a; style-src-elem c; style-src-attr d", p.show());
+
+		p = parse("default-src a; style-src-elem c; style-src-attr d");
+		assertEquals("default-src a; style-src-elem c; style-src-attr d", p.show());
+
+		p = parse("default-src a; style-src-elem a; style-src-attr a");
+		assertEquals("default-src a", p.show());
+	}
+	
 	@Test()
 	public void testPluginTypesParsing() {
 		ArrayList<Notice> notices = new ArrayList<>();
@@ -947,6 +1076,30 @@ public class ParserTest extends CSPTest {
 		assertEquals(1, p.getDirectives().size());
 		assertEquals(0, notices.size());
 		assertEquals("script-src 'strict-dynamic' 'unsafe-eval'", p.show());
+
+		notices.clear();
+		p = parseWithNotices("script-src-elem 'strict-dynamic' 'unsafe-eval'", notices);
+		assertEquals(1, p.getDirectives().size());
+		assertEquals(0, notices.size());
+		assertEquals("script-src-elem 'strict-dynamic' 'unsafe-eval'", p.show());
+
+		notices.clear();
+		p = parseWithNotices("script-src-attr 'strict-dynamic' 'unsafe-eval'", notices);
+		assertEquals(1, p.getDirectives().size());
+		assertEquals(0, notices.size());
+		assertEquals("script-src-attr 'strict-dynamic' 'unsafe-eval'", p.show());
+
+		notices.clear();
+		p = parseWithNotices("style-src-attr 'strict-dynamic' 'unsafe-eval'", notices);
+		assertEquals(1, p.getDirectives().size());
+		assertEquals(0, notices.size());
+		assertEquals("style-src-attr 'strict-dynamic' 'unsafe-eval'", p.show());
+
+		notices.clear();
+		p = parseWithNotices("style-src-attr 'strict-dynamic' 'unsafe-eval'", notices);
+		assertEquals(1, p.getDirectives().size());
+		assertEquals(0, notices.size());
+		assertEquals("style-src-attr 'strict-dynamic' 'unsafe-eval'", p.show());
 	}
 
 	@Test
@@ -1081,38 +1234,45 @@ public class ParserTest extends CSPTest {
 		Policy p;
 		ArrayList<Notice> notices = new ArrayList<>();
 		p = parseWithNotices("default-src 'unsafe-hashed-attributes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='", notices);
+		assertEquals(0, p.getDirectives().size());
+		assertEquals(2, notices.size());
+		assertEquals("The CSP specification renamed 'unsafe-hashed-attributes' to 'unsafe-hashes' (June 2018).", notices.get(0).message);
+		assertEquals("Expecting source-expression but found \"'unsafe-hashed-attributes'\".", notices.get(1).message);
+
+		notices.clear();
+		p = parseWithNotices("default-src 'unsafe-hashes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='", notices);
 		assertEquals(1, p.getDirectives().size());
 		assertEquals(0, notices.size());
 
 		notices.clear();
-		p = parseWithNotices("default-src 'unsafe-hashed-attributes'", notices);
+		p = parseWithNotices("default-src 'unsafe-hashes'", notices);
 		assertEquals(1, p.getDirectives().size());
 		assertEquals(1, notices.size());
-		assertEquals("The \"'unsafe-hashed-attributes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.", notices.get(0).message);
+		assertEquals("The \"'unsafe-hashes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.", notices.get(0).message);
 
 		notices.clear();
-		p = parseWithNotices("default-src 'unsafe-hashed-attributes' 'unsafe-hashed-attributes' 'unsafe-hashed-attributes'", notices);
+		p = parseWithNotices("default-src 'unsafe-hashes' 'unsafe-hashes' 'unsafe-hashes'", notices);
 		assertEquals(1, p.getDirectives().size());
 		assertEquals(3, notices.size());
-		assertEquals("Source list contains duplicate source expression \"'unsafe-hashed-attributes'\". All but the first instance will be ignored.", notices.get(0).message);
-		assertEquals("Source list contains duplicate source expression \"'unsafe-hashed-attributes'\". All but the first instance will be ignored.", notices.get(1).message);
-		assertEquals("The \"'unsafe-hashed-attributes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.", notices.get(2).message);
+		assertEquals("Source list contains duplicate source expression \"'unsafe-hashes'\". All but the first instance will be ignored.", notices.get(0).message);
+		assertEquals("Source list contains duplicate source expression \"'unsafe-hashes'\". All but the first instance will be ignored.", notices.get(1).message);
+		assertEquals("The \"'unsafe-hashes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.", notices.get(2).message);
 
 		notices.clear();
-		p = parseWithNotices("default-src 'unsafe-hashed-attributes' 'unsafe-hashed-attributes' 'unsafe-hashed-attributes' 'nonce-123'", notices);
+		p = parseWithNotices("default-src 'unsafe-hashes' 'unsafe-hashes' 'unsafe-hashes' 'nonce-123'", notices);
 		assertEquals(1, p.getDirectives().size());
 		assertEquals(4, notices.size());
-		assertEquals("Source list contains duplicate source expression \"'unsafe-hashed-attributes'\". All but the first instance will be ignored.", notices.get(0).message);
-		assertEquals("Source list contains duplicate source expression \"'unsafe-hashed-attributes'\". All but the first instance will be ignored.", notices.get(1).message);
+		assertEquals("Source list contains duplicate source expression \"'unsafe-hashes'\". All but the first instance will be ignored.", notices.get(0).message);
+		assertEquals("Source list contains duplicate source expression \"'unsafe-hashes'\". All but the first instance will be ignored.", notices.get(1).message);
 		assertEquals("Invalid base64-value (should be multiple of 4 bytes: 3).", notices.get(2).message);
-		assertEquals("The \"'unsafe-hashed-attributes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.", notices.get(3).message);
+		assertEquals("The \"'unsafe-hashes'\" keyword-source has no effect in source lists that do not contain hash-source in CSP3 and later.", notices.get(3).message);
 
 		notices.clear();
-		p = parseWithNotices("default-src 'unsafe-hashed-attributes' 'unsafe-hashed-attributes' 'unsafe-hashed-attributes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='", notices);
+		p = parseWithNotices("default-src 'unsafe-hashes' 'unsafe-hashes' 'unsafe-hashes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='", notices);
 		assertEquals(1, p.getDirectives().size());
 		assertEquals(2, notices.size());
-		assertEquals("Source list contains duplicate source expression \"'unsafe-hashed-attributes'\". All but the first instance will be ignored.", notices.get(0).message);
-		assertEquals("Source list contains duplicate source expression \"'unsafe-hashed-attributes'\". All but the first instance will be ignored.", notices.get(1).message);
+		assertEquals("Source list contains duplicate source expression \"'unsafe-hashes'\". All but the first instance will be ignored.", notices.get(0).message);
+		assertEquals("Source list contains duplicate source expression \"'unsafe-hashes'\". All but the first instance will be ignored.", notices.get(1).message);
 
 		notices.clear();
 		p = parseWithNotices("default-src 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='", notices);
@@ -1121,7 +1281,7 @@ public class ParserTest extends CSPTest {
 
 		// while grammar allows this, I am open to throw warnings about directives that don't make sense with 'usnafe-hashed-attributes'
 		notices.clear();
-		p = parseWithNotices("img-src 'unsafe-hashed-attributes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='", notices);
+		p = parseWithNotices("img-src 'unsafe-hashes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='", notices);
 		assertEquals(1, p.getDirectives().size());
 		assertEquals(0, notices.size());
 	}
@@ -1152,6 +1312,12 @@ public class ParserTest extends CSPTest {
 		assertEquals(0, notices.size());
 	}
 
+	@Test
+	public void testExpand() {
+		Policy p = Parser.parse("default-src a; child-src b;", "https://origin.com");
+		assertEquals("default-src a; child-src b", p.show());
+	}
+
 	@Test
 	public void testDuplicateSourceExpressions() {
 		Policy p;
diff --git a/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java b/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java
index b20460d..14075c5 100644
--- a/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java
+++ b/src/test/java/com/shapesecurity/salvation/PolicyMergeTest.java
@@ -61,7 +61,7 @@ public class PolicyMergeTest extends CSPTest {
 		p1.union(p2);
 		assertEquals("", p1.show());
 
-		p1 = Parser.parse("frame-ancestors bbb;", "https://origin1.com");
+		p1 = Parser.parse("frame-ancestors bbb;", "https:" + "//origin1.com");
 		p2 = Parser.parse("script-src a", "https://origin1.com");
 		p1.union(p2);
 		assertEquals("", p1.show());
@@ -81,6 +81,11 @@ public class PolicyMergeTest extends CSPTest {
 		p1.union(p2);
 		assertEquals("default-src a b; script-src a b x; style-src a b y", p1.show());
 
+		p1 = parse("default-src a b");
+		p2 = parse("default-src; script-src-elem a b; script-src-attr a b; style-src-elem a b; style-src-attr a b");
+		p1.union(p2);
+		assertEquals("default-src a b", p1.show());
+
 		p1 = parse("default-src *; script-src");
 		p2 = parse("default-src; script-src b");
 		p1.union(p2);
@@ -126,6 +131,11 @@ public class PolicyMergeTest extends CSPTest {
 	public void testIntersect() {
 		Policy p1, p2;
 
+		p1 = parse("default-src *;");
+		p2 = parse("script-src-elem a b; script-src-attr c d; style-src-elem a b; style-src-attr c d");
+		p1.intersect(p2);
+		assertEquals("default-src *; script-src-elem a b; script-src-attr c d; style-src-elem a b; style-src-attr c d", p1.show());
+
 		p1 = parse("default-src 'none';");
 		p2 = parse("default-src *;");
 		p1.intersect(p2);
@@ -581,7 +591,7 @@ public class PolicyMergeTest extends CSPTest {
 		q = parse("worker-src a");
 		p.union(q);
 		p.postProcessOptimisation();
-		assertEquals("", p.show());
+		assertEquals("worker-src a", p.show());
 
 		p = parse("child-src a; ");
 		q = parse("frame-src a ");
@@ -593,7 +603,7 @@ public class PolicyMergeTest extends CSPTest {
 		q = parse("child-src; worker-src x; frame-src y");
 		p.union(q);
 		p.postProcessOptimisation();
-		assertEquals("child-src a b; frame-src a b y", p.show());
+		assertEquals("child-src a b; frame-src a b y; worker-src a b x", p.show());
 
 		p = parse("child-src *; worker-src");
 		q = parse("child-src; worker-src b");
@@ -608,7 +618,7 @@ public class PolicyMergeTest extends CSPTest {
 		p = parse("child-src a");
 		q = parse("child-src; worker-src b");
 		p.union(q);
-		assertEquals("child-src a", p.show());
+		assertEquals("child-src a; worker-src a b", p.show());
 
 		p = parse("child-src a; worker-src b");
 		q = parse("child-src; worker-src c");
@@ -618,7 +628,7 @@ public class PolicyMergeTest extends CSPTest {
 		p = parse("child-src; worker-src a; frame-src b");
 		q = parse("child-src c");
 		p.union(q);
-		assertEquals("child-src c; frame-src b c", p.show());
+		assertEquals("child-src c; worker-src a c; frame-src b c", p.show());
 
 		p = parse("child-src a; worker-src b");
 		q = parse("child-src c; frame-src d");
@@ -628,7 +638,7 @@ public class PolicyMergeTest extends CSPTest {
 		p = parse("child-src b; worker-src a");
 		q = parse("child-src a");
 		p.union(q);
-		assertEquals("child-src b a", p.show());
+		assertEquals("child-src b a; worker-src a", p.show());
 
 		p = parse("default-src a");
 		q = parse("worker-src b; frame-src b;");
diff --git a/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java b/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
index f2a6db8..165d4b4 100644
--- a/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
+++ b/src/test/java/com/shapesecurity/salvation/PolicyQueryingTest.java
@@ -24,7 +24,9 @@ import com.shapesecurity.salvation.directives.MediaSrcDirective;
 import com.shapesecurity.salvation.directives.ObjectSrcDirective;
 import com.shapesecurity.salvation.directives.ReportUriDirective;
 import com.shapesecurity.salvation.directives.ScriptSrcDirective;
+import com.shapesecurity.salvation.directives.ScriptSrcElemDirective;
 import com.shapesecurity.salvation.directives.StyleSrcDirective;
+import com.shapesecurity.salvation.directives.StyleSrcElemDirective;
 import com.shapesecurity.salvation.directives.WorkerSrcDirective;
 import org.junit.Test;
 
@@ -373,27 +375,53 @@ public class PolicyQueryingTest extends CSPTest {
 		Policy p;
 
 		p = parse(
-				"script-src 'unsafe-hashed-attributes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
-		assertTrue("attribute with hash is allowed", p.allowsAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+				"script-src 'unsafe-hashes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
+		assertTrue("attribute with hash is allowed", p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
 				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
 		assertFalse("script hash is not allowed",
-				p.allowsAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+				p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+
+		p = parse(
+				"script-src-attr 'unsafe-hashes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
+		assertTrue("attribute with hash is allowed", p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+		assertFalse("script hash is not allowed",
+				p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+
+		p = parse(
+				"script-src-elem 'unsafe-hashes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
+		assertFalse("attribute with hash is allowed", p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+		assertFalse("script hash is not allowed",
+				p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
 
 		p = parse(
 				"script-src 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
-		assertFalse("attribute with hash is not allowed", p.allowsAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+		assertFalse("attribute with hash is not allowed", p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
 				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
 
 		p = parse(
-				"default-src 'unsafe-hashed-attributes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
-		assertTrue("attribute with hash is allowed", p.allowsAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+				"default-src 'unsafe-hashes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
+		assertTrue("attribute with hash is allowed", p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
 				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
 		assertFalse("script hash is not allowed",
-				p.allowsAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+				p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+
+		p = parse(
+				"script-src-attr 'unsafe-hashes' 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
+		assertTrue("attribute with hash is allowed", p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+		assertFalse("script hash is not allowed",
+				p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
 
 		p = parse(
 				"default-src 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
-		assertFalse("attribute with hash is not allowed", p.allowsAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+		assertFalse("attribute with hash is not allowed", p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+
+		p = parse(
+				"script-src-attr 'sha512-vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg=='");
+		assertFalse("attribute with hash is not allowed", p.allowsScriptAttributeWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
 				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
 	}
 
@@ -719,6 +747,27 @@ public class PolicyQueryingTest extends CSPTest {
 		assertFalse(p.allowsStyleWithNonce("forscript"));
 		assertFalse(p.allowsScriptWithNonce("forstyle"));
 		assertTrue(p.allowsStyleWithNonce("forstyle"));
+
+		p = Parser.parse("script-src-elem 'unsafe-inline' 'nonce-forscript' 'strict-dynamic'; style-src-elem 'unsafe-inline' 'nonce-forstyle'", "http://example.com");
+		assertTrue(p.containsSourceExpression(ScriptSrcElemDirective.class, x -> x == KeywordSource.StrictDynamic));
+		assertTrue(p.containsSourceExpression(ScriptSrcElemDirective.class, x -> x == KeywordSource.UnsafeInline));
+		assertTrue(p.containsSourceExpression(StyleSrcElemDirective.class, x -> x == KeywordSource.UnsafeInline));
+		assertFalse(p.allowsUnsafeInlineScript());
+		assertFalse(p.allowsUnsafeInlineStyle());
+		assertFalse(p.allowsScriptWithNonce("123"));
+		assertFalse(p.allowsStyleWithNonce("123"));
+		assertFalse(p.allowsScriptWithNonce("1234"));
+		assertFalse(p.allowsStyleWithNonce("1234"));
+		assertFalse(p.allowsScriptWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+		assertFalse(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value(
+				"vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==")));
+		assertFalse(p.allowsScriptWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+		assertFalse(p.allowsStyleWithHash(HashSource.HashAlgorithm.SHA512, new Base64Value("cGl6ZGE=")));
+		assertTrue(p.allowsScriptWithNonce("forscript"));
+		assertFalse(p.allowsStyleWithNonce("forscript"));
+		assertFalse(p.allowsScriptWithNonce("forstyle"));
+		assertTrue(p.allowsStyleWithNonce("forstyle"));
 	}
 
 	@Test
@@ -1226,13 +1275,13 @@ public class PolicyQueryingTest extends CSPTest {
 		h = new HostSource(null, "example.com", Constants.EMPTY_PORT, "/a;jsessionid=123");
 		d = new ScriptSrcDirective(Collections.singleton(h));
 		p.unionDirective(d);
-		assertEquals("script-src 'self' example.com/a%3Bjsessionid=123; worker-src 'self'", p.show());
+		assertEquals("script-src 'self' example.com/a%3Bjsessionid=123", p.show());
 
 		p = Parser.parse("script-src 'self'", "http://example.com");
 		h = new HostSource(null, "example.com", Constants.EMPTY_PORT, "/a,b");
 		d = new ScriptSrcDirective(Collections.singleton(h));
 		p.unionDirective(d);
-		assertEquals("script-src 'self' example.com/a%2Cb; worker-src 'self'", p.show());
+		assertEquals("script-src 'self' example.com/a%2Cb", p.show());
 
 		p = Parser.parse("script-src example.com/a%3Bjsessionid=123", "http://example.com");
 		assertTrue(p.containsSourceExpression(ScriptSrcDirective.class, x -> x.equals(new HostSource(null, "example.com", Constants.EMPTY_PORT, "/a%3Bjsessionid=123"))));
diff --git a/src/test/java/com/shapesecurity/salvation/PostProcessTest.java b/src/test/java/com/shapesecurity/salvation/PostProcessTest.java
index f711863..2deca47 100644
--- a/src/test/java/com/shapesecurity/salvation/PostProcessTest.java
+++ b/src/test/java/com/shapesecurity/salvation/PostProcessTest.java
@@ -20,11 +20,11 @@ public class PostProcessTest extends CSPTest {
 
 		p = parse("script-src 'unsafe-eval' 'nonce-123'; style-src 'unsafe-eval' 'nonce-123';");
 		p.postProcessOptimisation();
-		assertEquals("default-src 'unsafe-eval' 'nonce-123'", p.show());
+		assertEquals("default-src 'nonce-123' 'unsafe-eval'", p.show());
 
 		p = parse("script-src 'unsafe-eval' 'nonce-123'; style-src 'unsafe-eval';");
 		p.postProcessOptimisation();
-		assertEquals("script-src 'unsafe-eval' 'nonce-123'; style-src 'unsafe-eval'", p.show());
+		assertEquals("script-src 'nonce-123' 'unsafe-eval'; style-src 'unsafe-eval'", p.show());
 
 		p = parse("script-src 'self'; style-src 'self';");
 		p.postProcessOptimisation();