From 62883b1e2153281f9eeb82a35cac773c24dd24c1 Mon Sep 17 00:00:00 2001 From: suresh Date: Fri, 12 Jun 2026 13:59:15 -0700 Subject: [PATCH 01/12] Switch from granting broad public access to CloudFront access to Objects in the content bucket. Co-authored-by: Claude --- cicd/3-app/javabuilder/template.yml.erb | 30 ++++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/cicd/3-app/javabuilder/template.yml.erb b/cicd/3-app/javabuilder/template.yml.erb index 7ece8203..6e9b5c7a 100644 --- a/cicd/3-app/javabuilder/template.yml.erb +++ b/cicd/3-app/javabuilder/template.yml.erb @@ -487,16 +487,32 @@ Resources: Status: Enabled ExpirationInDays: 1 + ContentOriginAccessControl: + Type: AWS::CloudFront::OriginAccessControl + Properties: + OriginAccessControlConfig: + Name: !Sub "${SubdomainName}-${BaseDomainName}-content-oac" + OriginAccessControlOriginType: s3 + SigningBehavior: always + SigningProtocol: sigv4 + ContentBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref ContentBucket PolicyDocument: + Version: '2012-10-17' Statement: - - Action: ['s3:GetObject'] - Effect: Allow - Resource: !Sub "arn:aws:s3:::${ContentBucket}/*" - Principal: '*' + - Sid: AllowCloudFrontRead + Effect: Allow + Principal: + Service: cloudfront.amazonaws.com + Action: + - s3:GetObject + Resource: !Sub "arn:aws:s3:::${ContentBucket}/*" + Condition: + StringEquals: + AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${ContentCDN}" ContentApiCertificate: Type: AWS::CertificateManager::Certificate @@ -537,8 +553,10 @@ Resources: # Prefix: !Sub "${SubdomainName}-content.${BaseDomainName}" Origins: - Id: ContentBucket - DomainName: !GetAtt ContentBucket.DomainName - S3OriginConfig: {} + DomainName: !GetAtt ContentBucket.RegionalDomainName + OriginAccessControlId: !GetAtt ContentOriginAccessControl.Id + S3OriginConfig: + OriginAccessIdentity: "" DefaultCacheBehavior: TargetOriginId: ContentBucket AllowedMethods: [DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT] From 1d15b6457d1d005dd00cd25e72b478e7579590a7 Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Wed, 8 Jul 2026 15:38:47 -0700 Subject: [PATCH 02/12] claude first pass --- .../JavabuilderSecurityPolicy.java | 118 ++++++++++++++++++ .../javabuilder/LambdaRequestHandler.java | 8 ++ .../org/code/javabuilder/UserClassLoader.java | 8 ++ .../JavabuilderSecurityPolicyTest.java | 94 ++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java create mode 100644 org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java new file mode 100644 index 00000000..9ded8c70 --- /dev/null +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java @@ -0,0 +1,118 @@ +package org.code.javabuilder; + +import java.io.File; +import java.io.FilePermission; +import java.security.Permission; +import java.security.Policy; +import java.security.ProtectionDomain; + +/** + * Security policy that confines USER-level student code at runtime while leaving all framework, + * JDK, AWS SDK, and VALIDATOR-level code fully trusted. Student code is identified by being loaded + * from a USER-level {@link UserClassLoader}. + * + *

For confined student code this policy: + * + *

    + *
  • allows read/write/delete only under the writable temp directory, + *
  • allows read-only access to the app/runtime directories the JVM and student libraries need + * (so class loading, fonts, and bundled assets keep working), + *
  • denies all other filesystem access (notably /proc, /etc, $HOME, and env-backed files), + *
  • denies environment variable access (getenv), which on Lambda exposes the execution role's + * AWS credentials, + *
  • preserves every other capability student code has today. + *
+ * + *

NOTE: {@link SecurityManager} / {@link Policy} are deprecated in Java 17 and removed in Java + * 21. This control is valid on the current java11 runtime only. + */ +public class JavabuilderSecurityPolicy extends Policy { + private static final String ALL_FILES = "<>"; + private static final String GETENV_PREFIX = "getenv."; + + // The only root confined student code may write to / delete within. + private final String writableRoot; + // Roots confined student code may read from (a superset of writableRoot). Everything the JVM + // needs to load classes, fonts, and library assets lives under these; sensitive locations + // (/proc, /etc, $HOME, env) are intentionally excluded. + private final String[] readableRoots; + + public JavabuilderSecurityPolicy() { + this.writableRoot = canonicalize(System.getProperty("java.io.tmpdir")); + this.readableRoots = + new String[] { + this.writableRoot, + canonicalize(System.getProperty("java.home")), // JRE: class/resource loading + "/var/task", // deployed application code + dependency jars + "/var/lang", // managed runtime + "/opt" // Lambda layers: fonts, instrument samples, wrapper + }; + } + + @Override + public boolean implies(ProtectionDomain domain, Permission permission) { + if (!isConfinedStudentCode(domain)) { + // Framework / JDK / AWS SDK / validator code: full trust. + return true; + } + if (permission instanceof FilePermission) { + return isAllowedFileAccess((FilePermission) permission); + } + // Deny environment variable access; on Lambda this exposes AWS credentials. + if (permission instanceof RuntimePermission + && permission.getName() != null + && permission.getName().startsWith(GETENV_PREFIX)) { + return false; + } + // File and env access are the only things we confine here. Everything else student code can do + // today is preserved. + return true; + } + + private boolean isConfinedStudentCode(ProtectionDomain domain) { + if (domain == null || !(domain.getClassLoader() instanceof UserClassLoader)) { + return false; + } + // Validation code is trusted first-party code; only confine USER-level runs. + return ((UserClassLoader) domain.getClassLoader()).getPermissionLevel() + == RunPermissionLevel.USER; + } + + private boolean isAllowedFileAccess(FilePermission permission) { + if (ALL_FILES.equals(permission.getName())) { + return false; + } + final String path = canonicalize(permission.getName()); + if (path == null) { + return false; + } + final String actions = permission.getActions(); + if (actions.contains("write") || actions.contains("delete")) { + return isUnder(path, this.writableRoot); + } + // read / execute / readlink + for (String root : this.readableRoots) { + if (root != null && isUnder(path, root)) { + return true; + } + } + return false; + } + + private static boolean isUnder(String path, String root) { + return path.equals(root) || path.startsWith(root + File.separator); + } + + private static String canonicalize(String path) { + if (path == null) { + return null; + } + try { + // getCanonicalPath() does not itself trigger a SecurityManager check, so this does not + // recurse; resolving symlinks prevents a /tmp/link -> /etc escape. + return new File(path).getCanonicalPath(); + } catch (Exception e) { + return null; + } + } +} diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java index b9c86a0a..ae824da6 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Paths; +import java.security.Policy; import java.time.Clock; import java.time.Instant; import java.util.List; @@ -74,6 +75,13 @@ public LambdaRequestHandler() { // This will only be called once in the initial creation of the lambda instance. // Documentation: https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html CachedResources.create(); + + // Install the security policy once for the entire container. The policy scopes itself to + // USER-level student code (via UserClassLoader), so a fresh UserClassLoader per run means + // confinement is applied per run without any per-invocation setup. All other code is fully + // trusted, so framework and AWS SDK behavior is unaffected. + Policy.setPolicy(new JavabuilderSecurityPolicy()); + System.setSecurityManager(new SecurityManager()); COLD_BOOT_END = Clock.systemUTC().instant(); this.apiClient = AmazonApiGatewayManagementApiClientBuilder.standard() diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/UserClassLoader.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/UserClassLoader.java index 3c53fc15..fd4e2b3f 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/UserClassLoader.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/UserClassLoader.java @@ -31,6 +31,14 @@ public UserClassLoader( this.permissionLevel = permissionLevel; } + /** + * @return the permission level this class loader was created with. Used by {@link + * JavabuilderSecurityPolicy} to distinguish confined USER code from trusted VALIDATOR code. + */ + public RunPermissionLevel getPermissionLevel() { + return this.permissionLevel; + } + @Override public Class loadClass(String name) throws ClassNotFoundException { // Call super for user provided classes, as we need to verify users are not diff --git a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java new file mode 100644 index 00000000..b35b9340 --- /dev/null +++ b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java @@ -0,0 +1,94 @@ +package org.code.javabuilder; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.FilePermission; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.security.cert.Certificate; +import java.util.List; +import java.util.PropertyPermission; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class JavabuilderSecurityPolicyTest { + private JavabuilderSecurityPolicy policy; + private ProtectionDomain userDomain; + private ProtectionDomain validatorDomain; + private ProtectionDomain frameworkDomain; + + @BeforeEach + public void setUp() { + policy = new JavabuilderSecurityPolicy(); + userDomain = domainFor(userClassLoader(RunPermissionLevel.USER)); + validatorDomain = domainFor(userClassLoader(RunPermissionLevel.VALIDATOR)); + frameworkDomain = domainFor(JavabuilderSecurityPolicyTest.class.getClassLoader()); + } + + @Test + public void studentCanReadAndWriteUnderTmp() { + final String tmpFile = System.getProperty("java.io.tmpdir") + "/student-output.txt"; + assertTrue(policy.implies(userDomain, new FilePermission(tmpFile, "read"))); + assertTrue(policy.implies(userDomain, new FilePermission(tmpFile, "write"))); + assertTrue(policy.implies(userDomain, new FilePermission(tmpFile, "delete"))); + } + + @Test + public void studentCannotReadOutsideAllowedRoots() { + assertFalse(policy.implies(userDomain, new FilePermission("/etc/passwd", "read"))); + assertFalse(policy.implies(userDomain, new FilePermission("/proc/self/environ", "read"))); + assertFalse(policy.implies(userDomain, new FilePermission("<>", "read"))); + } + + @Test + public void studentCannotWriteOutsideTmp() { + // java.home is readable but not writable for student code. + final String runtimeFile = System.getProperty("java.home") + "/lib/evil.jar"; + assertTrue(policy.implies(userDomain, new FilePermission(runtimeFile, "read"))); + assertFalse(policy.implies(userDomain, new FilePermission(runtimeFile, "write"))); + } + + @Test + public void studentCanReadRuntimeDirectoriesForClassLoading() { + final String runtimeFile = System.getProperty("java.home") + "/lib/modules"; + assertTrue(policy.implies(userDomain, new FilePermission(runtimeFile, "read"))); + } + + @Test + public void studentCannotAccessEnvironmentVariables() { + assertFalse(policy.implies(userDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); + assertFalse(policy.implies(userDomain, new RuntimePermission("getenv.*"))); + } + + @Test + public void studentRetainsOtherPermissions() { + // File and env are the only things confined; other capabilities are preserved. + assertTrue(policy.implies(userDomain, new RuntimePermission("modifyThread"))); + assertTrue(policy.implies(userDomain, new PropertyPermission("user.language", "read"))); + } + + @Test + public void validatorCodeIsFullyTrusted() { + assertTrue(policy.implies(validatorDomain, new FilePermission("/etc/passwd", "read"))); + assertTrue( + policy.implies(validatorDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); + } + + @Test + public void frameworkCodeIsFullyTrusted() { + assertTrue(policy.implies(frameworkDomain, new FilePermission("/etc/passwd", "read,write"))); + assertTrue( + policy.implies(frameworkDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); + } + + private UserClassLoader userClassLoader(RunPermissionLevel level) { + return new UserClassLoader( + new URL[] {}, JavabuilderSecurityPolicyTest.class.getClassLoader(), List.of(), level); + } + + private ProtectionDomain domainFor(ClassLoader classLoader) { + return new ProtectionDomain( + new CodeSource(null, (Certificate[]) null), null, classLoader, null); + } +} From ef31e5fb02230cab9be3e8b12904f8b6b71c664e Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Wed, 8 Jul 2026 16:32:46 -0700 Subject: [PATCH 03/12] tighten up policy --- .../JavabuilderSecurityPolicy.java | 20 +++++++++---------- .../org/code/javabuilder/UserClassLoader.java | 8 -------- .../JavabuilderSecurityPolicyTest.java | 10 +++++++--- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java index 9ded8c70..aab6e21f 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java @@ -7,9 +7,10 @@ import java.security.ProtectionDomain; /** - * Security policy that confines USER-level student code at runtime while leaving all framework, - * JDK, AWS SDK, and VALIDATOR-level code fully trusted. Student code is identified by being loaded - * from a USER-level {@link UserClassLoader}. + * Security policy that confines student code at runtime while leaving all framework, JDK, and AWS + * SDK code fully trusted. Student code is identified by being loaded from a {@link + * UserClassLoader}. This covers both standard USER runs and the VALIDATOR-level validation run, + * which executes student code (the student's main method) within the same class loader. * *

For confined student code this policy: * @@ -52,7 +53,7 @@ public JavabuilderSecurityPolicy() { @Override public boolean implies(ProtectionDomain domain, Permission permission) { if (!isConfinedStudentCode(domain)) { - // Framework / JDK / AWS SDK / validator code: full trust. + // Framework / JDK / AWS SDK code: full trust. return true; } if (permission instanceof FilePermission) { @@ -70,12 +71,11 @@ public boolean implies(ProtectionDomain domain, Permission permission) { } private boolean isConfinedStudentCode(ProtectionDomain domain) { - if (domain == null || !(domain.getClassLoader() instanceof UserClassLoader)) { - return false; - } - // Validation code is trusted first-party code; only confine USER-level runs. - return ((UserClassLoader) domain.getClassLoader()).getPermissionLevel() - == RunPermissionLevel.USER; + // Confine any code loaded by a UserClassLoader. This covers both standard USER runs and the + // VALIDATOR-level validation run: validation loads and invokes the student's code (via the + // student's main method) in the same class loader, so this ensures student code is confined + // there too, not just in RUN and user-test modes. + return domain != null && domain.getClassLoader() instanceof UserClassLoader; } private boolean isAllowedFileAccess(FilePermission permission) { diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/UserClassLoader.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/UserClassLoader.java index fd4e2b3f..3c53fc15 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/UserClassLoader.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/UserClassLoader.java @@ -31,14 +31,6 @@ public UserClassLoader( this.permissionLevel = permissionLevel; } - /** - * @return the permission level this class loader was created with. Used by {@link - * JavabuilderSecurityPolicy} to distinguish confined USER code from trusted VALIDATOR code. - */ - public RunPermissionLevel getPermissionLevel() { - return this.permissionLevel; - } - @Override public Class loadClass(String name) throws ClassNotFoundException { // Call super for user provided classes, as we need to verify users are not diff --git a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java index b35b9340..10e6d2e0 100644 --- a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java +++ b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java @@ -69,9 +69,13 @@ public void studentRetainsOtherPermissions() { } @Test - public void validatorCodeIsFullyTrusted() { - assertTrue(policy.implies(validatorDomain, new FilePermission("/etc/passwd", "read"))); - assertTrue( + public void validatorRunIsAlsoConfined() { + // The validation run loads and executes student code in a VALIDATOR-level UserClassLoader, so + // it must be confined the same way as a USER run. + final String tmpFile = System.getProperty("java.io.tmpdir") + "/validation-output.txt"; + assertTrue(policy.implies(validatorDomain, new FilePermission(tmpFile, "write"))); + assertFalse(policy.implies(validatorDomain, new FilePermission("/etc/passwd", "read"))); + assertFalse( policy.implies(validatorDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); } From cb514edc30c6cb4674508e34f6c2cf4abd5bac38 Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Thu, 9 Jul 2026 13:24:47 -0700 Subject: [PATCH 04/12] speculative fix --- .../JavabuilderSecurityPolicy.java | 31 +++++++++++++++++++ .../javabuilder/LambdaRequestHandler.java | 16 +++++++--- .../JavabuilderSecurityPolicyTest.java | 10 ++++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java index aab6e21f..6ea7ea6d 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java @@ -2,9 +2,13 @@ import java.io.File; import java.io.FilePermission; +import java.net.URL; +import java.security.CodeSource; import java.security.Permission; import java.security.Policy; import java.security.ProtectionDomain; +import java.security.cert.Certificate; +import java.util.List; /** * Security policy that confines student code at runtime while leaving all framework, JDK, and AWS @@ -70,6 +74,33 @@ public boolean implies(ProtectionDomain domain, Permission permission) { return true; } + /** + * Forces every class that {@link #implies} references to load. This MUST be called before a + * SecurityManager is installed. + * + *

{@code implies} runs on every permission check, including the {@code File.exists()} calls + * the class loader makes to locate a class. If a class {@code implies} references (notably {@link + * UserClassLoader}) is still unloaded when the first such check fires, loading it re-enters + * {@code implies} before the load completes and throws {@link ClassCircularityError}. Exercising + * both the file and getenv branches here, while no SecurityManager is active, guarantees those + * classes are already loaded by the time checks begin. + */ + public void warmUp() { + final ProtectionDomain studentDomain = + new ProtectionDomain( + new CodeSource(null, (Certificate[]) null), + null, + new UserClassLoader( + new URL[] {}, + ClassLoader.getSystemClassLoader(), + List.of(), + RunPermissionLevel.USER), + null); + this.implies(studentDomain, new FilePermission("warmup", "read")); + this.implies(studentDomain, new FilePermission("warmup", "write")); + this.implies(studentDomain, new RuntimePermission("getenv.WARMUP")); + } + private boolean isConfinedStudentCode(ProtectionDomain domain) { // Confine any code loaded by a UserClassLoader. This covers both standard USER runs and the // VALIDATOR-level validation run: validation loads and invokes the student's code (via the diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java index ae824da6..5e540e1b 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java @@ -76,11 +76,17 @@ public LambdaRequestHandler() { // Documentation: https://docs.aws.amazon.com/lambda/latest/dg/java-handler.html CachedResources.create(); - // Install the security policy once for the entire container. The policy scopes itself to - // USER-level student code (via UserClassLoader), so a fresh UserClassLoader per run means - // confinement is applied per run without any per-invocation setup. All other code is fully - // trusted, so framework and AWS SDK behavior is unaffected. - Policy.setPolicy(new JavabuilderSecurityPolicy()); + // Install the security policy once for the entire container. The policy scopes itself to code + // loaded by a UserClassLoader, so a fresh UserClassLoader per run means confinement is applied + // per run without any per-invocation setup. All other code is fully trusted, so framework and + // AWS SDK behavior is unaffected. + // + // warmUp() must run before the SecurityManager is installed: the policy is consulted on every + // permission check (including the file reads the class loader performs), so any class it + // references must already be loaded to avoid a ClassCircularityError during class loading. + final JavabuilderSecurityPolicy securityPolicy = new JavabuilderSecurityPolicy(); + securityPolicy.warmUp(); + Policy.setPolicy(securityPolicy); System.setSecurityManager(new SecurityManager()); COLD_BOOT_END = Clock.systemUTC().instant(); this.apiClient = diff --git a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java index 10e6d2e0..74d6059d 100644 --- a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java +++ b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java @@ -79,6 +79,16 @@ public void validatorRunIsAlsoConfined() { policy.implies(validatorDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); } + @Test + public void warmUpDoesNotThrowAndPolicyStillEnforces() { + final JavabuilderSecurityPolicy freshPolicy = new JavabuilderSecurityPolicy(); + assertDoesNotThrow(freshPolicy::warmUp); + // Warming up must not weaken enforcement. + assertFalse(freshPolicy.implies(userDomain, new FilePermission("/etc/passwd", "read"))); + assertFalse( + freshPolicy.implies(userDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); + } + @Test public void frameworkCodeIsFullyTrusted() { assertTrue(policy.implies(frameworkDomain, new FilePermission("/etc/passwd", "read,write"))); From 4dfc4a85741146552c7155e75346ea4232229dc0 Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Thu, 9 Jul 2026 13:56:11 -0700 Subject: [PATCH 05/12] kick codepipeline From eb0e4228f89b3f260a90b4092612a68dea3a1f79 Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Thu, 9 Jul 2026 15:24:33 -0700 Subject: [PATCH 06/12] remove getenv restriction --- .../JavabuilderSecurityPolicy.java | 25 ++++++++----------- .../JavabuilderSecurityPolicyTest.java | 17 ++++--------- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java index 6ea7ea6d..9de14511 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java @@ -22,9 +22,8 @@ *

  • allows read/write/delete only under the writable temp directory, *
  • allows read-only access to the app/runtime directories the JVM and student libraries need * (so class loading, fonts, and bundled assets keep working), - *
  • denies all other filesystem access (notably /proc, /etc, $HOME, and env-backed files), - *
  • denies environment variable access (getenv), which on Lambda exposes the execution role's - * AWS credentials, + *
  • denies all other filesystem access (notably /proc, /etc, $HOME, and env-backed files such + * as /proc/self/environ), *
  • preserves every other capability student code has today. * * @@ -33,7 +32,6 @@ */ public class JavabuilderSecurityPolicy extends Policy { private static final String ALL_FILES = "<>"; - private static final String GETENV_PREFIX = "getenv."; // The only root confined student code may write to / delete within. private final String writableRoot; @@ -63,14 +61,12 @@ public boolean implies(ProtectionDomain domain, Permission permission) { if (permission instanceof FilePermission) { return isAllowedFileAccess((FilePermission) permission); } - // Deny environment variable access; on Lambda this exposes AWS credentials. - if (permission instanceof RuntimePermission - && permission.getName() != null - && permission.getName().startsWith(GETENV_PREFIX)) { - return false; - } - // File and env access are the only things we confine here. Everything else student code can do - // today is preserved. + // Filesystem access is the only thing we confine here. Everything else student code can do + // today is preserved. Note: we intentionally do NOT deny getenv, because the AWS SDK resolves + // credentials via System.getenv() while student code is on the call stack (e.g. when a println + // is flushed through the output adapter), and AccessController would then deny the SDK's own + // read. Env-var credential exposure is mitigated instead by the file policy (which blocks + // reading /proc/self/environ) and by minimizing the Lambda execution role. return true; } @@ -82,8 +78,8 @@ public boolean implies(ProtectionDomain domain, Permission permission) { * the class loader makes to locate a class. If a class {@code implies} references (notably {@link * UserClassLoader}) is still unloaded when the first such check fires, loading it re-enters * {@code implies} before the load completes and throws {@link ClassCircularityError}. Exercising - * both the file and getenv branches here, while no SecurityManager is active, guarantees those - * classes are already loaded by the time checks begin. + * the policy here, while no SecurityManager is active, guarantees those classes are already + * loaded by the time checks begin. */ public void warmUp() { final ProtectionDomain studentDomain = @@ -98,7 +94,6 @@ public void warmUp() { null); this.implies(studentDomain, new FilePermission("warmup", "read")); this.implies(studentDomain, new FilePermission("warmup", "write")); - this.implies(studentDomain, new RuntimePermission("getenv.WARMUP")); } private boolean isConfinedStudentCode(ProtectionDomain domain) { diff --git a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java index 74d6059d..b7a1199b 100644 --- a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java +++ b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java @@ -56,16 +56,13 @@ public void studentCanReadRuntimeDirectoriesForClassLoading() { } @Test - public void studentCannotAccessEnvironmentVariables() { - assertFalse(policy.implies(userDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); - assertFalse(policy.implies(userDomain, new RuntimePermission("getenv.*"))); - } - - @Test - public void studentRetainsOtherPermissions() { - // File and env are the only things confined; other capabilities are preserved. + public void studentRetainsNonFilePermissions() { + // Filesystem access is the only thing confined; other capabilities are preserved. getenv is + // intentionally NOT denied because the AWS SDK resolves credentials via getenv while student + // code is on the call stack (e.g. flushing a println through the output adapter). assertTrue(policy.implies(userDomain, new RuntimePermission("modifyThread"))); assertTrue(policy.implies(userDomain, new PropertyPermission("user.language", "read"))); + assertTrue(policy.implies(userDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); } @Test @@ -75,8 +72,6 @@ public void validatorRunIsAlsoConfined() { final String tmpFile = System.getProperty("java.io.tmpdir") + "/validation-output.txt"; assertTrue(policy.implies(validatorDomain, new FilePermission(tmpFile, "write"))); assertFalse(policy.implies(validatorDomain, new FilePermission("/etc/passwd", "read"))); - assertFalse( - policy.implies(validatorDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); } @Test @@ -85,8 +80,6 @@ public void warmUpDoesNotThrowAndPolicyStillEnforces() { assertDoesNotThrow(freshPolicy::warmUp); // Warming up must not weaken enforcement. assertFalse(freshPolicy.implies(userDomain, new FilePermission("/etc/passwd", "read"))); - assertFalse( - freshPolicy.implies(userDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); } @Test From 3bb93c61b4d9076e8cb9108afe79d7b70cf04f66 Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Thu, 9 Jul 2026 16:22:29 -0700 Subject: [PATCH 07/12] remove some comments --- .../code/javabuilder/JavabuilderSecurityPolicy.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java index 9de14511..5076e53f 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java @@ -22,9 +22,7 @@ *
  • allows read/write/delete only under the writable temp directory, *
  • allows read-only access to the app/runtime directories the JVM and student libraries need * (so class loading, fonts, and bundled assets keep working), - *
  • denies all other filesystem access (notably /proc, /etc, $HOME, and env-backed files such - * as /proc/self/environ), - *
  • preserves every other capability student code has today. + *
  • denies all other filesystem access * * *

    NOTE: {@link SecurityManager} / {@link Policy} are deprecated in Java 17 and removed in Java @@ -36,8 +34,7 @@ public class JavabuilderSecurityPolicy extends Policy { // The only root confined student code may write to / delete within. private final String writableRoot; // Roots confined student code may read from (a superset of writableRoot). Everything the JVM - // needs to load classes, fonts, and library assets lives under these; sensitive locations - // (/proc, /etc, $HOME, env) are intentionally excluded. + // needs to load classes, fonts, and library assets lives under these private final String[] readableRoots; public JavabuilderSecurityPolicy() { @@ -61,12 +58,6 @@ public boolean implies(ProtectionDomain domain, Permission permission) { if (permission instanceof FilePermission) { return isAllowedFileAccess((FilePermission) permission); } - // Filesystem access is the only thing we confine here. Everything else student code can do - // today is preserved. Note: we intentionally do NOT deny getenv, because the AWS SDK resolves - // credentials via System.getenv() while student code is on the call stack (e.g. when a println - // is flushed through the output adapter), and AccessController would then deny the SDK's own - // read. Env-var credential exposure is mitigated instead by the file policy (which blocks - // reading /proc/self/environ) and by minimizing the Lambda execution role. return true; } From 839c43b78c49ff8f7f4cb4f24adb80da8ae0dfab Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Tue, 14 Jul 2026 09:43:28 -0700 Subject: [PATCH 08/12] stricter permission --- .../JavabuilderSecurityPolicy.java | 25 ++++++++++++++++++- .../JavabuilderSecurityPolicyTest.java | 20 +++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java index 5076e53f..633bf6da 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java @@ -7,6 +7,7 @@ import java.security.Permission; import java.security.Policy; import java.security.ProtectionDomain; +import java.security.SecurityPermission; import java.security.cert.Certificate; import java.util.List; @@ -22,7 +23,9 @@ *

  • allows read/write/delete only under the writable temp directory, *
  • allows read-only access to the app/runtime directories the JVM and student libraries need * (so class loading, fonts, and bundled assets keep working), - *
  • denies all other filesystem access + *
  • denies all other filesystem access, + *
  • denies the permissions that would let confined code disable its own confinement (installing + * a new SecurityManager or Policy). * * *

    NOTE: {@link SecurityManager} / {@link Policy} are deprecated in Java 17 and removed in Java @@ -58,9 +61,29 @@ public boolean implies(ProtectionDomain domain, Permission permission) { if (permission instanceof FilePermission) { return isAllowedFileAccess((FilePermission) permission); } + if (isSandboxControlPermission(permission)) { + return false; + } return true; } + /** + * True for the permissions that would let confined code disable or replace its own confinement: + * installing/creating a {@link SecurityManager}, or any {@link SecurityPermission} (which covers + * {@code setPolicy}, {@code createPolicy.*}, and changing security properties such as the policy + * provider). Student code has no legitimate need for any of these. + */ + private static boolean isSandboxControlPermission(Permission permission) { + if (permission instanceof SecurityPermission) { + return true; + } + if (permission instanceof RuntimePermission) { + final String name = permission.getName(); + return "setSecurityManager".equals(name) || "createSecurityManager".equals(name); + } + return false; + } + /** * Forces every class that {@link #implies} references to load. This MUST be called before a * SecurityManager is installed. diff --git a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java index b7a1199b..3faae4a6 100644 --- a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java +++ b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java @@ -6,6 +6,7 @@ import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; +import java.security.SecurityPermission; import java.security.cert.Certificate; import java.util.List; import java.util.PropertyPermission; @@ -65,6 +66,25 @@ public void studentRetainsNonFilePermissions() { assertTrue(policy.implies(userDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); } + @Test + public void studentCannotDisableTheSandbox() { + // Confined code must never be allowed to install a new SecurityManager or Policy, which would + // turn off all confinement. This holds in both USER and VALIDATOR (reflection-capable) runs. + for (ProtectionDomain studentDomain : new ProtectionDomain[] {userDomain, validatorDomain}) { + assertFalse( + policy.implies(studentDomain, new RuntimePermission("setSecurityManager"))); + assertFalse( + policy.implies(studentDomain, new RuntimePermission("createSecurityManager"))); + assertFalse(policy.implies(studentDomain, new SecurityPermission("setPolicy"))); + assertFalse(policy.implies(studentDomain, new SecurityPermission("createPolicy.JavaPolicy"))); + assertFalse( + policy.implies(studentDomain, new SecurityPermission("setProperty.policy.provider"))); + } + // Framework code is unaffected: it retains full control of the security infrastructure. + assertTrue(policy.implies(frameworkDomain, new RuntimePermission("setSecurityManager"))); + assertTrue(policy.implies(frameworkDomain, new SecurityPermission("setPolicy"))); + } + @Test public void validatorRunIsAlsoConfined() { // The validation run loads and executes student code in a VALIDATOR-level UserClassLoader, so From b2dbaed262635bd14bc51052787f3fcb02d1f950 Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Tue, 14 Jul 2026 10:00:33 -0700 Subject: [PATCH 09/12] Revert "stricter permission" This reverts commit 839c43b78c49ff8f7f4cb4f24adb80da8ae0dfab. --- .../JavabuilderSecurityPolicy.java | 25 +------------------ .../JavabuilderSecurityPolicyTest.java | 20 --------------- 2 files changed, 1 insertion(+), 44 deletions(-) diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java index 633bf6da..5076e53f 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java @@ -7,7 +7,6 @@ import java.security.Permission; import java.security.Policy; import java.security.ProtectionDomain; -import java.security.SecurityPermission; import java.security.cert.Certificate; import java.util.List; @@ -23,9 +22,7 @@ *

  • allows read/write/delete only under the writable temp directory, *
  • allows read-only access to the app/runtime directories the JVM and student libraries need * (so class loading, fonts, and bundled assets keep working), - *
  • denies all other filesystem access, - *
  • denies the permissions that would let confined code disable its own confinement (installing - * a new SecurityManager or Policy). + *
  • denies all other filesystem access * * *

    NOTE: {@link SecurityManager} / {@link Policy} are deprecated in Java 17 and removed in Java @@ -61,29 +58,9 @@ public boolean implies(ProtectionDomain domain, Permission permission) { if (permission instanceof FilePermission) { return isAllowedFileAccess((FilePermission) permission); } - if (isSandboxControlPermission(permission)) { - return false; - } return true; } - /** - * True for the permissions that would let confined code disable or replace its own confinement: - * installing/creating a {@link SecurityManager}, or any {@link SecurityPermission} (which covers - * {@code setPolicy}, {@code createPolicy.*}, and changing security properties such as the policy - * provider). Student code has no legitimate need for any of these. - */ - private static boolean isSandboxControlPermission(Permission permission) { - if (permission instanceof SecurityPermission) { - return true; - } - if (permission instanceof RuntimePermission) { - final String name = permission.getName(); - return "setSecurityManager".equals(name) || "createSecurityManager".equals(name); - } - return false; - } - /** * Forces every class that {@link #implies} references to load. This MUST be called before a * SecurityManager is installed. diff --git a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java index 3faae4a6..b7a1199b 100644 --- a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java +++ b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java @@ -6,7 +6,6 @@ import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; -import java.security.SecurityPermission; import java.security.cert.Certificate; import java.util.List; import java.util.PropertyPermission; @@ -66,25 +65,6 @@ public void studentRetainsNonFilePermissions() { assertTrue(policy.implies(userDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); } - @Test - public void studentCannotDisableTheSandbox() { - // Confined code must never be allowed to install a new SecurityManager or Policy, which would - // turn off all confinement. This holds in both USER and VALIDATOR (reflection-capable) runs. - for (ProtectionDomain studentDomain : new ProtectionDomain[] {userDomain, validatorDomain}) { - assertFalse( - policy.implies(studentDomain, new RuntimePermission("setSecurityManager"))); - assertFalse( - policy.implies(studentDomain, new RuntimePermission("createSecurityManager"))); - assertFalse(policy.implies(studentDomain, new SecurityPermission("setPolicy"))); - assertFalse(policy.implies(studentDomain, new SecurityPermission("createPolicy.JavaPolicy"))); - assertFalse( - policy.implies(studentDomain, new SecurityPermission("setProperty.policy.provider"))); - } - // Framework code is unaffected: it retains full control of the security infrastructure. - assertTrue(policy.implies(frameworkDomain, new RuntimePermission("setSecurityManager"))); - assertTrue(policy.implies(frameworkDomain, new SecurityPermission("setPolicy"))); - } - @Test public void validatorRunIsAlsoConfined() { // The validation run loads and executes student code in a VALIDATOR-level UserClassLoader, so From 9274501dfa7b2ec9c00ac21ca0ab2157623cf8da Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Tue, 14 Jul 2026 10:31:18 -0700 Subject: [PATCH 10/12] deny execute --- .../code/javabuilder/JavabuilderSecurityPolicy.java | 8 ++++++-- .../javabuilder/JavabuilderSecurityPolicyTest.java | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java index 5076e53f..c5c458a6 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java @@ -22,7 +22,8 @@ *

  • allows read/write/delete only under the writable temp directory, *
  • allows read-only access to the app/runtime directories the JVM and student libraries need * (so class loading, fonts, and bundled assets keep working), - *
  • denies all other filesystem access + *
  • never allows execute + *
  • denies all other filesystem access * * *

    NOTE: {@link SecurityManager} / {@link Policy} are deprecated in Java 17 and removed in Java @@ -104,10 +105,13 @@ private boolean isAllowedFileAccess(FilePermission permission) { return false; } final String actions = permission.getActions(); + if (actions.contains("execute")) { + return false; + } if (actions.contains("write") || actions.contains("delete")) { return isUnder(path, this.writableRoot); } - // read / execute / readlink + // read / readlink for (String root : this.readableRoots) { if (root != null && isUnder(path, root)) { return true; diff --git a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java index b7a1199b..39d4bd2c 100644 --- a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java +++ b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java @@ -65,6 +65,17 @@ public void studentRetainsNonFilePermissions() { assertTrue(policy.implies(userDomain, new RuntimePermission("getenv.AWS_SECRET_ACCESS_KEY"))); } + @Test + public void studentCannotExecuteEvenUnderTmp() { + final String tmpBinary = System.getProperty("java.io.tmpdir") + "/x"; + assertFalse(policy.implies(userDomain, new FilePermission(tmpBinary, "execute"))); + assertFalse(policy.implies(userDomain, new FilePermission(tmpBinary, "read,execute"))); + // Execute is denied everywhere, including the otherwise-readable runtime roots. + final String runtimeBinary = System.getProperty("java.home") + "/bin/java"; + assertFalse(policy.implies(userDomain, new FilePermission(runtimeBinary, "execute"))); + assertFalse(policy.implies(validatorDomain, new FilePermission(tmpBinary, "execute"))); + } + @Test public void validatorRunIsAlsoConfined() { // The validation run loads and executes student code in a VALIDATOR-level UserClassLoader, so From 7350ba4abbcb565185371efb4a926b3df5ba2875 Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Tue, 14 Jul 2026 13:16:25 -0700 Subject: [PATCH 11/12] clean up --- .../javabuilder/JavabuilderSecurityPolicy.java | 17 ++++++++++------- .../code/javabuilder/LambdaRequestHandler.java | 8 +------- .../JavabuilderSecurityPolicyTest.java | 7 +++---- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java index c5c458a6..55188cb3 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/JavabuilderSecurityPolicy.java @@ -48,6 +48,9 @@ public JavabuilderSecurityPolicy() { "/var/lang", // managed runtime "/opt" // Lambda layers: fonts, instrument samples, wrapper }; + // Warm up here, at construction, so it is guaranteed to run before the caller installs a + // SecurityManager. + warmUp(); } @Override @@ -63,17 +66,17 @@ public boolean implies(ProtectionDomain domain, Permission permission) { } /** - * Forces every class that {@link #implies} references to load. This MUST be called before a - * SecurityManager is installed. + * Forces every class that #implies references to load. Called from the constructor so it always + * runs before a SecurityManager is installed. * - *

    {@code implies} runs on every permission check, including the {@code File.exists()} calls - * the class loader makes to locate a class. If a class {@code implies} references (notably {@link - * UserClassLoader}) is still unloaded when the first such check fires, loading it re-enters - * {@code implies} before the load completes and throws {@link ClassCircularityError}. Exercising + * #implies runs on every permission check, including the File.exists() calls + * the class loader makes to locate a class. If a class #implies references + * is still unloaded when the first such check fires, loading it re-enters + * #implies before the load completes and throws ClassCircularityError. Exercising * the policy here, while no SecurityManager is active, guarantees those classes are already * loaded by the time checks begin. */ - public void warmUp() { + private void warmUp() { final ProtectionDomain studentDomain = new ProtectionDomain( new CodeSource(null, (Certificate[]) null), diff --git a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java index 5e540e1b..c70f0360 100644 --- a/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java +++ b/org-code-javabuilder/lib/src/main/java/org/code/javabuilder/LambdaRequestHandler.java @@ -78,14 +78,8 @@ public LambdaRequestHandler() { // Install the security policy once for the entire container. The policy scopes itself to code // loaded by a UserClassLoader, so a fresh UserClassLoader per run means confinement is applied - // per run without any per-invocation setup. All other code is fully trusted, so framework and - // AWS SDK behavior is unaffected. - // - // warmUp() must run before the SecurityManager is installed: the policy is consulted on every - // permission check (including the file reads the class loader performs), so any class it - // references must already be loaded to avoid a ClassCircularityError during class loading. + // per run without any per-invocation setup. final JavabuilderSecurityPolicy securityPolicy = new JavabuilderSecurityPolicy(); - securityPolicy.warmUp(); Policy.setPolicy(securityPolicy); System.setSecurityManager(new SecurityManager()); COLD_BOOT_END = Clock.systemUTC().instant(); diff --git a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java index 39d4bd2c..825d8dd0 100644 --- a/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java +++ b/org-code-javabuilder/lib/src/test/java/org/code/javabuilder/JavabuilderSecurityPolicyTest.java @@ -86,10 +86,9 @@ public void validatorRunIsAlsoConfined() { } @Test - public void warmUpDoesNotThrowAndPolicyStillEnforces() { - final JavabuilderSecurityPolicy freshPolicy = new JavabuilderSecurityPolicy(); - assertDoesNotThrow(freshPolicy::warmUp); - // Warming up must not weaken enforcement. + public void constructorWarmUpDoesNotThrowAndPolicyStillEnforces() { + // Construction warms the policy up; it must not throw and must not weaken enforcement. + final JavabuilderSecurityPolicy freshPolicy = assertDoesNotThrow(JavabuilderSecurityPolicy::new); assertFalse(freshPolicy.implies(userDomain, new FilePermission("/etc/passwd", "read"))); } From 85226fed1810817821344936241c5132ce94bd5c Mon Sep 17 00:00:00 2001 From: Molly Moen Date: Tue, 14 Jul 2026 14:05:39 -0700 Subject: [PATCH 12/12] revert template changes --- cicd/3-app/javabuilder/template.yml.erb | 30 +++++-------------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/cicd/3-app/javabuilder/template.yml.erb b/cicd/3-app/javabuilder/template.yml.erb index 6e9b5c7a..7ece8203 100644 --- a/cicd/3-app/javabuilder/template.yml.erb +++ b/cicd/3-app/javabuilder/template.yml.erb @@ -487,32 +487,16 @@ Resources: Status: Enabled ExpirationInDays: 1 - ContentOriginAccessControl: - Type: AWS::CloudFront::OriginAccessControl - Properties: - OriginAccessControlConfig: - Name: !Sub "${SubdomainName}-${BaseDomainName}-content-oac" - OriginAccessControlOriginType: s3 - SigningBehavior: always - SigningProtocol: sigv4 - ContentBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref ContentBucket PolicyDocument: - Version: '2012-10-17' Statement: - - Sid: AllowCloudFrontRead - Effect: Allow - Principal: - Service: cloudfront.amazonaws.com - Action: - - s3:GetObject - Resource: !Sub "arn:aws:s3:::${ContentBucket}/*" - Condition: - StringEquals: - AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${ContentCDN}" + - Action: ['s3:GetObject'] + Effect: Allow + Resource: !Sub "arn:aws:s3:::${ContentBucket}/*" + Principal: '*' ContentApiCertificate: Type: AWS::CertificateManager::Certificate @@ -553,10 +537,8 @@ Resources: # Prefix: !Sub "${SubdomainName}-content.${BaseDomainName}" Origins: - Id: ContentBucket - DomainName: !GetAtt ContentBucket.RegionalDomainName - OriginAccessControlId: !GetAtt ContentOriginAccessControl.Id - S3OriginConfig: - OriginAccessIdentity: "" + DomainName: !GetAtt ContentBucket.DomainName + S3OriginConfig: {} DefaultCacheBehavior: TargetOriginId: ContentBucket AllowedMethods: [DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT]