diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Revision history for thrift-compiler
 
+## 0.3.0.0
+
+* Added parser support for 'sink' and 'stream'; both are ignored
+  by the compiler
+
 ## 0.2.0.0
 
 * The folly dependency is now bundled in the Hackage package folly-clib,
diff --git a/Thrift/Compiler/GenHaskell.hs b/Thrift/Compiler/GenHaskell.hs
--- a/Thrift/Compiler/GenHaskell.hs
+++ b/Thrift/Compiler/GenHaskell.hs
@@ -12,11 +12,15 @@
   , writeModule, showModule
   , ThriftModule(..)
   , commonPragmas
+  , NamespacePathValidationError(..)
   ) where
 
+import Control.Exception (Exception, throwIO)
+import Control.Monad (unless)
 import Data.List
 import Data.List.Extra
 import Data.Text (Text)
+import Data.Typeable (Typeable)
 import Language.Haskell.Exts hiding (parse, Decl, name, app)
 import System.Directory
 import System.FilePath
@@ -51,17 +55,51 @@
   , tmModuleName :: String
   }
 
+-- | Exception thrown when a namespace directive would result in files
+-- being generated outside the designated output directory
+data NamespacePathValidationError = NamespacePathValidationError
+  { npveOutputDir :: FilePath
+  , npveGeneratedPath :: FilePath
+  , npveOriginalPath :: FilePath
+  } deriving (Show, Typeable)
+
+instance Exception NamespacePathValidationError
+
 writeHsCode :: Options Haskell -> Program Haskell Thrift.Loc -> IO [FilePath]
 writeHsCode opts prog =
   -- Write the Generated Files
   mapM writeModule =<< genHsCode opts prog
 
+-- | Validate that a generated path is contained within the output directory
+-- Prevents namespace directives from writing files outside the intended
+-- output directory (see T243717416)
+validateOutputPath :: FilePath -> FilePath -> IO ()
+validateOutputPath outputDir generatedPath = do
+  -- Canonicalize both paths to resolve .., symlinks, etc.
+  canonicalOutput <- canonicalizePath outputDir
+  canonicalGenerated <- canonicalizePath generatedPath
+
+  -- Check if generated path is inside output directory
+  unless (canonicalOutput `isPrefixOf` canonicalGenerated) $
+    throwIO $ NamespacePathValidationError
+      { npveOutputDir = canonicalOutput
+      , npveGeneratedPath = canonicalGenerated
+      , npveOriginalPath = generatedPath
+      }
+  where
+    isPrefixOf :: FilePath -> FilePath -> Bool
+    isPrefixOf prefix path =
+      let prefix' = addTrailingPathSeparator prefix
+          path' = addTrailingPathSeparator path
+      in prefix' `Data.List.isPrefixOf` path'
+
 genHsCode :: Options Haskell -> Program Haskell Thrift.Loc -> IO [ThriftModule]
 genHsCode Options{..} prog@Program{..} = do
   let
     progHSPath = Text.unpack $ Text.replace "." "/" progHSName
     HsOpts{..} = optsLangSpecific
     dir = progOutPath </> hsoptsGenPrefix </> progHSPath
+  validateOutputPath optsOutPath dir
   relativeDir <- makeRelativeToCurrentDirectory dir
 
   let
diff --git a/Thrift/Compiler/Lexer.x b/Thrift/Compiler/Lexer.x
--- a/Thrift/Compiler/Lexer.x
+++ b/Thrift/Compiler/Lexer.x
@@ -80,6 +80,7 @@
   "binary"             { basicToken BINARY }
   "senum"              { basicToken SENUM }
   "stream"             { basicToken STREAM }
+  "sink"               { basicToken SINK }
   "void"               { basicToken VOID }
   "union"              { basicToken UNION }
   "view"               { basicToken VIEW }
@@ -160,7 +161,7 @@
   | SYMBOL String
   | TRUE | FALSE
   -- Stuff we don't use (yet)
-  | SENUM | STREAM | VOID | BINARY | INTERACTION
+  | SENUM | STREAM | VOID | BINARY | INTERACTION | SINK
   | UNION | VIEW | EXCEPTION | SERVICE
   | ONEWAY | EXTENDS | THROWS | ASYNC | PERFORMS
   | SAFE | TRANSIENT | STATEFUL | PERMANENT | SERVER | CLIENT
diff --git a/Thrift/Compiler/OptParse.hs b/Thrift/Compiler/OptParse.hs
--- a/Thrift/Compiler/OptParse.hs
+++ b/Thrift/Compiler/OptParse.hs
@@ -97,6 +97,7 @@
   _ <- switch (long "allow-neg-keys")
   _ <- switch (long "allow-neg-enum-vals")
   _ <- maybeStr [long "templates"]
+  _ <- maybeStr [long "extra-validation"]
   pure $ case lo of
     TheseLangOpts optsLangSpecific -> TheseOptions Options{..}
 
diff --git a/Thrift/Compiler/Parser.y b/Thrift/Compiler/Parser.y
--- a/Thrift/Compiler/Parser.y
+++ b/Thrift/Compiler/Parser.y
@@ -76,6 +76,7 @@
        binary      { Tok BINARY _ }
        senum       { Tok SENUM _ }
        stream      { Tok STREAM _ }
+       sink        { Tok SINK _ }
        interaction { Tok INTERACTION _ }
        void        { Tok VOID _ }
        union       { Tok UNION _ }
@@ -134,13 +135,22 @@
     }
   | StructuredAnnotations package stringLit
     { Just HPackage
-      { pkgUri = lParsed $3
+      { pkgUri = Just (lParsed $3)
       , pkgKeywordLoc = getLoc $2
-      , pkgUriLoc     = lLoc $3
-      , pkgQuoteType  = lRep $3
+      , pkgUriLoc     = Just (lLoc $3)
+      , pkgQuoteType  = Just (lRep $3)
       , pkgSAnns      = $1
       }
     }
+  | StructuredAnnotations package ';'
+    { Just HPackage
+      { pkgUri = Nothing
+      , pkgKeywordLoc = getLoc $2
+      , pkgUriLoc     = Nothing
+      , pkgQuoteType  = Nothing
+      , pkgSAnns      = $1
+      }
+    }
 
 Include
   : include     { L (getLoc $1) Include }
@@ -616,6 +626,22 @@
           , rsStream = (Stream tStream $6 (annTy1 $3 $4 $7))
           }
     }
+  | sink '<' AnnotatedType Throws '>' ',' stream '<' AnnotatedType Throws '>'
+    { case $9 of
+        Some t -> ResponseAndStreamReturn
+          { rsReturn = Nothing
+          , rsComma = Nothing
+          , rsStream = (Stream t $10 (annTy1 $7 $8 $11))
+          }
+    }
+  | AnnotatedType ',' sink '<' AnnotatedType Throws '>' ',' stream '<' AnnotatedType Throws '>'
+    { case $11 of
+        Some t -> ResponseAndStreamReturn
+          { rsReturn = Nothing
+          , rsComma = Nothing
+          , rsStream = (Stream t $12 (annTy1 $9 $10 $13))
+          }
+    }
 
 Throw :: { Parsed (Field 'ThrowsField) }
 Throw : StructuredAnnotations intLit ':' AnnotatedType Symbol MaybeConst Annotations Separator
@@ -707,6 +733,8 @@
   | readonly    { L (getLoc $1) "readonly" }
   | idempotent  { L (getLoc $1) "idempotent" }
   | package     { L (getLoc $1) "package" }
+  | sink        { L (getLoc $1) "sink" }
+  | stream      { L (getLoc $1) "stream" }
 
 stringLit : stringTok
   {% case $1 of
diff --git a/Thrift/Compiler/Types.hs b/Thrift/Compiler/Types.hs
--- a/Thrift/Compiler/Types.hs
+++ b/Thrift/Compiler/Types.hs
@@ -118,10 +118,10 @@
     , nmQuoteType  :: Maybe QuoteType
     }
   | HPackage
-    { pkgUri         :: Text
+    { pkgUri         :: Maybe Text
     , pkgKeywordLoc  :: Located a
-    , pkgUriLoc      :: Located a
-    , pkgQuoteType   :: QuoteType
+    , pkgUriLoc      :: Maybe (Located a)
+    , pkgQuoteType   :: Maybe QuoteType
     , pkgSAnns       :: [StructuredAnnotation s l a]
     }
 
@@ -855,7 +855,7 @@
      -> SCHEMA l 'StructSchema s
      -> SCHEMA l 'StructSchema ('(name, Maybe t) ': s)
 
--- | Renamed for target langauge
+-- | Renamed for target language
 data Name = Name
   { sourceName   :: ThriftName
   , resolvedName :: Name_ 'Resolved
diff --git a/test/if/bidi.thrift b/test/if/bidi.thrift
new file mode 100644
--- /dev/null
+++ b/test/if/bidi.thrift
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace cpp2 apache.thrift.detail.test
+
+exception BiDiSinkException {
+  1: string message;
+}
+
+exception BiDiStreamException {
+  1: string message;
+}
+
+exception BiDiMethodException {
+  1: string message;
+}
+
+service TestBiDiService {
+  // @lint-ignore THRIFTCHECKS new unreleased feature
+  sink<string>, stream<string> echo();
+
+  // @lint-ignore THRIFTCHECKS new unreleased feature
+  string, sink<string>, stream<string> echoWithResponse(1: string initial);
+
+  // @lint-ignore THRIFTCHECKS new unreleased feature
+  sink<i64>, stream<i64> intStream();
+
+  // @lint-ignore THRIFTCHECKS new unreleased feature
+  sink<i64 throws (1: BiDiSinkException sinkEx)>, stream<
+    i64 throws (1: BiDiStreamException sinkEx)
+  > canThrow() throws (1: BiDiMethodException methodEx);
+}
diff --git a/tests/if/constants.thrift b/tests/if/constants.thrift
--- a/tests/if/constants.thrift
+++ b/tests/if/constants.thrift
@@ -6,6 +6,8 @@
  * LICENSE file in the root directory of this source tree.
  */
 
+// include "thrift/annotation/thrift.thrift"
+
 const i32 i32Const = 99;
 
 const bool boolConst = 0;
diff --git a/tests/if/no_structs.thrift b/tests/if/no_structs.thrift
new file mode 100644
--- /dev/null
+++ b/tests/if/no_structs.thrift
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+include "if/foo.thrift"
+
+const foo.Foo fooConst = {"foo2": true};
diff --git a/thrift-compiler.cabal b/thrift-compiler.cabal
--- a/thrift-compiler.cabal
+++ b/thrift-compiler.cabal
@@ -3,7 +3,7 @@
 -- Copyright (c) Facebook, Inc. and its affiliates.
 
 name:                thrift-compiler
-version:             0.2.0.0
+version:             0.3.0.0
 synopsis: A compiler from the Thrift Interface Definition Language (IDL) to Haskell
 homepage:            https://github.com/facebookincubator/hsthrift
 bug-reports:         https://github.com/facebookincubator/hsthrift/issues
@@ -94,12 +94,12 @@
         array >= 0.5.3 && < 0.6,
         mtl >= 2.2.2 && < 2.4,
         optparse-applicative >= 0.17 && < 0.19,
-        aeson-pretty >= 0.8.10 && < 0.9,
+        aeson-pretty >= 0.8.9 && < 0.9,
         either >= 5.0.2 && < 5.1,
-        extra >= 1.8 && < 1.9,
+        extra >= 1.7.14 && < 1.9,
         fb-util >= 0.1.0 && < 0.3,
-        some >= 1.0.6 && < 1.1,
-        text-show >= 3.10.5 && < 3.11,
+        some >= 1.0.4 && < 1.1,
+        text-show >= 3.10.4 && < 3.11,
         haskell-src-exts >=1.20.3 && <1.24,
         haskell-names < 0.10,
         base >=4.11.1 && <4.20,
